游戏中需要实现一个小功能,显示一个玩家的能力图,这个图是一个有6个顶点任意摆放组合的多边形。而绘制多边形主要用到的知识就是Mesh构建,mesh的构建主要需要顶点列表,三角形列表,法线列表、uv列表等等等等,在这里我们只考虑顶点列表和三角形列表。那么我们需要做的就是给定一组顶点之后,如何用三角形进行划分,以便绘制。

以下讨论的多边形:1.三角形顶点列表为顺时针顺序。2.多边形不能包含空洞。

参照文章:https://blog.csdn.net/huangzengman/article/details/77114082,表示感谢

1.凸多边形

凸多边形三角剖分比较简单,可以使用第一个点依次连接后面的其他点来组成一组共同顶点的三角形。当然这是最简单的凸多边形剖分算法,还有其他最优剖分算法,需要考虑权重等,但我们不需要。

   

a                                                                                                     b

图a为凸多边形,标出了顶点序号。

图b为三角剖分后,unity中所显示的三角形网格。

2.凹多边形

凹多边形的剖分则需要判断点与多边形的关系,以此来确定可划分顶点与不可划分顶点。

 

a                                                                                                                                                b

图a为凹多边形(没有空洞),标出了顶点序号。

图b为三角剖分后,unity中所显示的三角形网格。

代码如下,参考上述连接:

1.MeshDrawBase.cs           基础类

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public abstract class MeshDrawBase : MonoBehaviour { protected MeshFilter targetFilter;
protected Mesh mesh;
protected int[] tris;
protected Vector2[] uvs;
protected Vector3[] normals; // Use this for initialization
void Awake () {
targetFilter = GetComponent<MeshFilter>();
} // Update is called once per frame
protected virtual void Update () {
DrawMesh();
} protected abstract void DrawMesh();
}

2.Polygon.cs                       用于绘制多边形

using System.Collections.Generic;
using UnityEngine; public class Polygon : MeshDrawBase
{
public List<Vector3> points = new List<Vector3>();
public List<int> indexes = new List<int>();
int index = 0;
Color[] colors; void Start()
{
/*points.Add(new Vector3(0, 0, 0));
points.Add(new Vector3(0, 1, 0));
points.Add(new Vector3(1, 1, 0));
points.Add(new Vector3(0.7f, 0.8f, 0));
points.Add(new Vector3(1, 0.5f, 0));
points.Add(new Vector3(0.7f, 0.3f, 0));
points.Add(new Vector3(1, 0, 0));
indexes.Add(0);
indexes.Add(1);
indexes.Add(2);
indexes.Add(3);
indexes.Add(4);
indexes.Add(5);
indexes.Add(6);*/
} protected override void DrawMesh()
{
if(Input.GetKeyDown(KeyCode.D))
{
DrawPolygon();
}
} private void DrawPolygon()
{
mesh = new Mesh();
mesh.name = "Polygon";
mesh.vertices = points.ToArray(); tris = Triangulation.WidelyTriangleIndex(new List<Vector3>(points), indexes).ToArray(); mesh.triangles = tris; normals = new Vector3[mesh.vertices.Length];
for(int i = 0; i < mesh.vertices.Length; ++i)
{
normals[i] = new Vector3(0, 0, 1);
} mesh.normals = normals; mesh.RecalculateBounds();
mesh.RecalculateTangents(); targetFilter.mesh = mesh;
} protected override void Update()
{
base.Update();
if(Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, Mathf.Infinity))
{
var worldHitPos = hit.point;
var localHitPos = transform.InverseTransformPoint(worldHitPos); points.Add(localHitPos);
indexes.Add(index++);
}
} if(Input.GetKeyDown(KeyCode.R))
{
this.Reset();
}
} private void Reset()
{
points.Clear(); targetFilter.mesh = null;
Destroy(mesh);
} private void OnGUI()
{
if (points.Count == 0) return; GUI.color = Color.red; for(int i = 0; i < points.Count; ++i)
{
var worldPos = transform.TransformPoint(points[i]);
var screenPos = Camera.main.WorldToScreenPoint(worldPos);
var uiPos = new Vector3(screenPos.x, Camera.main.pixelHeight - screenPos.y, screenPos.z); GUI.Label(new Rect(uiPos, new Vector2(100, 80)), i.ToString());
}
} private void OnDrawGizmos()
{
if (points.Count == 0) return; Gizmos.color = Color.cyan;
foreach(var pos in points)
{
var worldPos = transform.TransformPoint(pos);
Gizmos.DrawWireSphere(worldPos, .2f);
}
}
}

