public List <PolygroupModifier> AdvancedSubtractPolygroup(PolygroupModifier subjectPolygroup, List <Vector2> clippingPolygon)
        {
            // The polygons to keep from the subject polygroup, by index
            List <int> keptIndices = new List <int>();

            // The new triangles added from the triangulated clipped polygons
            List <List <Vector2> > newTriangles = new List <List <Vector2> >();

            foreach (int i in subjectPolygroup.keptIndices)
            {
                List <Vector2> originalPolygon = subjectPolygroup.originalPolygroup[i];
                if (SubtractInternal(originalPolygon, clippingPolygon, out List <DTPolygon> subOutput))
                {
                    DTProfilerMarkers.Triangulation.Begin();
                    List <List <Vector2> > triangles = DTUtility.TriangulateAll(subOutput, TriangleNetTriangulator.Instance);
                    DTProfilerMarkers.Triangulation.End();
                    newTriangles.AddRange(triangles);
                }
                else
                {
                    keptIndices.Add(i);
                }
            }

            return(Polygrouper.AdvancedCreatePolygroups(subjectPolygroup.originalPolygroup, keptIndices, newTriangles));
        }
예제 #2
0
        public static void ConnectionGizmo(CurvySplineSegment cp)
        {
            Matrix4x4 mat = Gizmos.matrix;

            Gizmos.matrix = Matrix;

            Color c = Color.black;

            if (cp.ConnectionSyncPosition)
            {
                if (cp.ConnectionSyncRotation)
                {
                    c = Color.white;
                }
                else
                {
                    c = Color.red;
                }
            }
            else
            if (cp.ConnectionSyncRotation)
            {
                c = new Color(1, 1, 0);
            }

            Gizmos.color = c;
            Vector3 p = cp.transform.localPosition;

            Gizmos.DrawWireSphere(p, DTUtility.GetHandleSize(p) * CurvyGlobalManager.GizmoControlPointSize * 1.3f);
            Gizmos.matrix = mat;
        }
        public List <DTPolygon> Subtract(DTPolygon subject, DTPolygon clippingPolygon)
        {
            if (!DTUtility.BoundsCheck(subject.Contour, clippingPolygon.Contour))
            {
                // There is no overlap at all, so output a copy of the subject polygon
                return(new List <DTPolygon>()
                {
                    new DTPolygon(new List <Vector2>(subject.Contour))
                });
            }

            clipper.Clear();

            // Add subject polygon paths
            clipper.AddPath(subject.Contour.ToIntPointList(), PolyType.ptSubject, true);
            foreach (var hole in subject.Holes)
            {
                clipper.AddPath(hole.ToIntPointList(), PolyType.ptSubject, true);
            }

            // Add clipping polygon paths
            clipper.AddPath(clippingPolygon.Contour.ToIntPointList(), PolyType.ptClip, true);
            foreach (var hole in clippingPolygon.Holes)
            {
                clipper.AddPath(hole.ToIntPointList(), PolyType.ptClip, true);
            }

            // Execute subtraction and store result in a PolyTree so that we can easily identify holes
            PolyTree clipperOutput = new PolyTree();

            clipper.Execute(ClipType.ctDifference, clipperOutput, PolyFillType.pftEvenOdd, PolyFillType.pftNonZero);

            // Convert Polytree into list of DTPolygons
            return(clipperOutput.ToDTPolygons());
        }
