不规则物体添加碰撞器

一、脚本的使用方法:
1、模型必须有Mesh网格;
2、模型根目录有MeshFilter组件,并且关联了Mesh网格;
3、把脚本挂载到模型上,点击脚本上的Calculate按钮;
4、此时在模型根目录下会出现Colliders文件,里面有好多个box碰撞器;
5、碰撞器添加成功,脚本可以去掉了。
二、注意:
1、这个脚本是一个辅助添加碰撞器的一个工具;
2、添加完了之后就可以把脚本去掉了,不用一直挂着;
3、碰撞器全都是BoxCollider;
4、(痛点)这些碰撞器不会跟随模型动画的移动而移动;
三、脚本如下:

using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Debug = UnityEngine.Debug;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class NonConvexMeshCollider : MonoBehaviour
{
    private bool mergeBoxesToReduceNumber = true;
    private int spatialTreeLevelDepth = 9;
    public bool outputTimeMeasurements = false;

    [Tooltip("Will create a child game object called 'Colliders' to store the generated colliders in. \n\rThis leads to a cleaner and more organized structure. \n\rPlease note that collisions will then report the child game object. So you may want to check for transform.parent.gameObject on your collision check.")]
    public bool createChildGameObject = true;
    [Tooltip("Takes a bit more time to compute, but leads to more performance optimized colliders (less boxes).")]
    public bool avoidGapsInside = false;
    [Tooltip("Makes sure all box colliders are generated completely on the inside of the mesh. More expensive to compute, but desireable if you need to avoid false collisions of objects very close to another, like rings of a chain for example.")]
    public bool avoidExceedingMesh = false;
    [Tooltip("The number of boxes your mesh will be segmented into, on each axis (x, y and z). \n\rHigher values lead to more accurate colliders but on the other hand makes computation and collision checks more expensive.")]
    public int boxesPerEdge = 20;
    [Tooltip("The physics material to apply to the generated compound colliders.")]
    public PhysicMaterial physicsMaterialForColliders;

    public const bool DebugOutput = false;

    public void Calculate()
    {
        var sw = Stopwatch.StartNew();

        if (boxesPerEdge > 100)
            boxesPerEdge = 100;
        if (avoidExceedingMesh && boxesPerEdge > 50)
            boxesPerEdge = 50;
        if (boxesPerEdge < 1)
            boxesPerEdge = 3;

        var go = gameObject;
        var meshFilter = go.GetComponent<MeshFilter>();
        if (meshFilter == null) return;
        if (meshFilter.sharedMesh == null) return;
        var rbdy = go.GetComponent<Rigidbody>();
        var hadNonKinematicRigidbody = false;
        if (rbdy != null && !rbdy.isKinematic)
        {
            hadNonKinematicRigidbody = true;
            rbdy.isKinematic = true;
        }
        var hadRigidbodyWithGravityUse = false;
        if (rbdy != null && rbdy.useGravity)
        {
            hadRigidbodyWithGravityUse = true;
            rbdy.useGravity = false;
        }
        if (!createChildGameObject)
        {
            foreach (var bc in go.GetComponents<BoxCollider>())
                DestroyImmediate(bc);
        }
        var originalLayer = go.layer;
        var collisionLayer = GetFirstEmptyLayer();
        go.layer = collisionLayer;

        var parentGo = go.transform.parent;

        var localPos = go.transform.localPosition;
        var localRot = go.transform.localRotation;
        var localScale = go.transform.localScale;
        var tempParent = new GameObject("Temp_CompoundColliderParent");
        go.transform.parent = tempParent.transform;
        go.transform.localPosition = Vector3.zero;
        go.transform.localRotation = Quaternion.Euler(Vector3.zero);
        go.transform.localScale = Vector3.one;

        try
        {
            var collidersGo = CreateColliderChildGameObject(go, meshFilter);

            //create compound colliders
            var boxes = CreateMeshIntersectingBoxes(collidersGo).ToArray();

            //merge boxes to create bigger and less ones
            var mergedBoxes =
                mergeBoxesToReduceNumber
                    ? MergeBoxes(boxes.ToArray())
                    : boxes;

            foreach (var b in mergedBoxes)
            {
                var bc = (createChildGameObject ? collidersGo : go).AddComponent<BoxCollider>();
                bc.size = b.Size;
                bc.center = b.Center;
                if (physicsMaterialForColliders != null)
                    bc.material = physicsMaterialForColliders;
            }
            Debug.Log("NonConvexMeshCollider: " + mergedBoxes.Length + " box colliders created");

            //cleanup stuff not needed anymore on collider child obj
            DestroyImmediate(collidersGo.GetComponent<MeshFilter>());
            DestroyImmediate(collidersGo.GetComponent<MeshCollider>());
            DestroyImmediate(collidersGo.GetComponent<Rigidbody>());
            if (!createChildGameObject)
                DestroyImmediate(collidersGo);
            else if (collidersGo)
                collidersGo.layer = originalLayer;
        }
        finally
        {
            //reset original state of root object
            go.transform.parent = parentGo;
            go.transform.localPosition = localPos;
            go.transform.localRotation = localRot;
            go.transform.localScale = localScale;
            go.layer = originalLayer;
            if (hadNonKinematicRigidbody)
                rbdy.isKinematic = false;
            if (hadRigidbodyWithGravityUse)
                rbdy.useGravity = true;
            DestroyImmediate(tempParent);
        }

        sw.Stop();
        if (outputTimeMeasurements)
            Debug.Log("Total duration: " + sw.Elapsed);
    }

    public class BoundingBox
    {
        public BoundingBox(params Interval[] intervalsXyz) : this(intervalsXyz[0], intervalsXyz[1], intervalsXyz[2])
        {

        }
        public BoundingBox(Interval intervalX, Interval intervalY, Interval intervalZ)
        {
            IntervalX = intervalX;
            IntervalY = intervalY;
            IntervalZ = intervalZ;
        }

        public Interval IntervalX { get; private set; }
        public Interval IntervalY { get; private set; }
        public Interval IntervalZ { get; private set; }

        public bool IntersectsRayToPositiveX(Vector3 origin)
        {
            var tx1 = IntervalX.Min - origin.x;
            var tx2 = IntervalX.Max - origin.x;

            var tmin = Math.Min(tx1, tx2);
            var tmax = Math.Max(tx1, tx2);

            tmin = Math.Max(tmin, 0);
            tmax = Math.Min(tmax, 0);

            return tmax >= tmin;
        }

        public bool Intersects(Ray r)
        {
            // r.dir is unit direction vector of ray
            var dirfracx = 1.0f / r.direction.x;
            var dirfracy = 1.0f / r.direction.y;
            var dirfracz = 1.0f / r.direction.z;
            // lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
            // r.org is origin of ray
            var t1 = (IntervalX.Min - r.origin.x) * dirfracx;
            var t2 = (IntervalX.Max - r.origin.x) * dirfracx;
            var t3 = (IntervalY.Min - r.origin.y) * dirfracy;
            var t4 = (IntervalY.Max - r.origin.y) * dirfracy;
            var t5 = (IntervalZ.Min - r.origin.z) * dirfracz;
            var t6 = (IntervalZ.Max - r.origin.z) * dirfracz;

            var tmin = Math.Max(Math.Max(Math.Min(t1, t2), Math.Min(t3, t4)), Math.Min(t5, t6));
            var tmax = Math.Min(Math.Min(Math.Max(t1, t2), Math.Max(t3, t4)), Math.Max(t5, t6));

            float t;

            // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us
            if (tmax < 0)
            {
                t = tmax;
                return false;
            }

            // if tmin > tmax, ray doesn't intersect AABB
            if (tmin > tmax)
            {
                t = tmax;
                return false;
            }

            t = tmin;
            return true;
        }

        public bool Intersects(BoundingBox other)
        {
            var result =
                IntervalX.Intersects(other.IntervalX) &&
                IntervalY.Intersects(other.IntervalY) &&
                IntervalZ.Intersects(other.IntervalZ);
            return result;
        }

        public override string ToString()
        {
            return string.Format("X: {0:N4}-{1:N4}, Y: {2:N4}-{3:N4}, Z: {4:N4}-{5:N4}",
                IntervalX.Min, IntervalX.Max,
                IntervalY.Min, IntervalY.Max,
                IntervalZ.Min, IntervalZ.Max);
        }
    }

    private Box[] MergeBoxes(Box[] boxes)
    {
        var sw = Stopwatch.StartNew();

        var mergeDirections = new[] {
            new Vector3Int(1,0,0),
            new Vector3Int(0,1,0),
            new Vector3Int(0,0,1),
            new Vector3Int(-1,0,0),
            new Vector3Int(0,-1,0),
            new Vector3Int(0,0,-1),
        };
        var foundSomethingToMerge = false;
        do
        {
            foreach (var mergeDirection in mergeDirections)
            {
                foundSomethingToMerge = false;
                foreach (var box in boxes)
                {
                    var merged = box.TryMerge(mergeDirection);
                    if (merged)
                        foundSomethingToMerge = true;
                }
                boxes = boxes.Select(b => b.Root).Distinct().ToArray();
            }
        } while (foundSomethingToMerge);
        var result = boxes.Select(b => b.Root).Distinct().ToArray();


        sw.Stop();
        if (outputTimeMeasurements)
            Debug.Log("Merged in " + sw.Elapsed);

        return result;
    }

    private static GameObject CreateColliderChildGameObject(GameObject go, MeshFilter meshFilter)
    {
        //ensure collider child gameobject exists
        var collidersTransform = go.transform.Find("Colliders");
        GameObject collidersGo;
        if (collidersTransform != null)
            collidersGo = collidersTransform.gameObject;
        else
        {
            collidersGo = new GameObject("Colliders");
            collidersGo.transform.parent = go.transform;
            collidersGo.transform.localRotation = Quaternion.Euler(Vector3.zero);
            collidersGo.transform.localPosition = Vector3.zero;
        }
        collidersGo.layer = go.layer;

        //reset collider child gameobject
        foreach (var bc in collidersGo.GetComponents<BoxCollider>())
            DestroyImmediate(bc);
        var mf = collidersGo.GetComponent<MeshFilter>();
        if (mf != null) DestroyImmediate(mf);
        var mc = collidersGo.GetComponent<MeshCollider>();
        if (mc != null) DestroyImmediate(mc);
        var rd = collidersGo.GetComponent<Rigidbody>();
        if (rd != null) DestroyImmediate(rd);
        rd = collidersGo.AddComponent<Rigidbody>();
        rd.isKinematic = true;
        rd.useGravity = false;

        //setup collider child gameobject
        mf = collidersGo.AddComponent<MeshFilter>();
        mf.sharedMesh = meshFilter.sharedMesh;
        //MakeMeshDoubleSided(mf.mesh);
        mc = collidersGo.AddComponent<MeshCollider>();
        mc.convex = false;
        return collidersGo;
    }

    private IEnumerable<Box> CreateMeshIntersectingBoxes(GameObject colliderGo)
    {
        var go = colliderGo.transform.parent.gameObject;
        var colliderLayer = colliderGo.layer;
        LayerMask colliderLayerMask = 1 << colliderLayer;
        var bounds = CalculateLocalBounds(go);
        var mesh = colliderGo.GetComponent<MeshFilter>().sharedMesh;
        var swTree = Stopwatch.StartNew();
        var tree = new SpatialBinaryTree(mesh, spatialTreeLevelDepth);
        swTree.Stop();
        if (outputTimeMeasurements)
            Debug.Log("SpatialTree Built in " + swTree.Elapsed);

        var boxes = new Box[boxesPerEdge, boxesPerEdge, boxesPerEdge];
        var boxColliderPositions = new bool[boxesPerEdge, boxesPerEdge, boxesPerEdge];
        var s = bounds.size / boxesPerEdge;
        var halfExtent = s / 2f;

        var directionsFromBoxCenterToCorners = new[]
        {
            new Vector3(1,1,1),
            new Vector3(1,1,-1),
            new Vector3(1,-1,1),
            new Vector3(1,-1,-1),
            new Vector3(-1,1,1),
            new Vector3(-1,1,-1),
            new Vector3(-1,-1,1),
            new Vector3(-1,-1,-1),
        };

        var pointInsideMeshCache = new Dictionary<Vector3, bool>();

        var sw = Stopwatch.StartNew();

        var colliders = new Collider[1000];
        for (var x = 0; x < boxesPerEdge; x++)
        {
            for (var y = 0; y < boxesPerEdge; y++)
            {
                for (var z = 0; z < boxesPerEdge; z++)
                {
                    var center = new Vector3(
                        bounds.center.x - bounds.size.x / 2 + s.x * x + halfExtent.x,
                        bounds.center.y - bounds.size.y / 2 + s.y * y + halfExtent.y,
                        bounds.center.z - bounds.size.z / 2 + s.z * z + halfExtent.z);

                    if (!avoidExceedingMesh)
                    {
                        if (avoidGapsInside)
                        {
                            var isInsideSurface = IsInsideMesh(center, tree, pointInsideMeshCache);
                            boxColliderPositions[x, y, z] = isInsideSurface;
                        }
                        else
                        {
                            var overlapsWithMeshSurface = Physics.OverlapBoxNonAlloc(center, halfExtent, colliders, Quaternion.identity, colliderLayerMask) > 0;
                            boxColliderPositions[x, y, z] = overlapsWithMeshSurface;
                        }
                        continue;
                    }

                    var allCornersInsideMesh =
                        (from d in directionsFromBoxCenterToCorners
                         select new Vector3(center.x + halfExtent.x * d.x, center.y + halfExtent.y * d.y, center.z + halfExtent.z * d.z))
                        .All(cornerPoint => IsInsideMesh(cornerPoint, tree, pointInsideMeshCache));
                    boxColliderPositions[x, y, z] = allCornersInsideMesh;
                }
            }
        }

        sw.Stop();
        if (outputTimeMeasurements)
            Debug.Log("Boxes analyzed in " + sw.Elapsed);

        for (var x = 0; x < boxesPerEdge; x++)
        {
            for (var y = 0; y < boxesPerEdge; y++)
            {
                for (var z = 0; z < boxesPerEdge; z++)
                {
                    if (!boxColliderPositions[x, y, z]) continue;
                    var center = new Vector3(
                        bounds.center.x - bounds.size.x / 2 + s.x * x + s.x / 2,
                        bounds.center.y - bounds.size.y / 2 + s.y * y + s.y / 2,
                        bounds.center.z - bounds.size.z / 2 + s.z * z + s.z / 2);
                    var b = new Box(boxes, center, s, new Vector3Int(x, y, z));
                    boxes[x, y, z] = b;
                    yield return b;
                }
            }
        }
    }


    private bool IsInsideMesh(Vector3 p, SpatialBinaryTree tree, Dictionary<Vector3, bool> pointInsideMeshCache)
    {
        bool isInsideMesh;
        if (pointInsideMeshCache.TryGetValue(Vector3.one, out isInsideMesh))
            return isInsideMesh;

        var r = new Ray(p, new Vector3(1, 0, 0));
        var intersectionCount = tree.GetTris(r).Count(t => t.Intersect(r));
        var isInside = intersectionCount % 2 != 0;

        pointInsideMeshCache[p] = isInside;
        return isInside;
    }

    private int GetFirstEmptyLayer()
    {
        for (int i = 8; i <= 31; i++) //user defined layers start with layer 8 and unity supports 31 layers
        {
            var layerN = LayerMask.LayerToName(i); //get the name of the layer
            if (layerN.Length == 0)
                return i;
        }
        throw new Exception("Didn't find unused layer for temporary assignment");
    }

    private static Bounds CalculateLocalBounds(GameObject go)
    {
        var bounds = new Bounds(go.transform.position, Vector3.zero);
        foreach (var renderer in go.GetComponentsInChildren<Renderer>())
            bounds.Encapsulate(renderer.bounds);
        var localCenter = bounds.center - go.transform.position;
        bounds.center = localCenter;
        return bounds;
    }

    public class Tri
    {
        private BoundingBox bounds;

        public Tri(Vector3 a, Vector3 b, Vector3 c)
        {
            A = a;
            B = b;
            C = c;
        }

        public Vector3 A { get; set; }
        public Vector3 B { get; set; }
        public Vector3 C { get; set; }

        public BoundingBox Bounds
        {
            get
            {
                if (bounds == null)
                {
                    var b = new BoundingBox(
                        Interval.From(A.x, B.x, C.x),
                        Interval.From(A.y, B.y, C.y),
                        Interval.From(A.z, B.z, C.z)
                    );
                    bounds = b;
                }
                return bounds;
            }
        }

        public bool Intersect(Ray ray)
        {
            // Vectors from p1 to p2/p3 (edges)
            //Find vectors for two edges sharing vertex/point p1
            var e1 = B - A;
            var e2 = C - A;

            // calculating determinant 
            var p = Vector3.Cross(ray.direction, e2);

            //Calculate determinat
            var det = Vector3.Dot(e1, p);

            //if determinant is near zero, ray lies in plane of triangle otherwise not
            if (det > -0.000001f && det < 0.000001f) return false;
            var invDet = 1.0f / det;

            //calculate distance from p1 to ray origin
            var t = ray.origin - A;

            //Calculate u parameter
            var u = Vector3.Dot(t, p) * invDet;

            //Check for ray hit
            if (u < 0 || u > 1) { return false; }

            //Prepare to test v parameter
            var q = Vector3.Cross(t, e1);

            //Calculate v parameter
            var v = Vector3.Dot(ray.direction, q) * invDet;

            //Check for ray hit
            if (v < 0 || u + v > 1) { return false; }

            if (Vector3.Dot(e2, q) * invDet > 0.000001f)
            {
                //ray does intersect
                return true;
            }

            // No hit at all
            return false;

        }


        /// <summary>
        /// Checks if the specified ray hits the trianglge descibed by p1, p2 and p3.
        /// Möller–Trumbore ray-triangle intersection algorithm implementation.
        /// </summary>
        /// <param name="p1">Vertex 1 of the triangle.</param>
        /// <param name="p2">Vertex 2 of the triangle.</param>
        /// <param name="p3">Vertex 3 of the triangle.</param>
        /// <param name="ray">The ray to test hit for.</param>
        /// <returns><c>true</c> when the ray hits the triangle, otherwise <c>false</c></returns>
        public static bool Intersect(Vector3 p1, Vector3 p2, Vector3 p3, Ray ray)
        {
            // Vectors from p1 to p2/p3 (edges)
            //Find vectors for two edges sharing vertex/point p1
            var e1 = p2 - p1;
            var e2 = p3 - p1;

            // calculating determinant 
            var p = Vector3.Cross(ray.direction, e2);

            //Calculate determinat
            var det = Vector3.Dot(e1, p);

            //if determinant is near zero, ray lies in plane of triangle otherwise not
            if (det > -0.000001f && det < 0.000001f) return false;
            var invDet = 1.0f / det;

            //calculate distance from p1 to ray origin
            var t = ray.origin - p1;

            //Calculate u parameter
            var u = Vector3.Dot(t, p) * invDet;

            //Check for ray hit
            if (u < 0 || u > 1) { return false; }

            //Prepare to test v parameter
            var q = Vector3.Cross(t, e1);

            //Calculate v parameter
            var v = Vector3.Dot(ray.direction, q) * invDet;

            //Check for ray hit
            if (v < 0 || u + v > 1) { return false; }

            if (Vector3.Dot(e2, q) * invDet > 0.000001f)
            {
                //ray does intersect
                return true;
            }

            // No hit at all
            return false;
        }
    }

    public class SpatialBinaryTree
    {
        public SpatialBinaryTree(Mesh m, int maxLevels)
        {
            var boundingBox = new BoundingBox(
                new Interval(m.vertices.Min(v => v.x), m.vertices.Max(v => v.x)),
                new Interval(m.vertices.Min(v => v.y), m.vertices.Max(v => v.y)),
                new Interval(m.vertices.Min(v => v.z), m.vertices.Max(v => v.z)));
            root = new SpatialBinaryTreeNode(0, maxLevels, boundingBox);

            var triCount = m.triangles.Length / 3;
            for (var i = 0; i < triCount; i++)
            {
                var v1 = m.vertices[m.triangles[i * 3]];
                var v2 = m.vertices[m.triangles[i * 3 + 1]];
                var v3 = m.vertices[m.triangles[i * 3 + 2]];
                var t = new Tri(v1, v2, v3);
                Add(t);
            }
        }

        public void Add(Tri t)
        {
            root.Add(t);
        }

        public IEnumerable<Tri> GetTris(Ray r)
        {
            return new HashSet<Tri>(root.GetTris(r));
        }

        private readonly SpatialBinaryTreeNode root;
    }

    public class SpatialBinaryTreeNode
    {
        private readonly int level;
        private readonly int maxLevels;
        private SpatialBinaryTreeNode childA;
        private SpatialBinaryTreeNode childB;
        private readonly List<Tri> tris;
        private readonly BoundingBox bounds;
        private readonly BoundingBox boundsChildA;
        private readonly BoundingBox boundsChildB;

        public SpatialBinaryTreeNode(int level, int maxLevels, BoundingBox bounds)
        {
            this.level = level;
            this.maxLevels = maxLevels;
            this.bounds = bounds;

            if (level >= maxLevels)
                tris = new List<Tri>();
            else
            {
                var lvlMod3 = level % 3;
                boundsChildA = new BoundingBox(
                    lvlMod3 == 0 ? bounds.IntervalX.LowerHalf : bounds.IntervalX,  //x
                    lvlMod3 == 1 ? bounds.IntervalY.LowerHalf : bounds.IntervalY,  //y
                    lvlMod3 == 2 ? bounds.IntervalZ.LowerHalf : bounds.IntervalZ); //z
                boundsChildB = new BoundingBox(
                    lvlMod3 == 0 ? bounds.IntervalX.UpperHalf : bounds.IntervalX,  //x
                    lvlMod3 == 1 ? bounds.IntervalY.UpperHalf : bounds.IntervalY,  //y
                    lvlMod3 == 2 ? bounds.IntervalZ.UpperHalf : bounds.IntervalZ); //z
            }
        }

        public void Add(Tri t)
        {
            if (tris != null)
            {
                tris.Add(t);
            }
            else
            {
                if (boundsChildA.Intersects(t.Bounds))
                {
                    if (childA == null)
                        childA = new SpatialBinaryTreeNode(level + 1, maxLevels, boundsChildA);
                    childA.Add(t);
                }
                if (boundsChildB.Intersects(t.Bounds))
                {
                    if (childB == null)
                        childB = new SpatialBinaryTreeNode(level + 1, maxLevels, boundsChildB);
                    childB.Add(t);
                }
            }
        }

        public IEnumerable<Tri> GetTris(Ray r)
        {
            if (!bounds.Intersects(r))
                yield break;

            if (tris != null)
            {
                foreach (var t in tris)
                    yield return t;
            }
            else
            {
                if (childA != null)
                    foreach (var t in childA.GetTris(r))
                        yield return t;

                if (childB != null)
                    foreach (var t in childB.GetTris(r))
                        yield return t;
            }
        }

        public override string ToString()
        {
            if (tris != null)
                return "Leaf node: " + tris.Count + " tris";
            return bounds.ToString();
        }
    }

    public class Interval
    {
        public Interval(float min, float max)
        {
            Min = min;
            Max = max;
            Center = (min + max) / 2f;
        }

        public float Min { get; set; }
        public float Max { get; set; }
        public float Center { get; private set; }
        public float Size { get { return Max - Min; } }

        public Interval LowerHalf { get { return new Interval(Min, Center); } }
        public Interval UpperHalf { get { return new Interval(Center, Max); } }

        public bool Contains(float v)
        {
            if (v < Min) return false;
            if (v >= Max) return false;
            return true;
        }

        public bool IsInLeftHalf(float v)
        {
            return v >= Min && v < Center;
        }

        public bool IsInRightHalf(float v)
        {
            return v > Center && v < Max;
        }

        public bool Intersects(Interval other)
        {
            return Min <= other.Max && other.Min <= Max;
        }

        public static Interval From(float a, float b, float c)
        {
            return new Interval(Math.Min(Math.Min(a, b), c), Math.Max(Math.Max(a, b), c));
        }

        public static Interval From(float a, float b)
        {
            return new Interval(Math.Min(a, b), Math.Max(a, b));
        }
    }

    public class Box
    {
        private readonly Box[,,] boxes;
        private readonly Vector3Int lastLevelGridPos;

        public Box(Box[,,] boxes, Vector3? center = null, Vector3? size = null, Vector3Int lastLevelGridPos = null)
        {
            this.boxes = boxes;
            this.lastLevelGridPos = lastLevelGridPos;
            this.center = center;
            this.size = size;
        }

        public Vector3 Center
        {
            get
            {
                if (center == null)
                {
                    if (Children == null) throw new Exception("Last level child box needs a center position");
                    var v = Vector3.zero;
                    foreach (var b in LastLevelBoxes)
                        v += b.Center;
                    v = v / LastLevelBoxes.Length;
                    center = v;
                }
                return center.Value;
            }
        }

        public Vector3 Size
        {
            get
            {
                if (size == null)
                {
                    if (Children == null) throw new Exception("Last level child box needs a size");
                    var singleBoxSize = LastLevelBoxes[0].Size;
                    size = new Vector3(GridSize.X * singleBoxSize.x, GridSize.Y * singleBoxSize.y, GridSize.Z * singleBoxSize.z);
                }
                return size.Value;
            }
        }

        private void MergeWith(Box other)
        {
            var b = new Box(boxes);
            foreach (var child in new[] { this, other })
                child.Parent = b;
            b.Children = new[] { this, other };
            Box temp = b;
        }

        public Box Parent { get; set; }
        public Box[] Children { get; set; }

        public IEnumerable<Box> Parents
        {
            get
            {
                var b = this;
                while (b.Parent != null)
                {
                    yield return b.Parent;
                    b = b.Parent;
                }
            }
        }

        public IEnumerable<Box> SelfAndParents
        {
            get
            {
                yield return this;
                foreach (var parent in Parents)
                    yield return parent;
            }
        }

        public Box Root
        {
            get
            {
                return Parent == null ? this : Parent.Root;
            }
        }

        public bool TryMerge(Vector3Int direction)
        {
            if (Parent != null) return false;
            foreach (var p in CoveredGridPositions)
            {
                var pos = new Vector3Int(p.X + direction.X, p.Y + direction.Y, p.Z + direction.Z);
                if (pos.X < 0 || pos.Y < 0 || pos.Z < 0)
                    continue;
                if (pos.X >= boxes.GetLength(0) || pos.Y >= boxes.GetLength(1) || pos.Z >= boxes.GetLength(2))
                    continue;
                var b = boxes[pos.X, pos.Y, pos.Z];
                if (b == null)
                    continue;
                b = b.Root;
                if (b == this)
                    continue;
                if (direction.X == 0 && b.GridSize.X != GridSize.X)
                    continue;
                if (direction.Y == 0 && b.GridSize.Y != GridSize.Y)
                    continue;
                if (direction.Z == 0 && b.GridSize.Z != GridSize.Z)
                    continue;
                if (direction.X == 0 && MinGridPos.X != b.MinGridPos.X)
                    continue;
                if (direction.Y == 0 && MinGridPos.Y != b.MinGridPos.Y)
                    continue;
                if (direction.Z == 0 && MinGridPos.Z != b.MinGridPos.Z)
                    continue;
                MergeWith(b);
                return true;
            }
            return false;
        }

        public IEnumerable<Box> ChildrenRecursive
        {
            get
            {
                if (Children == null) yield break;
                foreach (var c in Children)
                {
                    yield return c;
                    foreach (var cc in c.ChildrenRecursive)
                        yield return cc;
                }
            }
        }

        public IEnumerable<Box> SelfAndChildrenRecursive
        {
            get
            {
                yield return this;
                foreach (var c in ChildrenRecursive)
                    yield return c;
            }
        }

        private Box[] lastLevelBoxes;
        public Box[] LastLevelBoxes
        {
            get
            {
                if (lastLevelBoxes == null)
                    lastLevelBoxes = SelfAndChildrenRecursive.Where(c => c.Children == null).ToArray();
                return lastLevelBoxes;
            }
        }

        private IEnumerable<Vector3Int> CoveredGridPositions
        {
            get { return LastLevelBoxes.Select(c => c.lastLevelGridPos); }
        }

        private int MinGridPosX
        {
            get { return Children == null ? lastLevelGridPos.X : CoveredGridPositions.Min(p => p.X); }
        }

        private int MinGridPosY
        {
            get { return Children == null ? lastLevelGridPos.Y : CoveredGridPositions.Min(p => p.Y); }
        }

        private int MinGridPosZ
        {
            get { return Children == null ? lastLevelGridPos.Z : CoveredGridPositions.Min(p => p.Z); }
        }

        private int MaxGridPosX
        {
            get { return Children == null ? lastLevelGridPos.X : CoveredGridPositions.Max(p => p.X); }
        }

        private int MaxGridPosY
        {
            get { return Children == null ? lastLevelGridPos.Y : CoveredGridPositions.Max(p => p.Y); }
        }

        private int MaxGridPosZ
        {
            get { return Children == null ? lastLevelGridPos.Z : CoveredGridPositions.Max(p => p.Z); }
        }

        private Vector3Int minGridPos;
        private Vector3Int MinGridPos
        {
            get { return minGridPos ?? (minGridPos = new Vector3Int(MinGridPosX, MinGridPosY, MinGridPosZ)); }
        }

        private Vector3Int maxGridPos;
        private Vector3Int MaxGridPos
        {
            get { return maxGridPos ?? (maxGridPos = new Vector3Int(MaxGridPosX, MaxGridPosY, MaxGridPosZ)); }
        }

        private Vector3Int gridSize;
        private Vector3? center;
        private Vector3? size;

        private Vector3Int GridSize
        {
            get
            {
                if (gridSize == null)
                    gridSize = Children == null
                        ? Vector3Int.One
                        : new Vector3Int(
                            MaxGridPos.X - MinGridPos.X + 1,
                            MaxGridPos.Y - MinGridPos.Y + 1,
                            MaxGridPos.Z - MinGridPos.Z + 1);
                return gridSize;
            }
        }
    }

    public class Vector3Int
    {
        public Vector3Int(int x, int y, int z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public int X { get; set; }
        public int Y { get; set; }
        public int Z { get; set; }
        public static readonly Vector3Int One = new Vector3Int(1, 1, 1);

        protected bool Equals(Vector3Int other)
        {
            return X == other.X && Y == other.Y && Z == other.Z;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((Vector3Int)obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = X;
                hashCode = (hashCode * 397) ^ Y;
                hashCode = (hashCode * 397) ^ Z;
                return hashCode;
            }
        }

        public override string ToString()
        {
            return string.Format("X: {0}, Y: {1}, Z: {2}", X, Y, Z);
        }
    }

}

#if UNITY_EDITOR
[CanEditMultipleObjects]
[CustomEditor(typeof(NonConvexMeshCollider))]
public class NonConvexMeshColliderEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        var scripts = targets.OfType<NonConvexMeshCollider>();
        if (GUILayout.Button("Calculate"))
            foreach (var script in scripts)
                script.Calculate();
    }
}
#endif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343