3.Triangulation.cs               三角剖分工具类

using UnityEngine;
using System.Collections.Generic; /// <summary>
/// 三维模型重建中的凹多边形三角剖分,适用于不带空洞的凹多边形
/// ref: https://blog.csdn.net/huangzengman/article/details/77114082
/// </summary>
public static class Triangulation
{
const double epsilon = 1e-7; static bool floatLess(float value, float other)
{
return (other - value) > epsilon;
} static bool floatGreat(float value, float other)
{
return (value - other) > epsilon;
} static bool floatEqual(float value, float other)
{
return Mathf.Abs(value - other) < epsilon;
} static bool Vector3Equal(Vector3 a, Vector3 b)
{
return floatEqual(a.x, b.x) && floatEqual(a.y, b.y) && floatEqual(a.z, b.z);
} /// <summary>
/// 凸多边形,顺时针序列,以第1个点来剖分三角形,如下:
/// 0---1
/// | |
/// 3---2 --> (0, 1, 2)、(0, 2, 3)
/// </summary>
/// <param name="verts">顺时针排列的顶点列表</param>
/// <param name="indexes">顶点索引列表</param>
/// <returns>三角形列表</returns>
public static List<int> ConvexTriangleIndex(List<Vector3> verts, List<int> indexes)
{
int len = verts.Count;
//若是闭环去除最后一点
if (len > 1 && Vector3Equal(verts[0], verts[len - 1]))
{
len--;
}
int triangleNum = len - 2;
List<int> triangles = new List<int>(triangleNum * 3);
for (int i = 0; i < triangleNum; i++)
{
triangles.Add(indexes[0]);
triangles.Add(indexes[i + 1]);
triangles.Add(indexes[i + 2]);
}
return triangles;
} /// <summary>
/// 三角剖分
/// 1.寻找一个可划分顶点
/// 2.分割出新的多边形和三角形
/// 3.新多边形若为凸多边形,结束;否则继续剖分
///
/// 寻找可划分顶点
/// 1.顶点是否为凸顶点:顶点在剩余顶点组成的图形外
/// 2.新的多边形没有顶点在分割的三角形内
/// </summary>
/// <param name="verts">顺时针排列的顶点列表</param>
/// <param name="indexes">顶点索引列表</param>
/// <returns>三角形列表</returns>
public static List<int> WidelyTriangleIndex(List<Vector3> verts, List<int> indexes)
{
int len = verts.Count;
if (len <= 3) return ConvexTriangleIndex(verts, indexes); int searchIndex = 0;
List<int> covexIndex = new List<int>();
bool isCovexPolygon = true;//判断多边形是否是凸多边形 for (searchIndex = 0; searchIndex < len; searchIndex++)
{
List<Vector3> polygon = new List<Vector3>(verts.ToArray());
polygon.RemoveAt(searchIndex);
if (IsPointInsidePolygon(verts[searchIndex], polygon))
{
isCovexPolygon = false;
break;
}
else
{
covexIndex.Add(searchIndex);
}
} if (isCovexPolygon) return ConvexTriangleIndex(verts, indexes); //查找可划分顶点
int canFragementIndex = -1;//可划分顶点索引
for (int i = 0; i < len; i++)
{
if (i > searchIndex)
{
List<Vector3> polygon = new List<Vector3>(verts.ToArray());
polygon.RemoveAt(i);
if (!IsPointInsidePolygon(verts[i], polygon) && IsFragementIndex(i, verts))
{
canFragementIndex = i;
break;
}
}
else
{
if (covexIndex.IndexOf(i) != -1 && IsFragementIndex(i, verts))
{
canFragementIndex = i;
break;
}
}
} if (canFragementIndex < 0)
{
Debug.LogError("数据有误找不到可划分顶点");
return new List<int>();
} //用可划分顶点将凹多边形划分为一个三角形和一个多边形
List<int> tTriangles = new List<int>();
int next = (canFragementIndex == len - 1) ? 0 : canFragementIndex + 1;
int prev = (canFragementIndex == 0) ? len - 1 : canFragementIndex - 1;
tTriangles.Add(indexes[prev]);
tTriangles.Add(indexes[canFragementIndex]);
tTriangles.Add(indexes[next]);
//剔除可划分顶点及索引
verts.RemoveAt(canFragementIndex);
indexes.RemoveAt(canFragementIndex); //递归划分
List<int> leaveTriangles = WidelyTriangleIndex(verts, indexes);
tTriangles.AddRange(leaveTriangles); return tTriangles;
} /// <summary>
/// 是否是可划分顶点:新的多边形没有顶点在分割的三角形内
/// </summary>
private static bool IsFragementIndex(int index, List<Vector3> verts)
{
int len = verts.Count;
List<Vector3> triangleVert = new List<Vector3>();
int next = (index == len - 1) ? 0 : index + 1;
int prev = (index == 0) ? len - 1 : index - 1;
triangleVert.Add(verts[prev]);
triangleVert.Add(verts[index]);
triangleVert.Add(verts[next]);
for (int i = 0; i < len; i++)
{
if (i != index && i != prev && i != next)
{
if (IsPointInsidePolygon(verts[i], triangleVert))
{
return false;
}
}
}
return true;
} /// <summary>
/// 射线与线段相交性判断
/// </summary>
/// <param name="ray">射线</param>
/// <param name="p1">线段头</param>
/// <param name="p2">线段尾</param>
/// <returns></returns>
private static bool IsDetectIntersect(Ray2D ray, Vector3 p1, Vector3 p2)
{
float pointY;//交点Y坐标,x固定值
if (floatEqual(p1.x, p2.x))
{
return false;
}
else if(floatEqual(p1.y, p2.y))
{
pointY = p1.y;
}
else
{
//直线两点式方程:(y-y2)/(y1-y2) = (x-x2)/(x1-x2)
float a = p1.x - p2.x;
float b = p1.y - p2.y;
float c = p2.y / b - p2.x / a; pointY = b / a * ray.origin.x + b * c;
} if (floatLess(pointY, ray.origin.y))
{
//交点y小于射线起点y
return false;
}
else
{
Vector3 leftP = floatLess(p1.x, p2.x) ? p1 : p2;//左端点
Vector3 rightP = floatLess(p1.x, p2.x) ? p2 : p1;//右端点
//交点x位于线段两个端点x之外,相交与线段某个端点时,仅将射线L与左侧多边形一边的端点记为焦点(即就是:只将右端点记为交点)
if (!floatGreat(ray.origin.x, leftP.x) || floatGreat(ray.origin.x, rightP.x))
{
return false;
}
} return true;
} /// <summary>
/// 点与多边形的位置关系
/// </summary>
/// <param name="point">判定点</param>
/// <param name="polygonVerts">剩余顶点按顺序排列的多边形</param>
/// <returns>true:点在多边形之内,false:相反</returns>
private static bool IsPointInsidePolygon(Vector3 point, List<Vector3> polygonVerts)
{
int len = polygonVerts.Count;
Ray2D ray = new Ray2D(point, new Vector3(0, 1)); //y方向射线
int interNum = 0; for (int i = 1; i < len; i++)
{
if (IsDetectIntersect(ray, polygonVerts[i - 1], polygonVerts[i]))
{
interNum++;
}
} //不是闭环
if (!Vector3Equal(polygonVerts[0], polygonVerts[len - 1]))
{
if (IsDetectIntersect(ray, polygonVerts[len - 1], polygonVerts[0]))
{
interNum++;
}
}
int remainder = interNum % 2;
return remainder == 1;
}
}

