public void Box_PointOnFace()
    {
        using (var box = CreateBox())
        {
            HullDrawingUtility.DrawBasicHull(box, RigidTransform.identity, Color.black, 100);

            var rotateEveryAxis45 = Quaternion.Euler((float3)45);

            // up and rotated position
            var insideUnitBox  = new RigidTransform(rotateEveryAxis45, new float3(0.2f, 1.34f, 0.1f));
            var outsideUnitBox = new RigidTransform(rotateEveryAxis45, new float3(0.2f, 1.38f, 0.1f));

            Assert.IsTrue(HullCollision.IsColliding(insideUnitBox, box, RigidTransform.identity, box));
            Assert.IsFalse(HullCollision.IsColliding(outsideUnitBox, box, RigidTransform.identity, box));

            HullDrawingUtility.DrawBasicHull(box, insideUnitBox, Color.blue, 100);
            HullDrawingUtility.DrawBasicHull(box, outsideUnitBox, Color.black, 100);

            // down and rotated position
            insideUnitBox  = new RigidTransform(rotateEveryAxis45, new float3(0.2f, -1.34f, 0.1f));
            outsideUnitBox = new RigidTransform(rotateEveryAxis45, new float3(0.2f, -1.38f, 0.1f));

            Assert.IsTrue(HullCollision.IsColliding(insideUnitBox, box, RigidTransform.identity, box));
            Assert.IsFalse(HullCollision.IsColliding(outsideUnitBox, box, RigidTransform.identity, box));

            HullDrawingUtility.DrawBasicHull(box, insideUnitBox, Color.blue, 100);
            HullDrawingUtility.DrawBasicHull(box, outsideUnitBox, Color.black, 100);
        }
    }
    public void Box_EdgeSeparation()
    {
        using (var box = CreateBox())
        {
            // These two boxes are positioned so that both face SAT checks would show no separation,
            // a working SAT edge test is required to catch the false positive.

            var boxTransformA = new RigidTransform(Quaternion.Euler(-24.357f, -4.779f, -32.115f), new float3(-0.089f, -0.821f, -2.233f));
            var boxTransformB = new RigidTransform(Quaternion.Euler(55.943f, 21.207f, 47.057f), new float3(-0.207f, -0.06f, -1.256f));

            Assert.IsFalse(HullCollision.IsColliding(boxTransformA, box, boxTransformB, box));

            HullDrawingUtility.DrawBasicHull(box, boxTransformA, Color.blue, 100);
            HullDrawingUtility.DrawBasicHull(box, boxTransformB, Color.black, 100);
        }
    }
    public void Box_EdgeOnEdge()
    {
        using (var box = CreateBox())
        {
            HullDrawingUtility.DrawBasicHull(box, RigidTransform.identity, Color.black, 100);

            var rotateEveryAxis45 = Quaternion.Euler((float3)45);
            var insideUnitBox     = new RigidTransform(rotateEveryAxis45, new float3(0.2f, 1.1f, -0.80f));
            var outsideUnitBox    = new RigidTransform(rotateEveryAxis45, new float3(0.184f, 1.123f, -0.816f));

            Assert.IsTrue(HullCollision.IsColliding(insideUnitBox, box, RigidTransform.identity, box));
            Assert.IsFalse(HullCollision.IsColliding(outsideUnitBox, box, RigidTransform.identity, box));

            HullDrawingUtility.DrawBasicHull(box, insideUnitBox, Color.blue, 100);
            HullDrawingUtility.DrawBasicHull(box, outsideUnitBox, Color.black, 100);
        }
    }
    private void HandleHullCollisions()
    {
        for (int i = 0; i < Transforms.Count; ++i)
        {
            var tA = Transforms[i];
            if (tA == null)
            {
                continue;
            }

            var hullA      = Hulls[tA.GetInstanceID()].Hull;
            var transformA = new RigidTransform(tA.rotation, tA.position);

            HullDrawingUtility.DrawDebugHull(hullA, transformA, HullDrawingOptions);

            if (LogClosestPoint)
            {
                var sw3     = System.Diagnostics.Stopwatch.StartNew();
                var result3 = HullCollision.ClosestPoint(transformA, hullA, 0);
                sw3.Stop();

                var sw4     = System.Diagnostics.Stopwatch.StartNew();
                var result4 = HullOperations.ClosestPoint.Invoke(transformA, hullA, 0);
                sw4.Stop();

                if (DrawClosestPoint)
                {
                    DebugDrawer.DrawSphere(result4, 0.1f, Color.blue);
                    DebugDrawer.DrawLine(result4, Vector3.zero, Color.blue);
                }

                Debug.Log($"ClosestPoint between '{tA.name}' and world zero took: {sw3.Elapsed.TotalMilliseconds:N4}ms (Normal), {sw4.Elapsed.TotalMilliseconds:N4}ms (Burst)");
            }

            for (int j = i + 1; j < Transforms.Count; j++)
            {
                var tB = Transforms[j];
                if (tB == null)
                {
                    continue;
                }

                if (!tA.hasChanged && !tB.hasChanged)
                {
                    continue;
                }

                var hullB      = Hulls[tB.GetInstanceID()].Hull;
                var transformB = new RigidTransform(tB.rotation, tB.position);
                HullDrawingUtility.DrawDebugHull(hullB, transformB, HullDrawingOptions);

                DrawHullCollision(tA.gameObject, tB.gameObject, transformA, hullA, transformB, hullB);

                if (LogCollisions)
                {
                    var sw1     = System.Diagnostics.Stopwatch.StartNew();
                    var result1 = HullCollision.IsColliding(transformA, hullA, transformB, hullB);
                    sw1.Stop();

                    var sw2     = System.Diagnostics.Stopwatch.StartNew();
                    var result2 = HullOperations.IsColliding.Invoke(transformA, hullA, transformB, hullB);
                    sw2.Stop();

                    Debug.Assert(result1 == result2);

                    Debug.Log($"Collisions between '{tA.name}'/'{tB.name}' took: {sw1.Elapsed.TotalMilliseconds:N4}ms (Normal), {sw2.Elapsed.TotalMilliseconds:N4}ms (Burst)");
                }
            }
        }

        if (LogCollisions)
        {
            TestBatchCollision();
        }
    }
    public void DrawHullCollision(GameObject a, GameObject b, RigidTransform t1, NativeHull hull1, RigidTransform t2, NativeHull hull2)
    {
        var collision = HullCollision.GetDebugCollisionInfo(t1, hull1, t2, hull2);

        if (collision.IsColliding)
        {
            if (DrawIntersection) // Visualize all faces of the intersection
            {
                HullIntersection.DrawNativeHullHullIntersection(t1, hull1, t2, hull2);
            }

            if (DrawContact || LogContact)  // Visualize the minimal contact calcluation for physics
            {
                //var manifold = HullOperations.GetContact.Invoke(t1, hull1, t2, hull2);

                var sw1          = System.Diagnostics.Stopwatch.StartNew();
                var tmp          = new NativeManifold(Allocator.Persistent);
                var normalResult = HullIntersection.NativeHullHullContact(ref tmp, t1, hull1, t2, hull2);
                sw1.Stop();
                tmp.Dispose();

                var sw2         = System.Diagnostics.Stopwatch.StartNew();
                var burstResult = HullOperations.TryGetContact.Invoke(out NativeManifold manifold, t1, hull1, t2, hull2);
                sw2.Stop();

                if (LogContact)
                {
                    Debug.Log($"GetContact between '{a.name}'/'{b.name}' took: {sw1.Elapsed.TotalMilliseconds:N4}ms (Normal), {sw2.Elapsed.TotalMilliseconds:N4}ms (Burst)");
                }

                if (DrawContact && burstResult)
                {
                    // Do something with manifold

                    HullDrawingUtility.DebugDrawManifold(manifold);

                    //var points = manifold.Points;

                    for (int i = 0; i < manifold.Length; i++)
                    {
                        var point = manifold[i];
                        DebugDrawer.DrawSphere(point.Position, 0.02f);
                        DebugDrawer.DrawArrow(point.Position, manifold.Normal * 0.2f);

                        var penentrationPoint = point.Position + manifold.Normal * point.Distance;
                        DebugDrawer.DrawLabel(penentrationPoint, $"{point.Distance:N2}");

                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.InEdge1, t1, hull1);
                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.OutEdge1, t1, hull1);
                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.InEdge2, t1, hull1);
                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.OutEdge2, t1, hull1);

                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.InEdge1, t2, hull2);
                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.OutEdge1, t2, hull2);
                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.InEdge2, t2, hull2);
                        HullDrawingUtility.DrawEdge(point.Id.FeaturePair.OutEdge2, t2, hull2);

                        DebugDrawer.DrawDottedLine(point.Position, penentrationPoint);
                    }

                    manifold.Dispose();
                }
            }

            if (DrawIsCollided)
            {
                DebugDrawer.DrawSphere(t1.pos, 0.1f, UnityColors.GhostDodgerBlue);
                DebugDrawer.DrawSphere(t2.pos, 0.1f, UnityColors.GhostDodgerBlue);
            }
        }

        if (DrawClosestFace)
        {
            var color1 = collision.Face1.Distance > 0 ? UnityColors.Red.ToOpacity(0.3f) : UnityColors.Yellow.ToOpacity(0.3f);
            HullDrawingUtility.DrawFaceWithOutline(collision.Face1.Index, t1, hull1, color1, UnityColors.Black);

            var color2 = collision.Face2.Distance > 0 ? UnityColors.Red.ToOpacity(0.3f) : UnityColors.Yellow.ToOpacity(0.3f);
            HullDrawingUtility.DrawFaceWithOutline(collision.Face2.Index, t2, hull2, color2, UnityColors.Black);
        }
    }