/// <summary> /// Determines the collinearity between a point and an edge. /// </summary> /// <param name="point">The point.</param> /// <param name="edge">The edge.</param> /// <returns> /// The type of collinearity. /// </returns> internal static Collinearity GetCollinearity(Vector3F point, DcelEdge edge) { Vector3F v0 = edge.Origin.Position; Vector3F v1 = edge.Twin.Origin.Position; Vector3F segment = v1 - v0; float segmentLengthSquared = segment.LengthSquared; // Vector from segment to point. Vector3F v0ToPoint = point - v0; // Compute normal component of v0ToPoint (= v0ToPoint - "v0ToPoint projected on segment"). float v0ToPointDotSegment = Vector3F.Dot(v0ToPoint, segment); Vector3F normalFromLineToPoint = v0ToPoint - v0ToPointDotSegment / segmentLengthSquared * segment; if (normalFromLineToPoint.LengthSquared > Numeric.EpsilonF * (1 + segmentLengthSquared)) { // point is not on line. return Collinearity.NotCollinear; } if (v0ToPointDotSegment < -Numeric.EpsilonF * segmentLengthSquared) return Collinearity.CollinearBefore; if (v0ToPointDotSegment > (1 + Numeric.EpsilonF) * segmentLengthSquared) return Collinearity.CollinearAfter; return Collinearity.CollinearContained; }
/// <summary> /// Determines the collinearity between a point and an edge. /// </summary> /// <param name="point">The point.</param> /// <param name="edge">The edge.</param> /// <returns> /// The type of collinearity. /// </returns> internal static Collinearity GetCollinearity(Vector3F point, DcelEdge edge) { Vector3F v0 = edge.Origin.Position; Vector3F v1 = edge.Twin.Origin.Position; Vector3F segment = v1 - v0; float segmentLengthSquared = segment.LengthSquared; // Vector from segment to point. Vector3F v0ToPoint = point - v0; // Compute normal component of v0ToPoint (= v0ToPoint - "v0ToPoint projected on segment"). float v0ToPointDotSegment = Vector3F.Dot(v0ToPoint, segment); Vector3F normalFromLineToPoint = v0ToPoint - v0ToPointDotSegment / segmentLengthSquared * segment; if (normalFromLineToPoint.LengthSquared > Numeric.EpsilonF * (1 + segmentLengthSquared)) { // point is not on line. return(Collinearity.NotCollinear); } if (v0ToPointDotSegment < -Numeric.EpsilonF * segmentLengthSquared) { return(Collinearity.CollinearBefore); } if (v0ToPointDotSegment > (1 + Numeric.EpsilonF) * segmentLengthSquared) { return(Collinearity.CollinearAfter); } return(Collinearity.CollinearContained); }
/// <summary> /// Adds the given edge to the stack if its <see cref="DcelEdge.Tag"/> is not equal to the given /// tag. /// </summary> /// <param name="edge">The edge. (Can be <see langword="null"/>.)</param> /// <param name="stack">The stack.</param> /// <param name="tag">The tag.</param> private static void AddUntaggedEdgeToStack(DcelEdge edge, Stack <DcelEdge> stack, int tag) { Debug.Assert(stack != null); if (edge != null && edge.Tag != tag) { stack.Push(edge); } }
/// <summary> /// Builds the component lists. /// </summary> /// <param name="edge">The current edge.</param> /// <remarks> /// This method calls itself recursively. /// </remarks> private void BuildLists(DcelEdge edge) { if (edge == null) { return; } Stack <DcelEdge> todoStack = new Stack <DcelEdge>(); todoStack.Push(edge); while (todoStack.Count > 0) { edge = todoStack.Pop(); if (edge.InternalTag == 1) { continue; } // Register edge. edge.InternalTag = 1; _edges.Add(edge); // Register new vertices. if (edge.Origin != null && edge.Origin.InternalTag != 1) { edge.Origin.InternalTag = 1; _vertices.Add(edge.Origin); AddUntaggedEdgeToStackInternal(edge.Origin.Edge, todoStack, 1); } // Register new faces. if (edge.Face != null && edge.Face.InternalTag != 1) { edge.Face.InternalTag = 1; _faces.Add(edge.Face); AddUntaggedEdgeToStackInternal(edge.Face.Boundary, todoStack, 1); if (edge.Face.Holes != null) { for (int i = 0; i < edge.Face.Holes.Count; i++) { AddUntaggedEdgeToStackInternal(edge.Face.Holes[i], todoStack, 1); } } } // Follow neighboring edges. AddUntaggedEdgeToStackInternal(edge.Next, todoStack, 1); AddUntaggedEdgeToStackInternal(edge.Previous, todoStack, 1); AddUntaggedEdgeToStackInternal(edge.Twin, todoStack, 1); } // Reset tags. ResetInternalTags(); }
/// <summary> /// Sets the tags of the DCEL mesh component and linked components to the given tag value. /// </summary> /// <param name="edge">The edge. (Can be <see langword="null"/>.)</param> /// <param name="tag">The tag.</param> /// <remarks> /// This method does nothing if <paramref name="edge"/> is already tagged with the given value. /// </remarks> private static void TagLinkedComponents(DcelEdge edge, int tag) { // Important: This method does not create recursive calls. This could // lead to stack overflows very quickly! if (edge == null || edge.Tag == tag) { return; } Stack <DcelEdge> todoStack = new Stack <DcelEdge>(); todoStack.Push(edge); while (todoStack.Count > 0) { edge = todoStack.Pop(); if (edge.Tag == tag) { continue; } // Tag edge. edge.Tag = tag; // Tag vertex if (edge.Origin != null) { edge.Origin.Tag = 1; } // Tag faces. if (edge.Face != null && edge.Face.Tag != tag) { edge.Face.Tag = tag; AddUntaggedEdgeToStack(edge.Face.Boundary, todoStack, tag); if (edge.Face.Holes != null) { for (int i = 0; i < edge.Face.Holes.Count; i++) { AddUntaggedEdgeToStack(edge.Face.Holes[i], todoStack, tag); } } } // Follow connected edges. AddUntaggedEdgeToStack(edge.Next, todoStack, 1); AddUntaggedEdgeToStack(edge.Previous, todoStack, 1); AddUntaggedEdgeToStack(edge.Twin, todoStack, 1); } }
/// <summary> /// Initializes a new instance of the <see cref="VertexAdjacency"/> class. /// </summary> /// <param name="mesh">The mesh for which the adjacency information is built.</param> /// <exception cref="NotSupportedException"> /// Too many vertices in convex hull. Max. 65534 vertices in convex hull are supported. /// </exception> public VertexAdjacency(DcelMesh mesh) { if (mesh == null) { throw new ArgumentNullException("mesh"); } int numberOfVertices = mesh.Vertices.Count; if (numberOfVertices >= ushort.MaxValue) { throw new NotSupportedException("Too many vertices in convex hull. Max. 65534 vertices in convex hull are supported."); } Debug.Assert(mesh.Vertices.All(v => v.Tag == 0), "Tags of DcelMesh should be cleared."); // Store the index of each vertex in its tag for fast lookup. for (int i = 0; i < numberOfVertices; i++) { mesh.Vertices[i].Tag = i; } ListIndices = new ushort[numberOfVertices + 1]; List <ushort> adjacencyLists = new List <ushort>(numberOfVertices * 4); for (int i = 0; i < numberOfVertices; i++) { DcelVertex vertex = mesh.Vertices[i]; ListIndices[i] = (ushort)adjacencyLists.Count; // Gather all adjacent vertices. DcelEdge startEdge = vertex.Edge; DcelEdge edge = startEdge; do { DcelVertex adjacentVertex = edge.Next.Origin; int adjacentIndex = adjacentVertex.Tag; Debug.Assert(mesh.Vertices[adjacentIndex] == adjacentVertex, "Indices stored in DcelVertex.Tag are invalid."); adjacencyLists.Add((ushort)adjacentIndex); edge = edge.Twin.Next; } while (edge != startEdge); } // Add one additional entry which determines the end of the last adjacency list. ListIndices[numberOfVertices] = (ushort)adjacencyLists.Count; // Copy adjacency lists into static array. Lists = adjacencyLists.ToArray(); }
// Links to half-edges via Twin links if they are on the same edge (same vertices). private static void TryLink(DcelEdge edge0, DcelEdge edge1) { var start0 = edge0.Origin; var end0 = edge0.Next.Origin; var start1 = edge1.Origin; var end1 = edge1.Next.Origin; Debug.Assert(edge0 != edge1 && !(start0 == start1 && end0 == end1), "Two edges are identical."); if (start0 == end1 && end0 == start1) { edge0.Twin = edge1; edge1.Twin = edge0; } }
// edge vertices must have the plane distance in DcelVertex.UserData. This method computes // where the edge is split. private static Vector3F GetCutPosition(DcelEdge edge) { var startVertex = edge.Origin; var startDistance = (float)startVertex.UserData; var endVertex = edge.Twin.Origin; var endDistance = (float)endVertex.UserData; // Get interpolation parameter. var parameter = Math.Abs(startDistance) / (Math.Abs(startDistance) + Math.Abs(endDistance)); // Get position where edge is cut. var cutPosition = startVertex.Position * (1 - parameter) + endVertex.Position * parameter; return(cutPosition); }
/// <summary> /// Determines the collinearity between a point and an edge of a face. /// </summary> /// <param name="point">The point.</param> /// <param name="edge">The edge.</param> /// <param name="faceNormal">The face normal (of the face of the edge).</param> /// <param name="inFront"> /// A value that is positive if the point is in front (outside of the face). /// And the value is proportional to the distance of the point from the edge. /// </param> /// <returns>The collinearity type.</returns> internal static Collinearity GetCollinearity(Vector3F point, DcelEdge edge, Vector3F faceNormal, out float inFront) { Vector3F v0 = edge.Origin.Position; Vector3F v1 = edge.Twin.Origin.Position; Vector3F segment = v1 - v0; float segmentLengthSquared = segment.LengthSquared; // See Geometric Tools for Computer Graphics, p. 736 for explanation in 2D. // Here we do the same in 3D. Our face normal is normalized. Debug.Assert(faceNormal.IsNumericallyNormalized); // The needed normal is computed from the face normal. // The normal lies in the face plane and points away from the face. Vector3F normal = Vector3F.Cross(segment, faceNormal); float normalLengthSquared = normal.LengthSquared; Vector3F v0ToPoint = point - v0; float v0ToPointLengthSquared = v0ToPoint.LengthSquared; float dot = Vector3F.Dot(v0ToPoint, normal); inFront = dot; if (dot * dot > Numeric.EpsilonF * normalLengthSquared * v0ToPointLengthSquared) { if (dot > 0) { return(Collinearity.NotCollinearInFront); // Edge is visible from point. } if (dot < 0) { return(Collinearity.NotCollinearBehind); // Edge is not visible from point. } } dot = Vector3F.Dot(segment, v0ToPoint); if (dot < -Numeric.EpsilonF * segmentLengthSquared) { return(Collinearity.CollinearBefore); } if (dot > (1 + Numeric.EpsilonF) * segmentLengthSquared) { return(Collinearity.CollinearAfter); } return(Collinearity.CollinearContained); }
/// <summary> /// Sets the tags of the DCEL mesh component and linked components to the given tag value. /// </summary> /// <param name="edge">The edge. (Can be <see langword="null"/>.)</param> /// <param name="tag">The tag.</param> /// <remarks> /// This method does nothing if <paramref name="edge"/> is already tagged with the given value. /// </remarks> private static void TagLinkedComponents(DcelEdge edge, int tag) { // Important: This method does not create recursive calls. This could // lead to stack overflows very quickly! if (edge == null || edge.Tag == tag) return; Stack<DcelEdge> todoStack = new Stack<DcelEdge>(); todoStack.Push(edge); while (todoStack.Count > 0) { edge = todoStack.Pop(); if (edge.Tag == tag) continue; // Tag edge. edge.Tag = tag; // Tag vertex if (edge.Origin != null) edge.Origin.Tag = 1; // Tag faces. if (edge.Face != null && edge.Face.Tag != tag) { edge.Face.Tag = tag; AddUntaggedEdgeToStack(edge.Face.Boundary, todoStack, tag); if (edge.Face.Holes != null) for (int i = 0; i < edge.Face.Holes.Count; i++) AddUntaggedEdgeToStack(edge.Face.Holes[i], todoStack, tag); } // Follow connected edges. AddUntaggedEdgeToStack(edge.Next, todoStack, 1); AddUntaggedEdgeToStack(edge.Previous, todoStack, 1); AddUntaggedEdgeToStack(edge.Twin, todoStack, 1); } }
public static DcelMesh FromTriangleMesh(TriangleMesh mesh) { // TODO: To optimize, check tricks of TriangleMeshShape.ComputeNeighbors. if (mesh == null) { throw new ArgumentNullException("mesh"); } if (mesh.Vertices == null || mesh.Indices == null) { throw new ArgumentException("Input mesh has no vertices or vertex indices."); } // Create vertices. int numberOfVertices = mesh.Vertices.Count; var vertices = new List <DcelVertex>(numberOfVertices); foreach (var position in mesh.Vertices) { vertices.Add(new DcelVertex(position, null)); } // Weld similar vertices. for (int i = 0; i < numberOfVertices; i++) { for (int j = i + 1; j < numberOfVertices; j++) { if (Vector3F.AreNumericallyEqual(vertices[i].Position, vertices[j].Position)) { vertices[i] = vertices[j]; } } } // Create edges and faces for each triangle. // We need at least 3 edges for each triangle. We might need more edges if we have to // connect unconnected islands of triangles. var edges = new List <DcelEdge>(mesh.NumberOfTriangles * 3 * 2); var faces = new List <DcelFace>(mesh.NumberOfTriangles); for (int i = 0; i < mesh.NumberOfTriangles; i++) { // Get triangle indices. var index0 = mesh.Indices[i * 3 + 0]; var index1 = mesh.Indices[i * 3 + 1]; var index2 = mesh.Indices[i * 3 + 2]; // Get DCEL vertices. var vertex0 = vertices[index0]; var vertex1 = vertices[index1]; var vertex2 = vertices[index2]; // Create 3 edges. var edge0 = new DcelEdge(); var edge1 = new DcelEdge(); var edge2 = new DcelEdge(); // Create 1 face. var face = new DcelFace(); // Fill out face info. face.Boundary = edge0; // Fill out vertex info. vertex0.Edge = edge0; vertex1.Edge = edge1; vertex2.Edge = edge2; // Fill out edge info. // Twin links are created later. edge0.Face = face; edge0.Origin = vertex0; edge0.Next = edge1; edge0.Previous = edge2; edge1.Face = face; edge1.Origin = vertex1; edge1.Next = edge2; edge1.Previous = edge0; edge2.Face = face; edge2.Origin = vertex2; edge2.Next = edge0; edge2.Previous = edge1; // Add to lists. edges.Add(edge0); edges.Add(edge1); edges.Add(edge2); faces.Add(face); } // Connect triangles that share an edge. for (int i = 0; i < faces.Count; i++) { // Get face and its 3 edges. var faceI = faces[i]; var edgeI0 = faceI.Boundary; var edgeI1 = edgeI0.Next; var edgeI2 = edgeI1.Next; Debug.Assert(edgeI2.Next == edgeI0); for (int j = i + 1; j < faces.Count; j++) { // Get face and its 3 edges. var faceJ = faces[j]; var edgeJ0 = faceJ.Boundary; var edgeJ1 = edgeJ0.Next; var edgeJ2 = edgeJ1.Next; Debug.Assert(edgeJ2.Next == edgeJ0); TryLink(edgeI0, edgeJ0); TryLink(edgeI0, edgeJ1); TryLink(edgeI0, edgeJ2); TryLink(edgeI1, edgeJ0); TryLink(edgeI1, edgeJ1); TryLink(edgeI1, edgeJ2); TryLink(edgeI2, edgeJ0); TryLink(edgeI2, edgeJ1); TryLink(edgeI2, edgeJ2); } } // If the mesh is not closed, we have to add twin edges at the boundaries foreach (var edge in edges.ToArray()) { if (edge.Twin == null) { var twin = new DcelEdge(); twin.Origin = edge.Next.Origin; twin.Twin = edge; edge.Twin = twin; edges.Add(twin); } } // Yet, twin.Next/Previous were not set. foreach (var edge in edges) { if (edge.Previous == null) { // The previous edge has not been set. // Search the edges around the origin until we find the previous unconnected edge. var origin = edge.Origin; var originEdge = edge.Twin.Next; // Another edge with the same origin. while (originEdge.Twin.Next != null) { Debug.Assert(originEdge.Origin == origin); originEdge = originEdge.Twin.Next; } var previous = originEdge.Twin; previous.Next = edge; edge.Previous = previous; } } // Check if we have one connected mesh. if (vertices.Count > 0) { const int Tag = 1; TagLinkedComponents(vertices[0], Tag); // Check if all components were reached. if (vertices.Any(v => v.Tag != Tag) || edges.Any(e => e.Tag != Tag) || faces.Any(f => f.Tag != Tag)) { throw new NotSupportedException("The triangle mesh consists of several unconnected components or sub-meshes."); } } var dcelMesh = new DcelMesh { Vertex = vertices.FirstOrDefault() }; dcelMesh.ResetTags(); return(dcelMesh); }
// Same as AddUntaggedEdgeToStack but with InternalTag private static void AddUntaggedEdgeToStackInternal(DcelEdge edge, Stack<DcelEdge> stack, int internalTag) { Debug.Assert(stack != null); if (edge != null && edge.InternalTag != internalTag) stack.Push(edge); }
// Links to half-edges via Twin links if they are on the same edge (same vertices). private static void TryLink(DcelEdge edge0, DcelEdge edge1) { var start0 = edge0.Origin; var end0 = edge0.Next.Origin; var start1 = edge1.Origin; var end1 = edge1.Next.Origin; Debug.Assert(edge0 != edge1 && !(start0 == start1 && end0 == end1), "Two edges are identical."); if (start0 == end1 && end0 == start1) { edge0.Twin = edge1; edge1.Twin = edge0; } }
internal void MergeCoplanarFaces() { UpdateCache(); foreach (DcelEdge edge in _edges) { if (edge.InternalTag == 1) { // Edge has already been visited. continue; } DcelEdge twin = edge.Twin; DcelFace face0 = edge.Face; DcelFace face1 = twin.Face; if (face0 != null && face1 != null) { // Compare face normals. Vector3F normal0 = face0.Normal; Vector3F normal1 = face1.Normal; float cosα = Vector3F.Dot(normal0, normal1) / (normal0.Length * normal1.Length); if (Numeric.AreEqual(cosα, 1)) { // Faces are coplanar: // --> Merge faces and remove edge. // Get vertices and edges. DcelVertex vertex0 = edge.Origin; // Start vertex of edge. DcelVertex vertex1 = twin.Origin; // End vertex of edge. DcelEdge vertex0Incoming = edge.Previous; DcelEdge vertex0Outgoing = twin.Next; DcelEdge vertex1Incoming = twin.Previous; DcelEdge vertex1Outgoing = edge.Next; // Update vertices. vertex0.Edge = vertex0Outgoing; vertex1.Edge = vertex1Outgoing; // Update edges. vertex0Incoming.Next = vertex0Outgoing; vertex0Outgoing.Previous = vertex0Incoming; vertex1Incoming.Next = vertex1Outgoing; vertex1Outgoing.Previous = vertex1Incoming; // Throw away face1. Make sure that all edges point to face0. DcelEdge current = vertex0Outgoing; do { current.Face = face0; current = current.Next; } while (current != vertex0Outgoing); Dirty = true; } // Mark edges as visited. edge.InternalTag = 1; twin.InternalTag = 1; } } ResetTags(); }
/// <summary> /// Initializes a new instance of the <see cref="DcelVertex"/> class with a given position and /// edge. /// </summary> /// <param name="position">The position.</param> /// <param name="edge">The edge.</param> public DcelVertex(Vector3F position, DcelEdge edge) { Position = position; Edge = edge; }
/// <summary> /// Cuts mesh with a plane. /// </summary> /// <param name="plane">The plane.</param> /// <returns> /// <see langword="true"/> if the mesh was cut; otherwise, <see langword="false"/> if the mesh /// was not modified. /// </returns> /// <remarks> /// <para> /// The mesh is cut with the given plane. If any parts are in front of the plane, they are /// removed. Edges and faces that go through the plane are cut. The resulting mesh is closed /// with a new face in the cut plane. /// </para> /// <para> /// If the whole mesh is in front of the plane, the whole mesh is removed (<see cref="Vertex"/> /// is set to <see langword="null"/>). If the whole mesh is behind the plane, this method does /// nothing. /// </para> /// <para> /// <strong>Prerequisites:</strong> This operation uses <see cref="DcelVertex.UserData"/> of the /// vertices and it assumes that the mesh is valid, convex and closed. All faces must be /// triangles or convex polygons. /// </para> /// </remarks> public bool CutConvex(Plane plane) { // Cuts the mesh with the given plane. The part over the plane is removed. // Prerequisites: // - Mesh is valid, convex and closed. // - Faces are convex polygons. // - Vertex user data is null. // Notes: // - Vertex user data is used. // Get AABB of whole mesh. var aabb = GetAabb(); // Create an epsilon relative to the mesh size. float epsilon = Numeric.EpsilonF * (1 + aabb.Extent.Length); // Store distance d to plane in all vertices. d is negative if vertex is below the plane. float minDistance = Single.PositiveInfinity; float maxDistance = Single.NegativeInfinity; foreach (var vertex in Vertices) { float d = Vector3F.Dot(vertex.Position, plane.Normal) - plane.DistanceFromOrigin; vertex.UserData = d; minDistance = Math.Min(d, minDistance); maxDistance = Math.Max(d, maxDistance); } if (minDistance > -epsilon) { // All vertices are in or above the plane. - The whole mesh is cut away. Vertex = null; Dirty = true; return(true); } if (maxDistance < epsilon) { // All vertices are in or under the plane. - The mesh is not cut. return(false); } // Now, we know that the mesh must be cut. // Find a first edge that starts under/in the plane and must be cut. DcelEdge first = null; foreach (var edge in Edges) { var startDistance = (float)edge.Origin.UserData; var endDistance = (float)edge.Twin.Origin.UserData; if (startDistance < -epsilon && // The edge starts below the plane and the start vertex is definitely not on the plane. endDistance > -epsilon) // The edge ends in or above the plane. { first = edge; break; } } Debug.Assert(first != null, "The mesh is cut by the plane, but could not find an edge that is cut!?"); // The cut will be sealed with this face. DcelFace cap = new DcelFace(); var outEdge = first; // Edge that goes out of the plane and has to be cut. DcelVertex newVertex = new DcelVertex(); newVertex.Position = GetCutPosition(outEdge); newVertex.Edge = first.Twin; do { // Find inEdge (edge that goes into the plane and must be cut). var inEdge = outEdge.Next; while ((float)inEdge.Twin.Origin.UserData > -epsilon) // Loop until the edge end is definitely under the plane. { inEdge = inEdge.Next; } // The face will be cut between outEdge and inEdge. // Two new half edges: var newEdge = new DcelEdge(); // Edge for the cut face. var capEdge = new DcelEdge(); // Twin edge on the cap. // Update outEdge. outEdge.Next = newEdge; // Init newEdge. newEdge.Face = outEdge.Face; newEdge.Origin = newVertex; newEdge.Previous = outEdge; newEdge.Next = inEdge; newEdge.Twin = capEdge; // Find next newVertex on inEdge. if (inEdge.Twin != first) { // Find cut on inEdge. newVertex = new DcelVertex(); newVertex.Position = GetCutPosition(inEdge); newVertex.Edge = inEdge; } else { // We have come full circle. The inEdge is the twin of the first outEdge. newVertex = first.Next.Origin; } // Init capEdge. capEdge.Face = cap; capEdge.Origin = newVertex; capEdge.Twin = newEdge; if (cap.Boundary == null) { cap.Boundary = capEdge; } else { capEdge.Next = outEdge.Twin.Previous.Twin; capEdge.Next.Previous = capEdge; } // Update inEdge. inEdge.Origin = newVertex; inEdge.Previous = newEdge; // Make sure the cut face does not link to a removed edge. outEdge.Face.Boundary = outEdge; // The next outEdge is the twin of the inEdge. outEdge = inEdge.Twin; } while (outEdge != first); // We still have to link the first and the last cap edge. var firstCapEdge = cap.Boundary; var lastCapEdge = first.Twin.Previous.Twin; firstCapEdge.Next = lastCapEdge; lastCapEdge.Previous = firstCapEdge; // Make sure DcelMesh.Vertex is not one of the removed vertices. Vertex = newVertex; Dirty = true; // Clear vertex user data. foreach (var vertex in Vertices) { vertex.UserData = null; } return(true); }
/// <summary> /// Merges a point to a convex hull that is a line segment. /// </summary> /// <param name="point">The point.</param> private void GrowLinearConvex(Vector3F point) { Debug.Assert(_mesh != null); Debug.Assert(_mesh.Vertices.Count == 2, "DCEL mesh with 2 vertices was expected."); Debug.Assert(_type == ConvexHullType.Linear); Debug.Assert(!Vector3F.AreNumericallyEqual(_mesh.Vertex.Position, point)); Debug.Assert(!Vector3F.AreNumericallyEqual(_mesh.Vertices[1].Position, point)); // Abort if the point is equal to an existing point. if (Vector3F.AreNumericallyEqual(point, _mesh.Vertex.Position) || Vector3F.AreNumericallyEqual(point, _mesh.Vertex.Edge.Twin.Origin.Position)) return; var edge = _mesh.Vertex.Edge; var collinearity = DcelMesh.GetCollinearity(point, _mesh.Vertex.Edge); switch (collinearity) { case Collinearity.CollinearBefore: // The point is the new start of the line segment. edge.Origin.Position = point; _mesh.Dirty = true; return; case Collinearity.CollinearAfter: // The point is the new end of the line segment. edge.Twin.Origin.Position = point; _mesh.Dirty = true; return; case Collinearity.CollinearContained: // The point is in the line segment. // Point is in convex hull. return; } Debug.Assert(collinearity == Collinearity.NotCollinear || collinearity == Collinearity.NotCollinearBehind || collinearity == Collinearity.NotCollinearInFront); // Not Collinear --> Make triangle. _type = ConvexHullType.Planar; // Create new components. DcelVertex v0 = _mesh.Vertex; DcelVertex v1 = edge.Twin.Origin; DcelVertex v2 = new DcelVertex(point, null); DcelEdge e01 = edge; DcelEdge e10 = e01.Twin; DcelEdge e12 = new DcelEdge(); DcelEdge e21 = new DcelEdge(); DcelEdge e02 = new DcelEdge(); DcelEdge e20 = new DcelEdge(); DcelFace f012 = new DcelFace(); DcelFace f210 = new DcelFace(); // Link with existing components. e01.Next = e12; e01.Previous = e20; e10.Next = e02; e10.Previous = e21; // Link new components. v2.Edge = e20; e12.Origin = v1; e21.Origin = v2; e20.Origin = v2; e02.Origin = v0; e12.Twin = e21; e21.Twin = e12; e20.Twin = e02; e02.Twin = e20; e12.Previous = e01; e21.Previous = e02; e20.Previous = e12; e02.Previous = e10; e12.Next = e20; e21.Next = e10; e20.Next = e01; e02.Next = e21; // Link face. f012.Boundary = e01; f210.Boundary = e21; e01.Face = e12.Face = e20.Face = f012; e21.Face = e10.Face = e02.Face = f210; // Update _faces. _faces.Add(f012); _faces.Add(f210); _mesh.Dirty = true; Debug.Assert(_mesh.Faces.Count == _faces.Count, "Internal error in ConvexHullBuilder."); Debug.Assert(_mesh.IsTriangleMesh(), "DCEL mesh should be a triangle mesh."); Debug.Assert(_mesh.IsTwoSidedPolygon(), "DCEL mesh should be a two-sided polygon."); }
/// <summary> /// Determines the collinearity between a point and an edge of a face. /// </summary> /// <param name="point">The point.</param> /// <param name="edge">The edge.</param> /// <param name="faceNormal">The face normal (of the face of the edge).</param> /// <param name="inFront"> /// A value that is positive if the point is in front (outside of the face). /// And the value is proportional to the distance of the point from the edge. /// </param> /// <returns>The collinearity type.</returns> internal static Collinearity GetCollinearity(Vector3F point, DcelEdge edge, Vector3F faceNormal, out float inFront) { Vector3F v0 = edge.Origin.Position; Vector3F v1 = edge.Twin.Origin.Position; Vector3F segment = v1 - v0; float segmentLengthSquared = segment.LengthSquared; // See Geometric Tools for Computer Graphics, p. 736 for explanation in 2D. // Here we do the same in 3D. Our face normal is normalized. Debug.Assert(faceNormal.IsNumericallyNormalized); // The needed normal is computed from the face normal. // The normal lies in the face plane and points away from the face. Vector3F normal = Vector3F.Cross(segment, faceNormal); float normalLengthSquared = normal.LengthSquared; Vector3F v0ToPoint = point - v0; float v0ToPointLengthSquared = v0ToPoint.LengthSquared; float dot = Vector3F.Dot(v0ToPoint, normal); inFront = dot; if (dot * dot > Numeric.EpsilonF * normalLengthSquared * v0ToPointLengthSquared) { if (dot > 0) return Collinearity.NotCollinearInFront; // Edge is visible from point. if (dot < 0) return Collinearity.NotCollinearBehind; // Edge is not visible from point. } dot = Vector3F.Dot(segment, v0ToPoint); if (dot < -Numeric.EpsilonF * segmentLengthSquared) return Collinearity.CollinearBefore; if (dot > (1 + Numeric.EpsilonF) * segmentLengthSquared) return Collinearity.CollinearAfter; return Collinearity.CollinearContained; }
/// <summary> /// Creates a mesh for unit cube. /// </summary> /// <returns>The DCEL mesh that represent a unit cube.</returns> /// <remarks> /// The cube is centered at the origin and has 6 faces. The edge length is 2. /// </remarks> public static DcelMesh CreateCube() { #region ----- Vertices ----- var vertex0 = new DcelVertex(new Vector3F(-1, -1, -1), null); var vertex1 = new DcelVertex(new Vector3F( 1, -1, -1), null); var vertex2 = new DcelVertex(new Vector3F(-1, 1, -1), null); var vertex3 = new DcelVertex(new Vector3F( 1, 1, -1), null); var vertex4 = new DcelVertex(new Vector3F(-1, -1, 1), null); var vertex5 = new DcelVertex(new Vector3F( 1, -1, 1), null); var vertex6 = new DcelVertex(new Vector3F(-1, 1, 1), null); var vertex7 = new DcelVertex(new Vector3F( 1, 1, 1), null); #endregion #region ----- Faces ----- var near = new DcelFace(); // +z face var far = new DcelFace(); // -z face var top = new DcelFace(); // +y face var bottom = new DcelFace(); // -y face var left = new DcelFace(); // -x face var right = new DcelFace(); // +x face #endregion #region ----- Edges ----- var edge01 = new DcelEdge { Origin = vertex0, Face = bottom }; var edge10 = new DcelEdge { Origin = vertex1, Face = far }; var edge13 = new DcelEdge { Origin = vertex1, Face = right }; var edge31 = new DcelEdge { Origin = vertex3, Face = far }; var edge23 = new DcelEdge { Origin = vertex2, Face = far }; var edge32 = new DcelEdge { Origin = vertex3, Face = top }; var edge02 = new DcelEdge { Origin = vertex0, Face = far }; var edge20 = new DcelEdge { Origin = vertex2, Face = left }; var edge26 = new DcelEdge { Origin = vertex2, Face = top }; var edge62 = new DcelEdge { Origin = vertex6, Face = left }; var edge37 = new DcelEdge { Origin = vertex3, Face = right }; var edge73 = new DcelEdge { Origin = vertex7, Face = top }; var edge04 = new DcelEdge { Origin = vertex0, Face = left }; var edge40 = new DcelEdge { Origin = vertex4, Face = bottom }; var edge15 = new DcelEdge { Origin = vertex1, Face = bottom }; var edge51 = new DcelEdge { Origin = vertex5, Face = right }; var edge45 = new DcelEdge { Origin = vertex4, Face = near }; var edge54 = new DcelEdge { Origin = vertex5, Face = bottom }; var edge57 = new DcelEdge { Origin = vertex5, Face = near }; var edge75 = new DcelEdge { Origin = vertex7, Face = right }; var edge46 = new DcelEdge { Origin = vertex4, Face = left }; var edge64 = new DcelEdge { Origin = vertex6, Face = near }; var edge67 = new DcelEdge { Origin = vertex6, Face = top }; var edge76 = new DcelEdge { Origin = vertex7, Face = near }; #endregion #region ----- Set DcelVertex.Edge ----- vertex0.Edge = edge01; vertex1.Edge = edge10; vertex2.Edge = edge20; vertex3.Edge = edge31; vertex4.Edge = edge40; vertex5.Edge = edge57; vertex6.Edge = edge67; vertex7.Edge = edge76; #endregion #region ----- Set DcelFace.Boundary ----- near.Boundary = edge57; far.Boundary = edge10; left.Boundary = edge62; right.Boundary = edge51; top.Boundary = edge67; bottom.Boundary = edge01; #endregion #region ----- Set DcelEdge.Twin ----- edge01.Twin = edge10; edge10.Twin = edge01; edge13.Twin = edge31; edge31.Twin = edge13; edge23.Twin = edge32; edge32.Twin = edge23; edge02.Twin = edge20; edge20.Twin = edge02; edge26.Twin = edge62; edge62.Twin = edge26; edge37.Twin = edge73; edge73.Twin = edge37; edge04.Twin = edge40; edge40.Twin = edge04; edge15.Twin = edge51; edge51.Twin = edge15; edge45.Twin = edge54; edge54.Twin = edge45; edge57.Twin = edge75; edge75.Twin = edge57; edge46.Twin = edge64; edge64.Twin = edge46; edge67.Twin = edge76; edge76.Twin = edge67; #endregion #region ----- Set DcelEdge.Next/Previous ----- edge10.Next = edge02; edge02.Next = edge23; edge23.Next = edge31; edge31.Next = edge10; // far edge02.Previous = edge10; edge23.Previous = edge02; edge31.Previous = edge23; edge10.Previous = edge31; edge45.Next = edge57; edge57.Next = edge76; edge76.Next = edge64; edge64.Next = edge45; // near edge57.Previous = edge45; edge76.Previous = edge57; edge64.Previous = edge76; edge45.Previous = edge64; edge62.Next = edge20; edge20.Next = edge04; edge04.Next = edge46; edge46.Next = edge62; // left edge20.Previous = edge62; edge04.Previous = edge20; edge46.Previous = edge04; edge62.Previous = edge46; edge51.Next = edge13; edge13.Next = edge37; edge37.Next = edge75; edge75.Next = edge51; // right edge13.Previous = edge51; edge37.Previous = edge13; edge75.Previous = edge37; edge51.Previous = edge75; edge67.Next = edge73; edge73.Next = edge32; edge32.Next = edge26; edge26.Next = edge67; // top edge73.Previous = edge67; edge32.Previous = edge73; edge26.Previous = edge32; edge67.Previous = edge26; edge01.Next = edge15; edge15.Next = edge54; edge54.Next = edge40; edge40.Next = edge01; // bottom edge15.Previous = edge01; edge54.Previous = edge15; edge40.Previous = edge54; edge01.Previous = edge40; #endregion return new DcelMesh { Vertex = vertex0 }; }
/// <summary> /// Creates a mesh for unit cube. /// </summary> /// <returns>The DCEL mesh that represent a unit cube.</returns> /// <remarks> /// The cube is centered at the origin and has 6 faces. The edge length is 2. /// </remarks> public static DcelMesh CreateCube() { #region ----- Vertices ----- var vertex0 = new DcelVertex(new Vector3F(-1, -1, -1), null); var vertex1 = new DcelVertex(new Vector3F(1, -1, -1), null); var vertex2 = new DcelVertex(new Vector3F(-1, 1, -1), null); var vertex3 = new DcelVertex(new Vector3F(1, 1, -1), null); var vertex4 = new DcelVertex(new Vector3F(-1, -1, 1), null); var vertex5 = new DcelVertex(new Vector3F(1, -1, 1), null); var vertex6 = new DcelVertex(new Vector3F(-1, 1, 1), null); var vertex7 = new DcelVertex(new Vector3F(1, 1, 1), null); #endregion #region ----- Faces ----- var near = new DcelFace(); // +z face var far = new DcelFace(); // -z face var top = new DcelFace(); // +y face var bottom = new DcelFace(); // -y face var left = new DcelFace(); // -x face var right = new DcelFace(); // +x face #endregion #region ----- Edges ----- var edge01 = new DcelEdge { Origin = vertex0, Face = bottom }; var edge10 = new DcelEdge { Origin = vertex1, Face = far }; var edge13 = new DcelEdge { Origin = vertex1, Face = right }; var edge31 = new DcelEdge { Origin = vertex3, Face = far }; var edge23 = new DcelEdge { Origin = vertex2, Face = far }; var edge32 = new DcelEdge { Origin = vertex3, Face = top }; var edge02 = new DcelEdge { Origin = vertex0, Face = far }; var edge20 = new DcelEdge { Origin = vertex2, Face = left }; var edge26 = new DcelEdge { Origin = vertex2, Face = top }; var edge62 = new DcelEdge { Origin = vertex6, Face = left }; var edge37 = new DcelEdge { Origin = vertex3, Face = right }; var edge73 = new DcelEdge { Origin = vertex7, Face = top }; var edge04 = new DcelEdge { Origin = vertex0, Face = left }; var edge40 = new DcelEdge { Origin = vertex4, Face = bottom }; var edge15 = new DcelEdge { Origin = vertex1, Face = bottom }; var edge51 = new DcelEdge { Origin = vertex5, Face = right }; var edge45 = new DcelEdge { Origin = vertex4, Face = near }; var edge54 = new DcelEdge { Origin = vertex5, Face = bottom }; var edge57 = new DcelEdge { Origin = vertex5, Face = near }; var edge75 = new DcelEdge { Origin = vertex7, Face = right }; var edge46 = new DcelEdge { Origin = vertex4, Face = left }; var edge64 = new DcelEdge { Origin = vertex6, Face = near }; var edge67 = new DcelEdge { Origin = vertex6, Face = top }; var edge76 = new DcelEdge { Origin = vertex7, Face = near }; #endregion #region ----- Set DcelVertex.Edge ----- vertex0.Edge = edge01; vertex1.Edge = edge10; vertex2.Edge = edge20; vertex3.Edge = edge31; vertex4.Edge = edge40; vertex5.Edge = edge57; vertex6.Edge = edge67; vertex7.Edge = edge76; #endregion #region ----- Set DcelFace.Boundary ----- near.Boundary = edge57; far.Boundary = edge10; left.Boundary = edge62; right.Boundary = edge51; top.Boundary = edge67; bottom.Boundary = edge01; #endregion #region ----- Set DcelEdge.Twin ----- edge01.Twin = edge10; edge10.Twin = edge01; edge13.Twin = edge31; edge31.Twin = edge13; edge23.Twin = edge32; edge32.Twin = edge23; edge02.Twin = edge20; edge20.Twin = edge02; edge26.Twin = edge62; edge62.Twin = edge26; edge37.Twin = edge73; edge73.Twin = edge37; edge04.Twin = edge40; edge40.Twin = edge04; edge15.Twin = edge51; edge51.Twin = edge15; edge45.Twin = edge54; edge54.Twin = edge45; edge57.Twin = edge75; edge75.Twin = edge57; edge46.Twin = edge64; edge64.Twin = edge46; edge67.Twin = edge76; edge76.Twin = edge67; #endregion #region ----- Set DcelEdge.Next/Previous ----- edge10.Next = edge02; edge02.Next = edge23; edge23.Next = edge31; edge31.Next = edge10; // far edge02.Previous = edge10; edge23.Previous = edge02; edge31.Previous = edge23; edge10.Previous = edge31; edge45.Next = edge57; edge57.Next = edge76; edge76.Next = edge64; edge64.Next = edge45; // near edge57.Previous = edge45; edge76.Previous = edge57; edge64.Previous = edge76; edge45.Previous = edge64; edge62.Next = edge20; edge20.Next = edge04; edge04.Next = edge46; edge46.Next = edge62; // left edge20.Previous = edge62; edge04.Previous = edge20; edge46.Previous = edge04; edge62.Previous = edge46; edge51.Next = edge13; edge13.Next = edge37; edge37.Next = edge75; edge75.Next = edge51; // right edge13.Previous = edge51; edge37.Previous = edge13; edge75.Previous = edge37; edge51.Previous = edge75; edge67.Next = edge73; edge73.Next = edge32; edge32.Next = edge26; edge26.Next = edge67; // top edge73.Previous = edge67; edge32.Previous = edge73; edge26.Previous = edge32; edge67.Previous = edge26; edge01.Next = edge15; edge15.Next = edge54; edge54.Next = edge40; edge40.Next = edge01; // bottom edge15.Previous = edge01; edge54.Previous = edge15; edge40.Previous = edge54; edge01.Previous = edge40; #endregion return(new DcelMesh { Vertex = vertex0 }); }
/// <summary> /// Initializes a new instance of the <see cref="DcelFace"/> class from a given edge. /// </summary> /// <param name="boundary">An edge of the outer boundary.</param> public DcelFace(DcelEdge boundary) { Boundary = boundary; }
/// <summary> /// Initializes a new instance of the <see cref="DcelFace"/> class from a given edge. /// </summary> /// <param name="boundary">An edge of the outer boundary.</param> public DcelFace(DcelEdge boundary) { Boundary = boundary; }
/// <summary> /// Merges a point to a 3D convex hull. /// </summary> /// <param name="point">The point.</param> /// <param name="startFace"> /// A face which is visible from the given point. Can be <see langword="null"/>. /// </param> /// <returns><see langword="true"/> if the convex hull was grown.</returns> private bool GrowSpatialConvex(Vector3F point, DcelFace startFace) { Debug.Assert(_mesh != null); Debug.Assert(_mesh.Vertices.Count > 3, "DCEL mesh with more than 3 vertices expected."); Debug.Assert(_mesh.Faces.Count > 2, "DCEL mesh with more than 2 face expected."); // ----- General convex hull growth. // Find a face which is visible from point. DcelFace visibleFace = null; // TODO: It can happen that startFace is not in _mesh.Faces. // That means, _faces might contain more faces than _mesh.Faces! //Debug.Assert(startFace == null || _mesh.Faces.Contains(startFace), "Internal error in ConvexHullBuilder."); // Test the given face first. It should be visible, but the caller might have // made a different collinearity check which is not robust. if (startFace != null) { float distance; DcelMesh.GetCollinearity(point, startFace, out distance); if (distance > 0) visibleFace = startFace; } // startFace was not robust. Find another visible face. if (visibleFace == null) { int numberOfFaces = _faces.Count; for (int i = 0; i < numberOfFaces; i++) { DcelFace face = _faces[i]; if (face.Tag < 0) // No need to check faces which are on the final hull. continue; float distance; DcelMesh.GetCollinearity(point, face, out distance); if (distance > 0) { visibleFace = face; break; } } } if (visibleFace == null) { // Nothing to do because point is in the hull. return false; } // Check if tags are all <= 0. // Temporarily removed because this occurs with the Sponza model: // Debug.Assert(_mesh.Faces.All(f => f.Tag <= 0), "All tags should be <= 0."); // ----- Find and remove visible faces. // Use of tags: // - Face.Tag = -1 ... face on final hull. (Only set by of Create()). // - Face.Tag = 0 ... default // - Face.Tag = 1 ... face is visible and on stack; this face will be removed from the mesh. // - Edge.Tag = 0 ... default // - Edge.Tag = 1 ... edge is on boundary of visible area, visible area will be replaced // so this edge is on the boundary of a temporary hole. // - Edge.Tag = 2 ... hole edge that was handled _taggedEdges.Clear(); // Push not handled visible faces on a stack. Stack<DcelFace> visibleFaces = new Stack<DcelFace>(); visibleFaces.Push(visibleFace); visibleFace.Tag = 1; // Mark as "on the stack". _faces.Remove(visibleFace); // We store the hole edge where the neighbor triangle faces away most extreme from point. double minInFrontValue = double.PositiveInfinity; DcelEdge startEdge = null; // Search for all visible faces. while (visibleFaces.Count > 0) { visibleFace = visibleFaces.Pop(); // Get the 3 triangle edges of the visible face. DcelEdge[] edges = new DcelEdge[3]; edges[0] = visibleFace.Boundary; edges[1] = edges[0].Next; edges[2] = edges[1].Next; // Add visible neighbors to the stack. for (int i = 0; i < 3; i++) { DcelFace neighbor = edges[i].Twin.Face; // Maybe neighbor was already removed? if (neighbor == null) continue; // Get a measure for how visible the face is (> 0 means visible). float inFrontValue; //var collinearity = DcelMesh.GetCollinearity(point, neighbor, out inFrontValue); // If the point is in front of the face or in the face plane, then we can remove // the face. if (inFrontValue >= 0) // Using >= 0 without epsilon is important for numerical stability! { // Neighbor is visible or collinear. Push to stack (unless it is already on the stack). if (neighbor.Tag != 1) { _faces.Remove(neighbor); visibleFaces.Push(neighbor); neighbor.Tag = 1; } } else { if (minInFrontValue > inFrontValue) { startEdge = edges[i]; minInFrontValue = inFrontValue; } // Neighbor is not visible. This edge is part of the boundary of the hole. // Set tag to 1 to mark the edge. edges[i].Tag = 1; _taggedEdges.Add(edges[i]); } } } // Temporarily removed because this occurs with the Sponza model: // Debug.Assert(startEdge != null); if (startEdge == null) throw new GeometryException("Could not generate convex hull."); // Create a new vertex for the point. DcelVertex newVertex = new DcelVertex(point, null); // Traverse the boundary of the hole beginning at startEdge. DcelEdge currentEdge = startEdge; DcelEdge previousEdge = null; do { // Mark currentEdge as handled. currentEdge.Tag = 2; // Update the vertex-edge link because the link could point to a removed edge. currentEdge.Origin.Edge = currentEdge; // The next edge is part of the hole boundary and has not been handled yet. // Create new face. DcelFace face = new DcelFace(); face.Boundary = currentEdge; currentEdge.Face = face; _faces.Add(face); // Create 2 half-edges. currentEdge.Next = new DcelEdge(); currentEdge.Previous = new DcelEdge(); _taggedEdges.Add(currentEdge.Next); _taggedEdges.Add(currentEdge.Previous); currentEdge.Next.Face = face; currentEdge.Next.Origin = currentEdge.Twin.Origin; currentEdge.Next.Previous = currentEdge; currentEdge.Next.Next = currentEdge.Previous; currentEdge.Next.Tag = 2; currentEdge.Previous.Face = face; currentEdge.Previous.Origin = newVertex; currentEdge.Previous.Previous = currentEdge.Next; currentEdge.Previous.Next = currentEdge; currentEdge.Previous.Tag = 2; // Link new face with previous new face. if (previousEdge != null) { previousEdge.Next.Twin = currentEdge.Previous; currentEdge.Previous.Twin = previousEdge.Next; } // Search for next edge that is marked as a hole boundary edge. previousEdge = currentEdge; currentEdge = currentEdge.Twin.Previous.Twin; int i = 0; while (currentEdge.Tag == 0) { currentEdge = currentEdge.Previous.Twin; i++; if (i == 100) throw new GeometryException("Could not generate convex hull."); } } while (currentEdge.Tag == 1); Debug.Assert(previousEdge != null); Debug.Assert(currentEdge == startEdge); // Link the last new face with the first new face. previousEdge.Next.Twin = currentEdge.Previous; currentEdge.Previous.Twin = previousEdge.Next; // Set the data for the new vertex. newVertex.Edge = startEdge.Previous; // Set mesh.Vertex to a vertex that is part of the hull. _mesh.Vertex = startEdge.Origin; // Reset edge tags. No need to reset face tags because marked faces are not // part of the mesh anymore. int numberOfEdges = _taggedEdges.Count; for (int i = 0; i < numberOfEdges; i++) _taggedEdges[i].Tag = 0; _taggedEdges.Clear(); _mesh.Dirty = true; return true; }
public static DcelMesh FromTriangleMesh(TriangleMesh mesh) { // TODO: To optimize, check tricks of TriangleMeshShape.ComputeNeighbors. if (mesh == null) throw new ArgumentNullException("mesh"); if (mesh.Vertices == null || mesh.Indices == null) throw new ArgumentException("Input mesh has no vertices or vertex indices."); // Create vertices. int numberOfVertices = mesh.Vertices.Count; var vertices = new List<DcelVertex>(numberOfVertices); foreach (var position in mesh.Vertices) vertices.Add(new DcelVertex(position, null)); // Weld similar vertices. for (int i = 0; i < numberOfVertices; i++) for (int j = i + 1; j < numberOfVertices; j++) if (Vector3F.AreNumericallyEqual(vertices[i].Position, vertices[j].Position)) vertices[i] = vertices[j]; // Create edges and faces for each triangle. // We need at least 3 edges for each triangle. We might need more edges if we have to // connect unconnected islands of triangles. var edges = new List<DcelEdge>(mesh.NumberOfTriangles * 3 * 2); var faces = new List<DcelFace>(mesh.NumberOfTriangles); for (int i = 0; i < mesh.NumberOfTriangles; i++) { // Get triangle indices. var index0 = mesh.Indices[i * 3 + 0]; var index1 = mesh.Indices[i * 3 + 1]; var index2 = mesh.Indices[i * 3 + 2]; // Get DCEL vertices. var vertex0 = vertices[index0]; var vertex1 = vertices[index1]; var vertex2 = vertices[index2]; // Create 3 edges. var edge0 = new DcelEdge(); var edge1 = new DcelEdge(); var edge2 = new DcelEdge(); // Create 1 face. var face = new DcelFace(); // Fill out face info. face.Boundary = edge0; // Fill out vertex info. vertex0.Edge = edge0; vertex1.Edge = edge1; vertex2.Edge = edge2; // Fill out edge info. // Twin links are created later. edge0.Face = face; edge0.Origin = vertex0; edge0.Next = edge1; edge0.Previous = edge2; edge1.Face = face; edge1.Origin = vertex1; edge1.Next = edge2; edge1.Previous = edge0; edge2.Face = face; edge2.Origin = vertex2; edge2.Next = edge0; edge2.Previous = edge1; // Add to lists. edges.Add(edge0); edges.Add(edge1); edges.Add(edge2); faces.Add(face); } // Connect triangles that share an edge. for (int i = 0; i < faces.Count; i++) { // Get face and its 3 edges. var faceI = faces[i]; var edgeI0 = faceI.Boundary; var edgeI1 = edgeI0.Next; var edgeI2 = edgeI1.Next; Debug.Assert(edgeI2.Next == edgeI0); for (int j = i + 1; j < faces.Count; j++) { // Get face and its 3 edges. var faceJ = faces[j]; var edgeJ0 = faceJ.Boundary; var edgeJ1 = edgeJ0.Next; var edgeJ2 = edgeJ1.Next; Debug.Assert(edgeJ2.Next == edgeJ0); TryLink(edgeI0, edgeJ0); TryLink(edgeI0, edgeJ1); TryLink(edgeI0, edgeJ2); TryLink(edgeI1, edgeJ0); TryLink(edgeI1, edgeJ1); TryLink(edgeI1, edgeJ2); TryLink(edgeI2, edgeJ0); TryLink(edgeI2, edgeJ1); TryLink(edgeI2, edgeJ2); } } // If the mesh is not closed, we have to add twin edges at the boundaries foreach (var edge in edges.ToArray()) { if (edge.Twin == null) { var twin = new DcelEdge(); twin.Origin = edge.Next.Origin; twin.Twin = edge; edge.Twin = twin; edges.Add(twin); } } // Yet, twin.Next/Previous were not set. foreach (var edge in edges) { if (edge.Previous == null) { // The previous edge has not been set. // Search the edges around the origin until we find the previous unconnected edge. var origin = edge.Origin; var originEdge = edge.Twin.Next; // Another edge with the same origin. while (originEdge.Twin.Next != null) { Debug.Assert(originEdge.Origin == origin); originEdge = originEdge.Twin.Next; } var previous = originEdge.Twin; previous.Next = edge; edge.Previous = previous; } } // Check if we have one connected mesh. if (vertices.Count > 0) { const int Tag = 1; TagLinkedComponents(vertices[0], Tag); // Check if all components were reached. if (vertices.Any(v => v.Tag != Tag) || edges.Any(e => e.Tag != Tag) || faces.Any(f => f.Tag != Tag)) { throw new NotSupportedException("The triangle mesh consists of several unconnected components or sub-meshes."); } } var dcelMesh = new DcelMesh { Vertex = vertices.FirstOrDefault() }; dcelMesh.ResetTags(); return dcelMesh; }
/// <summary> /// Merges a point to a convex hull that is a polygon. /// </summary> /// <param name="point">The point.</param> private void GrowPlanarConvex(Vector3F point) { Debug.Assert(_mesh != null); Debug.Assert(_mesh.Vertices.Count > 2, "DCEL mesh with 3 or more vertices expected."); Debug.Assert(_type == ConvexHullType.Planar, "DCEL mesh with 2 faces expected."); DcelFace frontFace = _mesh.Vertex.Edge.Face; DcelFace backFace = frontFace.Boundary.Twin.Face; // This check: if (!_isPlanar) // is important! Because for planar input points we could not find an initial // tetrahedron. All points are in a plane. But sometimes GetCollinearity detects that // a point is not in the plane and this creates very degenerate spatial convex hulls. // For real spatial input points we have no problem because we have a good initial // tetrahedron. if (!_isPlanar) { var collinearity = DcelMesh.GetCollinearity(point, frontFace); if (collinearity == Collinearity.NotCollinearInFront) { // We build a pyramid where the front face is removed. GrowPlanarConvexToSpatial(frontFace, point); return; } if (collinearity == Collinearity.NotCollinearBehind) { // We build a pyramid where the back face is removed. GrowPlanarConvexToSpatial(backFace, point); return; } } // ----- The point is in the plane. // If we get to here, the initial tetrahedron is flat, so we create a flat hull. _isPlanar = true; // Get normal of front face. Vector3F faceNormal = frontFace.Normal / frontFace.Normal.Length; // Find a visible edge. DcelEdge currentEdge = _mesh.Vertex.Edge; DcelEdge visibleEdge = null; do { float distance; DcelMesh.GetCollinearity(point, currentEdge, faceNormal, out distance); if (distance >= 0) { // Current is visible. visibleEdge = currentEdge; break; } // Get next edge. currentEdge = currentEdge.Next; } while (currentEdge != _mesh.Vertex.Edge); if (visibleEdge == null) { // Point is in the convex hull. return; } // Move the point a bit away to avoid numerical problems. Vector3F v0 = visibleEdge.Origin.Position; Vector3F v1 = visibleEdge.Twin.Origin.Position; Vector3F segment = v1 - v0; Vector3F normal = Vector3F.Cross(segment, faceNormal); point += Numeric.EpsilonF * normal; // Now we need to find the lower vertex and upper vertex between which // the edges will be removed. // Two new edges will be added: (lowerVertex, Point) and (Point, upperVertex). DcelVertex lowerVertex = null; DcelVertex upperVertex = null; // Find upperVertex. currentEdge = visibleEdge.Next; do { float distance; DcelMesh.GetCollinearity(point, currentEdge, faceNormal, out distance); if (distance < 0) { // Edge is not visible. We have found the upper vertex. upperVertex = currentEdge.Origin; break; } currentEdge = currentEdge.Next; } while (currentEdge != visibleEdge.Next); // upperVertex == null should not happen. But numerical problems can always occur. // In that case we do nothing. Debug.Assert(upperVertex != null, "Could not find upperVertex."); if (upperVertex == null) return; // Find lowerVertex currentEdge = visibleEdge.Previous; do { float distance; DcelMesh.GetCollinearity(point, currentEdge, faceNormal, out distance); if (distance < 0) { // Edge is not visible. The end point of the edge is the lower vertex. lowerVertex = currentEdge.Next.Origin; break; } currentEdge = currentEdge.Previous; } while (currentEdge != visibleEdge.Previous); // lowerVertex == null should not happen. But numerical problems can always occur. // In that case we do nothing. Debug.Assert(lowerVertex != null, "Could not find lowerVertex."); if (lowerVertex == null) return; Debug.Assert(lowerVertex != upperVertex, "upperVertex and lowerVertex should not be identical."); #if DEBUG // Get the first visible edge of front face. This edge starts at lowerVertex. var firstVisibleEdge = lowerVertex.Edge; if (firstVisibleEdge.Face != frontFace) firstVisibleEdge = lowerVertex.Edge.Previous.Twin; // Check if all edges between lowerVertex and upperVertex are visible. var edge = firstVisibleEdge; while (edge.Origin != upperVertex) { float distance; DcelMesh.GetCollinearity(point, edge, faceNormal, out distance); Debug.Assert(distance >= 0); edge = edge.Next; } // Check if all edges between upperVertex and lowerVertex are not visible. while (edge.Origin != lowerVertex) { float distance; DcelMesh.GetCollinearity(point, edge, faceNormal, out distance); Debug.Assert(distance < 0); edge = edge.Next; } #endif // Create a new vertex for the point. DcelVertex newVertex = new DcelVertex(point, null); newVertex.Edge = new DcelEdge(); // Add two new edges between lowerVertex and upperVertex. DcelEdge lowerEdge = new DcelEdge(); DcelEdge lowerEdgeTwin = new DcelEdge(); DcelEdge upperEdge = newVertex.Edge; DcelEdge upperEdgeTwin = new DcelEdge(); lowerEdge.Origin = lowerVertex; lowerEdge.Face = frontFace; lowerEdge.Next = upperEdge; lowerEdge.Twin = lowerEdgeTwin; lowerEdgeTwin.Origin = newVertex; lowerEdgeTwin.Face = backFace; lowerEdgeTwin.Previous = upperEdgeTwin; lowerEdgeTwin.Twin = lowerEdge; upperEdge.Origin = newVertex; upperEdge.Face = frontFace; upperEdge.Previous = lowerEdge; upperEdge.Twin = upperEdgeTwin; upperEdgeTwin.Origin = upperVertex; upperEdgeTwin.Face = backFace; upperEdgeTwin.Next = lowerEdgeTwin; upperEdgeTwin.Twin = upperEdge; lowerEdge.Previous = (lowerVertex.Edge.Face == frontFace) ? lowerVertex.Edge.Previous : lowerVertex.Edge.Twin; upperEdge.Next = (upperVertex.Edge.Face == frontFace) ? upperVertex.Edge : upperVertex.Edge.Twin.Next; lowerEdgeTwin.Next = lowerEdge.Previous.Twin; upperEdgeTwin.Previous = upperEdge.Next.Twin; // Correct faces. frontFace.Boundary = currentEdge; // The last currentEdge was not visible, so it will stay. backFace.Boundary = currentEdge.Twin; // Correct lower and upper vertex. lowerVertex.Edge = lowerEdge; upperVertex.Edge = upperEdge.Next; // Correct edges before lowerEdge and after upperEdge. lowerEdge.Previous.Next = lowerEdge; lowerEdgeTwin.Next.Previous = lowerEdgeTwin; upperEdge.Next.Previous = upperEdge; upperEdgeTwin.Previous.Next = upperEdgeTwin; // _mesh.Vertex could be one of the removed vertices. --> Set a vertex that is in the hull. _mesh.Vertex = lowerVertex; _mesh.Dirty = true; Debug.Assert(_mesh.IsTwoSidedPolygon(), "DCEL mesh should be a two-sided polygon."); }
/// <summary> /// Initializes a new instance of the <see cref="DcelVertex"/> class with a given position and /// edge. /// </summary> /// <param name="position">The position.</param> /// <param name="edge">The edge.</param> public DcelVertex(Vector3F position, DcelEdge edge) { Position = position; Edge = edge; }
// edge vertices must have the plane distance in DcelVertex.UserData. This method computes // where the edge is split. private static Vector3F GetCutPosition(DcelEdge edge) { var startVertex = edge.Origin; var startDistance = (float)startVertex.UserData; var endVertex = edge.Twin.Origin; var endDistance = (float)endVertex.UserData; // Get interpolation parameter. var parameter = Math.Abs(startDistance) / (Math.Abs(startDistance) + Math.Abs(endDistance)); // Get position where edge is cut. var cutPosition = startVertex.Position * (1 - parameter) + endVertex.Position * parameter; return cutPosition; }
/// <summary> /// Cuts mesh with a plane. /// </summary> /// <param name="plane">The plane.</param> /// <returns> /// <see langword="true"/> if the mesh was cut; otherwise, <see langword="false"/> if the mesh /// was not modified. /// </returns> /// <remarks> /// <para> /// The mesh is cut with the given plane. If any parts are in front of the plane, they are /// removed. Edges and faces that go through the plane are cut. The resulting mesh is closed /// with a new face in the cut plane. /// </para> /// <para> /// If the whole mesh is in front of the plane, the whole mesh is removed (<see cref="Vertex"/> /// is set to <see langword="null"/>). If the whole mesh is behind the plane, this method does /// nothing. /// </para> /// <para> /// <strong>Prerequisites:</strong> This operation uses <see cref="DcelVertex.UserData"/> of the /// vertices and it assumes that the mesh is valid, convex and closed. All faces must be /// triangles or convex polygons. /// </para> /// </remarks> public bool CutConvex(Plane plane) { // Cuts the mesh with the given plane. The part over the plane is removed. // Prerequisites: // - Mesh is valid, convex and closed. // - Faces are convex polygons. // - Vertex user data is null. // Notes: // - Vertex user data is used. // Get AABB of whole mesh. var aabb = GetAabb(); // Create an epsilon relative to the mesh size. float epsilon = Numeric.EpsilonF * (1 + aabb.Extent.Length); // Store distance d to plane in all vertices. d is negative if vertex is below the plane. float minDistance = Single.PositiveInfinity; float maxDistance = Single.NegativeInfinity; foreach (var vertex in Vertices) { float d = Vector3F.Dot(vertex.Position, plane.Normal) - plane.DistanceFromOrigin; vertex.UserData = d; minDistance = Math.Min(d, minDistance); maxDistance = Math.Max(d, maxDistance); } if (minDistance > -epsilon) { // All vertices are in or above the plane. - The whole mesh is cut away. Vertex = null; Dirty = true; return true; } if (maxDistance < epsilon) { // All vertices are in or under the plane. - The mesh is not cut. return false; } // Now, we know that the mesh must be cut. // Find a first edge that starts under/in the plane and must be cut. DcelEdge first = null; foreach (var edge in Edges) { var startDistance = (float)edge.Origin.UserData; var endDistance = (float)edge.Twin.Origin.UserData; if (startDistance < -epsilon // The edge starts below the plane and the start vertex is definitely not on the plane. && endDistance > -epsilon) // The edge ends in or above the plane. { first = edge; break; } } Debug.Assert(first != null, "The mesh is cut by the plane, but could not find an edge that is cut!?"); // The cut will be sealed with this face. DcelFace cap = new DcelFace(); var outEdge = first; // Edge that goes out of the plane and has to be cut. DcelVertex newVertex = new DcelVertex(); newVertex.Position = GetCutPosition(outEdge); newVertex.Edge = first.Twin; do { // Find inEdge (edge that goes into the plane and must be cut). var inEdge = outEdge.Next; while ((float)inEdge.Twin.Origin.UserData > -epsilon) // Loop until the edge end is definitely under the plane. inEdge = inEdge.Next; // The face will be cut between outEdge and inEdge. // Two new half edges: var newEdge = new DcelEdge(); // Edge for the cut face. var capEdge = new DcelEdge(); // Twin edge on the cap. // Update outEdge. outEdge.Next = newEdge; // Init newEdge. newEdge.Face = outEdge.Face; newEdge.Origin = newVertex; newEdge.Previous = outEdge; newEdge.Next = inEdge; newEdge.Twin = capEdge; // Find next newVertex on inEdge. if (inEdge.Twin != first) { // Find cut on inEdge. newVertex = new DcelVertex(); newVertex.Position = GetCutPosition(inEdge); newVertex.Edge = inEdge; } else { // We have come full circle. The inEdge is the twin of the first outEdge. newVertex = first.Next.Origin; } // Init capEdge. capEdge.Face = cap; capEdge.Origin = newVertex; capEdge.Twin = newEdge; if (cap.Boundary == null) { cap.Boundary = capEdge; } else { capEdge.Next = outEdge.Twin.Previous.Twin; capEdge.Next.Previous = capEdge; } // Update inEdge. inEdge.Origin = newVertex; inEdge.Previous = newEdge; // Make sure the cut face does not link to a removed edge. outEdge.Face.Boundary = outEdge; // The next outEdge is the twin of the inEdge. outEdge = inEdge.Twin; } while (outEdge != first); // We still have to link the first and the last cap edge. var firstCapEdge = cap.Boundary; var lastCapEdge = first.Twin.Previous.Twin; firstCapEdge.Next = lastCapEdge; lastCapEdge.Previous = firstCapEdge; // Make sure DcelMesh.Vertex is not one of the removed vertices. Vertex = newVertex; Dirty = true; // Clear vertex user data. foreach (var vertex in Vertices) vertex.UserData = null; return true; }
/// <summary> /// Merges a point to a convex hull that is also a point. /// </summary> /// <param name="point">The point.</param> private void GrowPointConvex(Vector3F point) { Debug.Assert(_mesh != null); Debug.Assert(_mesh.Vertices.Count == 1, "DCEL mesh with 1 vertex was expected."); Debug.Assert(_type == ConvexHullType.Point); // Abort if the point is equal to the existing point in the mesh. if (Vector3F.AreNumericallyEqual(_mesh.Vertex.Position, point)) return; DcelVertex newVertex = new DcelVertex(point, null); DcelEdge edge = new DcelEdge(); DcelEdge twin = new DcelEdge(); // Link everything. edge.Origin = _mesh.Vertex; twin.Origin = newVertex; edge.Twin = twin; twin.Twin = edge; _mesh.Vertex.Edge = edge; newVertex.Edge = twin; _mesh.Dirty = true; _type = ConvexHullType.Linear; }