Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化

前段时间结束了一个3DX鱼项目,有个技术问题悬而未决。(即:如何把3D物理碰撞器映射到屏幕上做2D碰撞器,以简化碰撞的数学计算和解决一些视角偏差带来的碰撞不到BUG。).

本来也不是特别严重的问题,但是优化过路线算法和内存占用后,该问题带来性能影响的就变得比较突出。

最近终于有时间研究一下这个问题。最终方案效果还不错,分享一下我的成果。

原有场景及存在的问题

原有场景为了使2D的子弹能碰到3D场景中的物体,使用了一个极长的3DBoxCollider伸到3D场景中与3D的目标进行碰撞。

原有场景示意图

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第1张图片

但是这样处理有两个比较严重的问题,

  • 使用2D界面,直接与透视视角的3D物体进行碰撞,会出现偏差。PS:原有场景中差异没有那么明显。

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第2张图片Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第3张图片

  • 整个游戏中,实际上只有2D碰撞,使用3D碰撞消耗较大,特别是使用子弹Z轴太长的3DBoxCollider,造成无意义的额外消耗。

虽然之前通过调整场景等其他操作,做了一些效果优化,但是最终效果还是不够理想。所以,重新做了个方案。

前排提醒

该文章会介绍三种方案,实践部分前面会介绍三个方案公用的部分,后面会介绍三个方案的区别。

这些方案理论上适用于游戏场景中的被射击物体是3D的,3D场景有深度的。但是子弹是2D的游戏类型。参考上面的场景示意图。

PS:如果3D物体的空隙较大,或者动画动作较大,也不适合参考这个方案。

核心思路

处理思路如下,获取3D物体的合适的AABB-Bounds,调整角度和位置后(这里其实是做了个变相的OBB包围盒),映射到2D空间,在2D空间重建物体的2D包围盒,然后使用2D的子弹和2D的包围盒进行碰撞(这里偷了懒,直接使用Unity的2D碰撞器)。3D物体移动后更新包围盒的大小和位置。其中比较重要的是2D包围盒重建,也是整个过程中消耗比较大的部分。

AABB - OBB扫盲:

Unity3D AABB包围盒效果 https://blog.csdn.net/sinat_25415095/article/details/104588989

如何生成OBB(OrientedboundingBox)方向包围盒 https://blog.csdn.net/qing101hua/article/details/53100112

如何借用UnityAPI快速生成包围盒。

首先Unity提供了在网格,物理和Reneder三个地方提供了Bounds相关接口,均可以是AABB包围盒。

SKinedReneder.bounds

但是实际测试中发现SKinedReneder的Bounds会跟随动画的动作不断地变大和缩小。由于我游戏中的物体没有较大幅度的动作,而且变大的Bounds会为后续的映射带来较大的空隙,所以弃用。

Physics.bounds

由于该方案的目的就是剔除3D的物理碰撞器。所以弃用。

Mesh.bounds

网格的bounds不会变化,是我应用场景下的最优选,但是要做如下改动

  • 由于Unity提供的是AABB所以要根据3D物体的旋转来调整朝向。
  • 另外由于3D物体也会进行缩放,所以也要调整缩放(前面两个可以使用transform的矩阵进行运算)。
  • 最后该Bounds提供的是相对坐标,我们要映射到世界坐标中,所以要进行位置矫正。
  • 最后为了3D物体不受诸多因素的影响,这里为3D物体加了个父级空物体,专门用来控制Transform。

先取得Bounds的八个顶点:

八个顶点示意图:原谅我灵魂画师。。

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第4张图片

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第5张图片

效果示意:PS:这里父级是有旋转和缩放的

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第6张图片

