Earcut多边形三角剖分:从耳切算法到WebGL渲染实践
简介这是一份基于耳切法Ear Clipping的多边形三角化实现专门面向需要从无序顶点快速生成三角面片索引的开发者。资源内含改进后的耳朵切片算法利用z阶曲线散列进行优化并尝试扩展处理带孔洞、扭曲多边形、退化甚至自相交等复杂数据适合用于地理围栏区域划分、游戏地图碰撞体构建、几何算法学习与验证等场景。压缩包共33个文件以C/C源码为主体10个头文件与7个C文件构成核心算法4个HPP文件与2个CPP文件包含示例与集成入口另有4个CSV测试数据如顶点表、索引表方便验证效果并附带Visual Studio工程sln/vcxproj可直接编译运行整体仅4.29MB。目前已有454人学习/下载。该实现参考FIST的工业级三角化思路与David Eberly的耳切法论文内含libtess2对比模块读者可对照测试数据检查输出快速掌握优化技巧并嵌入自己的几何处理管线是一份精简而完整的实用参考。1. 多边形三角剖分的现成答案这套 Earcut 打包为什么值得用批处理分割、GIS 地块聚合、连续轮廓填充、WebGL 地图这块任何一次将多边形化作 GPU 能吃的网格传进去的顶点、传回来的索引、组装的三角形这套 Earcut 实现的三角剖分就是那个少写千行代码的出口。多边形装洞、凹形状翻转、顶点上百上千它一个函数调用出来一套可直接送进缓冲区的索引前端渲染不再靠 Canvas 硬填色后端生成网格拿到顶点和三角形也能直接驱动碰撞检测。做地块聚合、小工具可视化、图像外轮廓填充的都可以把三角化这个环节换成它。本包其实是一个打包好的 Earcut 算法实现核心就一个函数接受一组坐标返回非重叠三角形的顶点索引列表典型用途是把任意简单多边形允许凹、允许孔洞转成 GPU 或计算几何可以直接消费的三角形集合。对熟悉 two-dimensional 图形的人来说这就是最省事的现成模块不用自己实现 ear clipping也不用引入庞大几何库拆开包、调用 API、处理数组完事。2. 为什么会选 Earcut从耳切算法到带洞多边形的处理方式2.1 耳切法为什么高效三角剖分主流思路是 Delaunay但 Earcut 走的路线是耳切法。它不停遍历多边形顶点找到“耳朵”即一个顶点其左右邻居连成的对角线完全落在多边形内部剪掉循环往复最终只剩一个三角形。耳切最坏情况是 O(n²)但对大多数实际轮廓剪耳时受 Floyd 启发每一步都把多边形切成两个子多边形属于一种快速剖分策略。因为操作的是二维坐标每个耳部判定只需要看点积叉积几何判定简单几千个顶点也能保持在可接受范围。常见场景下的表现接近 O(n log n)。对大多数实现来说处理凹多边形也不难但纯耳切碰到孔洞容易出错。这套打包方案里孔洞会先与外部轮廓合并再执行统一剖分输出结果直接就是带洞网格的三角形列表不会额外生成桥接边也不用业务层担心打通洞的连接顺序。2.2 索引输出的意义展开坐标后从 API 返回的是一组 Int32Array或普通数组三角形索引。假设传入顶点坐标列表为 [a0a1, a1a2, a2a3, ...]其中的数字是原始坐标数组下标除以维度后的顶点序号。例如多边形四个坐标依次是 (1,1)、(5,1)、(5,5)、(1,5)那么输出索引 [0,1,2, 0,2,3] 表示两个三角形顶点 0-1-2 与 0-2-3。这样做的好处是几何结构只维护一份顶点缓存渲染时按索引顺序提交 gl.drawElements 即可。不需要额外传递每个三角形的独立坐标也节省顶点缓冲区的存储量。做物理碰撞时按索引逐三角形构建检测形状同样非常直接。2.3 数值类型与维度参数函数签名本质就一个参数维度 dim 决定坐标的排列方式。默认 dim2数组按 (x1,y1, x2,y2, ...) 排列。如果传入 dim3坐标排列为 (x,y,z, x,y,z, ...)此时 z 轴可以用于携带高度值或纹理映射参数。注意剖分过程完全忽略 z 轴z 值只是跟着顶点一起消耗索引并不会影响三角形划分想得到真正的 3D 曲面剖分它不会帮你做投影需要先把三维坐标投影到二维再剖分。3. 把这套打包展开最小可跑通的命令序列3.1 引入模块与数据准备以 JavaScript 写作最小示例准备好顶点坐标。使用 yarn/pnpm/npm 安装依赖后直接按 CommonJS 方式导入。const earcut require(./earcut); // 假设把打包文件放到项目根目录 // 定义三角形外环顺时针 const outerRing [ [0, 0], [10, 0], [10, 10], [0, 10] ]; // 定义三角形内孔逆时针 const hole [ [3, 3], [7, 3], [7, 7], [3, 7] ]; const coords []; outerRing.forEach(p coords.push(p[0], p[1])); const holeStart coords.length / 2; hole.forEach(p coords.push(p[0], p[1])); const triangles earcut(coords, [holeStart]); console.log(triangles);逻辑说明先拼接全部顶点为一个线性数组然后把孔洞起点记录为独立数组传给 holes 参数。earcut 内部会先对孔洞做剪耳融合然后统一三角化。输出索引的顺序为外环上的顶点下标天然在前孔洞顶点下标依次排在后面。参数说明holes 数组里每个值必须是孔洞的起始顶点在坐标数组中的下标除以 dim 后而不是坐标序号。多孔洞就按照这样的方式传一个数组进来。返回的是每个三角形三个顶点的索引排列方式是三的倍数。3.2 从索引恢复三角形坐标拿到索引后往往要组装成坐标集合。const positions [0, 0, 10, 0, 10, 10, 0, 10, 3, 3, 7, 3, 7, 7, 3, 7]; const triangleIndices earcut(positions, [4]); const trianglesCoords []; for (let i 0; i triangleIndices.length; i 3) { const a triangleIndices[i]; const b triangleIndices[i 1]; const c triangleIndices[i 2]; trianglesCoords.push([ [positions[a * 2], positions[a * 2 1]], [positions[b * 2], positions[b * 2 1]], [positions[c * 2], positions[c * 2 1]] ]); } console.log(trianglesCoords);参数说明由于默认 dim2顶点下标换算回坐标时需要乘以 2。整体逻辑是三位一组输出三角形。此时看 triangles 数组长度正好是 3 的倍数代表三角形个数是该长度除以 3。若开启了 dim3则换算时乘 3。4. 实际业务中的三个常用侧重点4.1 大数据量轮廓与性能取舍地图围栏、行政区轮廓、等高线这类典型输入顶点数量常在几千甚至上万。直接交给默认配置通常也能扛住。但有两个参数值得调一个是对退化三角形的剔除一个是排序策略。实际测试中坐标抖动会造成极窄的三角形带洞且顶点聚类严重的轮廓最容易出现这种问题。建议在业务侧先使用一个轻量的坐标approx简化将顶点距离过近的点合并再丢给剖分。输出三角形数量会明显减少整体网格更干净。4.2 带孔洞多边形顺序是玄学吗外环使用顺时针、孔洞使用逆时针是常见惯例但本包采用的是“内部自动识别”策略。如果输入数据方向统一处理还好但如果外环和孔洞方向相反注意力就要放在孔洞起点数组的构造上。核心要求是孔洞必须完全落在外部多边形内部且彼此不相交否则剖分会生成负面三角形。另外多个孔洞时如果孔洞之间有交叉重叠剖分结果可能会包含自交三角形这种输入属于非法多边形没有算法能完全兜底。直接建议在业务逻辑层做一次多边形合法性校验检测边是否相交、孔洞是否越界。4.3 从索引到渲染的一步到位配置拿到三角形索引直接生成 WebGL 渲染所需的数据。const renderVertices []; for (let i 0; i triangleIndices.length; i) { const index triangleIndices[i]; renderVertices.push(coords[index * 2], coords[index * 2 1]); } // 再写入 buffer这样做的问题在于三角形共享顶点时会重复存储浪费显存和绘制效率。常见做法是把 Earcut 的索引直接上传到 ELEMENT_ARRAY_BUFFER配合顶点属性缓冲使用 drawElements而不是先重构。如果渲染引擎强制要求每个三角形独立顶点则上面的展开方式可行但注意要同步展开 UV 等属性。5. Earcut 落地最容易翻车的四个坑5.1 孔洞数组起点传成序号而非下标现象传入 holes 后三角形乱七八糟甚至出现外部边与内部孔洞交错渲染完全不可控。原因api 的 holes 参数要求的是顶点下标即线性坐标数组中的位置除以维度而不是第几个顶点。手动拼接坐标时经常按“第几个坐标点”来传偏移一位导致内部桥接混乱。解决把孔洞起点记录为 coords.length / dim 的值而不是单独计数。例如已有 6 个坐标3 个顶点第二个孔洞起点写 3不写 2。多一个孔就记录一次当前坐标数组中的下标。5.2 坐标浮点抖动导致退化三角形现象剖分结果里有面积接近 0 的狭长三角形渲染时出现闪面、反光异常物理碰撞检测多出很多无效多边形。原因输入顶点带有大量小数点后多位如 1.0000001 与 1.0000000 两个点距离极近剖分时形成极小三角形除非运行强制合并。解决预处理时做一个坐标抖动修复常见的做法是保留 3 位小数或按一定阈值合并相邻顶点。下面给出一段简单的合并逻辑。function dedupeVertices(coords, tol 1e-4) { const unique []; const map new Map(); for (let i 0; i coords.length; i 2) { const x coords[i], y coords[i 1]; let bestIdx -1; for (let u 0; u unique.length; u) { if (Math.abs(unique[u][0] - x) tol Math.abs(unique[u][1] - y) tol) { bestIdx u; break; } } if (bestIdx -1) { bestIdx unique.length; unique.push([x, y]); } map.set(i / 2, bestIdx); } return { unique, map }; }5.3 自交多边形处理结果难以预期现象输入多边形边与边相交输出会出现重叠三角形视觉上出现面片遮挡边界变得渲染错误。原因耳切算法要求输入是简单多边形边不能交叉。自交区域没有明确定义“内部”earcut 无法保证正确。解决不要试图用 earcut 兜底先修复输入多边形。常见做法是先用几何库把自交多边形转为若干非自交多边形或者将形状按点位拆成多个子轮廓分别剖分。业务上给用户绘制工具的应实时捕获最后一次点位的连线若与已有边相交就在界面上给出提示源头规避。5.4 多边形方向混淆导致的孔洞删除现象三角形数量比预期少孔洞区域被填充了好像洞不存在一样。原因如果孔洞的顶点顺序与外环一致earcut 可能会将孔洞当作内部独立的轮廓处理而不是融合切除最终生成的三角形覆盖了孔洞区域。解决在调用前对每个孔洞执行一次方向判断。代码中判断面积符号并做翻转。function ringArea(coords, start, end, dim 2) { let area 0; for (let i start, j end - dim; i end; j i, i dim) { area coords[i] * coords[j 1] - coords[j] * coords[i 1]; } return area / 2; } // 若外环为正、孔洞在外部表现一致则需要将孔洞方向取反很多时间花在“为什么孔洞没打掉”上往往是方向没统一。本包虽然对方向有一定容忍但工程上仍建议统一外环顺时针、内环逆时针省去后续排查。6. 进阶用法从三角网格转成可用的邻接结构三角剖分输出具备天然邻接关系但索引列表本身不直接表达每个三角形的邻居是谁。在实现物理碰撞、网格简化、跳跃 flooding 算法处理时需要把索引转为邻接结构。常见做法是建立半边结构。function buildAdjacency(triangles, vertexCount) { const adj new Array(vertexCount).fill(null); // 记录每条半边起点终点 const halfEdge {}; for (let t 0; t triangles.length; t 3) { const a triangles[t], b triangles[t 1], c triangles[t 2]; const edges [ [a, b], [b, c], [c, a] ]; edges.forEach(([from, to], k) { const key from * vertexCount to; const oppKey to * vertexCount from; if (halfEdge[oppKey] ! undefined) { // 配对成功写邻接表 const triOpp halfEdge[oppKey]; // 记录三角形 tri 与三角形 triOpp 相邻 } else { halfEdge[key] t / 3; } }); } return adj; }代码逻辑说明利用顶点索引构建 key遍历当前三角形的三条边查找是否存在反向边如果存在说明邻接三角形已经出现两端共享这条边。使用该结构后续计算法线、平滑、轮廓提取都能直接找到邻居三角形。参数说明vertexCount 必须等于剖分输入的原始顶点去重后的数量如果直接使用 earcut 索引则 vertexCount 为坐标数组长度除以维度。如果用去重后的顶点更新索引则使用新的计数。我自己的切身体会是任何三角剖分方案都要在输入侧多花时间做合法性检查合法输入与非法输入的结果差距极大。不要在数据异常时怀疑剖分算法有问题先让数据解释自己。希望这组笔记能帮你减少一些试错时间。本文还有配套的精品资源点击获取

