耳切法简述

耳切法简述

将简单多边形分解成三角形称为多边形的三角剖分。对n个顶点的简单多边形的任何三角剖分都有n-2个三角形。其中最简单的算法,称为耳切法(EarClipping)

耳的定义

多边形的一个 “耳” 是由 V i 0 V_{i_{0}} Vi0 V i 1 V_{i_{1}} Vi1 V i 2 V_{i_{2}} Vi2三个连续顶点形成的三角形,其中 V i 1 V_{i_{1}} Vi1是一个凸顶点,该顶点的内角小于π弧度,从 V i 0 V_{i_{0}} Vi0 V i 2 V_{i_{2}} Vi2的线段完全位于多边形内部,该三角形内部除自身顶点外无其他多边形顶点。线段< V i 0 V_{i_{0}} Vi0, V i 2 V_{i_{2}} Vi2>称为多边形的对角线。顶点 V i 1 V_{i_{1}} Vi1称为耳尖。

算法步骤

第一步是将多边形存储为双向链表,以便可以快速删除耳尖。构建此列表是一个 O ( n ) O(n) O(n)过程。
第二步是迭代顶点并找到耳朵。对于每个顶点和相应的三角形,测试所有其他顶点,看看是否有任何顶点在三角形内部;如果没有一个在里面,就有耳朵。如果至少有一个在里面,就没有耳朵。实际上,在三角形测试中只考虑反射顶点就足够了。反射顶点是由共享它的两条边形成的内角大于 π 弧度的顶点。

多边形的数据结构使用数组同时维护四个双链表进行存储,而不是在标准列表数据结构中动态分配和释放内存。
多边形的顶点存储在循环列表 L L L中,凸顶点存储在线性列表 C C C中,反射顶点存储在线性列表 R R R中,耳尖存储在循环列表 E E E中。
图一 简单多边形