예제 #4
0
        public DTMesh PolygonToMesh(DTPolygon subject)
        {
            // Mark any unmarked holes in the contour, otherwise Triangle.NET won't handle them properly
            subject = DTUtility.IdentifyHoles(subject);
            if (subject.Contour.Count < 3)
            {
                return(new DTMesh());
            }
            // Don't triangulate if this is already a triangle
            else if (subject.Contour.Count == 3 && subject.Holes.Count == 0)
            {
                return(new DTMesh(subject.Contour, new List <List <int> >()
                {
                    new List <int>()
                    {
                        0, 1, 2
                    }
                }));
            }
            // If the polygon is convex, use simple fanning technique to triangulate
            else if (subject.IsConvex())
            {
                DTMesh mesh = new DTMesh(subject.Contour, new List <List <int> >());
                for (int i = 1; i < subject.Contour.Count - 1; i++)
                {
                    mesh.Partitions.Add(new List <int>()
                    {
                        0, i, i + 1
                    });
                }
                return(mesh);
            }

            // Format polygon input and execute
            Polygon polygon = new Polygon();

            polygon.Add(new Contour(subject.Contour.ToVertexList()), false);
            foreach (var hole in subject.Holes)
            {
                if (hole.Count >= 3)
                {
                    try {
                        polygon.Add(new Contour(hole.ToVertexList()), true);
                    }
                    catch (Exception) { }
                }
            }
            DTProfilerMarkers.TriangleNet.Begin();
            IMesh triangleNetOutput = polygon.Triangulate();

            ++callCount;
            DTProfilerMarkers.TriangleNet.End();

            // Convert Triangle.NET output into DTMesh
            List <Vector2>     vertices  = triangleNetOutput.Vertices.ToVector2List();
            List <List <int> > triangles = triangleNetOutput.Triangles.ToPartitionList();

            return(new DTMesh(vertices, triangles));
        }
        public override void ApplyPolygonList(List <DTPolygon> clippedPolygonList)
        {
            // The clipped polygons could potentially be concave or have holes, so we will triangulate each one before applying
            DTProfilerMarkers.Triangulation.Begin();
            dtPolygroup = DTUtility.TriangulateAll(clippedPolygonList, GetTriangulator());
            DTProfilerMarkers.Triangulation.End();

            // Collider from polygon
            DTProfilerMarkers.ApplyCollider.Begin();
            ApplyCollider(dtPolygroup);
            DTProfilerMarkers.ApplyCollider.End();

            // Create mesh from triangulated polygon
            ApplyRenderMesh(dtPolygroup.ToMesh());
        }
예제 #6
0
        public static void SegmentApproximationGizmo(CurvySplineSegment seg, Color col)
        {
            Matrix4x4 mat = Gizmos.matrix;

            Gizmos.matrix = Matrix;
            Gizmos.color  = col;
            Vector3 size = new Vector3(0.1f / seg.Spline.transform.localScale.x,
                                       0.1f / seg.Spline.transform.localScale.y,
                                       0.1f / seg.Spline.transform.localScale.z);

            for (int i = 0; i < seg.Approximation.Length; i++)
            {
                Vector3 p = seg.Approximation[i];
                Gizmos.DrawCube(p, DTUtility.GetHandleSize(p) * size);
            }
            Gizmos.matrix = mat;
        }
예제 #7
0
        public override void ApplyPolygonList(List <DTPolygon> clippedPolygonList)
        {
            // The clipped polygons could potentially be concave or have holes, so we will triangulate each one before applying
            DTProfilerMarkers.Triangulation.Begin();
            DTConvexPolygroup triangulatedPolygroup = DTUtility.TriangulateAll(clippedPolygonList, GetTriangulator());

            DTProfilerMarkers.Triangulation.End();

            // Collider from polygon
            DTProfilerMarkers.HertelMehlhorn.Begin();
            hmPolygroup = HertelMehlhorn.PolyPartitionHM.Instance.ExecuteToPolygroup(triangulatedPolygroup);
            DTProfilerMarkers.HertelMehlhorn.End();

            // Collider from polygon
            DTProfilerMarkers.ApplyCollider.Begin();
            ApplyCollider(hmPolygroup);
            DTProfilerMarkers.ApplyCollider.End();

            // Create mesh from triangulated polygon
            ApplyRenderMesh(triangulatedPolygroup.ToMesh());
        }
예제 #8
0
        public static void ControlPointGizmo(CurvySplineSegment cp, bool selected, Color col)
        {
            Matrix4x4 mat = Gizmos.matrix;

            Gizmos.matrix = Matrix4x4.identity;

            Gizmos.color = col;
            Vector3 p    = Matrix.MultiplyPoint(cp.transform.localPosition);
            float   size = (selected) ? 1 : 0.7f;

            if (cp.Spline.RestrictTo2D)
            {
                Gizmos.DrawCube(p, Vector3.one * DTUtility.GetHandleSize(p) * size * CurvyGlobalManager.GizmoControlPointSize);
            }
            else
            {
                Gizmos.DrawSphere(p, DTUtility.GetHandleSize(p) * size * CurvyGlobalManager.GizmoControlPointSize);
            }

            Gizmos.matrix = mat;
        }