相关新闻

连续时间LTI系统频域分析与滤波器设计:从时域卷积到巴特沃斯实现

连续时间LTI系统频域分析与滤波器设计:从时域卷积到巴特沃斯实现

简介:这份资源是面向通信工程、电子信息工程等专业本科生与研究生的信号与系统综合实验报告,围绕连续时间LTI系统展开时域与频域分析及滤波器设计。内容涵盖稳定系统的零极点设计、直接/级联/并联三种实现结构的信号流图与仿真对比、参数微扰下零极点与稳…

2026/10/17 7:42:02 阅读更多 →
算法导论英文原版第三版PDF:从伪代码到红黑树实现与复杂度分析

算法导论英文原版第三版PDF:从伪代码到红黑树实现与复杂度分析

简介:《算法导论》英文原版教材(第三版)PDF,面向计算机专业学生、算法学习者与求职备考人群,用于系统建立算法设计与分析的知识体系。全书共34章,从算法在计算中的角色讲起,依次覆盖插入排序、分…

2026/10/15 20:56:57 阅读更多 →
Win7 64位下USB转串口驱动安装指南:CH340/CP210x/FTDI/PL2303排错全收录

Win7 64位下USB转串口驱动安装指南:CH340/CP210x/FTDI/PL2303排错全收录

