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()); }
// 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); }
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) { // 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) { 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); }