예제 #9
0
        public DTConvexPolygroup PolygonToTriangleList(DTPolygon subject)
        {
            // Mark any unmarked holes in the contour, otherwise Triangle.NET won't handle them properly
            subject = DTUtility.IdentifyHoles(subject);
            if (subject.Contour.Count < 3)
            {
                return(new DTConvexPolygroup());
            }
            // Don't triangulate if this is already a triangle
            else if (subject.Contour.Count == 3 && subject.Holes.Count == 0)
            {
                return(new DTConvexPolygroup(new List <DTPolygon>()
                {
                    subject
                }));
            }

            // Format polygon input and execute
            Polygon polygon = new Polygon();

            polygon.Add(new Contour(subject.Contour.ToVertexList()), false);
            foreach (var hole in subject.Holes)
            {
                if (hole.Count >= 3)
                {
                    try {
                        polygon.Add(new Contour(hole.ToVertexList()), true);
                    }
                    catch (Exception) {}
                }
            }
            DTProfilerMarkers.TriangleNet.Begin();
            IMesh triangleNetOutput = polygon.Triangulate();

            ++callCount;
            DTProfilerMarkers.TriangleNet.End();

            // Convert Triangle.NET output into poly group
            return(new DTConvexPolygroup(triangleNetOutput.Triangles.Select(t => t.ToVertexList()).ToList()));
        }
        public override void ApplyPolygonList(List <DTPolygon> clippedPolygonList)
        {
            // The clipped polygons could potentially be concave or have holes, so we will triangulate each one before applying
            DTProfilerMarkers.Triangulation.Begin();
            DTConvexPolygroup triangleList = DTUtility.TriangulateAll(clippedPolygonList, GetTriangulator());

            DTProfilerMarkers.Triangulation.End();

            // Our Hertel-Mehlhorn implementation takes a DTMesh, so convert before instead of after
            DTMesh triangulatedMesh = triangleList.ToMesh();

            // Collider from polygon
            DTProfilerMarkers.HertelMehlhorn.Begin();
            hmMesh = HertelMehlhorn.CustomHM.Instance.ExecuteToMesh(triangulatedMesh);
            DTProfilerMarkers.HertelMehlhorn.End();

            // Collider from polygon
            DTProfilerMarkers.ApplyCollider.Begin();
            ApplyCollider(hmMesh);
            DTProfilerMarkers.ApplyCollider.End();

            // Create mesh from triangulated polygon
            ApplyRenderMesh(triangulatedMesh);
        }
 // Destroys all GameObjects.
 // This should be called at the end of each test.
 public static void CleanUp()
 {
     DTUtility.CleanUpGameObjects();
 }
        // Returns true if the subject polygon was modified at all
        private bool SubtractInternal(List <Vector2> subject, List <Vector2> clippingPolygon, out List <DTPolygon> outputPolygons)
        {
            if (!DTUtility.BoundsCheck(subject, clippingPolygon))
            {
                // There is no overlap at all, so output a copy of the subject polygon
                outputPolygons = new List <DTPolygon>()
                {
                    new DTPolygon(new List <Vector2>(subject))
                };
                return(false);
            }

            polyP = subject;
            polyQ = clippingPolygon;

            pIndex = 0;
            qIndex = 0;

            // The first entry intersection point of the first output polgon. If we return to this point, break the loop
            bool firstIntersectionFound  = false;
            int? firstIntersectionPIndex = null;
            int? firstIntersectionQIndex = null;

            // List of disjoint output polygons after subtraction
            outputPolygons = new List <DTPolygon>();

            // Working output polygon vertices
            polygonBegin = null;
            List <Vector2> pVertices = new List <Vector2>();
            List <Vector2> qVertices = new List <Vector2>();

            for (int i = 0; i <= 2 * (polyP.Count + polyQ.Count); ++i)
            {
                if (polygonBegin.HasValue)
                {
                    Vector2?exitIntersection = ExitIntersection();
                    if (exitIntersection.HasValue)
                    {
                        // Exit intersection point for output polygon: construct output polygon
                        DTPolygon poly = new DTPolygon();
                        poly.Contour.Add(polygonBegin.Value);
                        poly.Contour.AddRange(pVertices);
                        poly.Contour.Add(exitIntersection.Value);
                        poly.Contour.AddRange(qVertices.AsEnumerable().Reverse());

                        // Simplify polygon
                        poly = poly.Simplify();
                        if (poly != null)
                        {
                            outputPolygons.Add(poly);
                        }

                        // Clear working polygon vertices
                        polygonBegin = null;
                        pVertices.Clear();
                        qVertices.Clear();
                    }
                }
                else
                {
                    Vector2?entranceIntersection = EntranceIntersection();
                    if (entranceIntersection.HasValue)
                    {
                        // Loop exit condition: revisiting first intersection
                        if (firstIntersectionFound &&
                            ModP(pIndex) == ModP(firstIntersectionPIndex.Value) &&
                            ModQ(qIndex) == ModQ(firstIntersectionQIndex.Value))
                        {
                            break;
                        }

                        // Entry intersection point for output polygon
                        polygonBegin = entranceIntersection.Value;
                        pVertices.Clear();
                        qVertices.Clear();

                        // Keep track of this point if it is the entry intersection for the 1st output polygon
                        if (!firstIntersectionFound)
                        {
                            firstIntersectionFound  = true;
                            firstIntersectionPIndex = pIndex;
                            firstIntersectionQIndex = qIndex;
                        }
                    }
                }

                void advanceP()
                {
                    if (polygonBegin.HasValue)
                    {
                        pVertices.Add(P);
                    }
                    ++pIndex;
                }

                void advanceQ()
                {
                    if (polygonBegin.HasValue)
                    {
                        qVertices.Add(Q);
                    }
                    ++qIndex;
                }

                float pSide = (P - QPrev).Cross(QEdge);
                float qSide = (Q - PPrev).Cross(PEdge);
                float cross = QEdge.Cross(PEdge);

                if (cross <= 0)
                {
                    // QEdge heading inward

                    if (qSide < 0)
                    {
                        // Q inside P's half-plane, heading away from PEdge
                        advanceP();
                    }
                    else
                    {
                        // Q outside P's half-plane or on P's line, heading toward PEdge
                        advanceQ();
                    }
                }
                else
                {
                    // QEdge heading outward

                    if (pSide < 0)
                    {
                        // P inside Q's half-plane, heading away from QEdge
                        advanceQ();
                    }
                    else
                    {
                        // P outside Q's half-plane or on Q's line, heading toward QEdge
                        advanceP();
                    }
                }
            }

            // There were no intersections, so either one poly is entirely contained within the other, or there is no overlap at all
            if (outputPolygons.Count == 0)
            {
                if (polyP.Inside(polyQ))
                {
                    // P is entirely within Q, so do nothing. The entire polygon has been subtracted
                    return(true);
                }
                else if (polyQ.Inside(polyP))
                {
                    // Q is entirely within P, so output a copy of P, with Q (reversed) set as a hole
                    outputPolygons.Add(new DTPolygon(
                                           new List <Vector2>(polyP),
                                           new List <List <Vector2> >()
                    {
                        polyQ.AsEnumerable().Reverse().ToList()
                    }));
                    return(true);
                }
                else
                {
                    if (polyP.Simplify() == polyQ.Simplify())
                    {
                        // The polygons are equal, so do nothing. The entire polygon has been subtracted
                        return(true);
                    }
                    else
                    {
                        // There is no overlap at all, so output a copy of P
                        outputPolygons.Add(new DTPolygon(new List <Vector2>(polyP)));
                        return(false);
                    }
                }
            }

            return(true);
        }
