参考文章:https://www.cnblogs.com/zhiyishou/p/4430017.html

本文使用逐点插入法进行剖分,并使用Unity3D实现。

通过阅读文章《Triangulate》给出的伪代码进行具体编写,我加了些注释:

subroutine triangulate
input : vertex list
output : triangle list
initialize the triangle list
determine the supertriangle
add supertriangle vertices to the end of the vertex list
add the supertriangle to the triangle list
for each sample point in the vertex list #遍历传入的每一个点 initialize the edge buffer #这里要重置边缓冲区 for each triangle currently in the triangle list
calculate the triangle circumcircle center and radius
if the point lies in the triangle circumcircle then #如果该点位于三角形外接圆内,则
add the three triangle edges to the edge buffer #将三个三角形边添加到边缓冲区
remove the triangle from the triangle list #从三角形列表中删除三角形
endif
endfor delete all doubly specified edges from the edge buffer #从边缓冲区中删除所有双重指定的边(例如线段AB和线段BA被当成两个线段存入)
this leaves the edges of the enclosing polygon only
add to the triangle list all triangles formed between the point #将三边和当前点进行组合成任意三角形保存到三角形列表
and the edges of the enclosing polygon endfor
remove any triangles from the triangle list that use the supertriangle vertices #从三角形列表中删除任何使用超三角形顶点的三角形
remove the supertriangle vertices from the vertex list #从顶点列表中删除超三角形顶点(如果不需要继续使用顶点数据,这一步也可以不做)
end

通过Unity的Gizmo和协程,下面是我制作的逐点插入法动图流程:

灰色空心圆 - 输入点

红色十字 - 循环当前点

黄色阶段 - 新的一轮操作开始

红色阶段 - 进行外接圆的筛选

绿色阶段 - 点在外接圆内,拆除三角形,并将边加入边缓冲(EdgeBuffer)

蓝色阶段 - 通过边缓冲的数据和当前顶点,重新组合新的三角形

关于外接圆的计算,取三角形任意两条边的垂线的交点即可得到圆心,圆心和三角形任意一点的线段长度即为半径。

注意:原文链接github的js代码,外接圆半径部分忘了开方(也可能是优化操作,但若参考其实现需要补上)。

更多的具体流程就不写了,理解还是要看伪代码,并且参考原文也很详细。

