不規則物體添加碰撞器

一、腳本的使用方法:
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閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377