/// <summary> /// Checks if face should be added to cone /// </summary> /// <param name="face">The face.</param> /// <returns><c>true</c> if [is new member of] [the specified face]; otherwise, <c>false</c>.</returns> /// <exception cref="NotImplementedException"></exception> public override bool IsNewMemberOf(PolygonalFace face) { return(false); // todo throw new NotImplementedException(); }
private static bool FaceShouldBeOwnedFace(Edge edge, PolygonalFace face) { var otherEdgeVector = face.OtherVertex(edge.From, edge.To).Position.subtract(edge.To.Position, 3); var isThisNormal = edge.Vector.crossProduct(otherEdgeVector); return(face.Normal.dotProduct(isThisNormal, 3) > 0); }
/// <summary> /// Updates the with. /// </summary> /// <param name="face">The face.</param> public override void UpdateWith(PolygonalFace face) { var numFaces = Faces.Count; double[] inBetweenPoint; var distance = MiscFunctions.SkewedLineIntersection(face.Center, face.Normal, Anchor, Axis, out inBetweenPoint); var fractionToMove = 1 / numFaces; var moveVector = Anchor.crossProduct(face.Normal); if (moveVector.dotProduct(face.Center.subtract(inBetweenPoint, 3)) < 0) { moveVector = moveVector.multiply(-1); } moveVector.normalizeInPlace(3); /**** set new Anchor (by averaging in with last n values) ****/ Anchor = Anchor.add(new[] { moveVector[0] * fractionToMove * distance, moveVector[1] * fractionToMove * distance, moveVector[2] * fractionToMove * distance }, 3); /* to adjust the Axis, we will average the cross products of the new face with all the old faces */ var totalAxis = new double[3]; for (var i = 0; i < numFaces; i++) { var newAxis = face.Normal.crossProduct(Faces[i].Normal); if (newAxis.dotProduct(Axis, 3) < 0) { newAxis.multiply(-1); } totalAxis = totalAxis.add(newAxis, 3); } var numPrevCrossProducts = numFaces * (numFaces - 1) / 2; totalAxis = totalAxis.add(Axis.multiply(numPrevCrossProducts), 3); /**** set new Axis (by averaging in with last n values) ****/ Axis = totalAxis.divide(numFaces + numPrevCrossProducts).normalize(3); foreach (var v in face.Vertices) { if (!Vertices.Contains(v)) { Vertices.Add(v); } } var totalOfRadii = Vertices.Sum(v => MiscFunctions.DistancePointToLine(v.Position, Anchor, Axis)); /**** set new Radius (by averaging in with last n values) ****/ Radius = totalOfRadii / Vertices.Count; base.UpdateWith(face); }
/// <summary> /// Updates the with. /// </summary> /// <param name="face">The face.</param> public override void UpdateWith(PolygonalFace face) { var numFaces = Faces.Count; var distance = MiscFunctions.SkewedLineIntersection(face.Center, face.Normal, Anchor, Axis, out var inBetweenPoint); var fractionToMove = 1 / numFaces; var moveVector = Anchor.Cross(face.Normal); if (moveVector.Dot(face.Center.Subtract(inBetweenPoint)) < 0) { moveVector = moveVector * -1; } moveVector = moveVector.Normalize(); /**** set new Anchor (by averaging in with last n values) ****/ Anchor = Anchor + new Vector3( moveVector.X * fractionToMove * distance, moveVector.Y * fractionToMove * distance, moveVector.Z * fractionToMove * distance ); /* to adjust the Axis, we will average the cross products of the new face with all the old faces */ var totalAxis = new Vector3(); foreach (var oldFace in Faces) { var newAxis = face.Normal.Cross(oldFace.Normal); if (newAxis.Dot(Axis) < 0) { newAxis = -1 * newAxis; } totalAxis = totalAxis + newAxis; } var numPrevCrossProducts = numFaces * (numFaces - 1) / 2; totalAxis = totalAxis + (Axis * numPrevCrossProducts); /**** set new Axis (by averaging in with last n values) ****/ Axis = totalAxis.Normalize(); foreach (var v in face.Vertices) { if (!Vertices.Contains(v)) { Vertices.Add(v); } } var totalOfRadii = Vertices.Sum(v => MiscFunctions.DistancePointToLine(v.Coordinates, Anchor, Axis)); /**** set new Radius (by averaging in with last n values) ****/ Radius = totalOfRadii / Vertices.Count; base.UpdateWith(face); }
internal TVGLConvexHull(IList <Vertex> allVertices, IList <Vertex> convexHullPoints, IList <int> convexHullFaceIndices, double[] center = null, double volume = double.NaN, double surfaceArea = double.NaN) { Vertices = convexHullPoints.ToArray(); var numCvxHullFaces = convexHullFaceIndices.Count / 3; Faces = new PolygonalFace[numCvxHullFaces]; var checkSumMultipliers = new long[3]; for (var i = 0; i < 3; i++) { checkSumMultipliers[i] = (long)Math.Pow(Constants.CubeRootOfLongMaxValue, i); } var alreadyCreatedFaces = new HashSet <long>(); for (int i = 0; i < numCvxHullFaces; i++) { var orderedIndices = new List <int> { convexHullFaceIndices[3 * i], convexHullFaceIndices[3 * i + 1], convexHullFaceIndices[3 * i + 2] }; orderedIndices.Sort(); var checksum = orderedIndices.Select((t, j) => t * checkSumMultipliers[j]).Sum(); if (alreadyCreatedFaces.Contains(checksum)) { continue; } alreadyCreatedFaces.Add(checksum); var faceVertices = new[] { allVertices[convexHullFaceIndices[3 * i]], allVertices[convexHullFaceIndices[3 * i + 1]], allVertices[convexHullFaceIndices[3 * i + 2]], }; Faces[i] = new PolygonalFace(faceVertices, false); } Edges = MakeEdges(Faces, Vertices); if (center == null || double.IsNaN(volume) || double.IsNaN(surfaceArea)) { TessellatedSolid.DefineCenterVolumeAndSurfaceArea(Faces, out Center, out Volume, out SurfaceArea); } else { Center = center; Volume = volume; SurfaceArea = surfaceArea; } }
/// <summary> /// Updates the with. /// </summary> /// <param name="face">The face.</param> public override void UpdateWith(PolygonalFace face) { Normal = Normal.multiply(Faces.Count).add(face.Normal, 3).divide(Faces.Count + 1); Normal.normalizeInPlace(); var newVerts = new List <Vertex>(); var newDistanceToPlane = 0.0; foreach (var v in face.Vertices.Where(v => !Vertices.Contains(v))) { newVerts.Add(v); newDistanceToPlane += v.Position.dotProduct(Normal, 3); } DistanceToOrigin = (Vertices.Count * DistanceToOrigin + newDistanceToPlane) / (Vertices.Count + newVerts.Count); base.UpdateWith(face); }
/// <summary> /// Stores the face with negligible area. /// </summary> /// <param name="ts">The ts.</param> /// <param name="face">The face.</param> private static void StoreFaceWithNegligibleArea(TessellatedSolid ts, PolygonalFace face) { //This is not truly an error, to don't change the NoErrors boolean. if (ts.Errors.FacesWithNegligibleArea == null) { ts.Errors.FacesWithNegligibleArea = new List <PolygonalFace> { face } } ; else if (!ts.Errors.FacesWithNegligibleArea.Contains(face)) { ts.Errors.FacesWithNegligibleArea.Add(face); } }
/// <summary> /// Stores the face with one vertex. /// </summary> /// <param name="ts">The ts.</param> /// <param name="face">The face.</param> private static void StoreFaceWithOneVertex(TessellatedSolid ts, PolygonalFace face) { ts.Errors.NoErrors = false; if (ts.Errors.FacesWithOneVertex == null) { ts.Errors.FacesWithOneVertex = new List <PolygonalFace> { face } } ; else { ts.Errors.FacesWithOneVertex.Add(face); } }
private static PolygonLight GetPolygonFromFace(PolygonalFace face, Dictionary <int, PointLight> projectedPoints, bool forceToBePositive) { if (face.Vertices.Count != 3) { throw new Exception("This method was only developed with triangles in mind."); } //Make sure the polygon is ordered correctly (we already know this face is positive) var points = face.Vertices.Select(v => projectedPoints[v.IndexInList]).ToList(); var facePolygon = new PolygonLight(points); if (forceToBePositive && facePolygon.Area < 0) { facePolygon = PolygonLight.Reverse(facePolygon); } return(facePolygon); }
/// <summary> /// Stores the edge does not link back to face. /// </summary> /// <param name="ts">The ts.</param> /// <param name="face">The face.</param> /// <param name="edge">The edge.</param> private static void StoreEdgeDoesNotLinkBackToFace(TessellatedSolid ts, PolygonalFace face, Edge edge) { ts.Errors.NoErrors = false; if (ts.Errors.EdgesThatDoNotLinkBackToFace == null) { ts.Errors.EdgesThatDoNotLinkBackToFace = new List <(PolygonalFace, Edge)> { (face, edge) } } ; else { ts.Errors.EdgesThatDoNotLinkBackToFace.Add((face, edge)); } }
/// <summary> /// Stores the vertex does not link back to face. /// </summary> /// <param name="ts">The ts.</param> /// <param name="face">The face.</param> /// <param name="vertex">The vertex.</param> private static void StoreVertexDoesNotLinkBackToFace(TessellatedSolid ts, PolygonalFace face, Vertex vertex) { ts.Errors.NoErrors = false; if (ts.Errors.VertsThatDoNotLinkBackToFace == null) { ts.Errors.VertsThatDoNotLinkBackToFace = new List <(PolygonalFace, Vertex)> { (face, vertex) } } ; else { ts.Errors.VertsThatDoNotLinkBackToFace.Add((face, vertex)); } }
/// <summary> /// Initializes a new instance of the <see cref="Edge" /> class. /// </summary> /// <param name="fromVertex">From vertex.</param> /// <param name="toVertex">To vertex.</param> /// <param name="ownedFace">The face.</param> /// <param name="otherFace">The other face.</param> /// <param name="doublyLinkedVertices">if set to <c>true</c> [doubly linked vertices].</param> /// <param name="edgeReference">The edge reference.</param> /// <exception cref="Exception"></exception> public Edge(Vertex fromVertex, Vertex toVertex, PolygonalFace ownedFace, PolygonalFace otherFace, bool doublyLinkedVertices, long edgeReference = 0) : this(fromVertex, toVertex, doublyLinkedVertices) { if (edgeReference > 0) { EdgeReference = edgeReference; } else { TessellatedSolid.SetAndGetEdgeChecksum(this); } _ownedFace = ownedFace; _otherFace = otherFace; ownedFace?.AddEdge(this); otherFace?.AddEdge(this); }
/// <summary> /// Adds face to sphere /// </summary> /// <param name="face">The face.</param> public override void UpdateWith(PolygonalFace face) { var distance = MiscFunctions.DistancePointToLine(Center, face.Center, face.Normal, out var pointOnLine); var fractionToMove = 1 / Faces.Count; var moveVector = pointOnLine.Subtract(Center); Center = Center + new Vector3( moveVector.X * fractionToMove * distance, moveVector.Y * fractionToMove * distance, moveVector.Z * fractionToMove * distance ); var totalOfRadii = Vertices.Sum(v => Vector3.Distance(Center, v.Coordinates)); Radius = totalOfRadii / Vertices.Count; base.UpdateWith(face); }
/// <summary> /// Determines whether [is new member of] [the specified face]. /// </summary> /// <param name="face">The face.</param> /// <returns><c>true</c> if [is new member of] [the specified face]; otherwise, <c>false</c>.</returns> public override bool IsNewMemberOf(PolygonalFace face) { if (Tolerance.IsPracticallySame(0.0)) { Tolerance = Constants.ErrorForFaceInSurface; } if (Faces.Contains(face)) { return(false); } if (!face.Normal.Dot(Normal).IsPracticallySame(1.0, Tolerance)) { return(false); } //Return true if all the vertices are within the tolerance //Note that the Dot term and distance to origin, must have the same sign, //so there is no additional need moth absolute value methods. return(face.Vertices.All(v => Normal.Dot(v.Coordinates).IsPracticallySame(DistanceToOrigin, Tolerance))); }
/// <summary> /// Adjusts the position of kept vertex experimental. /// </summary> /// <param name="keepVertex">The keep vertex.</param> /// <param name="removedVertex">The removed vertex.</param> /// <param name="removeFace1">The remove face1.</param> /// <param name="removeFace2">The remove face2.</param> internal static void AdjustPositionOfKeptVertexExperimental(Vertex keepVertex, Vertex removedVertex, PolygonalFace removeFace1, PolygonalFace removeFace2) { //average positions var newPosition = keepVertex.Coordinates + removedVertex.Coordinates; var radius = keepVertex.Coordinates.Distance(removedVertex.Coordinates) / 2.0; keepVertex.Coordinates = newPosition.Divide(2); var avgNormal = (removeFace1.Normal + removeFace2.Normal).Normalize(); var otherVertexAvgDistanceToEdgePlane = keepVertex.Edges.Select(e => e.OtherVertex(keepVertex).Coordinates.Dot(avgNormal)).Sum() / (keepVertex.Edges.Count - 1); var distanceOfEdgePlane = keepVertex.Coordinates.Dot(avgNormal); // use a sigmoid function to determine how far out to move the vertex var x = 0.05 * (distanceOfEdgePlane - otherVertexAvgDistanceToEdgePlane) / radius; var length = 2 * radius * x / Math.Sqrt(1 + x * x) - radius; keepVertex.Coordinates = keepVertex.Coordinates + (avgNormal * length); }
/// <summary> /// Adjusts the position of kept vertex experimental. /// </summary> /// <param name="keepVertex">The keep vertex.</param> /// <param name="removedVertex">The removed vertex.</param> /// <param name="removeFace1">The remove face1.</param> /// <param name="removeFace2">The remove face2.</param> internal static void AdjustPositionOfKeptVertexExperimental(Vertex keepVertex, Vertex removedVertex, PolygonalFace removeFace1, PolygonalFace removeFace2) { //average positions var newPosition = keepVertex.Position.add(removedVertex.Position, 3); var radius = keepVertex.Position.subtract(removedVertex.Position, 3).norm2() / 2.0; keepVertex.Position = newPosition.divide(2); var avgNormal = removeFace1.Normal.add(removeFace2.Normal, 3).normalize(3); var otherVertexAvgDistanceToEdgePlane = keepVertex.Edges.Select(e => e.OtherVertex(keepVertex).Position.dotProduct(avgNormal, 3)).Sum() / (keepVertex.Edges.Count - 1); var distanceOfEdgePlane = keepVertex.Position.dotProduct(avgNormal, 3); // use a sigmoid function to determine how far out to move the vertex var x = 0.05 * (distanceOfEdgePlane - otherVertexAvgDistanceToEdgePlane) / radius; var length = 2 * radius * x / Math.Sqrt(1 + x * x) - radius; keepVertex.Position = keepVertex.Position.add(avgNormal.multiply(length), 3); }
/// <summary> /// Determines whether [is new member of] [the specified face]. /// </summary> /// <param name="face">The face.</param> /// <returns><c>true</c> if [is new member of] [the specified face]; otherwise, <c>false</c>.</returns> public override bool IsNewMemberOf(PolygonalFace face) { if (Faces.Contains(face)) { return(false); } if (Math.Abs(face.Normal.dotProduct(Axis, 3)) > Constants.ErrorForFaceInSurface) { return(false); } foreach (var v in face.Vertices) { if (Math.Abs(MiscFunctions.DistancePointToLine(v.Position, Anchor, Axis) - Radius) > Constants.ErrorForFaceInSurface * Radius) { return(false); } } return(true); }
/// <summary> /// Adds face to sphere /// </summary> /// <param name="face">The face.</param> public override void UpdateWith(PolygonalFace face) { double[] pointOnLine; var distance = MiscFunctions.DistancePointToLine(Center, face.Center, face.Normal, out pointOnLine); var fractionToMove = 1 / Faces.Count; var moveVector = pointOnLine.subtract(Center, 3); Center = Center.add(new[] { moveVector[0] * fractionToMove * distance, moveVector[1] * fractionToMove * distance, moveVector[2] * fractionToMove * distance }, 3); var totalOfRadii = Vertices.Sum(v => MiscFunctions.DistancePointToPoint(Center, v.Position)); Radius = totalOfRadii / Vertices.Count; base.UpdateWith(face); }
/// <summary> /// Updates surface by adding face /// </summary> /// <param name="face">The face.</param> public virtual void UpdateWith(PolygonalFace face) { Area += face.Area; foreach (var v in face.Vertices.Where(v => !Vertices.Contains(v))) { Vertices.Add(v); } foreach (var e in face.Edges.Where(e => !InnerEdges.Contains(e))) { if (_outerEdges.Contains(e)) { _outerEdges.Remove(e); _innerEdges.Add(e); } else { _outerEdges.Add(e); } } Faces.Add(face); }
/// <summary> /// Checks if the face is a member of the sphere /// </summary> /// <param name="face">The face.</param> /// <returns>Boolean.</returns> public override bool IsNewMemberOf(PolygonalFace face) { if (Faces.Contains(face)) { return(false); } if (Math.Abs(face.Normal.dotProduct(face.Center.subtract(Center, 3)) - 1) > Constants.ErrorForFaceInSurface) { return(false); } foreach (var v in face.Vertices) { if (Math.Abs(MiscFunctions.DistancePointToPoint(v.Position, Center) - Radius) > Constants.ErrorForFaceInSurface * Radius) { return(false); } } return(true); }
/// <summary> /// Checks if the face is a member of the sphere /// </summary> /// <param name="face">The face.</param> /// <returns>Boolean.</returns> public override bool IsNewMemberOf(PolygonalFace face) { if (Faces.Contains(face)) { return(false); } if (Math.Abs(face.Normal.Dot(face.Center - Center) - 1) > Constants.ErrorForFaceInSurface) { return(false); } foreach (var v in face.Vertices) { if (Math.Abs(v.Coordinates.Distance(Center) - Radius) > Constants.ErrorForFaceInSurface * Radius) { return(false); } } return(true); }
internal TVGLConvexHull(IList <Vertex> allVertices, IList <Vertex> convexHullPoints, IList <int> convexHullFaceIndices, double tolerance) { Vertices = convexHullPoints.ToArray(); var numCvxHullFaces = convexHullFaceIndices.Count / 3; Faces = new PolygonalFace[numCvxHullFaces]; var checkSumMultipliers = new long[3]; for (var i = 0; i < 3; i++) { checkSumMultipliers[i] = (long)Math.Pow(Constants.CubeRootOfLongMaxValue, i); } var alreadyCreatedFaces = new HashSet <long>(); for (int i = 0; i < numCvxHullFaces; i++) { var orderedIndices = new List <int> { convexHullFaceIndices[3 * i], convexHullFaceIndices[3 * i + 1], convexHullFaceIndices[3 * i + 2] }; orderedIndices.Sort(); var checksum = orderedIndices.Select((t, j) => t * checkSumMultipliers[j]).Sum(); if (alreadyCreatedFaces.Contains(checksum)) { continue; } alreadyCreatedFaces.Add(checksum); var faceVertices = new[] { allVertices[convexHullFaceIndices[3 * i]], allVertices[convexHullFaceIndices[3 * i + 1]], allVertices[convexHullFaceIndices[3 * i + 2]], }; Faces[i] = new PolygonalFace(faceVertices, false); } Edges = MakeEdges(Faces, Vertices); SurfaceArea = Faces.Sum(face => face.Area); TessellatedSolid.CalculateVolumeAndCenter(Faces, tolerance, out Volume, out Center); }
/// <summary> /// Initializes a new instance of the <see cref="Edge" /> class. /// </summary> /// <param name="fromVertex">From vertex.</param> /// <param name="toVertex">To vertex.</param> /// <param name="ownedFace">The face.</param> /// <param name="otherFace">The other face.</param> /// <param name="doublyLinkedVertices">if set to <c>true</c> [doubly linked vertices].</param> /// <param name="edgeReference">The edge reference.</param> /// <exception cref="Exception"></exception> public Edge(Vertex fromVertex, Vertex toVertex, PolygonalFace ownedFace, PolygonalFace otherFace, bool doublyLinkedVertices, long edgeReference = 0) : this(fromVertex, toVertex, doublyLinkedVertices) { if (edgeReference > 0) { EdgeReference = edgeReference; } else { TessellatedSolid.SetAndGetEdgeChecksum(this); } _ownedFace = ownedFace; _otherFace = otherFace; if (ownedFace != null) { ownedFace.AddEdge(this); } if (otherFace != null) { otherFace.AddEdge(this); } DefineInternalEdgeAngle(); }
/// <summary> /// Updates the with. /// </summary> /// <param name="face">The face.</param> public override void UpdateWith(PolygonalFace face) { if (Faces == null) { Faces = new HashSet <PolygonalFace>(); } Normal = (Faces.Count * Normal) + face.Normal; Normal = Vector3.Normalize(Normal); var numNewVerts = 0; var newDistanceToPlane = 0.0; if (Vertices == null) { Vertices = new HashSet <Vertex>(); } foreach (var v in face.Vertices.Where(v => !Vertices.Contains(v))) { numNewVerts++; newDistanceToPlane += v.Coordinates.Dot(Normal); } DistanceToOrigin = (Vertices.Count * DistanceToOrigin + newDistanceToPlane) / (Vertices.Count + numNewVerts); base.UpdateWith(face); }
/// <summary> /// Complexifies the tessellation so that no edge is longer than provided the maximum edge length /// or for adding the provided number of faces - whichever comes first /// </summary> /// <param name="ts">The ts.</param> /// <param name="numberOfFaces">The number of new faces to add.</param> /// <param name="maxLength">The maximum length.</param> public static void Complexify(TessellatedSolid ts, int numberOfFaces, double maxLength) { var edgeQueue = new SimplePriorityQueue <Edge, double>(new ReverseSort()); foreach (var e in ts.Edges) { edgeQueue.Enqueue(e, e.Length); } var addedEdges = new List <Edge>(); var addedVertices = new List <Vertex>(); var addedFaces = new List <PolygonalFace>(); var edge = edgeQueue.Dequeue(); var iterations = numberOfFaces > 0 ? (int)Math.Ceiling(numberOfFaces / 2.0) : numberOfFaces; while (iterations-- != 0 && edge.Length >= maxLength) { var origLeftFace = edge.OtherFace; var origRightFace = edge.OwnedFace; var leftFarVertex = origLeftFace.OtherVertex(edge); var rightFarVertex = origRightFace.OtherVertex(edge); var fromVertex = edge.From; var toVertex = edge.To; var addedVertex = new Vertex(DetermineIntermediateVertexPosition(fromVertex, toVertex)); // modify original faces with new intermediate vertex var index = origLeftFace.Vertices.IndexOf(toVertex); origLeftFace.Vertices[index] = addedVertex; origLeftFace.Update(); addedVertex.Faces.Add(origLeftFace); index = origRightFace.Vertices.IndexOf(toVertex); origRightFace.Vertices[index] = addedVertex; origRightFace.Update(); addedVertex.Faces.Add(origRightFace); var newLeftFace = new PolygonalFace(new[] { toVertex, addedVertex, leftFarVertex }); var newRightFace = new PolygonalFace(new[] { addedVertex, toVertex, rightFarVertex }); toVertex.Faces.Remove(origLeftFace); toVertex.Faces.Remove(origRightFace); var inlineEdge = new Edge(addedVertex, toVertex, newRightFace, newLeftFace, true); toVertex.Edges.Remove(edge); edge.To = addedVertex; addedVertex.Edges.Add(edge); edge.Update(); var newLeftEdge = new Edge(leftFarVertex, addedVertex, origLeftFace, newLeftFace, true); var newRightEdge = new Edge(rightFarVertex, addedVertex, newRightFace, origRightFace, true); origLeftFace.AddEdge(newLeftEdge); origRightFace.AddEdge(newRightEdge); var bottomEdge = toVertex.Edges.First(e => e.OtherVertex(toVertex) == leftFarVertex); if (bottomEdge.OwnedFace == origLeftFace) { bottomEdge.OwnedFace = newLeftFace; } else { bottomEdge.OtherFace = newLeftFace; } newLeftFace.AddEdge(bottomEdge); bottomEdge.Update(); bottomEdge = toVertex.Edges.First(e => e.OtherVertex(toVertex) == rightFarVertex); if (bottomEdge.OwnedFace == origRightFace) { bottomEdge.OwnedFace = newRightFace; } else { bottomEdge.OtherFace = newRightFace; } newRightFace.AddEdge(bottomEdge); bottomEdge.Update(); // need to re-add the edge. It was modified in the SplitEdge function (now, half the lenght), but // it may still be met by this criteria edgeQueue.Enqueue(edge, edge.Length); edgeQueue.Enqueue(inlineEdge, inlineEdge.Length); addedEdges.Add(inlineEdge); edgeQueue.Enqueue(newLeftEdge, newLeftEdge.Length); addedEdges.Add(newLeftEdge); edgeQueue.Enqueue(newRightEdge, newRightEdge.Length); addedEdges.Add(newRightEdge); addedFaces.Add(newLeftFace); addedFaces.Add(newRightFace); addedVertices.Add(addedVertex); edge = edgeQueue.First(); } ts.AddVertices(addedVertices); ts.AddEdges(addedEdges); ts.AddFaces(addedFaces); }
/// <summary> /// Combines the vertices of edge. /// </summary> /// <param name="edge">The edge.</param> /// <param name="removedVertexOut">The removed vertex out.</param> /// <param name="removedEdge1Out">The removed edge1 out.</param> /// <param name="removedEdge2Out">The removed edge2 out.</param> /// <param name="removedFace1">The removed face1.</param> /// <param name="removedFace2">The removed face2.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> private static bool CombineVerticesOfEdge(Edge edge, out Vertex removedVertexOut, out Edge removedEdge1Out, out Edge removedEdge2Out, out PolygonalFace removedFace1, out PolygonalFace removedFace2) { var keepVertex = edge.To; // arbitrarily choose the To as the keep vertex, but this may be swapped below var removedVertex = edge.From; // if the To has some missing faces if (keepVertex == removedVertex) { removedVertexOut = null; removedEdge2Out = removedEdge1Out = null; removedFace1 = removedFace2 = null; return(false); } removedFace1 = edge.OwnedFace; removedFace2 = edge.OtherFace; var removedEdge1 = removedFace1 == null ? null : removedFace1.OtherEdge(keepVertex, true); var removedEdge2 = removedFace2 == null ? null : removedFace2.OtherEdge(keepVertex, true); var keepEdge1 = removedFace1 == null ? null : removedFace1.OtherEdge(removedVertex, true); var keepEdge2 = removedFace2 == null ? null : removedFace2.OtherEdge(removedVertex, true); if (removedEdge1 != null && removedEdge2 != null && (keepEdge1 == null || keepEdge2 == null)) { // swap with removed. var tempVertex = keepVertex; keepVertex = removedVertex; removedVertex = tempVertex; var tempEdge = keepEdge1; keepEdge1 = removedEdge1; removedEdge1 = tempEdge; tempEdge = keepEdge2; keepEdge2 = removedEdge2; removedEdge2 = tempEdge; } var otherEdgesOnTheKeepSide = keepVertex.Edges.Where(e => e != edge && e != keepEdge1 && e != keepEdge2).ToList(); var otherEdgesOnTheRemoveSide = removedVertex.Edges.Where(e => e != edge && e != removedEdge1 && e != removedEdge2).ToList(); if ( // this is a topologically important check. It ensures that the edge is not deleted if // it serves an important role in ensuring the proper topology of the solid otherEdgesOnTheKeepSide.Select(e => e.OtherVertex(keepVertex)) .Intersect(otherEdgesOnTheRemoveSide.Select(e => e.OtherVertex(removedVertex))) .Any()) { removedVertexOut = null; removedEdge2Out = removedEdge1Out = null; removedFace1 = removedFace2 = null; return(false); } // move edges connected to removeVertex to the keepVertex and let keepVertex link back to these edges foreach (var e in otherEdgesOnTheRemoveSide) { keepVertex.Edges.Add(e); if (e.From == removedVertex) { e.From = keepVertex; } else { e.To = keepVertex; } } // move faces connected to removeVertex to the keepVertex and let keepVertex link back to these edges. foreach (var face in removedVertex.Faces) { if (face == removedFace1 || face == removedFace2) { continue; } keepVertex.Faces.Add(face); face.Vertices[face.Vertices.IndexOf(removedVertex)] = keepVertex; } // conversely keepVertex should forget about the edge and the remove faces keepVertex.Edges.Remove(edge); keepVertex.Faces.Remove(removedFace1); keepVertex.Faces.Remove(removedFace2); var farVertex = removedFace1 == null ? null : removedFace1.OtherVertex(edge, true); if (farVertex != null) { farVertex.Edges.Remove(removedEdge1); farVertex.Faces.Remove(removedFace1); } farVertex = removedFace2 == null ? null : removedFace2.OtherVertex(edge, true); if (farVertex != null) { farVertex.Edges.Remove(removedEdge2); farVertex.Faces.Remove(removedFace2); } // for the winged edges (removedEdge1 and removedEdge2) that are removed, connected their faces to // the new edge // first on the "owned side of edge" var fromFace = removedEdge1 == null ? null : removedEdge1.OwnedFace == removedFace1 ? removedEdge1.OtherFace : removedEdge1.OwnedFace; if (fromFace != null) { var index = fromFace.Edges.IndexOf(removedEdge1); if (index >= 0 && index < fromFace.Edges.Count) { fromFace.Edges[index] = keepEdge1; } } if (keepEdge1 != null && keepEdge1.OwnedFace == removedFace1) { keepEdge1.OwnedFace = fromFace; } else if (keepEdge1 != null) { keepEdge1.OtherFace = fromFace; } // second on the "other side of edge" fromFace = removedEdge2 == null ? null : removedEdge2.OwnedFace == removedFace2 ? removedEdge2.OtherFace : removedEdge2.OwnedFace; if (fromFace != null) { var index = fromFace.Edges.IndexOf(removedEdge2); if (index >= 0 && index < fromFace.Edges.Count) { fromFace.Edges[index] = keepEdge2; } } if (keepEdge2 != null && keepEdge2.OwnedFace == removedFace2) { keepEdge2.OwnedFace = fromFace; } else if (keepEdge2 != null) { keepEdge2.OtherFace = fromFace; } keepVertex.Position = DetermineIntermediateVertexPosition(keepVertex, removedVertex); foreach (var e in keepVertex.Edges) { e.Update(); } foreach (var f in keepVertex.Faces) { f.Update(); } removedVertexOut = removedVertex; removedEdge1Out = removedEdge1; removedEdge2Out = removedEdge2; return(true); }
/// <summary> /// Simplifies the model by merging the eliminating edges that are closer together /// than double the shortest edge length /// </summary> /// <param name="ts">The ts.</param> public static void SimplifyFlatPatches(this TessellatedSolid ts) { // throw new NotImplementedException(); var edgesToRemove = new List <Edge>(); var edgesToAdd = new List <Edge>(); var facesToRemove = new List <PolygonalFace>(); var facesToAdd = new List <PolygonalFace>(); var verticesToRemove = new List <Vertex>(); var flats = TVGL.MiscFunctions.FindFlats(ts.Faces); if (ts.Primitives == null) { ts.Primitives = new List <PrimitiveSurface>(); } foreach (var flat in flats) { if (flat.InnerEdges.Count < flat.Faces.Count) { continue; } var newFaces = new List <PolygonalFace>(); var outerEdgeHashSet = new HashSet <Edge>(flat.OuterEdges); facesToRemove.AddRange(flat.Faces); edgesToRemove.AddRange(flat.InnerEdges); var innerVertices = new HashSet <Vertex>(flat.InnerEdges.Select(e => e.To)); innerVertices.UnionWith(flat.InnerEdges.Select(e => e.From)); innerVertices.RemoveWhere(v => outerEdgeHashSet.Overlaps(v.Edges)); verticesToRemove.AddRange(innerVertices); var vertexLoops = OrganizeIntoLoop(flat.OuterEdges, flat.Normal); List <List <Vertex[]> > triangulatedListofLists = TriangulatePolygon.Run(new[] { vertexLoops }, flat.Normal); var triangulatedList = triangulatedListofLists.SelectMany(tl => tl).ToList(); var oldEdgeDictionary = flat.OuterEdges.ToDictionary(TessellatedSolid.SetAndGetEdgeChecksum); Dictionary <long, Edge> newEdgeDictionary = new Dictionary <long, Edge>(); foreach (var triangle in triangulatedList) { var newFace = new PolygonalFace(triangle, flat.Normal); if (newFace.Area.IsNegligible() && newFace.Normal.Any(double.IsNaN)) { continue; } newFaces.Add(newFace); for (var j = 0; j < 3; j++) { var fromVertex = newFace.Vertices[j]; var toVertex = newFace.NextVertexCCW(fromVertex); var checksum = TessellatedSolid.GetEdgeChecksum(fromVertex, toVertex); if (oldEdgeDictionary.ContainsKey(checksum)) { //fix up old outer edge. var edge = oldEdgeDictionary[checksum]; if (fromVertex == edge.From) { edge.OwnedFace = newFace; } else { edge.OtherFace = newFace; } newFace.AddEdge(edge); oldEdgeDictionary.Remove(checksum); } else if (newEdgeDictionary.ContainsKey(checksum)) { //Finish creating edge. var newEdge = newEdgeDictionary[checksum]; newEdge.OtherFace = newFace; newFace.AddEdge(newEdge); newEdgeDictionary.Remove(checksum); edgesToAdd.Add(newEdge); } else { newEdgeDictionary.Add(checksum, new Edge(fromVertex, toVertex, newFace, null, false, checksum)); } } } ts.Primitives.Add(new Flat(newFaces)); } ts.RemoveVertices(verticesToRemove); //todo: check if the order of these five commands ts.RemoveFaces(facesToRemove); // matters. There may be an ordering that is more efficient ts.AddFaces(facesToAdd); ts.RemoveEdges(edgesToRemove); ts.AddEdges(edgesToAdd); }
/// <summary> /// Create the Polygonal Faces for a new Tesselated Solid by extruding the given loop along the given normal. /// Setting midPlane to true, extrudes half forward and half reverse. /// </summary> /// <param name="loops"></param> /// <param name="extrudeDirection"></param> /// <param name="distance"></param> /// <param name="midPlane"></param> /// <returns></returns> public static List <PolygonalFace> ReturnFacesFromLoops(IEnumerable <IEnumerable <double[]> > loops, double[] extrudeDirection, double distance, bool midPlane = false) { //This simplifies the cases we have to handle by always extruding in the positive direction if (distance < 0) { distance = -distance; extrudeDirection = extrudeDirection.multiply(-1); } //First, make sure we are using "clean" loops. (e.g. not connected to any faces or edges) var cleanLoops = new List <List <Vertex> >(); var i = 0; foreach (var loop in loops) { var cleanLoop = new List <Vertex>(); foreach (var vertexPosition in loop) { //If a midPlane extrusion, move the original vertices backwards by 1/2 the extrude distance. //These vertices will be used as the base for offsetting the paired vertices forward by the //entire extrude distance. if (midPlane) { var midPlaneVertexPosition = vertexPosition.add(extrudeDirection.multiply(-distance / 2), 3); cleanLoop.Add(new Vertex(midPlaneVertexPosition, i)); } else { cleanLoop.Add(new Vertex(vertexPosition, i)); } i++; } cleanLoops.Add(cleanLoop); } var distanceFromOriginAlongDirection = extrudeDirection.dotProduct(cleanLoops.First().First().Position, 3); //First, triangulate the loops var listOfFaces = new List <PolygonalFace>(); var backTransform = new double[, ] { }; var paths = cleanLoops.Select(loop => MiscFunctions.Get2DProjectionPointsAsLightReorderingIfNecessary(loop.ToArray(), extrudeDirection, out backTransform)).ToList(); List <PointLight[]> points2D; List <Vertex[]> triangles; try { //Reset the list of triangles triangles = new List <Vertex[]>(); //Do some polygon functions to clean up issues and try again //This is important because the Get2DProjections may produce invalid paths and because //triangulate will try 3 times before throwing the exception to go to the catch. paths = PolygonOperations.Union(paths, true, PolygonFillType.EvenOdd); //Since triangulate polygon needs the points to have references to their vertices, we need to add vertex references to each point //This also means we need to recreate cleanLoops //Also, give the vertices indices. cleanLoops = new List <List <Vertex> >(); points2D = new List <PointLight[]>(); var j = 0; foreach (var path in paths) { var pathAsPoints = path.Select(p => new PointLight(p.X, p.Y, true)).ToArray(); var area = new PolygonLight(path).Area; points2D.Add(pathAsPoints); var cleanLoop = new List <Vertex>(); foreach (var point in pathAsPoints) { var position = new[] { point.X, point.Y, 0.0, 1.0 }; var vertexPosition1 = backTransform.multiply(position).Take(3).ToArray(); //The point has been located back to its original position. It is not necessarily the correct distance along the cutting plane normal. //So, we must move it to be on the plane //This next line gets a second vertex to use for the point on plane function var vertexPosition2 = vertexPosition1.add(extrudeDirection.multiply(5), 3); var vertex = MiscFunctions.PointOnPlaneFromIntersectingLine(extrudeDirection, distanceFromOriginAlongDirection, new Vertex(vertexPosition1), new Vertex(vertexPosition2)); vertex.IndexInList = j; point.References.Add(vertex); cleanLoop.Add(vertex); j++; } cleanLoops.Add(cleanLoop); } bool[] isPositive = null; var triangleFaceList = TriangulatePolygon.Run2D(points2D, out _, ref isPositive); foreach (var face in triangleFaceList) { triangles.AddRange(face); } } catch { try { //Reset the list of triangles triangles = new List <Vertex[]>(); //Do some polygon functions to clean up issues and try again paths = PolygonOperations.Union(paths, true, PolygonFillType.EvenOdd); paths = PolygonOperations.OffsetRound(paths, distance / 1000); paths = PolygonOperations.OffsetRound(paths, -distance / 1000); paths = PolygonOperations.Union(paths, true, PolygonFillType.EvenOdd); //Since triangulate polygon needs the points to have references to their vertices, we need to add vertex references to each point //This also means we need to recreate cleanLoops //Also, give the vertices indices. cleanLoops = new List <List <Vertex> >(); points2D = new List <PointLight[]>(); var j = 0; foreach (var path in paths) { var pathAsPoints = path.Select(p => new PointLight(p.X, p.Y, true)).ToArray(); points2D.Add(pathAsPoints); var cleanLoop = new List <Vertex>(); foreach (var point in pathAsPoints) { var position = new[] { point.X, point.Y, 0.0, 1.0 }; var vertexPosition1 = backTransform.multiply(position).Take(3).ToArray(); //The point has been located back to its original position. It is not necessarily the correct distance along the cutting plane normal. //So, we must move it to be on the plane //This next line gets a second vertex to use for the point on plane function var vertexPosition2 = vertexPosition1.add(extrudeDirection.multiply(5), 3); var vertex = MiscFunctions.PointOnPlaneFromIntersectingLine(extrudeDirection, distanceFromOriginAlongDirection, new Vertex(vertexPosition1), new Vertex(vertexPosition2)); vertex.IndexInList = j; point.References.Add(vertex); cleanLoop.Add(vertex); j++; } cleanLoops.Add(cleanLoop); } bool[] isPositive = null; var triangleFaceList = TriangulatePolygon.Run2D(points2D, out _, ref isPositive); foreach (var face in triangleFaceList) { triangles.AddRange(face); } } catch { Debug.WriteLine("Tried extrusion twice and failed."); return(null); } } //Second, build up the a set of duplicate vertices var vertices = new HashSet <Vertex>(); foreach (var vertex in cleanLoops.SelectMany(loop => loop)) { vertices.Add(vertex); } var pairedVertices = new Dictionary <Vertex, Vertex>(); foreach (var vertex in vertices) { var newVertex = new Vertex(vertex.Position.add(extrudeDirection.multiply(distance), 3)); pairedVertices.Add(vertex, newVertex); } //Third, create the triangles on the two ends //var triangleDictionary = new Dictionary<PolygonalFace, PolygonalFace>(); var topFaces = new List <PolygonalFace>(); foreach (var triangle in triangles) { //Create the triangle in plane with the loops var v1 = triangle[1].Position.subtract(triangle[0].Position, 3); var v2 = triangle[2].Position.subtract(triangle[0].Position, 3); //This model reverses the triangle vertex ordering as necessary to line up with the normal. var topTriangle = v1.crossProduct(v2).dotProduct(extrudeDirection.multiply(-1), 3) < 0 ? new PolygonalFace(triangle.Reverse(), extrudeDirection.multiply(-1), true) : new PolygonalFace(triangle, extrudeDirection.multiply(-1), true); topFaces.Add(topTriangle); listOfFaces.Add(topTriangle); //Create the triangle on the opposite side of the extrusion var bottomTriangle = new PolygonalFace( new List <Vertex> { pairedVertices[triangle[0]], pairedVertices[triangle[2]], pairedVertices[triangle[1]] }, extrudeDirection, false); listOfFaces.Add(bottomTriangle); //triangleDictionary.Add(topTriangle, bottomTriangle); } //Fourth, create the triangles on the sides //The normals of the faces are dependent on the whether the loops are ordered correctly from the view of the extrude direction //This influences which order the vertices are used to create triangles. for (var j = 0; j < cleanLoops.Count; j++) { var loop = cleanLoops[j]; //Determine if the loop direction is correct by using the top face var v1 = loop[0]; var v2 = loop[1]; //Find the face with both of these vertices PolygonalFace firstFace = null; foreach (var face in topFaces) { if (face.Vertices[0] == v1 || face.Vertices[1] == v1 || face.Vertices[2] == v1) { if (face.Vertices[0] == v2 || face.Vertices[1] == v2 || face.Vertices[2] == v2) { firstFace = face; break; } } } if (firstFace == null) { throw new Exception("Did not find face with both the vertices"); } if (firstFace.NextVertexCCW(v1) == v2) { //Do nothing } else if (firstFace.NextVertexCCW(v2) == v1) { //Reverse the loop loop.Reverse(); } else { throw new Exception(); } //The loop is now ordered correctly //It does not matter whether the loop is positive or negative, only that it is ordered correctly for the given extrude direction for (var k = 0; k < loop.Count; k++) { var g = k + 1; if (k == loop.Count - 1) { g = 0; } //Create the new triangles listOfFaces.Add(new PolygonalFace(new List <Vertex>() { loop[k], pairedVertices[loop[k]], pairedVertices[loop[g]] })); listOfFaces.Add(new PolygonalFace(new List <Vertex>() { loop[k], pairedVertices[loop[g]], loop[g] })); } } return(listOfFaces); }
/// <summary> /// Returns owned and other face in that order /// </summary> /// <returns></returns> internal static (PolygonalFace, PolygonalFace) GetOwnedAndOtherFace(long edgeChecksum, PolygonalFace face1, PolygonalFace face2) { var(from, to) = GetVertexIndices(edgeChecksum); //We are going to enforce that the edge is defined along the vertices, such that it goes from the smaller //vertex index to the larger. var v0 = face1.Vertices.First(v => v.IndexInList == from); var v1 = face1.Vertices.First(v => v.IndexInList == to); var v2 = face1.Vertices.First(v => v.IndexInList != to && v.IndexInList != from); var vector1 = v1.Position.subtract(v0.Position); var vector2 = v2.Position.subtract(v1.Position); var dot = vector1.crossProduct(vector2).dotProduct(face1.Normal); //The owned face(the face in which the from-to direction makes sense // - that is, produces the proper cross-product normal). return(Math.Sign(dot) > 0 ? (face1, face2) : (face2, face1)); }
private static IEnumerable <List <PointLight> > GetSurfacePaths(List <HashSet <PolygonalFace> > surfaces, double[] normal, double minAreaToConsider, TessellatedSolid originalSolid, Dictionary <int, List <PointLight> > projectedFacePolygons) { originalSolid.HasUniformColor = false; var red = new Color(KnownColors.Red); var allPaths = new List <List <PointLight> >(); foreach (var surface in surfaces) { //Get the surface inner and outer edges var outerEdges = new HashSet <Edge>(); var innerEdges = new HashSet <Edge>(); foreach (var face in surface) { if (face.Edges.Count != 3) { throw new Exception(); } foreach (var edge in face.Edges) { //if (innerEdges.Contains(edge)) continue; if (!outerEdges.Contains(edge)) { outerEdges.Add(edge); } else if (outerEdges.Contains(edge)) { innerEdges.Add(edge); outerEdges.Remove(edge); } else { throw new Exception(); } } } var surfacePaths = new List <List <PointLight> >(); var assignedEdges = new HashSet <Edge>(); while (outerEdges.Any()) { //Get the start vertex and edge and save them to the lists var startEdge = outerEdges.First(); var startVertex = startEdge.From; var loop = new List <Vertex> { startVertex }; var nextVertex = startEdge.To; var edgeLoop = new List <(Edge, Vertex, Vertex)> { (startEdge, startVertex, nextVertex) }; assignedEdges.Add(startEdge); //Initialize the current vertex and edge var vertex = startEdge.From; var currentEdge = startEdge; //Loop until back to the start vertex while (nextVertex.IndexInList != startVertex.IndexInList) { //Get the next edge var nextEdges = nextVertex.Edges.Where(e => !assignedEdges.Contains(e)).Where(e => outerEdges.Contains(e)) .ToList(); if (nextEdges.Count == 0) { Debug.WriteLine("Surface paths do not wrap around properly. Artificially closing loop."); break; } if (nextEdges.Count > 1) { //There are multiple edges to go to next. Simply reversing will cause an issue //if the same thing happens along the other direction. //To avoid this, we go in the direction of the current edge's surface face, until we //hit an edge. var minAngle = 2 * Math.PI; var currentFace = surface.Contains(currentEdge.OtherFace) ? currentEdge.OtherFace : currentEdge.OwnedFace; //currentFace.Color = new Color(KnownColors.White); var otherVertex = currentFace.OtherVertex(currentEdge.To, currentEdge.From); var angle1 = MiscFunctions.ProjectedExteriorAngleBetweenVerticesCCW(vertex, nextVertex, otherVertex, normal); var angle2 = MiscFunctions.ProjectedInteriorAngleBetweenVerticesCCW(vertex, nextVertex, otherVertex, normal); if (angle1 < angle2) { //Use the exterior angle foreach (var edge in nextEdges) { var furtherVertex = edge.OtherVertex(nextVertex); var angle = MiscFunctions.ProjectedExteriorAngleBetweenVerticesCCW(vertex, nextVertex, furtherVertex, normal); if (!(angle < minAngle)) { continue; } minAngle = angle; //Update the current edge currentEdge = edge; } } else { //Use the interior angle foreach (var edge in nextEdges) { var furtherVertex = edge.OtherVertex(nextVertex); var angle = MiscFunctions.ProjectedInteriorAngleBetweenVerticesCCW(vertex, nextVertex, furtherVertex, normal); if (!(angle < minAngle)) { continue; } minAngle = angle; //Update the current edge currentEdge = edge; PolygonalFace faceInQuestion = null; if (surface.Contains(edge.OwnedFace) && surface.Contains(edge.OtherFace)) { //edge.OwnedFace.Color = new Color(KnownColors.Green); //edge.OtherFace.Color = red; //Presenter.ShowVertexPathsWithSolid(new List<List<List<Vertex>>> { error2Loops }, // new List<TessellatedSolid> { originalSolid }); } else if (surface.Contains(edge.OwnedFace)) { faceInQuestion = edge.OtherFace; } else if (surface.Contains(edge.OtherFace)) { faceInQuestion = edge.OwnedFace; } if (faceInQuestion != null) { //faceInQuestion.Color = red; //var n2 = PolygonalFace.DetermineNormal(faceInQuestion.Vertices, out _); //Presenter.ShowAndHang(originalSolid); //Presenter.ShowVertexPathsWithSolid(new List<List<List<Vertex>>> { error2Loops }, // new List<TessellatedSolid> { originalSolid }); } } } foreach (var edge in nextEdges) { if (currentFace.Edges.Contains(edge)) { if (edge == currentEdge) { break; //This is what we want } //Presenter.ShowVertexPathsWithSolid(new List<List<List<Vertex>>> { error2Loops }, // new List<TessellatedSolid> { originalSolid }); } } } else { //Update the current edge currentEdge = nextEdges.First(); } //Update the current vertex vertex = nextVertex; loop.Add(vertex); //Get the next vertex nextVertex = currentEdge.OtherVertex(vertex); edgeLoop.Add((currentEdge, vertex, nextVertex)); assignedEdges.Add(currentEdge); } //To determine order: //The vertices should be listed such that their edge vector cross producted with the second point, //toward a third point on the positive face that provided this edge lines up with the normal. //If that is incorrect, then this may be a hole. //All edges should agree on this test. var correct = 0; var needsReversal = 0; foreach (var edgeTuple in edgeLoop) { var edge = edgeTuple.Item1; outerEdges.Remove(edge); var isOtherFace = surface.Contains(edge.OtherFace); var isOwnedFace = surface.Contains(edge.OwnedFace); if (isOwnedFace == isOtherFace) { throw new Exception("Should be one and only one face for this edge on this surface"); } var positiveFaceBelongingToEdge = isOwnedFace ? edge.OwnedFace : edge.OtherFace; var vertex3 = positiveFaceBelongingToEdge.OtherVertex(edge); var v1 = vertex3.Position.subtract(edgeTuple.Item3.Position, 3); //To point according to our loop var v2 = edgeTuple.Item3.Position.subtract(edgeTuple.Item2.Position, 3); //To minus from var dot = v2.crossProduct(v1).dotProduct(positiveFaceBelongingToEdge.Normal, 3); if (dot > 0) { correct++; } else { needsReversal++; } } if (needsReversal > correct) { loop.Reverse(); } //if(needsReversal*correct != 0) Debug.WriteLine("Reversed Loop Count: " + needsReversal + " Forward Loop Count: " + correct); //Get2DProjections does not project directionally (normal and normal.multiply(-1) return the same transform) //However, the way we are unioning the polygons and eliminating overhand polygons seems to be taking care of this var surfacePath = MiscFunctions.Get2DProjectionPointsAsLight(loop, normal).ToList(); var area2D = MiscFunctions.AreaOfPolygon(surfacePath); if (area2D.IsNegligible(minAreaToConsider)) { continue; } //Trust the ordering from the face normals. A self intersecting polygon may have a negative area, //but in-fact be positive once it undergoes a Fill Positive union. Same goes for positive areas. //if (Math.Sign(area2D) != Math.Sign(area3D)) surfacePath.Reverse(); surfacePaths.Add(surfacePath); } if (!surfacePaths.Any()) { continue; } allPaths.AddRange(surfacePaths); } //By unioning the path into non-self intersecting paths, //partially covered holes will be reduced to their final non-covered size. //This is necessary for the next few checks in determining if it is a hole or an overhang. //This union operation is the trickiest union in the silhouette function to reason through. //Using positive fill or even/odd perform pretty well, but they union overlapping //negative regions. This is undesirable, since we do not want to union a hole //with an overlapping region. For this reason, Union Non-Zero is used. It keeps //the holes in their proper orientation and does not combine them together. var nonSelfIntersectingPaths = PolygonOperations.Union(allPaths, false, PolygonFillType.NonZero); var correctedSurfacePath = EliminateOverhangPolygons(nonSelfIntersectingPaths, projectedFacePolygons); //if (allPaths.Sum(p => p.Count) > 10) Presenter.ShowAndHang(nonSelfIntersectingPaths); return(correctedSurfacePath); }