使用方法:新建一个Quad,将Polygon.cs脚本添加上,用鼠标点击来确定多边形的个顶点位置,之后按D进行绘制。

git:https://gitee.com/planefight/RunSprite_Tri.git

参照论文:

三维模型重建中的凹多边形三角剖分

Unity 凹多边形三角剖分的更多相关文章

  1. 源于《Unity官方实例教程 “Space Shooter”》思路分析及相应扩展

    教程来源于:Unity官方实例教程 Space Shooter(一)-(五)       http://www.jianshu.com/p/8cc3a2109d3b 一.经验总结 教程中步骤清晰,并且 ...

  2. Unity3d入门 - 关于unity工具的熟悉

    上周由于工作内容较多,花在unity上学习的时间不多,但总归还是学习了一些东西,内容如下: .1 根据相关的教程在mac上安装了unity. .2 学习了unity的主要的工具分布和对应工具的相关的功 ...

  3. 聊聊Unity项目管理的那些事:Git-flow和Unity

    0x00 前言 目前所在的团队实行敏捷开发已经有了一段时间了.敏捷开发中重要的一个话题便是如何对项目进行恰当的版本管理.项目从最初使用svn到之后的Git One Track策略再到现在的GitFlo ...

  4. Unity游戏内版本更新

    最近研究了一下游戏内apk包更新的方法. ios对于应用的管理比较严格,除非热更新脚本,不太可能做到端内大版本包的更新.然而安卓端则没有此限制.因此可以做到不跳到网页或应用商店,就覆盖更新apk包. ...

  5. Unity 序列化

    Script Serialization http://docs.unity3d.com/Manual/script-Serialization.html 自定义序列化及例子: http://docs ...

  6. Unity 序列化 总结

    查找了 Script Serialization http://docs.unity3d.com/Manual/script-Serialization.html 自定义序列化及例子: http:// ...

  7. ASP.NET MVC5+EF6+EasyUI 后台管理系统(64)-补充WebApi与Unity注入-配置文件

    系列目录 上一篇演示了WebApi利用Unity注入 很多人问我如何用配置文件来配置注入,本节演示如何利用配置文件来注入,道理是一样的,跳转到上一节下载源码一起来动手! 1.打开源码定位到文件Depe ...

  8. ASP.NET MVC5+EF6+EasyUI 后台管理系统(64)-WebApi与Unity注入

    系列目录 前言: 有时候我们系统需要开放数据给手机App端或其他移动设备,不得不说Asp.net WebApi是目前首选 本节记录Asp.net MVC WebApi怎么利用Unity注入.系列开头已 ...

  9. 使用Microsoft的IoC框架:Unity来对.NET应用进行解耦

    1.IoC/DI简介 IoC 即 Inversion of Control,DI 即 Dependency Injection,前一个中文含义为控制反转,后一个译为依赖注入,可以理解成一种编程模式,详 ...

  10. Unity C#最佳实践(上)

    本文为<effective c#>的读书笔记,此书类似于大名鼎鼎的<effective c++>,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针 ...

