前言

先说一下,写这篇博文的动机,原文的博主代码写的十分潇洒,以至于代码说明和注释都没有,最近恰逢看到,所以以此博文来分析其中的算法和流程

参考博文:https://blog.csdn.net/linxinfa/article/details/78816362



github网址:https://github.com/linxinfa/Unity-ArbitraryPolygonMesh



先复习一下线代

向量的混合积的数学意义是:两个向量叉乘的结果是一个新向量,这个新向量垂直于原向量组成的平面,并且新向量的长度等于原向量合成的平行四边形的面积

向量的混合积是三个向量组成的平行六面体的体积。叉乘可以看成高是单位长度的平行六面体的体积,也就是其平行四边形的面积

操作步骤

  • 在场景中创建多个几何体作为mesh多边形的顶点
  • 在父物体上,创建几个子物体,顺时针摆放这些物体



代码步骤

  • 先通过叉乘计算多边形的面积,叉乘严格按照Inspector面板的摆放顺序进行计算,保证是同一顺序时针,通过判断计算面积结果正负性(Unity采用右手坐标系,但在此处还是加上判断),提前设置顶点(V[n])的顺序
    // 计算多边形的面积,按照同一顺序进行叉乘
private float Area()
{
// n = 5
int n = m_points.Count;
float A = 0.0f;
Vector2 pval = Vector2.zero;
Vector2 qval = Vector2.zero;
for (int p = 0; p < n; p++)
{
pval = m_points[p];
qval = m_points[(p + 1) % n];
A += pval.x * qval.y - qval.x * pval.y;
}
return (A * 0.5f);
}
  • 按照面积的正负性,改变新创建的顶点数组顺序
        if (Area() > 0)
{
// 0 1 2 3 4
for (int v = 0; v < n; ++v)
V[v] = v;
}
else
{
// 将顶点顺序逆过来
// 4 3 2 1 0
for (int v = 0; v < n; ++v)
V[v] = (n - 1) - v;
}
  • 此处改了一点参考博文的代码,因为n边形的一个顶点出发只能引出(n-2)条对角线,每次枚举三个连续顶点,判断是否能组成三角形(Snip),因为我们提前设置了每个顶点所能发射的最多对角线数量,所以此处无需考虑三个连续顶点重复采用,如果count为0了,可以理解有nv次机会判断是否形成三角形,如果没更新count(也就是没走if里面)则直接返回
        int nv = n;
int count = nv;
int u, w;
// n边形的一个顶点出发只能引出(n-2)条对角线
for (int v = nv - 1; nv >= 3;)
{
// 形成不了三角形时会走这个return
if ((count--) <= 0)
return indices.ToArray(); // 连续三个顶点 不超过nv
u = v;
v = u + 1;
w = u + 2; u %= nv;
v %= nv;
w %= nv; // u v w连续三个顶点能组成三角形,且保证顺序是合理的
if (Snip(u, v, w, nv, V))
{
int a, b, c;
a = V[u];
b = V[v];
c = V[w]; // 将顶点按照顺序放入list中
indices.Add(a);
indices.Add(b);
indices.Add(c); // 做了代码修改,原文写法比较麻烦,将V数组中的值从后往前挪一位
for (int s = v; s + 1 < nv; ++s)
V[s] = V[s + 1]; nv--;
// 原博文是 count = 2 * nv 但其实没必要
count = nv;
}
}
  • 判断是否能形成三角形
    private bool Snip(int u, int v, int w, int n, int[] V)
{
Vector2 A = m_points[V[u]];
Vector2 B = m_points[V[v]];
Vector2 C = m_points[V[w]]; // 面积如果小于Mathf.Epsilon(接近0的最小正浮点数),就为不能切分成三角形
// AB向量叉乘AC向量结果为ABC三顶点形成封闭图形的面积的两倍,判断是否能形成三角形
if (Mathf.Epsilon > ((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x)))
return false;
for (int p = 0; p < n; ++p)
{
// 直到p为没选到的边
if ((p == u) || (p == v) || (p == w))
continue; Vector2 P = m_points[V[p]];
// 防止线边交叉
if (InsideTriangle(A, B, C, P))
return false;
}
return true;
}
  • 判断线边交叉情况,一般情况下返回值都是false(cCROSSap一般与aCROSSbp和bCROSScp正负性相反,因为选点的时候故意为之的),除非线边交叉,发生叉乘的正负性发生了改变
  • 如下图这种情况,如果注释了InsideTriangle()函数,网格会生成不出来,因为出现了线边交叉情况,导致可能绘制顶点的顺序有顺时针有逆时针,从而导致只有绘制面到了背面或者直接就不出来效果
  • 叉乘结果
\[ BC×BP > 0\\
CA×CP > 0\\
AB×AP > 0
\]

  • 正常情况
\[ BC×BP > 0\\
CA×CP > 0\\
AB×AP < 0
\]

    private bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
float cCROSSap, bCROSScp, aCROSSbp; ax = C.x - B.x; ay = C.y - B.y;
bx = A.x - C.x; by = A.y - C.y;
cx = B.x - A.x; cy = B.y - A.y;
apx = P.x - A.x; apy = P.y - A.y;
bpx = P.x - B.x; bpy = P.y - B.y;
cpx = P.x - C.x; cpy = P.y - C.y; // BC向量与BP向量叉乘
aCROSSbp = ax * bpy - ay * bpx;
// CA向量与CP向量叉乘
bCROSScp = bx * cpy - by * cpx;
// AB向量与AP向量叉乘
cCROSSap = cx * apy - cy * apx; // 保证叉乘的顺序都是一致的
// 此处算的结果一般都为false
// 画个图就可以得知
if (((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)))
{
Debug.Log($"A.x = {A.x}, A.y = {A.y}");
Debug.Log($"B.x = {B.x}, B.y = {B.y}");
Debug.Log($"C.x = {C.x}, C.y = {C.y}");
Debug.Log($"P.x = {P.x}, P.y = {P.y}");
}
// Debug.Log(((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)));
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
}