上图的简单多边形,其顶点位置为:
V 0 = ( 3 , 48 ) , V 1 = ( 52 , 8 ) , V 2 = ( 99 , 50 ) , V 3 = ( 138 , 25 ) , V 4 = ( 175 , 77 ) , V 5 = ( 131 , 72 ) , V 6 = ( 111 , 113 ) , V 7 = ( 72 , 43 ) , V 8 = ( 26 , 55 ) , V 9 = ( 29 , 100 ) V_{0}=(3,48), V_{1}=(52,8), V_{2}=(99,50), V_{3}=(138,25), V_{4}=(175,77), V_{5}=(131,72), V_{6}=(111,113), V_{7}=(72,43), V_{8}=(26,55), V_{9}=(29,100) V0=(3,48),V1=(52,8),V2=(99,50),V3=(138,25),V4=(175,77),V5=(131,72),V6=(111,113),V7=(72,43),V8=(26,55),V9=(29,100)

  1. 凸顶点列表 C = 0 , 1 , 3 , 4 , 6 , 9 C=0,1,3,4,6,9 C=0,1,3,4,6,9
    反射顶点的初始列表 R = 2 , 5 , 7 , 8 R=2,5,7,8 R=2,5,7,8
    耳朵的列表 E = 3 , 4 , 6 , 9 E=3,4,6,9 E=3,4,6,9

    顶点 3 3 3 的耳朵去掉,所以三角剖分中的第1个三角形 T 2 , 3 , 4 T_{2,3,4} T2,3,4
    注意:顶点 8 8 8在三角形 T 9 , 0 , 1 T_{9,0,1} T9,0,1中,顶点 7 7 7在三角形 T 0 , 1 , 2 T_{0,1,2} T0,1,2 中。
    1

  2. 凸顶点的列表 C = 0 , 1 , 4 , 6 , 9 C=0,1,4,6,9 C=0,1,4,6,9
    反射顶点的列表 R = 2 , 5 , 7 , 8 R=2,5,7,8 R=2,5,7,8
    耳朵的列表 E = 4 , 6 , 9 E=4,6,9 E=4,6,9

    顶点 4 4 4 的耳朵去掉,所以三角剖分中的第2个三角形 T 2 , 4 , 5 T_{2,4,5} T2,4,5
    2

  3. 凸顶点的列表 C = 0 , 1 , 5 , 6 , 9 C=0,1,5,6,9 C=0,1,5,6,9
    反射顶点的列表 R = 2 , 7 , 8 R=2,7,8 R=2,7,8
    耳朵的列表 E = 5 , 6 , 9 E=5,6,9 E=5,6,9

    顶点 5 5 5 的耳朵去掉,所以三角剖分中的第3个三角形 T 2 , 5 , 6 T_{2,5,6} T2,5,6
    3

  4. 凸顶点的初始列表 C = 0 , 1 , 2 , 6 , 9 C=0,1,2,6,9 C=0,1,2,6,9
    反射顶点的初始列表 R = 7 , 8 R=7,8 R=7,8
    耳朵的列表 E = 6 , 9 E=6,9 E=6,9

    顶点 6 6 6 的耳朵去掉,所以三角剖分中的第4个三角形 T 2 , 6 , 7 T_{2,6,7} T2,6,7
    4

  5. 凸顶点的列表 C = 0 , 1 , 2 , 9 C=0,1,2,9 C=0,1,2,9
    反射顶点的列表 R = 7 , 8 R=7,8 R=7,8
    耳朵的列表 E = 9 , 2 E=9,2 E=9,2

    顶点 9 9 9 的耳朵去掉,所以三角剖分中的第5个三角形 T 0 , 8 , 9 T_{0,8,9} T0,8,9
    5

  6. 凸顶点的列表 C = 0 , 1 , 2 , 8 C=0,1,2,8 C=0,1,2,8
    反射顶点的列表 R = 7 R=7 R=7
    耳朵的列表 E = 0 , 2 , 8 E=0,2,8 E=0,2,8

    顶点 0 0 0 的耳朵去掉,所以三角剖分中的第6个三角形 T 0 , 1 , 8 T_{0,1,8} T0,1,8
    6

  7. 凸顶点的列表 C = 1 , 2 , 8 C=1,2,8 C=1,2,8
    反射顶点的列表 R = 7 R=7 R=7
    耳朵的列表 E = 2 , 8 E=2,8 E=2,8

    顶点 2 2 2 的耳朵去掉,所以三角剖分中的第7个三角形 T 1 , 2 , 7 T_{1,2,7} T1,2,7,同时最后一个三角形 T 1 , 7 , 8 T_{1,7,8} T1,7,8

7
8. 剖分后的三角列表

T 2 , 3 , 4 T_{2,3,4} T2,3,4 T 2 , 4 , 5 T_{2,4,5} T2,4,5 T 2 , 5 , 6 T_{2,5,6} T2,5,6 T 2 , 6 , 7 T_{2,6,7} T2,6,7 T 0 , 8 , 9 T_{0,8,9} T0,8,9 T 0 , 1 , 8 T_{0,1,8} T0,1,8 T 1 , 2 , 7 T_{1,2,7} T1,2,7 T 1 , 7 , 8 T_{1,7,8} T1,7,8
8

代码示例

虚幻引擎的实现源码:UE\Engine\Source\Runtime\GeometryCore\Private\CompGeom\PolygonTriangulation.cpp