随机推荐

  1. STM32定时器TIM_OC1PreloadConfig、TIM_ARRPreloadConfig函数详解

    -------------------------------------------版权声明:本文为CSDN博主「qlexcel」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处 ...

  2. 「JOI Open 2022」Giraffes 题解

    设我们将要给出的观感好的排列为 \(q\),我们希望求出 \(\sum[p_i=q_i]\) 的最大值(这里指不移动的长颈鹿个数). 结论一:当且仅当左右端点有当前区间最大值或者最小值时条件才能成立. ...

  3. Bugku-ez_misc[wp]

    题目描述 1.拿到一份abc.rar文件,里面有一个加密call.png (1)通过file  . binwalk . 伪加密一顿小输出都不行,于是放进16进制编辑器查看: 文件头不对! (2)修改为 ...

  4. Mac Parallels Desktop篇 安装Windows 10

    Parallels Desktop安装Windows 10系统: 1.首先下载并安装Parallels Desktop ,下载 Windows 10 系统镜像 2.打开Parallels Deskto ...

  5. Tiup离线安装TIDB集群4.0.16版本

    环境:centos7.6 中控机:8.213.8.25(内网) 可用服务器8.213.8.25-8.213.8.29 一.准备 TiUP 离线组件包 方法1:外网下载离线安装包拷贝进内网服务器 在Ti ...

  6. Pytest 插件

    1. 执行标记用例执行次数 首先安装 repeat: pip install pytest-repeat @pytest.mark.repeat(n)执行当前用例 n 次 然后再往下执行其他用例 im ...

  7. ubuntu18.04 安装cython_bbox

    方式一: pip install cython_bbox 方式二:下载地址 https://github.com/samson-wang/cython_bbox 解压后并cd cython_bbox- ...

  8. xrdp 启动分析

    前言:xrdp模块说明 参考文档:xrdp-genkeymap man page - xrdp - System Administration (mankier.com) xrdp:远程桌面协议 (R ...

  9. PHP_递归实现无限级分类

    <?php /** * 递归方法实现无限级别分类 * @param array $list 要生成树形列表的数组[该数组中必须要有主键id 和 父级pid] * @param int $pid= ...

  10. Mysql-不同场景下操作/查询数据库表

    1. 通过关联字段把一张表的字段值更新另一张表的字段值 update table_a a, table_b b set a.username = b.username where a.id = b.i ...