示意代码:

                //要用网格的Bounds而不是Render的,因为Render会在旋转时更新,导致bounds变大。
                Bounds bounds = screenList[i].GetComponent().mesh.bounds;
                Vector3 position = screenList[i].transform.parent.position;
                Matrix4x4 matrix4x4 = screenList[i].transform.parent.localToWorldMatrix;
                //算出8个顶点,然后连接出12个边
                //bounds.min是x和y都最小的地方
                //bounds.max是x和y都最大的地方
                //先算出下面那个面的四个顶点
                Vector3 max = bounds.max;
                Vector3 min = bounds.min;
                Vector3 corner_down_1 = bounds.min;
                corner_down_1 = matrix4x4 * corner_down_1;
                corner_down_1 = corner_down_1 + position;

                Vector3 corner_down_2 = new Vector3(max.x, min.y, min.z);
                corner_down_2 = matrix4x4 * corner_down_2;
                corner_down_2 = corner_down_2 + position;

                Vector3 corner_down_3 = new Vector3(max.x, min.y, max.z);
                corner_down_3 = matrix4x4 * corner_down_3;
                corner_down_3 = corner_down_3 + position;

                Vector3 corner_down_4 = new Vector3(min.x, min.y, max.z);
                corner_down_4 = matrix4x4 * corner_down_4;
                corner_down_4 = corner_down_4 + position;

                //然后算出上面四个顶点
                Vector3 corner_top_1 = new Vector3(min.x, max.y, min.z);
                corner_top_1 = matrix4x4 * corner_top_1;
                corner_top_1 = corner_top_1 + position;

                Vector3 corner_top_2 = new Vector3(max.x, max.y, min.z);
                corner_top_2 = matrix4x4 * corner_top_2;
                corner_top_2 = corner_top_2 + position;

                Vector3 corner_top_3 = bounds.max;
                corner_top_3 = matrix4x4 * corner_top_3;
                corner_top_3 = corner_top_3 + position;

                Vector3 corner_top_4 = new Vector3(min.x, max.y, max.z);
                corner_top_4 = matrix4x4 * corner_top_4;
                corner_top_4 = corner_top_4 + position;

                Vector3 corner_center = bounds.center;
                corner_center = matrix4x4 * corner_center;
                corner_center = corner_center + position;
                //按照立方体的构图规律,把12条边绘制出来
                //首先绘制底面四个边
                Debug.DrawLine(corner_down_1, corner_down_2, Color.red);
                Debug.DrawLine(corner_down_2, corner_down_3, Color.red);
                Debug.DrawLine(corner_down_3, corner_down_4, Color.red);
                Debug.DrawLine(corner_down_4, corner_down_1, Color.red);
                //绘制顶面四个边
                Debug.DrawLine(corner_top_1, corner_top_2, Color.yellow);
                Debug.DrawLine(corner_top_2, corner_top_3, Color.yellow);
                Debug.DrawLine(corner_top_3, corner_top_4, Color.yellow);
                Debug.DrawLine(corner_top_4, corner_top_1, Color.yellow);
                //绘制侧面四个边
                Debug.DrawLine(corner_top_1, corner_down_1, Color.green);
                Debug.DrawLine(corner_top_2, corner_down_2, Color.green);
                Debug.DrawLine(corner_top_3, corner_down_3, Color.green);
                Debug.DrawLine(corner_top_4, corner_down_4, Color.green);

 

三种2D包围盒方案简介

OBB盒方案

前面说会先使用Unity的API,便捷的实现3D的OBB包围盒,然后把该的所有顶点映射到2D中,就变成了在2D中的散列点,然后再使用这些点重建2D的OBB。另外我们可以把3DOBB的方向一起映射过来。这样就可以提前确定2DOBB的方向性。减少运算量。不过做之前看了OBB的生成算法,觉得这种方案运算量太大。更重要的是,由于3D中使用的就是OBB包围盒,2D再重建OBB包围盒,会导致整个2D包围盒比3D物体的实际屏幕大小大了很多。所以弃用该方案,不过可以作为一种参考思路。Unity中有很多OBB盒的生成工具,感兴趣的可以使用第三方工具生成一下。2D Physics Toolkit

方案预估,效果一般-性能消耗较大。

最接近外包凸多边形方案

先看效果示意图:

PS: 0-7标号是OBB映射到屏幕的8个排序后的顶点序号。

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第7张图片Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第8张图片

相较于OBB包围盒,我们拿到2D的散列点之后其实可以把所有外围的点,连接起来,便能得到更贴合3D物体的多边形包围盒。

但是由于点的分布是不规律的,想要连接外包围盒,需要先对所有的点斜率进行排序,以实现按顺序链接的效果。然后还要再剔除内凹点,以防止连成一个内凹的多边形。这些都需要一定的运算量。

不排序的情况示意:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第9张图片Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第10张图片

排序后不去除凹点的情况示意:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第11张图片Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第12张图片

示意代码:这个完整代码太长了,放到最后了,也可以下载一下测试样例工程。https://download.csdn.net/download/lanazyit/14927515

方案预估,效果好,性能消耗大

近似四边形方案

先看效果示意图:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第13张图片

做上一种最接近外包凸多边形方案的过程中发现运算消耗其实也比较大,而我所应用的场景其实对碰撞的边缘精确度没有特别严格的要求。所以想了一个这种的方案,以追求更快的运算效率。

首先,如果我们把Center和3D物体的Forward方向都映射到屏幕上,并以Center为原点,Forward方向为Y轴方向做2D坐标系。会发现8个顶点一定是近似均匀的分布在,该坐标系的四个象限。