//
// Triangulate using ear clipping
// This is based on the 3D triangulation code from MeshDescription.cpp, simplified for 2D polygons
// 
template<typename T>
void PolygonTriangulation::TriangulateSimplePolygon(const TArray<TVector2<T>>& VertexPositions, TArray<FIndex3i>& OutTriangles, bool bOrientAsHoleFill)
{
	struct Local
	{
		static inline bool IsTriangleFlipped(T OrientationSign, const TVector2<T>& VertexPositionA, const TVector2<T>& VertexPositionB, const TVector2<T>& VertexPositionC)
		{
			T TriSignedArea = TTriangle2<T>::SignedArea(VertexPositionA, VertexPositionB, VertexPositionC);
			return TriSignedArea * OrientationSign < 0;
		}
	};
	
	
	OutTriangles.Reset();

	// Polygon must have at least three vertices/edges, or result is empty
	int32 PolygonVertexCount = VertexPositions.Num();
	if (PolygonVertexCount < 3)
	{
		return;
	}


	// compute signed area of polygon
	double PolySignedArea2 = 0;
	for (int i = 0; i < PolygonVertexCount; ++i)
	{
		const TVector2<T>& v1 = VertexPositions[i];
		const TVector2<T>& v2 = VertexPositions[(i + 1) % PolygonVertexCount];
		PolySignedArea2 += v1.X*v2.Y - v1.Y*v2.X;
	}

	bool bIsClockwise = PolySignedArea2 < 0;
	T OrientationSign = (bIsClockwise) ? -T(1) : T(1);




	// If perimeter has 3 vertices, just copy content of perimeter out 
	if (PolygonVertexCount == 3)
	{
		OutTriangles.Add(bOrientAsHoleFill ? FIndex3i(0, 2, 1) : FIndex3i(0, 1, 2));
		return;
	}

	// Make a simple linked list array of the previous and next vertex numbers, for each vertex number
	// in the polygon.  This will just save us having to iterate later on.
	TArray<int32> PrevVertexNumbers, NextVertexNumbers;

	PrevVertexNumbers.SetNumUninitialized(PolygonVertexCount, false);
	NextVertexNumbers.SetNumUninitialized(PolygonVertexCount, false);

	for (int32 VertexNumber = 0; VertexNumber < PolygonVertexCount; ++VertexNumber)
	{
		PrevVertexNumbers[VertexNumber] = VertexNumber - 1;
		NextVertexNumbers[VertexNumber] = VertexNumber + 1;
	}
	PrevVertexNumbers[0] = PolygonVertexCount - 1;
	NextVertexNumbers[PolygonVertexCount - 1] = 0;


	int32 EarVertexNumber = 0;
	int32 EarTestCount = 0;
	for (int32 RemainingVertexCount = PolygonVertexCount; RemainingVertexCount >= 3; )
	{
		bool bIsEar = true;

		// If we're down to only a triangle, just treat it as an ear.  Also, if we've tried every possible candidate
		// vertex looking for an ear, go ahead and just treat the current vertex as an ear.  This can happen when 
		// vertices are collinear or other degenerate cases.
		if (RemainingVertexCount > 3 && EarTestCount < RemainingVertexCount)
		{
			const TVector2<T>& PrevVertexPosition = VertexPositions[PrevVertexNumbers[EarVertexNumber]];
			const TVector2<T>& EarVertexPosition = VertexPositions[EarVertexNumber];
			const TVector2<T>& NextVertexPosition = VertexPositions[NextVertexNumbers[EarVertexNumber]];

			// Figure out whether the potential ear triangle is facing the same direction as the polygon
			// itself.  If it's facing the opposite direction, then we're dealing with a concave triangle
			// and we'll skip it for now.
			if (!Local::IsTriangleFlipped(
				OrientationSign, PrevVertexPosition, EarVertexPosition, NextVertexPosition))
			{
				int32 TestVertexNumber = NextVertexNumbers[NextVertexNumbers[EarVertexNumber]];

				do
				{
					// Test every other remaining vertex to make sure that it doesn't lie inside our potential ear
					// triangle.  If we find a vertex that's inside the triangle, then it cannot actually be an ear.
					const TVector2<T>& TestVertexPosition = VertexPositions[TestVertexNumber];
					if (TTriangle2<T>::IsInside(PrevVertexPosition, EarVertexPosition, NextVertexPosition, TestVertexPosition))
					{
						bIsEar = false;
						break;
					}

					TestVertexNumber = NextVertexNumbers[TestVertexNumber];
				} while (TestVertexNumber != PrevVertexNumbers[EarVertexNumber]);
			}
			else
			{
				bIsEar = false;
			}
		}

		if (bIsEar)
		{
			// OK, we found an ear!  Let's save this triangle in our output buffer.
			{
				int32 A = PrevVertexNumbers[EarVertexNumber]
					, B = EarVertexNumber
					, C = NextVertexNumbers[EarVertexNumber];
				OutTriangles.Add(bOrientAsHoleFill ? FIndex3i(A, C, B) : FIndex3i(A, B, C));
			}

			// Update our linked list.  We're effectively cutting off the ear by pointing the ear vertex's neighbors to
			// point at their next sequential neighbor, and reducing the remaining vertex count by one.
			{
				NextVertexNumbers[PrevVertexNumbers[EarVertexNumber]] = NextVertexNumbers[EarVertexNumber];
				PrevVertexNumbers[NextVertexNumbers[EarVertexNumber]] = PrevVertexNumbers[EarVertexNumber];
				--RemainingVertexCount;
			}

			// Move on to the previous vertex in the list, now that this vertex was cut
			EarVertexNumber = PrevVertexNumbers[EarVertexNumber];

			EarTestCount = 0;
		}
		else
		{
			// The vertex is not the ear vertex, because it formed a triangle that either had a normal which pointed in the opposite direction
			// of the polygon, or at least one of the other polygon vertices was found to be inside the triangle.  Move on to the next vertex.
			EarVertexNumber = NextVertexNumbers[EarVertexNumber];

			// Keep track of how many ear vertices we've tested, so that if we exhaust all remaining vertices, we can
			// fall back to clipping the triangle and adding it to our mesh anyway.  This is important for degenerate cases.
			++EarTestCount;
		}
	}

	check(OutTriangles.Num() > 0);
}

