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)); }
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()); }
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()); }
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; }
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()); }
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; }
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); }
public override void ApplyPolygonList(List <DTPolygon> clippedPolygonList) { DTConvexPolygroup triangles = DTUtility.TriangulateAll(clippedPolygonList, GetTriangulator()); ApplyPolygroupModifier(new PolygroupModifier(null, null, triangles)); }
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); }