防爬虫水印:酒馆笔记原创https://blog.csdn.net/lanazyit,转载请注明出处。

示意图:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第14张图片

另外,由于我传过来的是父级的Up,并且子节点一直相对父级维持在identity状态,所以会发现 Bounds的 down面的四个顶点一定在 3,4象限。top 在一定在该坐标系的第 1,4 象限,另外min和max是在3D空间中基于center对称的。

以下方案为我试出来的经验方案,没有找到太多的理论依据,如果看不懂可以跳过,直接看下面的图

基于以上信息,我只将max点,max面的另一个参照顶点(该参照点取法看示意图,只保证与min组的参照顶点关于center对称即可)为一组,min,min面的另一个参照顶点为一组,共四个点映射到2D。

然后以min组为例做若干操作:

  • 比较min组中的所有点相对于Center-Forward直线的距离,取距离较大的那一个点为down_Point,
  • 然后再将 down_Point,Center-Forward直线取对称点down_Point_Symmetric,
  • 最后将两组的得到的两个down_Point和两个down_Point_Symmetric按顺序连接起来,即可得到包围范围较大的四边形伪包围盒。

取参照点示意图:
Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第15张图片

屏幕上生成对称点后的示意图:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第16张图片

生成的碰撞器效果图:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第17张图片

方案预估,效果一般,性能消耗小

示意代码:这个完整代码太长了,放到最后了,也可以下载一下测试样例工程。https://download.csdn.net/download/lanazyit/14927515

性能比较

性能只以时间为参照,不太严谨,但也能体现相对的性能关系,在简单的空场景下,生成50000个3D物体的2D包围盒映射,两种方案的耗时比较:

需要注意的是:这里都是只是实现方案的测试代码,代码还没有整理和重构,比如最佳多边形的排序算法,频繁的对象申请,等等都没有处理。所以只做参考。

近似四边形方案的耗时为:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第18张图片

最接近外包凸多边形方案的耗时为:

Unity3d碰撞器映射到2D碰撞器_物理引擎性能优化_第19张图片

近似四边形方案的明显快了许多0.3S左右,也就是每个碰撞器快了6微秒。

思路延伸

当我们映射到2D空间后,其实可以使用四叉树针对具体的碰撞检测再做优化,以减少每个子弹的碰撞检测消耗。但是由于我场景中同一时间最多只有100个左右的射击对象,也由于时间关系,这里就没做了。换而直接使用的Unity里的2D多边形碰撞器。

总结

一:做的过程中有很多想当然的部分,最初以为映射到2D运算量就会最小,性能会优化很多。实际这是个误区,在测试场景中验证(使用1000个不断移动的3D盒子和子弹进行碰撞),粗略估计性能只提升了10%不到。

因为3D碰撞器中特别是3DBoxCollider,其实就是两个长方体的相交检测,消耗很小。而该方案中Bounds的生成,角度计算,映射到屏幕,都需要消耗运算量。不过,实际项目场景中运行效果不错,可能是项目中多使用的是胶囊体碰撞器,又与测试场景有差异。

二:未找到合适的检验性能消耗的方式,一切数据为估计,最后只能建了一个极简场景,然后用控制变量法来测试性能变化。

三:与我个人而言,这个需求做完。X鱼这类游戏中再能学到,能实践的东西,少矣。希望以后有机会可以做更有挑战,更有意思的游戏。

引用:

一个非常全面的常用的计算机几何算法论文网站 http://geomalgorithms.com/index.html

解决多边形内凹,剔除内凹顶点方案 https://blog.csdn.net/buqulinghun/article/details/53735214

论文:平面多边形凹凸性的顶角判别法,万书亭,韩庆瑶

最后:

由于本人水平都有限,方案多有不成熟的地方。如果大家有更好的方案或建议。请务必在下方留言,想学,拜托。

如果对你帮助,点个赞可以嘛。谢谢啦

 

最接近外包凸多边形方案代码:

PS:如下代码只是测试代码,能运行,但是并不是最简化代码,使用请自行简化。

测试样例工程:

https://download.csdn.net/download/lanazyit/14927515

代码参考

几何算法:点集合构造简单多边形

解决多边形内凹,剔除内凹顶点方案

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System;


public static class DrawBounds_ConvexPolygon
{
    public static bool create = false;
    public static Text staText;
    public static PolygonCollider2D polygonCollider2D;
    public static List cacth = new List();