相关链接

  1. 多边形三角化
  2. TriangulationByEarClipping.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/945798.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

国内外大模型以及部署

国内15家AI大模型应用盘点 AI大模型 秘塔AI搜索 秘塔AI搜索免登录&#xff0c;免费的问答大模型。 开源大模型 Ollama Ollama是一个专注于提供 大语言模型&#xff08;LLM&#xff09; 本地化部署和运行的工具和资源的平台。它旨在帮助用户轻松地在自己的设备上运行和定制…

2024年终总结:非常充实的一年

一、业务方面 2024年是业务全面拓展与技术深耕的一年。从日常的开发维护到新产品研发&#xff0c;从降本增效到业务创新&#xff0c;每一步都在不断累积成长。以下是我的年度业务总结&#xff1a; 日常工作&#xff1a;聚焦于软件开发、维护、运营和售后工作&#xff0c;同时…

UE5材质节点VertexNormalWs/PixelNormalWS

VertexNormalWs顶点法线方向&#xff0c;此节点可以做物体上积雪、青苔等效果 PixelNormalWS像素法线方向

MAC环境安装(卸载)软件

MAC环境安装&#xff08;卸载&#xff09;软件 jdknode安装node&#xff0c;并实现不同版本的切换背景 卸载node从node官网下载pkg安装的node卸载用 homebrew 安装的node如果你感觉删的不够干净&#xff0c;可以再细分删除验证删除结果 jdk 1.下载jdk 先去官网下载自己需要的版…

玩具租赁系统设计与实现(文末附源码)

博主介绍&#xff1a;✌全网粉丝50W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLM…

C403 unity打开方法

1 unity hub右键以管理员方式打开。 2 注册登录账户 如果出现 如果还是不行&#xff0c;把地址栏的网址复制&#xff0c;在google浏览器中打开 如果出现安全策略&#xff0c;就不勾选安全防护 尝试方案1 把unityhub在任务管理器中关闭 如果验证码发送成功&#xff0c;还是进不…

log4j2的Strategy、log4j2的DefaultRolloverStrategy、删除过期文件

文章目录 一、DefaultRolloverStrategy1.1、DefaultRolloverStrategy节点1.1.1、filePattern属性1.1.2、DefaultRolloverStrategy删除原理 1.2、Delete节点1.2.1、maxDepth属性 二、知识扩展2.1、DefaultRolloverStrategy与Delete会冲突吗&#xff1f;2.1.1、场景一&#xff1a…

【记录】vue 添加全局 dialog 弹框

页面展示 代码 /components/GlobalDialog/index.vue <template><div class"global_dialog" v-if"isVisible"><div class"global_dialog_header"><div class"global_dialog_header_title">{{ title }}</d…

QT------模型/视图