效果





Unity动态构建mesh绘制多边形算法流程分析和实践的更多相关文章

  1. Gradient Boost 算法流程分析

    我们在很多Gradient Boost相关的论文及分析文章中都可以看到下面的公式: 但是,对这个公式的理解,我一直也是一知半解,最近,终于下决心对其进行了深入理解. 步骤1:可以看作优化目标的损失函数 ...

  2. Android笔记--View绘制流程源码分析(一)

    Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...

  3. Unity中动态创建Mesh

    什么是Mesh? Mesh是指的模型的网格,3D模型是由多边形拼接而成,而多边形实际上又是由多个三角形拼接而成的.即一个3D模型的表面其实是由多个彼此相连的三角面构成.三维空间中,构成这些三角形的点和 ...

  4. Unity 绘制多边形

    最近工程需要用到一个多边形用来查看角色属性,于是就研究了下Mesh用网格做了一个.遗憾的的 UGUI 渲染不了 3D 物体,然后又用了一段时间研究了下UGUI的网格绘制. 不过终于还是完成了,虽然有些 ...

  5. 在Unity中使用UGUI修改Mesh绘制几何图形

    在商店看到这样一个例子,表示很有兴趣,他们说是用UGUI做的.我想,像这种可以随便变形的图形,我第一个就想到了网格变形. 做法1: 细心的朋友应该会发现,每个UGUI可见元素,都有一个‘Canvas ...

  6. [WebGL入门]十四,绘制多边形

    注意:文章翻译http://wgld.org/.原作者杉本雅広(doxas),文章中假设有我的额外说明,我会加上[lufy:].另外,鄙人webgl研究还不够深入.一些专业词语,假设翻译有误,欢迎大家 ...

  7. Mesh绘制雷达图(UGUI)

    参考资料:http://www.cnblogs.com/jeason1997/p/5130413.html ** 描述:雷达图 刷新 radarDate.SetVerticesDirty(); usi ...

  8. [C#] C# 与 Nessus 交互,动态构建扫描任务计划

    C# 与 Nessus 交互,动态构建扫描任务计划 目录 什么是 Nessus? 创建会话类 NessusSession 登录测试 创建操作类 NessusManager 操作测试 什么是 Nessu ...

  9. 【Silverlight】Bing Maps学习系列(五):绘制多边形(Polygon)图形(转)

    [Silverlight]Bing Maps学习系列(五):绘制多边形(Polygon)图形 Bing Maps Silverlight Control支持用户自定义绘制多边形(Polygon)图形, ...

随机推荐

  1. PAT (Basic Level) Practice (中文)1054 求平均值 (20 分) 凌宸1642

    PAT (Basic Level) Practice (中文)1054 求平均值 (20 分) 题目描述 本题的基本要求非常简单:给定 N 个实数,计算它们的平均值.但复杂的是有些输入数据可能是非法的 ...

  2. UnboundLocalError: local variable 'foo' referenced before assignment Python常见错误

    在定义局部变量前在函数中使用局部变量(此时有与局部变量同名的全局变量存在) 在函数中使用局部变来那个而同时又存在同名全局变量时是很复杂的, 使用规则:如果在函数中定义了任何东西,如果它只是在函数中使用 ...

  3. 记录给树莓派刷Raspberry Pi OS(Raspbian)系统的配置流程

    准备材料 树莓派(一定要贴散热片,最好再加个小风扇) TF内存卡 (记得选传输规范为Class10标准的) 读卡器 电脑(这里我使用的电脑是Windows系统,其它系统可能与下面的步骤有出入,还望悉知 ...

  4. [树形DP]二叉苹果树

    二 叉 苹 果 树 二叉苹果树 二叉苹果树 题目描述 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点) 这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定 ...

  5. 轻松理解 Spring AOP

    目录 Spring AOP 简介 Spring AOP 的基本概念 面向切面编程 AOP 的目的 AOP 术语和流程 术语 流程 五大通知执行顺序 例子 图例 实际的代码 使用 Spring AOP ...

  6. SwiftUI 简明教程之文本与图片

    本文为 Eul 样章,如果您喜欢,请移步 AppStore/Eul 查看更多内容. Eul 是一款 SwiftUI & Combine 教程类 App(iOS.macOS),以文章(文字.图片 ...

  7. nginx配置实例及多服务器负载

    目录 nginx配置实例 多服务器负载 nginx配置实例 nginx.conf worker_processes 1; events { worker_connections 1024; } htt ...

  8. 用递归求n皇后问题

    此问题是指在n*n的国际象棋棋盘上 ,放置n个皇后,使得这n个皇后均不在,同一行,同一列,同一对角线上,求出合法的方案的数目. 本题可以简单转化为就是求n的全排列中的数放在棋盘上使得这几组数,符合均不 ...

  9. Java 轻松理解深拷贝与浅拷贝

    目录 前言 直接赋值 拷贝 浅拷贝 举例 原理 深拷贝 实现: Serializable 实现深拷贝 总结 前言 本文代码中有用到一些注解,主要是Lombok与junit用于简化代码. 主要是看到一堆 ...

  10. Mybatis(一)Porxy动态代理和sql解析替换

    JDK常用核心原理 概述 在 Mybatis 中,常用的作用就是讲数据库中的表的字段映射为对象的属性,在进入Mybatis之前,原生的 JDBC 有几个步骤:导入 JDBC 驱动包,通过 Driver ...