    public static void DoDraw(List screenList, Image uiPoint, Camera fishCamera, Canvas canvas, PolygonCollider2D paramPolygonCollider2D, Text text)
    {
        //Unity使用bounds绘制不规则图形边框
        for (int i = 0; i < screenList.Count; i++)
        {
            if (screenList[i].activeSelf == true)
            {
                //要用网格的Bounds而不是Render的,因为Render会在旋转时更新,导致bounds变大。
                Bounds bounds = screenList[i].GetComponent().mesh.bounds;
                Vector3 position = screenList[i].transform.parent.position;
                Matrix4x4 matrix4x4 = screenList[i].transform.parent.localToWorldMatrix;
                //算出8个顶点,然后连接出12个边
                //bounds.min是x和y都最小的地方
                //bounds.max是x和y都最大的地方
                //先算出下面那个面的四个顶点
                Vector3 max = bounds.max;
                Vector3 min = bounds.min;
                Vector3 corner_down_1 = bounds.min;
                corner_down_1 = matrix4x4 * corner_down_1;
                corner_down_1 = corner_down_1 + position;

                Vector3 corner_down_2 = new Vector3(max.x, min.y, min.z);
                corner_down_2 = matrix4x4 * corner_down_2;
                corner_down_2 = corner_down_2 + position;

                Vector3 corner_down_3 = new Vector3(max.x, min.y, max.z);
                corner_down_3 = matrix4x4 * corner_down_3;
                corner_down_3 = corner_down_3 + position;

                Vector3 corner_down_4 = new Vector3(min.x, min.y, max.z);
                corner_down_4 = matrix4x4 * corner_down_4;
                corner_down_4 = corner_down_4 + position;

                //然后算出上面四个顶点
                Vector3 corner_top_1 = new Vector3(min.x, max.y, min.z);
                corner_top_1 = matrix4x4 * corner_top_1;
                corner_top_1 = corner_top_1 + position;

                Vector3 corner_top_2 = new Vector3(max.x, max.y, min.z);
                corner_top_2 = matrix4x4 * corner_top_2;
                corner_top_2 = corner_top_2 + position;

                Vector3 corner_top_3 = bounds.max;
                corner_top_3 = matrix4x4 * corner_top_3;
                corner_top_3 = corner_top_3 + position;

                Vector3 corner_top_4 = new Vector3(min.x, max.y, max.z);
                corner_top_4 = matrix4x4 * corner_top_4;
                corner_top_4 = corner_top_4 + position;

                Vector3 corner_center = bounds.center;
                corner_center = matrix4x4 * corner_center;
                corner_center = corner_center + position;
                //按照立方体的构图规律,把12条边绘制出来
                //首先绘制底面四个边
                Debug.DrawLine(corner_down_1, corner_down_2, Color.red);
                Debug.DrawLine(corner_down_2, corner_down_3, Color.red);
                Debug.DrawLine(corner_down_3, corner_down_4, Color.red);
                Debug.DrawLine(corner_down_4, corner_down_1, Color.red);
                //绘制顶面四个边
                Debug.DrawLine(corner_top_1, corner_top_2, Color.yellow);
                Debug.DrawLine(corner_top_2, corner_top_3, Color.yellow);
                Debug.DrawLine(corner_top_3, corner_top_4, Color.yellow);
                Debug.DrawLine(corner_top_4, corner_top_1, Color.yellow);
                //绘制侧面四个边
                Debug.DrawLine(corner_top_1, corner_down_1, Color.green);
                Debug.DrawLine(corner_top_2, corner_down_2, Color.green);
                Debug.DrawLine(corner_top_3, corner_down_3, Color.green);
                Debug.DrawLine(corner_top_4, corner_down_4, Color.green);

                //创建3DPoint,以监测位置是否合理
                if (!create)
                {
                    create = true;
                    //    GameObject.Instantiate(Resources.Load("Capsule"), corner_down_1, Quaternion.identity).name = "corner_down_1";
                    //    GameObject.Instantiate(Resources.Load("Cube"), corner_down_2, Quaternion.identity).name = "corner_down_2";
                    //    GameObject.Instantiate(Resources.Load("Cylinder"), corner_down_3, Quaternion.identity).name = "corner_down_3";
                    //    GameObject.Instantiate(Resources.Load("Sphere"), corner_down_4, Quaternion.identity).name = "corner_down_4";
                    //应该是筛选四个角的
                    polygonCollider2D = paramPolygonCollider2D;
                    staText = text;
                    List tempPoints = new List(8);
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_down_1, "corner_down_1"));
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_down_2, "corner_down_2"));
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_down_3, "corner_down_3"));
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_down_4, "corner_down_4"));
                    //然后算出上面四个顶点
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_top_1, "corner_top_1"));
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_top_2, "corner_top_2"));
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_top_3, "corner_top_3"));
                    tempPoints.Add(CreateUIPoint(uiPoint, fishCamera, canvas, corner_top_4, "corner_top_4"));
                    //绘制中心点
                    corner_center = CreateUIPoint(uiPoint, fishCamera, canvas, corner_center, "corner_center");

                    //按照斜率排序
                    tempPoints = GetSortCorner(tempPoints);
                    //绘制排好序的点
                    for (int j = 0; j < tempPoints.Count; j++)
                    {
                        CreateUINum(canvas, tempPoints[j], j);
                    }
                    //去除内凹点
                    //RemoveConcavePoints(tempPoints);

                    polygonCollider2D.points = tempPoints.ToArray();
                }
            }
        }
    }
    
    private static Vector3 CreateUIPoint(Image uiPoint, Camera fishCamera, Canvas canvas, Vector3 corner_top_1, string name)
    {
        Image tempObj;
        Vector3 ui_corner_top_1 = fishCamera.WorldToScreenPoint(corner_top_1);
        tempObj = GameObject.Instantiate(uiPoint);
        tempObj.name = name;
        tempObj.gameObject.SetActive(true);
        tempObj.transform.parent = canvas.transform;
        tempObj.transform.localPosition = new Vector3(ui_corner_top_1.x - 1624 / 2, ui_corner_top_1.y - 750 / 2, ui_corner_top_1.z);
        tempObj.transform.localScale = Vector3.one;
        cacth.Add(tempObj.gameObject);
        return tempObj.transform.localPosition;
    }

    private static Vector3 CreateUINum(Canvas canvas, Vector2 corner_top_1, int num)
    {
        Text tempObj;
        tempObj = GameObject.Instantiate(staText);
        tempObj.name = "num" + num;
        tempObj.gameObject.SetActive(true);
        tempObj.transform.parent = canvas.transform;
        tempObj.transform.localPosition = corner_top_1;
        tempObj.transform.GetComponent().text = num + "";
        tempObj.transform.localScale = Vector3.one;
        cacth.Add(tempObj.gameObject);
        return tempObj.transform.localPosition;
    }
    private static List GetSortCorner(List paramPoints)
    {
        //转换列表,以实现自定义的GeometryPoint
        List geometrypoints = new List();

        //选取x坐标最大的点
        int MaxXPointIndex = 0;
        for (int i = 0; i < paramPoints.Count; i++)
        {
            if (paramPoints[i].x > paramPoints[MaxXPointIndex].x)
            {
                MaxXPointIndex = i;
                Debug.Log(MaxXPointIndex + " MaxXPointIndex");
            }
        }
        Debug.Log(MaxXPointIndex + " MaxXPointIndex");
        Debug.Log(paramPoints.Count + " paramPoints.Count");
        //计算斜率
        for (int i = 0; i < paramPoints.Count; i++)
        {
            if (i == MaxXPointIndex)
            {
                geometrypoints.Add(new GeometryPoint(paramPoints[i].x, paramPoints[i].y, float.MaxValue));
            }
            else
            {
                if (paramPoints[i].x == paramPoints[MaxXPointIndex].x)//与最大x坐标的x相同的点,因为x坐标之差为零,所以取SLOPE最大值
                {
                    geometrypoints.Add(new GeometryPoint(paramPoints[i].x, paramPoints[i].y, float.MaxValue));
                }
                else//计算斜率,注意正切函数在-0.5Pi和0.5Pi之间是单调递增的
                {
                    geometrypoints.Add(new GeometryPoint(paramPoints[i].x, paramPoints[i].y, (paramPoints[i].y - paramPoints[MaxXPointIndex].y) / (paramPoints[MaxXPointIndex].x - paramPoints[i].x)));
                }
            }
        }
        // 对斜率进行排序
        geometrypoints.Sort();
        for (int i = 0; i < paramPoints.Count; i++)
        {
            paramPoints[i] = new Vector2(geometrypoints[i].x, geometrypoints[i].y);
        }
        return paramPoints;
    }

    /**
     * 顶点判决法,并将凹多边形近似处理为凸多边形
     */
    private static void RemoveConcavePoints(List paramPoints)
    {
        //1.找Y坐标最大的顶角 // 在一堆散列的坐标中,Y坐标最大的理论上来讲,应该有若干个坐标点分布在他的左右
        int maxIndex = 0;
        float maxY = paramPoints[0].y;
        float maxX = float.MinValue ;
        for (int i = 1; i < paramPoints.Count; i++)
        {
            if (paramPoints[i].y < maxY)
            {
                maxY = paramPoints[i].y;
                maxX = paramPoints[i].x;
                maxIndex = i;
            }
            else if (paramPoints[i].y == maxY)
            {
                //y值相同
                if (paramPoints[i].x > maxX)
                {
                    maxX = paramPoints[i].x;
                    maxIndex = i;
                }
            }
        }

        //2.1 找到顶角后,判断是否是凹点如果是,则移除
        // 逐次的往后求sin  sin < 0 :凸点:凹点;
        int preIndex = maxIndex;
        for (int i = maxIndex + 1; i + 1 < paramPoints.Count && i - 1 >= 0; i++)
        {
            filter(paramPoints, ref preIndex, i, i + 1);
        }

        if (paramPoints.Count > 2)
        {
            //2.2 求最后一个角
            filter(paramPoints, ref preIndex, paramPoints.Count - 1, 0);

            //2.3求第0个角
            filter(paramPoints, ref preIndex, 0, 1);

        }

        //2.4 找出顶角前面的凹点
        for (int i = 1; i < maxIndex + 1; i++)
        {
            filter(paramPoints, ref preIndex, i, i + 1);
        }


    }
    /**
     * @brief MyPainterWidget::filter
     * @param pre 前一个凸点
     * @param cur 当前点
     * @param next 下一个点
     * 记录最新凸点,剔除凹点
     */
    private static void filter(List paramPoints, ref int preIndex, int cur, int next)
    {
        if (!isBump(paramPoints[preIndex], paramPoints[cur], paramPoints[next]))
        {
            //更新前面最新的一个凸点
            preIndex = cur;
        }
        else
        {
            //从容器中移除当前凹点
            paramPoints.RemoveAt(cur);
            //由于删除后,索引会移位,所以这里输出会有问题
            Debug.Log("删除内凹点,index " + cur);
        }
    }
    //三个点组成的夹角,判断该点是否是凸点
    private static bool isBump(Vector2 p1, Vector2 p2, Vector2 p3)
    {
        float sin = ((p1.x - p2.x) * (p3.y - p2.y) - (p3.x - p2.x) * (p1.y - p2.y));

        return sin < 0;

    }
}





