定义
先讲什么是邻接三角面:
一个三角面,有三条边,这三条边不但属于这个三角面,而且还可能属于其他相邻的三角面。这些相邻的三角面就是邻接三角面。最少有一个邻接三角面(在边角处),最多有三个邻接三角面(在中间)
邻接的英文是adjacent
算法
算法概述
分两步:
- 建立边到三角面的映射。也就是找到边所属的三角面。
- 对每个三角面的三条边,分别查询边到三角面映射,就可以得到邻接三角面。
在此期间,我们需要利用以下关键特性:
尽管是同一条边,但是由于所属三角面不同,他们的序号恰好是相反的。也就是同一条边以相反的序号存储了两边。
数据结构
我们利用C++中的unorded_map。
首先定义边到三角面映射,保存到map1中:
std::unordered_map<edgeType, triType, tuple_hash, tuple_equal> map1;//边到三角面
还要定义另一个映射表,从三角面到邻接三角面的。map2
using multiTriType = std::vector<triType>;
std::unordered_map<triType, multiTriType, tuple_hash, tuple_equal> map2;//面到邻接面(多个)
其中
using edgeType = vec2i;
using triType = vec3i;
并且C++本身只支持单个数字的哈希,不支持三个数的哈希。因此我们要重载unordered_map中的哈希函数(也就是第三个类型参数)。最好还要指明什么样的哈希是相同的哈希值(也就是第四个类型参数)。
这时就需要把三个数合成一个哈希值。通常就用三个乱序数字的异或来实现。这三个magic number可以取
2718281828, 3141592653, 1618033989
假如使用zeno框架,我们直接使用现成的函数即可。只需要include如下头文件。
#include <zeno/utils/tuple_hash.h>
具体实现
- 先建立边到面的映射表。存到map1里。
// 1. 先建立边到面的映射表。存到map1里。
std::unordered_map<edgeType, triType, tuple_hash, tuple_equal> map1;//边到三角面
for(auto const & t : tris)
{
vec2i e1{t[0], t[1]}; //三角面的三条边
vec2i e2{t[1], t[2]};
vec2i e3{t[2], t[0]};
map1.emplace(e1,t);
map1.emplace(e2,t);
map1.emplace(e3,t);
}
- 找到三角面的邻接面。
遍历所有边到三角面的映射表。
对每条边,假如恰好有个与其序号相反的边也在表内,那么这条边就是它的邻接边。
那么邻接边所属的三角面就是它的一个邻接三角面。存到map2里。
//2. 找到三角面的邻接面。
//遍历所有边到三角面的映射表。
//对每条边,假如恰好有个与其序号相反的边也在表内,那么这条边就是它的邻接边。
//那么邻接边所属的三角面就是它的一个邻接三角面。存到map2里。
using multiTriType = std::vector<triType>;
std::unordered_map<triType, multiTriType, tuple_hash, tuple_equal> map2;//面到邻接面(多个)
for(auto &[k,v]:map1) //k是边,v是所属的面
{
edgeType inv{k[1],k[0]}; //相反序号的边
if(map1.find(inv) != map1.end())
{
const triType & one= map1.at(inv); //其中一个邻接面
map2[v].push_back(one);//放到邻接面表中
}
}
以上就是算法的全部内容。到此,我们拥有了map2。通过查询map2,那么就能找到相应的邻接三角面了。
然而zeno不方便传递map数据结构。zeno中最常用的是传递vector数据(也就是attrVec),并且只能是一维vector(不能是vector套vector)。
所以假如你使用zeno,我们必须想办法把数据转换为vector形式的。
于是我们就把邻接三角面用tris中的下标来表示。如果不存在,那就存-1。这样传递的数据就是一个vector的数组了。
首先我们得把构建一个反向映射的map,给定三个数,得到相应的序号。
//把tris和lines都存成反向的序号map。也就是键是tris的三个int,值则是tris数组中的序号。
std::unordered_map<triType, int, tuple_hash, tuple_equal> triMap;
for (int i = 0; i < tris.size(); i++)
triMap[tris[i]] = i;
其次我们建立一个新的attrVec,把数据都放进去。
//3. 最后把邻接三角面存到tris的属性 adjTriId 当中。用于可视化
auto & adjTriId = prim->tris.add_attr<vec3i>("adjTriId");
std::fill(adjTriId.begin(),adjTriId.end(),vec3i{-1,-1,-1});
for (size_t i = 0; i < tris.size(); i++)
{
const multiTriType & adjs = map2.at(tris[i]); //这是一组邻接面,可能有多个,是个vector
for(int j=0; j < adjs.size(); j++) //取出每个邻接面
{
int ind = triMap.at(adjs[j]); //找到在tris中对应的序号
adjTriId[i][j] = ind; //j是0,1,2
}
}