简介:这是一份专为 Windows 7 64 位系统准备的 USB 转串口驱动资源,适用于需要通过 USB 接口连接老式串行设备(如工业控制、GPS、调制解调器等)的用户,解决现代电脑缺少物理串口导致设备无法识别的问题。压缩包共含 25…

2026/10/15 20:56:57 阅读更多 →

最新新闻

ThreadX串口通信实战:从信号量到DMA空闲中断的丢数据排查指南

ThreadX串口通信实战:从信号量到DMA空闲中断的丢数据排查指南

简介:本资源面向STM32嵌入式开发者与实时操作系统学习者,聚焦ThreadX下UART串口DMA通信的完整工程实现,解决裸机串口收发效率低、任务间同步困难等问题。工程基于STM32CubeMX生成,结合ThreadX的队列与信号量机制,演示D…

2026/10/17 7:42:12 阅读更多 →
从Agent到AI IDE:一位开发者的年度工具链进化实录

从Agent到AI IDE:一位开发者的年度工具链进化实录

年初那阵子,我参加了几场线上的技术交流,大家开口闭口都是Agent,各种演示视频看着确实唬人——什么自动写周报、自动跑数据分析、自动修Bug。到了下半年,风向变了,圈子里的热门话题从Agent转向了AI IDE,一批…