public struct GeometryPoint : IComparable
{
    public GeometryPoint(float x, float y, float slope = float.NaN)
    {
        this._x = x;
        this._y = y;
        this.slope = slope;
    }
    private float _x;
    public float x
    {
        get { return _x; }
        set { _x = value; }
    }
    private float _y;
    public float y
    {
        get { return _y; }
        set { _y = value; }
    }
    private float slope;
    public float SLOPE
    {
        get { return slope; }
        set { slope = value; }
    }

    public int CompareTo(GeometryPoint p)
    {
        if (this.slope < p.slope)
        {
            return -1;
        }
        else if (this.slope > p.slope)
        {
            return 1;
        }
        else
        {
            if (this._x == p._x && this.SLOPE == p.SLOPE && this.SLOPE == float.MaxValue)
            {
                if (this._y == p._y)
                {
                    return 0;
                }
                else if (this._y < p._y)
                {
                    return 1;
                }
                else//(this.y > p.y)
                {
                    return -1;
                }
            }
            return 0;
        }
    }
    public override string ToString()
    {
        return string.Format("x:{0},y:{1},slope:{2}", _x, _y, slope);
    }
}

近似四边形方案代码:


using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System;


public static class DrawBounds_FastFourPoint
{
    public static bool create = false;
    public static Text staText;
    public static PolygonCollider2D polygonCollider2D;
    public static void DoDraw(List screenList, Image uiPoint, Camera camera, Canvas canvas, PolygonCollider2D paramPolygonCollider2D, Text text)
    {
        //Unity使用bounds绘制不规则图形边框
        for (int i = 0; i < screenList.Count; i++)
        {
            if (screenList[i].activeSelf == true)
            {
                //要用网格的Bounds而不是Render的,因为Render会在旋转时更新,导致bounds变大。
                Bounds bounds = screenList[i].GetComponent().mesh.bounds;
                Matrix4x4 matrix4x4 = screenList[i].transform.parent.localToWorldMatrix;
                Vector3 position = screenList[i].transform.parent.position;


                //算出8个顶点,然后连接出12个边
                //bounds.min是x和y都最小的地方
                //bounds.max是x和y都最大的地方
                //先算出下面那个面的四个顶点
                Vector3 max = bounds.max;
                Vector3 min = bounds.min;
                Vector3 corner_down_1 = bounds.min;
                corner_down_1 = matrix4x4 * corner_down_1;
                corner_down_1 = corner_down_1 + position;

                Vector3 corner_down_2 = new Vector3(max.x, min.y, min.z);
                corner_down_2 = matrix4x4 * corner_down_2;
                corner_down_2 = corner_down_2 + position;

                Vector3 corner_down_3 = new Vector3(max.x, min.y, max.z);
                corner_down_3 = matrix4x4 * corner_down_3;
                corner_down_3 = corner_down_3 + position;

                Vector3 corner_down_4 = new Vector3(min.x, min.y, max.z);
                corner_down_4 = matrix4x4 * corner_down_4;
                corner_down_4 = corner_down_4 + position;

                //然后算出上面四个顶点
                Vector3 corner_top_1 = new Vector3(min.x, max.y, min.z);
                corner_top_1 = matrix4x4 * corner_top_1;
                corner_top_1 = corner_top_1 + position;

                Vector3 corner_top_2 = new Vector3(max.x, max.y, min.z);
                corner_top_2 = matrix4x4 * corner_top_2;
                corner_top_2 = corner_top_2 + position;

                Vector3 corner_top_3 = bounds.max;
                corner_top_3 = matrix4x4 * corner_top_3;
                corner_top_3 = corner_top_3 + position;

                Vector3 corner_top_4 = new Vector3(min.x, max.y, max.z);
                corner_top_4 = matrix4x4 * corner_top_4;
                corner_top_4 = corner_top_4 + position;

                Vector3 corner_center = bounds.center;
                corner_center = matrix4x4 * corner_center;
                corner_center = corner_center + position;
                
                //这里要注意,由于测试场景的特殊性,测试物体已经没有父级所以不需要转换到世界坐标
                Vector3 up = screenList[i].transform.parent.up;
                //up = matrix4x4 * up;
                //up = up + position;

                //按照立方体的构图规律,把12条边绘制出来
                //首先绘制底面四个边
                Debug.DrawLine(corner_down_1, corner_down_2, Color.red);
                Debug.DrawLine(corner_down_2, corner_down_3, Color.red);
                Debug.DrawLine(corner_down_3, corner_down_4, Color.red);
                Debug.DrawLine(corner_down_4, corner_down_1, Color.red);
                //绘制顶面四个边
                Debug.DrawLine(corner_top_1, corner_top_2, Color.yellow);
                Debug.DrawLine(corner_top_2, corner_top_3, Color.yellow);
                Debug.DrawLine(corner_top_3, corner_top_4, Color.yellow);
                Debug.DrawLine(corner_top_4, corner_top_1, Color.yellow);
                //绘制侧面四个边
                Debug.DrawLine(corner_top_1, corner_down_1, Color.green);
                Debug.DrawLine(corner_top_2, corner_down_2, Color.green);
                Debug.DrawLine(corner_top_3, corner_down_3, Color.green);
                Debug.DrawLine(corner_top_4, corner_down_4, Color.green);

                //创建3DPoint,以监测位置是否合理
                if (!create)
                {
                    create = true;
                    GameObject.Instantiate(Resources.Load("Capsule"), up, Quaternion.identity).name = "up";
                    //    GameObject.Instantiate(Resources.Load("Cube"), corner_down_2, Quaternion.identity).name = "corner_down_2";
                    //    GameObject.Instantiate(Resources.Load("Cylinder"), corner_down_3, Quaternion.identity).name = "corner_down_3";
                    //    GameObject.Instantiate(Resources.Load("Sphere"), corner_down_4, Quaternion.identity).name = "corner_down_4";
                    //应该是筛选四个角的
                    polygonCollider2D = paramPolygonCollider2D;
                    staText = text;

                    corner_down_1 = CreateUIPoint(uiPoint, camera, canvas, corner_down_1, "corner_down_1",Color.red);
                    corner_down_2 = CreateUIPoint(uiPoint, camera, canvas, corner_down_2, "corner_down_2", Color.red);
                    corner_down_3 = CreateUIPoint(uiPoint, camera, canvas, corner_down_3, "corner_down_3", Color.red);
                    corner_down_4 =  CreateUIPoint(uiPoint, camera, canvas, corner_down_4, "corner_down_4", Color.red);

                    corner_top_1 = CreateUIPoint(uiPoint, camera, canvas, corner_top_1, "corner_top_1", Color.red);
                    corner_top_2 = CreateUIPoint(uiPoint, camera, canvas, corner_top_2, "corner_top_2", Color.red);
                    corner_top_3 = CreateUIPoint(uiPoint, camera, canvas, corner_top_3, "corner_top_3", Color.red);
                    corner_top_4 = CreateUIPoint(uiPoint, camera, canvas, corner_top_4, "corner_top_4", Color.red);
                    //绘制中心点
                    Vector2 point0 = CreateUIPoint(uiPoint, camera, canvas, corner_center, "corner_center", Color.yellow);
                    //绘制up点
                    Vector2 point1 = CreateUIPoint(uiPoint, camera, canvas, up, "up", Color.green);

                    //在 corner_down_1,corner_down_4,corner_top_2,corner_top_3 筛选出“最佳”的两个点。
                    //先重建对称轴, 这里取生成直线的通用公式。
                    float lineK1 = (point0.y - point1.y);
                    float lineK2 = (point1.x - point0.x);
                    float lineK3 = (point0.x * point1.y - point1.x * point0.y);
                    float lineK4 = lineK1+ lineK2+ lineK3;
                    float lineK5 = 1;// Mathf.Sqrt( Mathf.Pow(lineK2,2) + Mathf.Pow(point1.y - point0.y, 2));
                    //按照距离筛选两个点
                    float distanceOne;
                    float distanceTemp;


                    //计算底部两个的距离
                    Vector2 one = corner_down_1;
                    distanceOne = GetDistanceByPointAndLine(corner_down_1, lineK1, lineK2, lineK3, lineK4, lineK5);
                    distanceTemp = GetDistanceByPointAndLine(corner_down_4, lineK1, lineK2, lineK3, lineK4, lineK5);
                    if (distanceOne < distanceTemp)
                    {
                        distanceOne = distanceTemp;
                        one = corner_down_4;
                    }
                    CreateUIPoint(uiPoint, camera, canvas, one, "one", Color.black, true);

                    //计算顶部两个的距离
                    Vector2 two = corner_top_2;
                    float distanceTwo;
                    distanceTwo = GetDistanceByPointAndLine(corner_top_2, lineK1, lineK2, lineK3, lineK4, lineK5);
                    distanceTemp = GetDistanceByPointAndLine(corner_top_3, lineK1, lineK2, lineK3, lineK4, lineK5);
                    if (distanceTwo < distanceTemp)
                    {
                        distanceTwo = distanceTemp;
                        two = corner_top_3;
                    }
                    CreateUIPoint(uiPoint, camera, canvas, two, "two", Color.white, true);

                    //求对称坐标
                    float lineK6 = -2 * lineK2;
                    float lineK7 = -2 * lineK1;
                    float lineK8 = -2 * lineK3;
                    float lineK9 = lineK1 * lineK1;
                    float lineK10 = lineK2 * lineK2;
                    float lineK11 = (lineK7 * one.x + lineK6 * one.y + lineK8) / (lineK9 + lineK10);
                    float threeX = one.x + lineK1 * lineK11;
                    float threeY = one.y + lineK2 * lineK11;


                    Vector2 three = new Vector2(threeX, threeY);
                    CreateUIPoint(uiPoint, camera, canvas, three, "three", Color.black, true);
                    lineK11 = (lineK7 * two.x + lineK6 * two.y + lineK8) / (lineK9 + lineK10);
                    float fourX = two.x + lineK1 * lineK11;
                    float fourY = two.y + lineK2 * lineK11;
                    Vector2 four = new Vector2(fourX, fourY);
                    CreateUIPoint(uiPoint, camera, canvas, four, "four", Color.white, true);

                    polygonCollider2D.points = new Vector2[4] { one, three, two, four };


                }
            }
        }
    }
    
    private static Vector3 CreateUIPoint(Image uiPoint, Camera camera, Canvas canvas, Vector3 corner, string name, Color color, bool isScreenPoint = false)
    {
        Image tempObj;

      
        tempObj = GameObject.Instantiate(uiPoint);
        tempObj.name = name;
        tempObj.gameObject.SetActive(true);
        tempObj.transform.parent = canvas.transform;
        if (isScreenPoint)
        {
            tempObj.transform.localPosition = corner;
        }
        else
        {
            Vector3 ui_corner_top_1 = camera.WorldToScreenPoint(corner);
            tempObj.transform.localPosition = new Vector3(ui_corner_top_1.x - 1624 / 2, ui_corner_top_1.y - 750 / 2, ui_corner_top_1.z);
        }
        tempObj.transform.localScale = Vector3.one;
        tempObj.GetComponent().color = color;
        return tempObj.transform.localPosition;
    } 

    private static float GetDistanceByPointAndLine(Vector2 point, float lineK1, float lineK2, float lineK3, float lineK4, float lineK5)
    {
        return Mathf.Abs( (lineK1 * point.x + lineK2 * point.y + lineK3) / lineK5);
    }
}

 

你可能感兴趣的:(疑难杂症,游戏开发,unity3d)