最后给出实现:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class DelaunayTriangle : MonoBehaviour
{
public struct EdgeBuffer
{
public Vector2 P0;
public Vector2 P1;
} public struct Triangle
{
public Vector2 A;
public Vector2 B;
public Vector2 C;
} public Transform[] points;//通过层级面板放入测试点
private Bounds mBounds;
private List<Triangle> mTriangleList = new List<Triangle>(); private void Update()
{
float kSuperTriangleScale = 5f;
float kStepDuration = 0.5f; Vector3 min = Vector3.one * 10000f;
Vector3 max = Vector3.one * -10000f; for (int i = 0; i < points.Length; i++)
{
Vector3 point = points[i].position;
if (point.x < min.x) min = new Vector3(point.x, min.y, min.z);
if (point.z < min.z) min = new Vector3(min.x, min.y, point.z); if (point.x > max.x) max = new Vector3(point.x, max.y, max.z);
if (point.z > max.z) max = new Vector3(max.x, max.y, point.z);
} mBounds = new Bounds() {min = min, max = max};
mBounds.size *= kSuperTriangleScale;//此处做法比较粗暴
mBounds.center += mBounds.extents * 0.5f; Vector2 superTriangleA = new Vector2(mBounds.min.x, mBounds.min.z);
Vector2 superTriangleB = new Vector2(mBounds.min.x, mBounds.max.z);
Vector2 superTriangleC = new Vector2(mBounds.max.x, mBounds.min.z); List<Vector2> vertexList = new List<Vector2>();
List<EdgeBuffer> edgeBufferList = new List<EdgeBuffer>();
mTriangleList.Clear();
mTriangleList.Add(new Triangle() {A = superTriangleA, B = superTriangleB, C = superTriangleC}); for (int i = 0; i < points.Length; i++)
{
Vector3 position = points[i].position;
vertexList.Add(new Vector2(position.x, position.z));
} vertexList.Add(superTriangleA);
vertexList.Add(superTriangleB);
vertexList.Add(superTriangleC); for (int i = 0; i < vertexList.Count; i++)//顶点遍历
{
Vector2 vertex = vertexList[i]; edgeBufferList.Clear(); for (int j = mTriangleList.Count - 1; j >= 0; j--)
{
Triangle triangle = mTriangleList[j]; (Vector2 center, float radius) = Circumcircle(triangle.A, triangle.B, triangle.C);
//外接圆计算 if (Vector2.Distance(vertex, center) <= radius)
{
edgeBufferList.Add(new EdgeBuffer() {P0 = triangle.A, P1 = triangle.B});
edgeBufferList.Add(new EdgeBuffer() {P0 = triangle.B, P1 = triangle.C});
edgeBufferList.Add(new EdgeBuffer() {P0 = triangle.C, P1 = triangle.A}); mTriangleList.RemoveAt(j);
}//若点在外接圆内则移除三角形,并将三角形三个边加入边缓冲
} Dedup(edgeBufferList);//边缓冲去重 for (int j = 0; j < edgeBufferList.Count; j++)
{
EdgeBuffer edgeBuffer = edgeBufferList[j];
Triangle triangle = new Triangle()
{
A = edgeBuffer.P0,
B = edgeBuffer.P1,
C = vertex
}; mTriangleList.Add(triangle);
}//重新组合三角形
} for (int j = mTriangleList.Count - 1; j >= 0; j--)
{
Triangle triangle = mTriangleList[j]; if (triangle.A == superTriangleA || triangle.B == superTriangleA || triangle.C == superTriangleA
|| triangle.A == superTriangleB || triangle.B == superTriangleB || triangle.C == superTriangleB
|| triangle.A == superTriangleC || triangle.B == superTriangleC || triangle.C == superTriangleC)
{
mTriangleList.RemoveAt(j);
}
}//移除连接超三角形的所有三角形
} private void Dedup(List<EdgeBuffer> edgeBufferList)
{
for (int i = edgeBufferList.Count - 1; i >= 0; i--)
{
for (int j = i - 1; j >= 0; j--)
{
EdgeBuffer x = edgeBufferList[i];
EdgeBuffer y = edgeBufferList[j]; if ((x.P0 == y.P0 && x.P1 == y.P1) || (x.P0 == y.P1 && x.P1 == y.P0))
{
edgeBufferList.RemoveAt(i);
edgeBufferList.RemoveAt(j); i = edgeBufferList.Count - 1;
break;
}
}
}
} private (Vector2 center, float radius) Circumcircle(Vector2 a, Vector2 b, Vector3 c)
{
float kEps = 0.000001f; float x1 = a.x;
float y1 = a.y;
float x2 = b.x;
float y2 = b.y;
float x3 = c.x;
float y3 = c.y;
float fabsy1y2 = Mathf.Abs(y1 - y2);
float fabsy2y3 = Mathf.Abs(y2 - y3);
float xc = 0f;
float yc = 0f;
float m1 = 0f;
float m2 = 0f;
float mx1 = 0f;
float mx2 = 0f;
float my1 = 0f;
float my2 = 0f;
float dx = 0f;
float dy = 0f; if (fabsy1y2 < kEps)
{
m2 = -((x3 - x2) / (y3 - y2));
mx2 = (x2 + x3) / 2.0f;
my2 = (y2 + y3) / 2.0f;
xc = (x2 + x1) / 2.0f;
yc = m2 * (xc - mx2) + my2;
} else if (fabsy2y3 < kEps)
{
m1 = -((x2 - x1) / (y2 - y1));
mx1 = (x1 + x2) / 2.0f;
my1 = (y1 + y2) / 2.0f;
xc = (x3 + x2) / 2.0f;
yc = m1 * (xc - mx1) + my1;
} else
{
m1 = -((x2 - x1) / (y2 - y1));
m2 = -((x3 - x2) / (y3 - y2));
mx1 = (x1 + x2) / 2.0f;
mx2 = (x2 + x3) / 2.0f;
my1 = (y1 + y2) / 2.0f;
my2 = (y2 + y3) / 2.0f;
xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
yc = (fabsy1y2 > fabsy2y3) ? m1 * (xc - mx1) + my1 : m2 * (xc - mx2) + my2;
} dx = x2 - xc;
dy = y2 - yc; return (center: new Vector2(xc, yc), radius: Mathf.Sqrt(dx * dx + dy * dy));
} private void OnDrawGizmos()
{
for (int i = 0; i < mTriangleList.Count; i++)
{
Triangle triangle = mTriangleList[i]; Gizmos.DrawLine(new Vector3(triangle.A.x, 0f, triangle.A.y), new Vector3(triangle.B.x, 0f, triangle.B.y));
Gizmos.DrawLine(new Vector3(triangle.B.x, 0f, triangle.B.y), new Vector3(triangle.C.x, 0f, triangle.C.y));
Gizmos.DrawLine(new Vector3(triangle.C.x, 0f, triangle.C.y), new Vector3(triangle.A.x, 0f, triangle.A.y));
}
}
}

将脚本挂载至场景,并配置。测试效果如下:

Delaunay三角剖分实现的更多相关文章

  1. Voronoi图和Delaunay三角剖分

    刷题的时候发现了这么一个新的东西:Voronoi图和Delaunay三角剖分 发现这个东西可以$O(nlogn)$解决平面图最小生成树问题感觉非常棒 然后就去学了.. 看的n+e的blog,感谢n+e ...

  2. paper 153:Delaunay三角剖分算法--get 这个小技术吧!

    直接摘自百度百科,希望大家能根据下面的介绍稍微理顺思路,按需使用,加油! 解释一下:点集的三角剖分(Triangulation),对数值分析(比如有限元分析)以及图形学来说,都是极为重要的一项预处理技 ...

  3. Delaunay三角剖分及MATLAB实例

    https://blog.csdn.net/piaoxuezhong/article/details/68065170 一.原理部分 点集的三角剖分(Triangulation),对数值分析(如有限元 ...

  4. Delaunay三角剖分算法

    在图像处理中,经常会使用到三角剖分算法: 具体定义及其算法可以参考:http://baike.so.com/doc/5447649.html 下面放出来代码: Delaunay接口为存C: 测试是使用 ...

  5. Delaunay三角剖分

    Bowyer-Watson算法:1.假设已经生成了连接若干个顶点的Delaunay三角网格:2.加入一个新的节点,找出所有外接圆包含新加入节点的三角形,并将这些三角形删除形成一个空洞:3.空洞的节点与 ...

  6. 使用Delaunay三角剖分解决求多边形面积的问题

    朋友那边最近有个需求,需要框选一个选区,然后根据选区中的点求出面积.并且让我尝试用Delaunay来解决 似乎音译过来应该是德诺类 大致如下: 我在github上找了一个可以用的Delaunay库 h ...

  7. Voronoi图与Delaunay三角剖分

    详情请见[ZJOI2018]保镖 题解随笔 - 99 文章 - 0 评论 - 112

  8. 三角剖分算法(delaunay)

    开篇 在做一个Low Poly的课题,而这种低多边形的成像效果在现在设计中越来越被喜欢,其中的低多边形都是由三角形组成的. 而如何自动生成这些看起来很特殊的三角形,就是本章要讨论的内容. 项目地址:  ...

  9. Delaunay Triangulation in OpenCascade

    Delaunay Triangulation in OpenCascade eryar@163.com 摘要:本文简要介绍了Delaunay三角剖分的基础理论,并使用OpenCascade的三角剖分算 ...

  10. Triangle - Delaunay Triangulator

    Triangle - Delaunay Triangulator  eryar@163.com Abstract. Triangle is a 2D quality mesh generator an ...

随机推荐

  1. OpenHarmony——内核对象队列之算法详解(下)

    前言 OpenAtom OpenHarmony(以下简称"OpenHarmony") LiteOS-M 内核是面向 IoT 领域构建的轻量级物联网操作系统内核,具有小体积.低功耗. ...

  2. OpenAtom OpenHarmony分论坛,今天14:00见!附大事记精彩发布

    2022开放原子全球开源峰会 OpenAtom OpenHarmony分论坛 万物互联,使能千行百业 整装待发!精彩今日揭晓与您相约7月27日 14:00  

  3. Java 包装类:原始数据类型与迭代器

    Java Iterator Iterator 接口提供了一种迭代集合的方法,即顺序访问集合中的每个元素.它支持 hasNext() 和 next() 方法,用于检查是否存在下一个元素以及获取下一个元素 ...

  4. C 语言教程:数据类型和格式说明符

    C 语言中的数据类型 C 中的变量必须是指定的数据类型,并且您必须在 printf() 函数中使用格式说明符来显示它: // 创建变量 int myNum = 5; // 整数(没有小数点) floa ...

  5. C#读 .properties文件

    引用:https://www.cnblogs.com/wardensky/p/5331851.html properties文件 MONGO_URL = mongodb://172.16.20.3/s ...

  6. HMS Core打造影音娱乐行业解决方案,助推视听新浪潮

    6月28日,HDD·HMS Core. Sparkle影音娱乐线上沙龙在各大直播平台与开发者们见面.本次线上沙龙围绕影音娱乐行业现状观察和趋势.用户数据洞察分析以及HMS Core影音娱乐行业解决方案 ...

  7. Tailscale 的 TLS 证书过期,网站挂了 90 分钟!

    3月7日,基于 WireGuard 的知名 VPN 厂商 Tailscale 的官方网站 tailscale.com 因 TLS 证书过期而中断服务约90分钟. 虽然影响有限,但这起事件还是在 Hac ...

  8. 数据库自增ID用完了会怎么样?

    记一个数据库方面的面试题:数据库自增ID用完了会怎么样? MySQL中 int 类型是4个字节,如果是有符号,那就是 [ -2^31 , 2^31-1 ], 如果是无符号,那就是 [ -2^31 , ...

  9. 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(3)

    1.问题描述: 调用HarmonyOS API发送通知,能查到状态是送达终端设备,但是终端设备上没收到通知卡片. 解决方案: 通知应用大图标不能超过30kb,通知参数限制,参考如下:https://g ...

  10. 鸿蒙智联生态产品《接入智慧生活App开发指导》(官方更新版)

    原文:https://mp.weixin.qq.com/s/BDC-12aiZz2EhtjYLR7QIg,点击链接查看更多技术内容. 在HarmonyOS Connect生态产品应用开发过程中,很多开 ...