예제 #13
0
        public override void ApplyPolygonList(List <DTPolygon> clippedPolygonList)
        {
            DTConvexPolygroup triangles = DTUtility.TriangulateAll(clippedPolygonList, GetTriangulator());

            ApplyPolygroupModifier(new PolygroupModifier(null, null, triangles));
        }
예제 #14
0
        public void ExecuteExplosions(IEnumerable <Explosion> explosions, IEnumerable <DestructibleObject> dtObjects, IPolygonSubtractor subtractor)
        {
            // Store destructible objects in a new list, since we may add or remove some during processing
            List <DestructibleObject> dtObjectList = dtObjects.ToList();
            // Add new destructible objects to this list instead of objectList until finished processing the current explosion
            List <DestructibleObject> pendingAdditions = new List <DestructibleObject>();

            // Process all objects for all explosions
            foreach (var exp in explosions)
            {
                List <List <DTPolygon> > relevantObjectPolygons = new List <List <DTPolygon> >();
                List <int> relevantObjectIndices = new List <int>();
                for (int i = 0; i < dtObjectList.Count; ++i)
                {
                    var dtObj = dtObjectList[i];

                    // Do basic AABB-circle check to see whether we can skip processing this destructible object with this explosion
                    int bc = DTUtility.BoundsCheck(dtObj, exp);
                    if (bc == -1)
                    {
                        // Object is not affected by explosion
                        continue;
                    }
                    else if (bc == 1)
                    {
                        // Object is completely removed by explosion
                        dtObjectList[i] = null;
                        UnityEngine.Object.Destroy(dtObj.gameObject);
                        continue;
                    }
                    else
                    {
                        // Add object to the input list for the subtractor
                        relevantObjectPolygons.Add(dtObj.GetTransformedPolygonList());
                        relevantObjectIndices.Add(i);
                        continue;
                    }
                }

                var result = subtractor.SubtractBulk(relevantObjectPolygons, new DTPolygon[] { exp.DTPolygon });

                // Iterate results corresponding to each input polygon group
                for (int i = 0; i < result.Count; i++)
                {
                    // Add new destructible objects for any output polygons that could not be matched with an input polygon
                    if (i >= relevantObjectPolygons.Count)
                    {
                        GameObject         go        = new GameObject();
                        DestructibleObject newObj    = go.AddComponent <DestructibleObject>();
                        List <DTPolygon>   polygroup = result[i][0];
                        newObj.ApplyTransformedPolygonList(polygroup);
                        pendingAdditions.Add(newObj);

                        continue;
                    }

                    // We know that these output polygons correspond to one or more pieces of this existing destructible object
                    DestructibleObject dtObj = dtObjectList[relevantObjectIndices[i]];

                    if (result[i].Count == 0)
                    {
                        // If no output polygons, remove the current destrucible object
                        dtObjectList[relevantObjectIndices[i]] = null;
                        UnityEngine.Object.Destroy(dtObj.gameObject);
                        continue;
                    }
                    else
                    {
                        // Otherwise apply the output polygons (fragments) to GameObjects (new or reused)
                        foreach (List <DTPolygon> polygroup in result[i])
                        {
                            if (polygroup != result[i].Last())
                            {
                                // Duplicate the GameObject that was clipped by the explosion, so that we maintain properties such as velocity
                                GameObject         go     = UnityEngine.Object.Instantiate(dtObj.gameObject, dtObj.transform.parent);
                                DestructibleObject newObj = go.GetComponent <DestructibleObject>();

                                // Apply the new clipped polygon
                                newObj.ApplyTransformedPolygonList(polygroup);

                                // Add it to the objectList, but not until after finished processing this explosion
                                pendingAdditions.Add(newObj);
                                continue;
                            }
                            else
                            {
                                // Reuse the existing GameObject by applying the new clipped polygon to it
                                dtObj.ApplyTransformedPolygonList(polygroup);
                                continue;
                            }
                        }
                    }
                }

                // Delete any entries that were set to null
                for (int i = 0; i < dtObjectList.Count; ++i)
                {
                    if (dtObjectList[i] == null)
                    {
                        dtObjectList.RemoveAt(i--);
                    }
                }

                // Add pendingAdditions elements to objectList so that they are included when processing the next explosion in explosions
                dtObjectList.AddRange(pendingAdditions);
                pendingAdditions.Clear();
            }
        }
        public void ExecuteExplosions(IEnumerable <Explosion> explosions, IEnumerable <DestructibleObject> dtObjects, IPolygonSubtractor subtractor)
        {
            ORourkeSubtractor oRourkeSub = (ORourkeSubtractor)subtractor;

            if (oRourkeSub == null)
            {
                throw new NotSupportedException("This explosion executor only supports ORourkeSubtractor");
            }

            TriangleNetTriangulator.Instance.callCount = 0;

            // Store destructible objects in a new list, since we may add or remove some during processing
            List <DestructibleObject> dtObjectList = dtObjects.ToList();
            // Add new destructible objects to this list instead of objectList until finished processing the current explosion
            List <DestructibleObject> pendingAdditions = new List <DestructibleObject>();

            // Process all objects for all explosions
            foreach (var exp in explosions)
            {
                for (int i = 0; i < dtObjectList.Count; i++)
                {
                    // Remove this object from the list if it has been destroyed
                    if (dtObjectList[i] == null)
                    {
                        dtObjectList.RemoveAt(i--);
                        continue;
                    }
                    // Cast this DO to an advanced DO
                    var dtObj = (DO_Advanced_Triangle_Clip_Collide)dtObjectList[i];
                    if (dtObj == null)
                    {
                        throw new NotSupportedException("This explosion executor only supports DO_Advanced_Triangle_Clip_Collide");
                    }

                    // Do basic AABB-circle check to see whether we can skip processing this destructible object with this explosion
                    int bc = DTUtility.BoundsCheck(dtObj, exp);
                    if (bc == -1)
                    {
                        // Object is not affected by explosion
                        continue;
                    }
                    else if (bc == 1)
                    {
                        // Object is completely removed by explosion
                        dtObjectList.RemoveAt(i--);
                        UnityEngine.Object.Destroy(dtObj.gameObject);
                        continue;
                    }

                    // Leave the polygroup in local coordinates and transform the explosion instead to the DO's space.
                    // Note that this is stored in the subject DO and will be referenced by all PolygroupModifiers,
                    // so changes to the original DO will affect all PolygroupModifiers. This shouldn't cause any
                    // problems since we only need the indices anyway.
                    PolygroupModifier inputPolygroup       = dtObj.GetPolygroup();
                    List <Vector2>    transformedExplosion = dtObj.InverseTransformPoints(exp.DTPolygon.Contour);

                    // Subtract explosion polygon from destructible object polygon group
                    DTProfilerMarkers.SubtractPolygroup.Begin();
                    List <PolygroupModifier> result = oRourkeSub.AdvancedSubtractPolygroup(inputPolygroup, transformedExplosion);
                    DTProfilerMarkers.SubtractPolygroup.End();

                    int count = result.Count();
                    if (count == 0)
                    {
                        // If no output polygons, remove the current destrucible object
                        dtObjectList.RemoveAt(i--);
                        UnityEngine.Object.Destroy(dtObj.gameObject);
                        continue;
                    }
                    else
                    {
                        // Otherwise apply the output polygons (fragments) to GameObjects (new or reused)
                        for (int j = 0; j < result.Count; j++)
                        {
                            if (j < result.Count - 1)
                            {
                                // Duplicate the GameObject that was clipped by the explosion, so that we maintain properties such as velocity and also maintain the same collider + mesh
                                GameObject go     = UnityEngine.Object.Instantiate(dtObj.gameObject, dtObj.transform.parent);
                                var        newObj = go.GetComponent <DO_Advanced_Triangle_Clip_Collide>();
                                newObj.SetPolygroup(new PolygroupModifier(new DTConvexPolygroup(inputPolygroup.originalPolygroup), inputPolygroup.keptIndices, null));

                                // Apply the new clipped polygon list
                                newObj.ApplyPolygroupModifier(result[j]);

                                // Add it to the objectList, but not until after finished processing this explosion
                                pendingAdditions.Add(newObj);
                                continue;
                            }
                            else
                            {
                                // Reuse the existing GameObject by applying the new clipped polygon to it
                                dtObj.ApplyPolygroupModifier(result[j]);
                                continue;
                            }
                        }
                    }
                }
                // Add pendingAdditions elements to objectList so that they are included when processing the next explosion in explosions
                dtObjectList.AddRange(pendingAdditions);
                pendingAdditions.Clear();
            }

            Debug.Log("# Objects:" + dtObjectList.Count);
            Debug.Log("# Polygons:" + dtObjectList.Sum(obj => obj.GetTransformedPolygonList().Count));
            Debug.Log("# Triangulation Calls:" + TriangleNetTriangulator.Instance.callCount);
        }
        public void ExecuteExplosions(IEnumerable <Explosion> explosions, IEnumerable <DestructibleObject> dtObjects, IPolygonSubtractor subtractor)
        {
            TriangleNetTriangulator.Instance.callCount = 0;

            // Store destructible objects in a new list, since we may add or remove some during processing
            List <DestructibleObject> dtObjectList = dtObjects.ToList();
            // Add new destructible objects to this list instead of objectList until finished processing the current explosion
            List <DestructibleObject> pendingAdditions = new List <DestructibleObject>();

            // Process all objects for all explosions
            foreach (var exp in explosions)
            {
                for (int i = 0; i < dtObjectList.Count; i++)
                {
                    DestructibleObject dtObj = dtObjectList[i];

                    // Do basic AABB-circle check to see whether we can skip processing this destructible object with this explosion
                    int bc = DTUtility.BoundsCheck(dtObj, exp);
                    if (bc == -1)
                    {
                        // Object is not affected by explosion
                        continue;
                    }
                    else if (bc == 1)
                    {
                        // Object is completely removed by explosion
                        dtObjectList.RemoveAt(i--);
                        UnityEngine.Object.Destroy(dtObj.gameObject);
                        continue;
                    }

                    List <DTPolygon> inputPolygroup = dtObj.GetTransformedPolygonList();

                    // Subtract explosion polygon from destructible object polygon group
                    DTProfilerMarkers.SubtractPolygroup.Begin();
                    List <List <DTPolygon> > result = subtractor.SubtractPolygroup(inputPolygroup, new List <DTPolygon>()
                    {
                        exp.DTPolygon
                    });
                    DTProfilerMarkers.SubtractPolygroup.End();

                    int count = result.Count();
                    if (count == 0)
                    {
                        // If no output polygons, remove the current destrucible object
                        dtObjectList.RemoveAt(i--);
                        UnityEngine.Object.Destroy(dtObj.gameObject);
                        continue;
                    }
                    else
                    {
                        // Otherwise apply the output polygons (fragments) to GameObjects (new or reused)
                        foreach (List <DTPolygon> polygroup in result)
                        {
                            if (polygroup != result.Last())
                            {
                                // Duplicate the GameObject that was clipped by the explosion, so that we maintain properties such as velocity
                                GameObject         go     = UnityEngine.Object.Instantiate(dtObj.gameObject, dtObj.transform.parent);
                                DestructibleObject newObj = go.GetComponent <DestructibleObject>();

                                // Apply the new clipped polygon list
                                newObj.ApplyTransformedPolygonList(polygroup);

                                // Add it to the objectList, but not until after finished processing this explosion
                                pendingAdditions.Add(newObj);
                                continue;
                            }
                            else
                            {
                                // Reuse the existing GameObject by applying the new clipped polygon to it
                                dtObj.ApplyTransformedPolygonList(polygroup);
                                continue;
                            }
                        }
                    }
                }
                // Add pendingAdditions elements to objectList so that they are included when processing the next explosion in explosions
                dtObjectList.AddRange(pendingAdditions);
                pendingAdditions.Clear();
            }

            Debug.Log("# Objects:" + dtObjectList.Count);
            Debug.Log("# Polygons:" + dtObjectList.Sum(obj => obj.GetTransformedPolygonList().Count));
            Debug.Log("# Triangulation Calls:" + TriangleNetTriangulator.Instance.callCount);
        }