/// <summary> /// Extracts all minimal cycles (neither cycle contains another nor intersects another). /// </summary> /// <param name="cycles">The cycles to extract from.</param> /// <returns>All extracted minimal cycles in the given enumerable.</returns> private IEnumerable <IReadOnlyCollection <Vector3> > ExtractMinimalCycles( IEnumerable <IReadOnlyCollection <Vector3> > cycles) { var minimalCycles = new LinkedList <IReadOnlyCollection <Vector3> >(); // Filter away any cycles that overlap minimal ones; they're not minimal foreach (var cycle in cycles) { // Cancel if requested if (CancelToken.IsCancellationRequested) { return(null); } // Find out if the cycle is overlapping any minimal one var cycleXz = cycle.Select(Vec3ToVec2); var isOverlapping = minimalCycles.Any(minimalCycle => Maths2D.AnyPolygonCenterOverlaps(cycleXz, minimalCycle.Select(Vec3ToVec2))); // If this cycle doesn't overlap any minimal cycle, it is minimal as well if (!isOverlapping && !minimalCycles.Any(cycle.ContainsAll)) { minimalCycles.AddLast(cycle); } } return(minimalCycles); }
bool PointInTriangles(Vector2[] p, int[] tris, Vector2 v) { for (int t = 0; t < tris.Length; t += 3) { if (Maths2D.PointInTriangle(p[tris[t]], p[tris[t + 1]], p[tris[t + 2]], v)) { return(true); } } return(false); }
// A parent is a shape which fully contains another shape public bool IsParentOf(CompositeShapeData otherShape) { if (otherShape.parents.Contains(this)) { return(true); } if (parents.Contains(otherShape)) { return(false); } // check if first point in otherShape is inside this shape. If not, parent test fails. // if yes, then continue to line seg intersection test between the two shapes // (this point test is important because without it, if all line seg intersection tests fail, // we wouldn't know if otherShape is entirely inside or entirely outside of this shape) bool pointInsideShape = false; for (int i = 0; i < triangles.Length; i += 3) { if (Maths2D.PointInTriangle(polygon.points[triangles[i]], polygon.points[triangles[i + 1]], polygon.points[triangles[i + 2]], otherShape.points[0])) { pointInsideShape = true; break; } } if (!pointInsideShape) { return(false); } // Check for intersections between line segs of this shape and otherShape (any intersections will fail the parent test) for (int i = 0; i < points.Length; i++) { LineSegment parentSeg = new LineSegment(points[i], points[(i + 1) % points.Length]); for (int j = 0; j < otherShape.points.Length; j++) { LineSegment childSeg = new LineSegment(otherShape.points[j], otherShape.points[(j + 1) % otherShape.points.Length]); if (Maths2D.LineSegmentsIntersect(parentSeg.a, parentSeg.b, childSeg.a, childSeg.b)) { return(false); } } } return(true); }
// Test if the shapes overlap partially (test will fail if one shape entirely contains other shape, i.e. one is parent of the other). public bool OverlapsPartially(CompositeShapeData otherShape) { // Check for intersections between line segs of this shape and otherShape (any intersection will validate the overlap test) for (int i = 0; i < points.Length; i++) { LineSegment segA = new LineSegment(points[i], points[(i + 1) % points.Length]); for (int j = 0; j < otherShape.points.Length; j++) { LineSegment segB = new LineSegment(otherShape.points[j], otherShape.points[(j + 1) % otherShape.points.Length]); if (Maths2D.LineSegmentsIntersect(segA.a, segA.b, segB.a, segB.b)) { return(true); } } } return(false); }
// Checks if any of the line segments making up this shape intersect public bool IntersectsWithSelf() { for (int i = 0; i < points.Length; i++) { LineSegment segA = new LineSegment(points[i], points[(i + 1) % points.Length]); for (int j = i + 2; j < points.Length; j++) { if ((j + 1) % points.Length == i) { continue; } LineSegment segB = new LineSegment(points[j], points[(j + 1) % points.Length]); if (Maths2D.LineSegmentsIntersect(segA.a, segA.b, segB.a, segB.b)) { return(true); } } } return(false); }
// check if triangle contains any verts (note, only necessary to check reflex verts). bool TriangleContainsVertex(Vertex v0, Vertex v1, Vertex v2) { LinkedListNode <Vertex> vertexNode = vertsInClippedPolygon.First; for (int i = 0; i < vertsInClippedPolygon.Count; i++) { if (!vertexNode.Value.isConvex) // convex verts will never be inside triangle { Vertex vertexToCheck = vertexNode.Value; if (vertexToCheck.index != v0.index && vertexToCheck.index != v1.index && vertexToCheck.index != v2.index) // dont check verts that make up triangle { if (Maths2D.PointInTriangle(v0.position, v1.position, v2.position, vertexToCheck.position)) { return(true); } } } vertexNode = vertexNode.Next; } return(false); }
public override IEnumerable <Plot> Generate() { var plots = new HashSet <Plot>(); var roadNetwork = Injector.Get(); var rand = new System.Random(); var p1 = new Plot(RandomRotatedRect(rand, new Vector3(0f, 0f, 0f), new Vector3(5f, 0f, 5f), 5f, 5f)); plots.Add(p1); bool roadCollision = false; foreach (var(start, end) in roadNetwork.GetRoadParts()) { if (Maths2D.LinePolyCollision(start, end, p1.Vertices)) { roadCollision = true; } } Debug.Log("Plots are colliding with road (t/f): " + roadCollision); return(plots); }
/// <summary> /// Gets all minimal cycles in the XZ-plane where the road network's XZ-projection intersections are found. /// </summary> /// <returns>All minimal cycles in the XZ-plane</returns> private IEnumerable <IReadOnlyCollection <Vector3> > GetMinimalCyclesInXZ() { // XZ-projection of the undirected road network var roadNetwork = Injector.Get().GetXZProjection().GetAsUndirected(); // If there aren't at least three vertices in the road network, there can't possibly be a cycle in it if (roadNetwork.VertexCount < 3) { return(new List <IReadOnlyCollection <Vector3> >()); } // Get all cycles in the road network and sort them from the smallest area // to the largest in order to later only save the minimal cycles var cycles = GetAllCycles(roadNetwork).ToList(); cycles.Sort((cycle1, cycle2) => { var area1 = Maths2D.CalculatePolygonArea(cycle1.Select(Vec3ToVec2)); var area2 = Maths2D.CalculatePolygonArea(cycle2.Select(Vec3ToVec2)); return(area1 <area2 ? -1 : area1> area2 ? 1 : 0); }); return(ExtractMinimalCycles(cycles)); }
void SetInnerRing(Vector3[] verts, List <Vector3> validQuads, List <Vector3> validTris) { bool isClosed = verts[0] == verts[verts.Length - 1]; Vector2[] points = new List <Vector2>(verts.Select(v => new Vector2(v.x, v.z))).GetRange(0, verts.Count() - (isClosed ? 1 : 0)).ToArray(); debugList = new List <Vector3>(); List <Vector3> pathVerts = new List <Vector3>(); if (points.Length >= 3) { Polygon polygon = new Polygon(points); int[] triangles = new Triangulator(polygon).Triangulate(); Vector3 min = GetScaledMin(); for (float i = min.x; i < bounds.max.x; i += gridScale) { for (float j = min.z; j < bounds.max.z; j += gridScale) { bool intersects1 = false; bool intersects2 = false; bool intersects34 = false; for (int k = 0; k < outerRing.Count - 1; k++) { if (Maths2D.LineSegmentsIntersect(new Vector2(i, j), new Vector2(i + gridScale, j), new Vector2(outerRing[k].x, outerRing[k].z), new Vector2(outerRing[k + 1].x, outerRing[k + 1].z))) { intersects1 = true; break; } if (Maths2D.LineSegmentsIntersect(new Vector2(i, j), new Vector2(i, j + gridScale), new Vector2(outerRing[k].x, outerRing[k].z), new Vector2(outerRing[k + 1].x, outerRing[k + 1].z))) { intersects2 = true; break; } if (Maths2D.LineSegmentsIntersect(new Vector2(i + gridScale, j), new Vector2(i + gridScale, j + gridScale), new Vector2(outerRing[k].x, outerRing[k].z), new Vector2(outerRing[k + 1].x, outerRing[k + 1].z)) || Maths2D.LineSegmentsIntersect(new Vector2(i, j + gridScale), new Vector2(i + gridScale, j + gridScale), new Vector2(outerRing[k].x, outerRing[k].z), new Vector2(outerRing[k + 1].x, outerRing[k + 1].z))) { intersects34 = true; } } if (intersects1) { if (PointInTriangles(polygon.points, triangles, new Vector2(i, j))) { pathVerts.Add(new Vector3(i, pathHeight, j)); } if (PointInTriangles(polygon.points, triangles, new Vector2(i + gridScale, j))) { pathVerts.Add(new Vector3(i + gridScale, pathHeight, j)); } } else if (intersects2) { if (PointInTriangles(polygon.points, triangles, new Vector2(i, j))) { pathVerts.Add(new Vector3(i, pathHeight, j)); } if (PointInTriangles(polygon.points, triangles, new Vector2(i, j + gridScale))) { pathVerts.Add(new Vector3(i, pathHeight, j + gridScale)); } } else if (!intersects34 && PointInTriangles(polygon.points, triangles, new Vector2(i, j))) { validQuads.Add(new Vector3(i, pathHeight, j)); } if (intersects1 || intersects2 || intersects34) { List <Vector3> possibleTris = new List <Vector3>(); if (PointInTriangles(polygon.points, triangles, new Vector2(i, j))) { possibleTris.Add(new Vector3(i, pathHeight, j)); } if (PointInTriangles(polygon.points, triangles, new Vector2(i + gridScale, j))) { possibleTris.Add(new Vector3(i + gridScale, pathHeight, j)); } if (PointInTriangles(polygon.points, triangles, new Vector2(i, j + gridScale))) { possibleTris.Add(new Vector3(i, pathHeight, j + gridScale)); } if (PointInTriangles(polygon.points, triangles, new Vector2(i + gridScale, j + gridScale))) { possibleTris.Add(new Vector3(i + gridScale, pathHeight, j + gridScale)); } if (possibleTris.Count == 3) { validTris.AddRange(possibleTris); } if (possibleTris.Count == 2) { if (!possibleTris.Contains(new Vector3(i, pathHeight, j)) && !possibleTris.Contains(new Vector3(i + gridScale, pathHeight, j)) && PointInTriangles(polygon.points, triangles, new Vector2(i + gridScale + gridScale, j))) { pathVerts.Add(possibleTris[1]); //validTris.AddRange(new Vector3[] { possibleTris[0], possibleTris[1], new Vector3(i + gridScale + gridScale, 0, j) }); } /* * if (!possibleTris.Contains(new Vector3(i + gridScale, 0, j)) && !possibleTris.Contains(new Vector3(i + gridScale, 0, j + gridScale)) && * PointInTriangles(polygon.points, triangles, new Vector2(i + gridScale, j - gridScale)) && !PointInTriangles(polygon.points, triangles, new Vector2(i, j + gridScale + gridScale))) { * //validTris.AddRange(new Vector3[] { possibleTris[0], possibleTris[1], new Vector3(i + gridScale, 0, j - gridScale) }); * }*/ } } } } pathVerts = Utility.TrimDuplicates(pathVerts); innerRing = new List <Vector3> { pathVerts[0] }; Vector3 previous = pathVerts[0]; int reverseCount = pathVerts.Count; //if (debug) debugList.Add(pathVerts[0]); pathVerts.RemoveAt(0); while (pathVerts.Count > 0 && reverseCount > 0) { Vector3 closest = previous; foreach (Vector3 vert in pathVerts) { if (Vector3.Distance(previous, vert) < gridScale * 1.1f) { closest = vert; break; } } if (closest == previous) { foreach (Vector3 vert in pathVerts) { if (Vector3.Distance(previous, vert) < gridScale * 1.5f) { closest = vert; break; } } } if (closest != previous) { innerRing.Add(closest); pathVerts.Remove(closest); if (debug) { debugList.Add(closest); } previous = closest; } else { --reverseCount; innerRing.Reverse(); previous = innerRing[innerRing.Count - 1]; } } if (pathVerts.Count > 0) { for (int i = 0; i < pathVerts.Count; i++) { debugSegList.Add(new LineSegment(pathVerts[i], pathVerts[i] + Vector3.up * 10)); } innerRing.AddRange(pathVerts); print("Verts Left: " + pathVerts.Count); } if (Vector3.Distance(innerRing[0], Utility.NoY(outerRing[0])) > gridScale * 1.5f && !isClosed) { innerRing.Reverse(); } } }
internal Lot(IList <Vector3> vertices) { Vertices = vertices; this.area = Maths2D.CalculatePolygonArea((IEnumerable <Vector2>)ToXZ(vertices)); }
// Creates a linked list of all vertices in the polygon, with the hole vertices joined to the hull at optimal points. LinkedList <Vertex> GenerateVertexList(Polygon polygon) { LinkedList <Vertex> vertexList = new LinkedList <Vertex>(); LinkedListNode <Vertex> currentNode = null; // Add all hull points to the linked list for (int i = 0; i < polygon.numHullPoints; i++) { int prevPointIndex = (i - 1 + polygon.numHullPoints) % polygon.numHullPoints; int nextPointIndex = (i + 1) % polygon.numHullPoints; bool vertexIsConvex = IsConvex(polygon.points[prevPointIndex], polygon.points[i], polygon.points[nextPointIndex]); Vertex currentHullVertex = new Vertex(polygon.points[i], i, vertexIsConvex); if (currentNode == null) { currentNode = vertexList.AddFirst(currentHullVertex); } else { currentNode = vertexList.AddAfter(currentNode, currentHullVertex); } } // Process holes: List <HoleData> sortedHoleData = new List <HoleData>(); for (int holeIndex = 0; holeIndex < polygon.numHoles; holeIndex++) { // Find index of rightmost point in hole. This 'bridge' point is where the hole will be connected to the hull. Vector2 holeBridgePoint = new Vector2(float.MinValue, 0); int holeBridgeIndex = 0; for (int i = 0; i < polygon.numPointsPerHole[holeIndex]; i++) { if (polygon.GetHolePoint(i, holeIndex).x > holeBridgePoint.x) { holeBridgePoint = polygon.GetHolePoint(i, holeIndex); holeBridgeIndex = i; } } sortedHoleData.Add(new HoleData(holeIndex, holeBridgeIndex, holeBridgePoint)); } // Sort hole data so that holes furthest to the right are first sortedHoleData.Sort((x, y) => (x.bridgePoint.x > y.bridgePoint.x) ? -1 : 1); foreach (HoleData holeData in sortedHoleData) { // Find first edge which intersects with rightwards ray originating at the hole bridge point. Vector2 rayIntersectPoint = new Vector2(float.MaxValue, holeData.bridgePoint.y); List <LinkedListNode <Vertex> > hullNodesPotentiallyInBridgeTriangle = new List <LinkedListNode <Vertex> >(); LinkedListNode <Vertex> initialBridgeNodeOnHull = null; currentNode = vertexList.First; while (currentNode != null) { LinkedListNode <Vertex> nextNode = (currentNode.Next == null) ? vertexList.First : currentNode.Next; Vector2 p0 = currentNode.Value.position; Vector2 p1 = nextNode.Value.position; // at least one point must be to right of holeData.bridgePoint for intersection with ray to be possible if (p0.x > holeData.bridgePoint.x || p1.x > holeData.bridgePoint.x) { // one point is above, one point is below if (p0.y > holeData.bridgePoint.y != p1.y > holeData.bridgePoint.y) { float rayIntersectX = p1.x; // only true if line p0,p1 is vertical if (!Mathf.Approximately(p0.x, p1.x)) { float intersectY = holeData.bridgePoint.y; float gradient = (p0.y - p1.y) / (p0.x - p1.x); float c = p1.y - gradient * p1.x; rayIntersectX = (intersectY - c) / gradient; } // intersection must be to right of bridge point if (rayIntersectX > holeData.bridgePoint.x) { LinkedListNode <Vertex> potentialNewBridgeNode = (p0.x > p1.x) ? currentNode : nextNode; // if two intersections occur at same x position this means is duplicate edge // duplicate edges occur where a hole has been joined to the outer polygon bool isDuplicateEdge = Mathf.Approximately(rayIntersectX, rayIntersectPoint.x); // connect to duplicate edge (the one that leads away from the other, already connected hole, and back to the original hull) if the // current hole's bridge point is higher up than the bridge point of the other hole (so that the new bridge connection doesn't intersect). bool connectToThisDuplicateEdge = holeData.bridgePoint.y > potentialNewBridgeNode.Previous.Value.position.y; if (!isDuplicateEdge || connectToThisDuplicateEdge) { // if this is the closest ray intersection thus far, set bridge hull node to point in line having greater x pos (since def to right of hole). if (rayIntersectX < rayIntersectPoint.x || isDuplicateEdge) { rayIntersectPoint.x = rayIntersectX; initialBridgeNodeOnHull = potentialNewBridgeNode; } } } } } // Determine if current node might lie inside the triangle formed by holeBridgePoint, rayIntersection, and bridgeNodeOnHull // We only need consider those which are reflex, since only these will be candidates for visibility from holeBridgePoint. // A list of these nodes is kept so that in next step it is not necessary to iterate over all nodes again. if (currentNode != initialBridgeNodeOnHull) { if (!currentNode.Value.isConvex && p0.x > holeData.bridgePoint.x) { hullNodesPotentiallyInBridgeTriangle.Add(currentNode); } } currentNode = currentNode.Next; } // Check triangle formed by hullBridgePoint, rayIntersection, and bridgeNodeOnHull. // If this triangle contains any points, those points compete to become new bridgeNodeOnHull LinkedListNode <Vertex> validBridgeNodeOnHull = initialBridgeNodeOnHull; foreach (LinkedListNode <Vertex> nodePotentiallyInTriangle in hullNodesPotentiallyInBridgeTriangle) { if (nodePotentiallyInTriangle.Value.index == initialBridgeNodeOnHull.Value.index) { continue; } // if there is a point inside triangle, this invalidates the current bridge node on hull. if (Maths2D.PointInTriangle(holeData.bridgePoint, rayIntersectPoint, initialBridgeNodeOnHull.Value.position, nodePotentiallyInTriangle.Value.position)) { // Duplicate points occur at hole and hull bridge points. bool isDuplicatePoint = validBridgeNodeOnHull.Value.position == nodePotentiallyInTriangle.Value.position; // if multiple nodes inside triangle, we want to choose the one with smallest angle from holeBridgeNode. // if is a duplicate point, then use the one occurring later in the list float currentDstFromHoleBridgeY = Mathf.Abs(holeData.bridgePoint.y - validBridgeNodeOnHull.Value.position.y); float pointInTriDstFromHoleBridgeY = Mathf.Abs(holeData.bridgePoint.y - nodePotentiallyInTriangle.Value.position.y); if (pointInTriDstFromHoleBridgeY < currentDstFromHoleBridgeY || isDuplicatePoint) { validBridgeNodeOnHull = nodePotentiallyInTriangle; } } } // Insert hole points (starting at holeBridgeNode) into vertex list at validBridgeNodeOnHull currentNode = validBridgeNodeOnHull; for (int i = holeData.bridgeIndex; i <= polygon.numPointsPerHole[holeData.holeIndex] + holeData.bridgeIndex; i++) { int previousIndex = currentNode.Value.index; int currentIndex = polygon.IndexOfPointInHole(i % polygon.numPointsPerHole[holeData.holeIndex], holeData.holeIndex); int nextIndex = polygon.IndexOfPointInHole((i + 1) % polygon.numPointsPerHole[holeData.holeIndex], holeData.holeIndex); if (i == polygon.numPointsPerHole[holeData.holeIndex] + holeData.bridgeIndex) // have come back to starting point { nextIndex = validBridgeNodeOnHull.Value.index; // next point is back to the point on the hull } bool vertexIsConvex = IsConvex(polygon.points[previousIndex], polygon.points[currentIndex], polygon.points[nextIndex]); Vertex holeVertex = new Vertex(polygon.points[currentIndex], currentIndex, vertexIsConvex); currentNode = vertexList.AddAfter(currentNode, holeVertex); } // Add duplicate hull bridge vert now that we've come all the way around. Also set its concavity Vector2 nextVertexPos = (currentNode.Next == null) ? vertexList.First.Value.position : currentNode.Next.Value.position; bool isConvex = IsConvex(holeData.bridgePoint, validBridgeNodeOnHull.Value.position, nextVertexPos); Vertex repeatStartHullVert = new Vertex(validBridgeNodeOnHull.Value.position, validBridgeNodeOnHull.Value.index, isConvex); vertexList.AddAfter(currentNode, repeatStartHullVert); //Set concavity of initial hull bridge vert, since it may have changed now that it leads to hole vert LinkedListNode <Vertex> nodeBeforeStartBridgeNodeOnHull = (validBridgeNodeOnHull.Previous == null) ? vertexList.Last : validBridgeNodeOnHull.Previous; LinkedListNode <Vertex> nodeAfterStartBridgeNodeOnHull = (validBridgeNodeOnHull.Next == null) ? vertexList.First : validBridgeNodeOnHull.Next; validBridgeNodeOnHull.Value.isConvex = IsConvex(nodeBeforeStartBridgeNodeOnHull.Value.position, validBridgeNodeOnHull.Value.position, nodeAfterStartBridgeNodeOnHull.Value.position); } return(vertexList); }
// v1 is considered a convex vertex if v0-v1-v2 are wound in a counter-clockwise order. bool IsConvex(Vector2 v0, Vector2 v1, Vector2 v2) { return(Maths2D.SideOfLine(v0, v2, v1) == -1); }