2026/10/17 7:42:12 阅读更多 →
从零搭建深度学习围棋AI:特征编码、网络训练与MCTS联调实战

从零搭建深度学习围棋AI:特征编码、网络训练与MCTS联调实战

把特征、网络、搜索这三件事从"听说过"变成"跑得通",是我写这篇续篇最想解决的问题。上一篇写深度学习与围棋的结合思路时,有不少读者留言说原理能看懂,但一到自己动手搭系统就卡壳:棋盘状态怎么编码&#xf…

2026/10/17 7:42:12 阅读更多 →
从零构建围棋AI:深度学习与蒙特卡洛树搜索实战解析

从零构建围棋AI:深度学习与蒙特卡洛树搜索实战解析

先算一笔账。19路围棋的合法局面数量大约在10的170次方量级,这个数字比宇宙中的原子总数还要多出不少量级。传统搜索树和简单局面评估在这种复杂度面前基本是失效的,这也是二十多年前程序员用绝对搜索算法做围棋程序时始终卡在业余低段的根本原因。深度学…

2026/10/17 7:42:12 阅读更多 →
数据增强实战指南:五大策略提升竞赛模型泛化能力

数据增强实战指南:五大策略提升竞赛模型泛化能力

数据竞赛打久了,你会越来越明显地感觉到一件事:模型结构的边际收益已经越来越低,真正让你在排行榜上稳住位置的,往往是别人看不见的那部分数据工作。数据增强就是其中最基础、也最容易被低估的一块。在大数据竞赛里,数…

2026/10/17 7:42:12 阅读更多 →
【共创稿事节】HarmonyOS 7 Agent 开发实战 09:Agent 测试评测集

【共创稿事节】HarmonyOS 7 Agent 开发实战 09:Agent 测试评测集

做 Agent 应用最怕的不是「写不出第一版」,而是「改了第三版之后,发现第一版明明答对的题现在答错了」。大模型有随机性、提示词有耦合、工具调用有边界,靠人肉聊天抽查既覆盖不全又无法回放。这一篇我们给 Agent 配上一套离线评测集&#xf…

2026/10/17 7:41:11 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →