2021SC@SDUSC
Dust3D在网格无缝缝合时使用了半边数据结构存储相关数据。

表示多边形网格的一个常用方式就是使用共享的顶点列表和面的列表。这样的表示方法在许多情况下都非常方便和高效,但是在某些特定的领域,反而会效率比较低。

例如,网格简化通常需要把一条边折叠成一个顶点。这个操作需要删除与这条边邻接的面并且更新面中存储的与这条边相关的顶点数据。这种多边形“手术”需要我们了解网格的组成部分的邻接关系,例如面和顶点。虽然我们肯定可以用之前提及的网格表示方法来实现网格简化,但是代价会比较高。许多情况下需要遍历面或者点的列表,或者二者都有。

在一个多边形网格上其他类型的的临近查询包括:

  • 哪条边用到了这个点
  • 哪条边用到这个点
  • 哪个面临近这条边
  • 哪条边临近这个面
  • 哪个面临近这个面

为了使得这些类型的临近查询更加有效,发展出更加精细的边界表示方案(b-reps),更加明确地建模表示点、边和面,并且也临近信息也相应地储存进去了。

这些表示类型中最常见的其中一种是“翼边数据结构”。其中每条边包含了指向其两个顶点,两个邻接面的指针以及指向从其终点延伸出去的四条边的指针。这种结构可以让我们在常数时间内判断哪些点与面与该边连接,但是面对其他类型的查询,这种结构会带来更大的开销。

“半边数据结构”是一种略微复杂的边表示方法,并且在其上做之前提到的所有操作都可以在常数时间完成。更优秀的是,即使包含了面、顶点和边的邻接信息,数据结构的大小是固定的且紧凑的。

文章目录

  • 翼边结构
  • 半边结构
  • convertHalfEdgesToEdgeLoops()
  • buildHalfEdgeToFaceMap()

翼边结构

一种常见的数据结构就是翼边结构(The Winged-Edge Structure),其特点是将邻接关系都储存在网格的边上,翼边结构最大的优点在于其不仅能用在三角网格上还能用在任意形状的网格上,数据结构如下:

  • 对每个面,储存其中的一个边索引
  • 对每条边,储存其两个顶点,左右两个面,左边面与连接的两条边,右边面与之连接的两条边
  • 对每个点,储存其对应的一个边索引

    翼边结构的另一大优点就是在索引邻接关系的时候非常方便,因为边储存了足够多的信息,利用这个结构我们可以在网格中自由检索。

半边结构

半边数据结构(Half-Edge Data Structure )是一个以网格边为基础的数据结构,在这种数据结构中,我们认为网格的每条边被划分为两条方向相反的半边。

之所以称为半边数据结构,是因为在这个结构中并不会存储网格的边的信息,取而代之的是半边(half-edges)。半边这么叫,就是因为它其实是一条边的一半,等于是把一条边保持长度不变,形式上分为两条半边(因为按照定义边没有宽度或者至少是单位宽度,这就是一个假象划分)。这两条半边组合在一起称为一条边,也就是一条边等于一对半边。半边是有方向的,并且一条边的一对半边有相反的方向。

半边数据结构的三个重要的数据结构——顶点、半边、面片

  • 顶点(Vertex):包含出半边(OutgoingHalfedge)的指针或索引,在半边数据结构中的点储存着x,y,z的位置和以其为起始点的半边的指针
  • 半边(HalfEdge):包含终点(StartVertex)、邻接面(AdjacentFace)、下一条半边(NextHalfedge)、相反边(opposite)的指针或索引
  • 面片(Face):包含一条起始边(FirstHalfedge)的指针或索引对于一个半边数据结构的简单形式,一个面仅仅需要储存一个围绕它的边的指针,在一些特定场合可能要求我们储存比如材质和法向一类的信息。和上面一样,虽然有很多边围绕着面,我们只需要储存其中一条,而无所谓是哪一条

convertHalfEdgesToEdgeLoops()

MeshRecombiner类中的convertHalfEdgesToEdgeLoops()将半边结构转化为循环边列表。首先将每条半边存储在vertexLinkMap中,然后从第一个顶点向后遍历,当元素数未超过序列容量限制时,向edgeLoop中加入顶点,直至形成闭环。

bool MeshRecombiner::convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t, size_t>> &halfEdges,std::vector<std::vector<size_t>> *edgeLoops)
{std::map<size_t, size_t> vertexLinkMap;for (const auto &halfEdge: halfEdges) {auto inserResult = vertexLinkMap.insert(halfEdge);if (!inserResult.second) {return false;}}while (!vertexLinkMap.empty()) {std::vector<size_t> edgeLoop;//找到map中第一个节点的第一个值size_t vertex = vertexLinkMap.begin()->first;size_t head = vertex;bool loopBack = false;size_t limitLoop = MAX_EDGE_LOOP_LENGTH;//当元素数未超过序列容量限制时,向edgeLoop中加入新边while ((limitLoop--) > 0) {edgeLoop.push_back(vertex);  //将当前顶点加入edge loop中auto findNext = vertexLinkMap.find(vertex);//如果当前节点是map的最后一个节点,则开始新的循环if (findNext == vertexLinkMap.end())break;//如果当前节点的第二个值是map的头节点,则成功生成闭环的edge序列vertex = findNext->second;if (vertex == head) {loopBack = true;break;}}//生成闭环失败if (!loopBack) {return false;}//edgeLoop中边的个数小于3,无法形成闭环if (edgeLoop.size() < 3) {return false;}for (const auto &vertex: edgeLoop) {vertexLinkMap.erase(vertex);}edgeLoops->push_back(edgeLoop);}return true;
}

buildHalfEdgeToFaceMap()

bool MeshRecombiner::buildHalfEdgeToFaceMap(std::map<std::pair<size_t, size_t>, size_t> &halfEdgeToFaceMap)
{bool isSuccessful = true;for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) {const auto &face = (*m_faces)[faceIndex];for (size_t i = 0; i < face.size(); ++i) {size_t j = (i + 1) % face.size();//对于每一组半边,找到他们邻接的两个面const auto insertResult = halfEdgeToFaceMap.insert({{face[i], face[j]}, faceIndex});if (!insertResult.second) {//qDebug() << "Non manifold edge found:" << face[i] << face[j];isSuccessful = false;}}}return isSuccessful;
}

如有错误,欢迎指正。

源码分析学习记录(11)——半边结构相关推荐

  1. 源码分析学习记录(12)——自动UV展开

    2021SC@SDUSC 文章目录 UV 展开 创建割缝 UV展开的扭曲情况 UvUnwrap UV 展开 参数曲面的参数域变量一般用UV字母来表达,比如参数曲面F(u,v).所以一般叫的三维曲面本质 ...

  2. 源码分析学习记录(5)——骨骼存储与建立

    2021SC@SDUSC 文章目录 骨骼数据结构 updateMatrix 建立骨骼树形结构 骨骼决定了模型整体在世界坐标系中的位置和方位. 在渲染静态模型时, 由于模型的顶点都是定义在模型坐标系中的 ...

  3. 源码分析学习记录(9)——PBR材质

    2021SC@SDUSC Dust3D中的材质采用PBR模型.PBR就是Physically-Based Rendering的缩写,意为基于物理的渲染.它提供了一种光照和渲染方法,能够更精确的描绘光和 ...

  4. 源码分析学习记录(6)——蒙皮

    2021SC@SDUSC 文章目录 蒙皮算法 刚性绑定算法 柔性绑定算法 SkinnedMeshCreator createMeshFromTransform 蒙皮算法 骨骼的蒙皮算法,又称为骨骼与皮 ...

  5. r8169驱动源码阅读记录

    r8169驱动源码阅读记录 初始化 发包 收包 源码地址:linux-4.19.90\drivers\net\ethernet\realtek\r8169.c 源码阅读环境:Windows 搭建 op ...

  6. 分析jQuery源码时记录的一点感悟

    分析jQuery源码时记录的一点感悟       1.  链式写法       这是jQuery语法上的最大特色,也许该改改POJO里的set方法,和其他的非get方法什么的,可以把多行代码合并,减去 ...

  7. 视觉机器学习20讲-MATLAB源码示例(11)-流形学习算法

    视觉机器学习20讲-MATLAB源码示例(11)-流形学习算法 1. 流形学习算法 2. Matlab仿真 3. 仿真结果 4. 小结 1. 流形学习算法 流形学习是一类借鉴了拓扑流形概念的降维方法, ...

  8. yolov4源码_YOLOv4特征提取网络——CSPDarkNet结构解析及PyTorch实现

    1 YOLOv4目标检测模型 自从Redmon说他不在更新YOLO系列之后,我一度以为这么好用的框架就要慢慢淡入历史了,事实是我多虑了.YOLOv4在使用YOLO Loss的基础上,使用了新的back ...

  9. live555 源码分析:RTSPServer 组件结构

    前面几篇文章分析了 live555 中 RTSP 的处理逻辑,RTSP 处理有关组件的处理逻辑有点复杂,本文就再来梳理一下它们之间的关系. live555 中 RTSP 处理有关组件关系如下图: 事件 ...

最新文章

  1. IOSUIcontrol事件
  2. oracle-pl/sql之三
  3. 转 sklearn: TfidfVectorizer 中文处理及一些使用参数
  4. 网络推广下叮咚买菜已完成D轮融资,生鲜电商下一次融资又在何方?
  5. spring getbean 方法分析
  6. wordpress的手动更新
  7. Python中BufferedIOBase
  8. linux shell基础
  9. js java webservice_js调用webservice中的方法实现思路及代码
  10. redis 中一个字段 修改map_Redis 几种数据类型及应用场景
  11. android 9.0打开wifi,Android9.0 SystemUI 屏蔽打开wifi时不显示4G图标的逻辑
  12. 【学习OpenCV4】图像的模糊处理方法(均值滤波与高斯模糊)
  13. dumpsys gfxinfo packacges计算帧率
  14. 矩阵分析 (五) 矩阵的分解
  15. paip.提高用户体验----c++图片按钮方法总结
  16. Adobe Fireworks CS4 序列号(注册码)
  17. linux下thinkpad X1 carbon 2018 电源管理
  18. Spark 连接kafka报错: Error while fetching metadata with correlation id xx [topicName=INVALID_REPLICATIO]
  19. Meatycake,51nod2117,树状数组
  20. iOS 用内置浏览器Safari 打开网页

热门文章

  1. 开关电源matlab仿真文件,《MATLAB+在开关电源仿真中的应用.pdf》-支持高清全文免费浏览-max文档...
  2. 音响的灵魂! 世界顶级扬声器品牌介绍
  3. 企业邮箱申请流程有哪些?公司邮箱的优势有哪些?
  4. JavaScript中变量判断是否是数字,判断是否是整数,判断是否是正整数/负整数,判断奇数/偶数的方法
  5. tecplot改变x轴y轴
  6. 网络编程之端口与套接字
  7. 积分使用及有效期管理
  8. webvtt字幕转srt字幕方法
  9. 共享单车之数据可视化
  10. 阿昆同学的Java学习日记Day5