一、模型/视图结构概述 基本原理&#xff1a; Qt 的模型/视图&#xff08;Model/View&#xff09;架构将数据的存储和显示分离&#xff0c;提高了代码的可维护性和复用性。模型&#xff08;Model&#xff09;&#xff1a;负责存储和管理数据&#xff0c;提供数据的访问接口&am…

【YOLO 项目实战】(12)红外/可见光多模态目标检测

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【YOLO 项目实战】&#xff08;10&#xff09;YOLO8 环境配置与推理检测 【YOLO 项目实战】&#xff08;11&#xff09;YOLO8 数据集与模型训练 【YOLO 项目实战】&#xff08;12&#xff09;红外/可…

HTML——38.Span标签和字符实体

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>span标签和字符实体</title><style type"text/css">h1{text-align: center;}p{text-indent: 2em;}span{color: red;}</style></head><…

太速科技-688-基于 VM1302的双路100G光纤PCIe4.0X16加速计算卡

基于 VM1302的双路100G光纤PCIe4.0X16加速计算卡 一、产品概述 基于Xilinx芯片方案基础上研发的一款双口100 G FPGA光纤以太网PCI-Express v4.0 x16智能加速计算卡&#xff0c;该智能卡拥有高吞吐量、低延时的网络处理能力以及辅助CPU进行网络功能卸载的能力&#xff0c…

KMP 2024 年总结,Kotlin 崛起的一年

2024 Google I/O 上正式官宣了 KMP&#xff08;Kotlin Multiplatform&#xff09;项目&#xff0c;它是 Google Workspace 团队的一项长期「投资」项目&#xff0c;由 JetBrains 开发维护和开源的项目&#xff0c;简单来说&#xff0c;JetBrains 主导&#xff0c;Google Worksp…

企业数字化转型的构念及实现路径

引言 随着信息技术的飞速发展&#xff0c;数字化转型已成为企业持续竞争力的关键。企业数字化转型不仅仅是技术的更新换代&#xff0c;更是一场涉及组织结构、业务流程、企业文化等多方面的深刻变革。本文将探讨企业数字化转型的构念&#xff0c;并提出实现路径。 企业数字化转…

精密制造动力箱行业需要哪种多功能电表

一、在精密制造动力箱行业&#xff0c;多功能电表的选择需要考虑以下几个方面&#xff1a; 电力参数测量 多功能电表应能够测量单相或三相的电流、电压、有功功率、无功功率、视在功率、频率、功率因数等电力参数。这些参数对于监控和管理动力箱的运行状态至关重要。 电能计量…

《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS架构介绍

1.3 架构介绍 HarmonyOS整体遵从分层架构设计&#xff0c;从下向上依次为&#xff1a;内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 組件”逐级展开&#xff0c;在多设备部署场景下&#xff0c;支持根据实际需求裁剪某些非必要的组件。HarmonyOS…

电脑有杂音滋滋滋响怎么处理?电脑有杂音解决指南

在日常使用电脑的过程中&#xff0c;您是否曾遭遇过这样令人困扰的问题&#xff1a;无论是播放音乐、观看视频还是进行语音通话时&#xff0c;电脑总会时不时地发出“滋滋滋”的电脑有杂音&#xff0c;严重影响了听觉体验和工作效率。这种现象并非无解&#xff0c;本文将为您提…

html+css+js网页设计 美食 家美食1个页面

htmlcssjs网页设计 美食 家美食1个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#xf…

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>

题目&#xff1a; 解析&#xff1a; 决策树&#xff1a; 代码设计&#xff1a; 代码&#xff1a; 写法一&#xff1a;path为全局变量 private int ret,path,aim;public int findTargetSumWays(int[] nums, int target) {aim target;dfs(nums,0);return ret;}private void…

VisualStudio 2019 升级遇到的问题及解决

事件起因 今天计划想研究下.net core&#xff08;后面版本直接称为 .net &#xff09;,发现 .net sdk 5.0 最新版本安装不成功。解决之后&#xff0c;真是手欠&#xff0c;看着Visual Studio 2019 有更新了&#xff0c;就直接点击了&#xff0c;这时才发现问题大了。。。 安装…