public float? IntersectsWithRay(Vector2 origin, Vector2 direction) { float largestDistance = MathHelper.Max(A.Position.X - origin.X, B.Position.X - origin.X) * 2f; LineSegment raySegment = new LineSegment(new Vertex(origin, 0), new Vertex(origin + (direction * largestDistance), 0)); Vector2? intersection = FindIntersection(this, raySegment); float? value = null; if (intersection != null) value = Vector2.Distance(origin, intersection.Value); return value; }
public static Vector2? FindIntersection(LineSegment a, LineSegment b) { float x1 = a.A.Position.X; float y1 = a.A.Position.Y; float x2 = a.B.Position.X; float y2 = a.B.Position.Y; float x3 = b.A.Position.X; float y3 = b.A.Position.Y; float x4 = b.B.Position.X; float y4 = b.B.Position.Y; float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); float uaNum = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); float ubNum = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); float ua = uaNum / denom; float ub = ubNum / denom; if (MathHelper.Clamp(ua, 0f, 1f) != ua || MathHelper.Clamp(ub, 0f, 1f) != ub) return null; return a.A.Position + (a.B.Position - a.A.Position) * ua; }
/// <summary> /// Cuts a hole into a shape. /// </summary> /// <param name="shapeVerts">An array of vertices for the primary shape.</param> /// <param name="holeVerts">An array of vertices for the hole to be cut. It is assumed that these vertices lie completely within the shape verts.</param> /// <returns>The new array of vertices that can be passed to Triangulate to properly triangulate the shape with the hole.</returns> public static Vector2[] CutHoleInShape(Vector2[] shapeVerts, Vector2[] holeVerts) { Log("\nCutting hole into shape..."); //make sure the shape vertices are wound counter clockwise and the hole vertices clockwise shapeVerts = EnsureWindingOrder(shapeVerts, WindingOrder.CounterClockwise); holeVerts = EnsureWindingOrder(holeVerts, WindingOrder.Clockwise); //clear all of the lists polygonVertices.Clear(); earVertices.Clear(); convexVertices.Clear(); reflexVertices.Clear(); //generate the cyclical list of vertices in the polygon for (int i = 0; i < shapeVerts.Length; i++) polygonVertices.AddLast(new Vertex(shapeVerts[i], i)); CyclicalList<Vertex> holePolygon = new CyclicalList<Vertex>(); for (int i = 0; i < holeVerts.Length; i++) holePolygon.Add(new Vertex(holeVerts[i], i + polygonVertices.Count)); #if DEBUG StringBuilder vString = new StringBuilder(); foreach (Vertex v in polygonVertices) vString.Append(string.Format("{0}, ", v)); Log("Shape Vertices: {0}", vString); vString = new StringBuilder(); foreach (Vertex v in holePolygon) vString.Append(string.Format("{0}, ", v)); Log("Hole Vertices: {0}", vString); #endif FindConvexAndReflexVertices(); FindEarVertices(); //find the hole vertex with the largest X value Vertex rightMostHoleVertex = holePolygon[0]; foreach (Vertex v in holePolygon) if (v.Position.X > rightMostHoleVertex.Position.X) rightMostHoleVertex = v; //construct a list of all line segments where at least one vertex //is to the right of the rightmost hole vertex with one vertex //above the hole vertex and one below List<LineSegment> segmentsToTest = new List<LineSegment>(); for (int i = 0; i < polygonVertices.Count; i++) { Vertex a = polygonVertices[i].Value; Vertex b = polygonVertices[i + 1].Value; if ((a.Position.X > rightMostHoleVertex.Position.X || b.Position.X > rightMostHoleVertex.Position.X) && ((a.Position.Y >= rightMostHoleVertex.Position.Y && b.Position.Y <= rightMostHoleVertex.Position.Y) || (a.Position.Y <= rightMostHoleVertex.Position.Y && b.Position.Y >= rightMostHoleVertex.Position.Y))) segmentsToTest.Add(new LineSegment(a, b)); } //now we try to find the closest intersection point heading to the right from //our hole vertex. float? closestPoint = null; LineSegment closestSegment = new LineSegment(); foreach (LineSegment segment in segmentsToTest) { float? intersection = segment.IntersectsWithRay(rightMostHoleVertex.Position, Vector2.UnitX); if (intersection != null) { if (closestPoint == null || closestPoint.Value > intersection.Value) { closestPoint = intersection; closestSegment = segment; } } } //if closestPoint is null, there were no collisions (likely from improper input data), //but we'll just return without doing anything else if (closestPoint == null) return shapeVerts; //otherwise we can find our mutually visible vertex to split the polygon Vector2 I = rightMostHoleVertex.Position + Vector2.UnitX * closestPoint.Value; Vertex P = (closestSegment.A.Position.X > closestSegment.B.Position.X) ? closestSegment.A : closestSegment.B; //construct triangle MIP Triangle mip = new Triangle(rightMostHoleVertex, new Vertex(I, 1), P); //see if any of the reflex vertices lie inside of the MIP triangle List<Vertex> interiorReflexVertices = new List<Vertex>(); foreach (Vertex v in reflexVertices) if (mip.ContainsPoint(v)) interiorReflexVertices.Add(v); //if there are any interior reflex vertices, find the one that, when connected //to our rightMostHoleVertex, forms the line closest to Vector2.UnitX if (interiorReflexVertices.Count > 0) { float closestDot = -1f; foreach (Vertex v in interiorReflexVertices) { //compute the dot product of the vector against the UnitX Vector2 d = Vector2.Normalize(v.Position - rightMostHoleVertex.Position); float dot = Vector2.Dot(Vector2.UnitX, d); //if this line is the closest we've found if (dot > closestDot) { //save the value and save the vertex as P closestDot = dot; P = v; } } } //now we just form our output array by injecting the hole vertices into place //we know we have to inject the hole into the main array after point P going from //rightMostHoleVertex around and then back to P. int mIndex = holePolygon.IndexOf(rightMostHoleVertex); int injectPoint = polygonVertices.IndexOf(P); Log("Inserting hole at injection point {0} starting at hole vertex {1}.", P, rightMostHoleVertex); for (int i = mIndex; i <= mIndex + holePolygon.Count; i++) { Log("Inserting vertex {0} after vertex {1}.", holePolygon[i], polygonVertices[injectPoint].Value); polygonVertices.AddAfter(polygonVertices[injectPoint++], holePolygon[i]); } polygonVertices.AddAfter(polygonVertices[injectPoint], P); #if DEBUG vString = new StringBuilder(); foreach (Vertex v in polygonVertices) vString.Append(string.Format("{0}, ", v)); Log("New Shape Vertices: {0}\n", vString); #endif //finally we write out the new polygon vertices and return them out Vector2[] newShapeVerts = new Vector2[polygonVertices.Count]; for (int i = 0; i < polygonVertices.Count; i++) newShapeVerts[i] = polygonVertices[i].Value.Position; return newShapeVerts; }
/// <summary> /// Cuts a hole into a shape. /// </summary> /// <param name="shapeVerts">An array of vertices for the primary shape.</param> /// <param name="holeVerts">An array of vertices for the hole to be cut. It is assumed that these vertices lie completely within the shape verts.</param> /// <returns>The new array of vertices that can be passed to Triangulate to properly triangulate the shape with the hole.</returns> public static Vector2[] CutHoleInShape(Vector2[] shapeVerts, Vector2[] holeVerts) { Log("\nCutting hole into shape..."); //make sure the shape vertices are wound counter clockwise and the hole vertices clockwise //shapeVerts = EnsureWindingOrder(shapeVerts, WindingOrder.CounterClockwise); //holeVerts = EnsureWindingOrder(holeVerts, WindingOrder.Clockwise); //clear all of the lists polygonVertices.Clear(); earVertices.Clear(); convexVertices.Clear(); reflexVertices.Clear(); //generate the cyclical list of vertices in the polygon for (int i = 0; i < shapeVerts.Length; i++) { polygonVertices.AddLast(new Vertex(shapeVerts[i], i)); } CyclicalList <Vertex> holePolygon = new CyclicalList <Vertex>(); for (int i = 0; i < holeVerts.Length; i++) { holePolygon.Add(new Vertex(holeVerts[i], i + polygonVertices.Count)); } #if DEBUG StringBuilder vString = new StringBuilder(); foreach (Vertex v in polygonVertices) { vString.Append(string.Format("{0}, ", v)); } Log("Shape Vertices: {0}", vString); vString = new StringBuilder(); foreach (Vertex v in holePolygon) { vString.Append(string.Format("{0}, ", v)); } Log("Hole Vertices: {0}", vString); #endif FindConvexAndReflexVertices(); FindEarVertices(); //find the hole vertex with the largest X value Vertex rightMostHoleVertex = holePolygon[0]; foreach (Vertex v in holePolygon) { if (v.Position.x > rightMostHoleVertex.Position.x) { rightMostHoleVertex = v; } } //construct a list of all line segments where at least one vertex //is to the right of the rightmost hole vertex with one vertex //above the hole vertex and one below List <LineSegment> segmentsToTest = new List <LineSegment>(); for (int i = 0; i < polygonVertices.Count; i++) { Vertex a = polygonVertices[i].Value; Vertex b = polygonVertices[i + 1].Value; if ((a.Position.x > rightMostHoleVertex.Position.x || b.Position.x > rightMostHoleVertex.Position.x) && ((a.Position.y >= rightMostHoleVertex.Position.y && b.Position.y <= rightMostHoleVertex.Position.y) || (a.Position.y <= rightMostHoleVertex.Position.y && b.Position.y >= rightMostHoleVertex.Position.y))) { segmentsToTest.Add(new LineSegment(a, b)); } } //now we try to find the closest intersection point heading to the right from //our hole vertex. float? closestPoint = null; LineSegment closestSegment = new LineSegment(); foreach (LineSegment segment in segmentsToTest) { float?intersection = segment.IntersectsWithRay(rightMostHoleVertex.Position, new Vector2(1, 0)); if (intersection != null) { if (closestPoint == null || closestPoint.Value > intersection.Value) { closestPoint = intersection; closestSegment = segment; } } } //if closestPoint is null, there were no collisions (likely from improper input data), //but we'll just return without doing anything else if (closestPoint == null) { return(shapeVerts); } //otherwise we can find our mutually visible vertex to split the polygon Vector2 I = rightMostHoleVertex.Position + new Vector2(1, 0) * closestPoint.Value; Vertex P = (closestSegment.A.Position.x > closestSegment.B.Position.x) ? closestSegment.A : closestSegment.B; //construct triangle MIP Triangle mip = new Triangle(rightMostHoleVertex, new Vertex(I, 1), P); //see if any of the reflex vertices lie inside of the MIP triangle List <Vertex> interiorReflexVertices = new List <Vertex>(); foreach (Vertex v in reflexVertices) { if (mip.ContainsPoint(v)) { interiorReflexVertices.Add(v); } } //if there are any interior reflex vertices, find the one that, when connected //to our rightMostHoleVertex, forms the line closest to Vector2.UnitX if (interiorReflexVertices.Count > 0) { float closestDot = -1f; foreach (Vertex v in interiorReflexVertices) { //compute the dot product of the vector against the UnitX Vector2 d = (v.Position - rightMostHoleVertex.Position).normalized; float dot = Vector2.Dot(new Vector2(1, 0), d); //if this line is the closest we've found if (dot > closestDot) { //save the value and save the vertex as P closestDot = dot; P = v; } } } //now we just form our output array by injecting the hole vertices into place //we know we have to inject the hole into the main array after point P going from //rightMostHoleVertex around and then back to P. int mIndex = holePolygon.IndexOf(rightMostHoleVertex); int injectPoint = polygonVertices.IndexOf(P); Log("Inserting hole at injection point {0} starting at hole vertex {1}.", P, rightMostHoleVertex); for (int i = mIndex; i <= mIndex + holePolygon.Count; i++) { Log("Inserting vertex {0} after vertex {1}.", holePolygon[i], polygonVertices[injectPoint].Value); polygonVertices.AddAfter(polygonVertices[injectPoint++], holePolygon[i]); } polygonVertices.AddAfter(polygonVertices[injectPoint], P); #if DEBUG vString = new StringBuilder(); foreach (Vertex v in polygonVertices) { vString.Append(string.Format("{0}, ", v)); } Log("New Shape Vertices: {0}\n", vString); #endif //finally we write out the new polygon vertices and return them out Vector2[] newShapeVerts = new Vector2[polygonVertices.Count]; for (int i = 0; i < polygonVertices.Count; i++) { newShapeVerts[i] = polygonVertices[i].Value.Position; } return(newShapeVerts); }