/// <inheritdoc/> public EdgeWrap GetEdgeWrap(int faceIndex, int neighborIndex) { if (faceIndex < 0 || faceIndex >= _internalFaceCount) { throw new ArgumentOutOfRangeException("faceIndex"); } if (!axis0.isWrapped && !axis1.isWrapped) { return(EdgeWrap.None); } if (!_isInverted) { switch (neighborIndex) { case 0: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSidePosAxis0(faceIndex) ? EdgeWrap.NegVertToEdgeAxis0 : EdgeWrap.None) | (WrapsOnSideNegAxis1(faceIndex) ? EdgeWrap.NegEdgeToFaceAxis1 : EdgeWrap.None))); case 1: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSideNegAxis0(faceIndex) ? EdgeWrap.NegEdgeToFaceAxis0 : EdgeWrap.None) | (WrapsOnSidePosAxis1(faceIndex) ? EdgeWrap.PosEdgeToVertAxis1 : EdgeWrap.None))); case 2: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSidePosAxis0(faceIndex) ? EdgeWrap.PosEdgeToVertAxis0 : EdgeWrap.None) | (WrapsOnSidePosAxis1(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis1 : EdgeWrap.None))); case 3: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSidePosAxis0(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis0 : EdgeWrap.None) | (WrapsOnSidePosAxis1(faceIndex) ? EdgeWrap.NegVertToEdgeAxis1 : EdgeWrap.None))); default: throw new ArgumentOutOfRangeException("neighborIndex"); } } else { switch (neighborIndex) { case 0: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSidePosAxis0(faceIndex) ? EdgeWrap.PosEdgeToVertAxis0 : EdgeWrap.None) | (WrapsOnSideNegAxis1(faceIndex) ? EdgeWrap.NegEdgeToFaceAxis1 : EdgeWrap.None))); case 1: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSideNegAxis0(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis0 : EdgeWrap.None) | (WrapsOnSidePosAxis1(faceIndex) ? EdgeWrap.PosEdgeToVertAxis1 : EdgeWrap.None))); case 2: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSidePosAxis0(faceIndex) ? EdgeWrap.NegVertToEdgeAxis0 : EdgeWrap.None) | (WrapsOnSidePosAxis1(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis1 : EdgeWrap.None))); case 3: return(EdgeWrapUtility.FromEdgeRelations( (WrapsOnSidePosAxis0(faceIndex) ? EdgeWrap.NegEdgeToFaceAxis0 : EdgeWrap.None) | (WrapsOnSidePosAxis1(faceIndex) ? EdgeWrap.NegVertToEdgeAxis1 : EdgeWrap.None))); default: throw new ArgumentOutOfRangeException("neighborIndex"); } } }
// Pivot an edge clockwise around its implicit near vertex. // // \ / \ / // o-------o o-------o // | | | | // 2 N | 2 N | // | | | | // B V --> B<--E---V // / \ / \ / \ \ // 1 E \ 1 \ // \ v \ / \ \ / // A o A o // | P | | P | // 0 | 0 | // | | | | // o-------o o-------o // / \ / \ // // E: edge passed in as parameter // V: vertex to pivot E around clockwise // A: old vertex that E is originally pointing at // B: new vertex that E will now point at // P: previous face on the counter-clockwise side of E // N: next face on the clockwise side of E // 0: outer edge 0 points at A, inner edge 0 points away from A // 1: outer edge 1 points at B, inner edge 1 points at A, at N before pivot, at P after pivot // 2: outer edge 2 points away from B // Note: inner edges point downward, outer edges point upward private void PivotVertexEdgeForwardUnchecked(int edgeIndex, int twinEdgeIndex, int outerEdgeIndex1, int innerEdgeIndex1) { var innerEdgeIndex0 = edgeData[twinEdgeIndex].vNext; var outerEdgeIndex0 = edgeData[innerEdgeIndex0].twin; var outerEdgeIndex2 = edgeData[outerEdgeIndex1].fNext; var prevFaceIndex = edgeData[edgeIndex].face; var nextFaceIndex = edgeData[twinEdgeIndex].face; var oldVertexIndex = edgeData[edgeIndex].vertex; var newVertexIndex = edgeData[outerEdgeIndex1].vertex; // The edge was pointing at the old vertex, will now point at the new vertex. edgeData[edgeIndex].vertex = newVertexIndex; // The second inner edge was pointing at the next face, will now point at the previous face. edgeData[innerEdgeIndex1].face = prevFaceIndex; // Remove twin edge from old vertex linked list by skipping over it. edgeData[outerEdgeIndex1].vNext = innerEdgeIndex0; // Insert twin edge into new vertex linked list. edgeData[outerEdgeIndex2].vNext = twinEdgeIndex; edgeData[twinEdgeIndex].vNext = innerEdgeIndex1; // Remove second outer edge from next face linked list by skipping over it. edgeData[edgeIndex].fNext = outerEdgeIndex2; // Insert second outer edge into the previous face linked list. edgeData[outerEdgeIndex0].fNext = outerEdgeIndex1; edgeData[outerEdgeIndex1].fNext = twinEdgeIndex; // Reroot the vertex and face that just lost edges with edges guaranteed to still belong. vertexFirstEdgeIndices[oldVertexIndex] = outerEdgeIndex1; faceFirstEdgeIndices[nextFaceIndex] = edgeIndex; // Adjust neighbor counts. vertexNeighborCounts[oldVertexIndex] -= 1; vertexNeighborCounts[newVertexIndex] += 1; faceNeighborCounts[nextFaceIndex] -= 1; // Dropping below 0 is undefined behavior; it better not ever happen. faceNeighborCounts[prevFaceIndex] += 1; // Surpassing 32767 is undefined behavior; it better not ever happen. // Adjust edge wrap information, coallescing multiple edges together as appropriate. edgeData[edgeIndex].wrap = EdgeWrapUtility.ModifyTargetVertEdgeRelations(edgeData[edgeIndex].wrap, edgeData[outerEdgeIndex1].wrap); // Edge's target vertex changed, according to Inner Edge 1. edgeData[twinEdgeIndex].wrap = EdgeWrapUtility.ModifySourceVertEdgeRelations(edgeData[twinEdgeIndex].wrap, edgeData[innerEdgeIndex1].wrap); // Twin Edge's source vertex changed, according to Outer Edge 1. edgeData[innerEdgeIndex1].wrap = EdgeWrapUtility.ModifyTargetFaceEdgeRelations(edgeData[innerEdgeIndex1].wrap, edgeData[edgeIndex].wrap); // Inner Edge 1's target face changed, according to Twin Edge. edgeData[outerEdgeIndex1].wrap = EdgeWrapUtility.ModifySourceFaceEdgeRelations(edgeData[outerEdgeIndex1].wrap, edgeData[twinEdgeIndex].wrap); // Outer Edge 1's source face changed, according to Edge. }
/// <summary> /// Build a full topology data structure from the minimal data described by a face neighbor indexer. /// </summary> /// <param name="indexer">A minimal description of the faces and their neighbors constituting a topology.</param> /// <returns>A fully constructed topology matching the description of the provided face neighbor indexer.</returns> /// <remarks> /// <para>For the edge wrap data returned by the face neighbor indexer, only the vertex-to-edge, edge-to-vertex, /// and face-to-edge data needs to be supplied. If there are no external faces, then the edge-to-vertex data is /// also unnecessary.</para> /// </remarks> public static Topology BuildTopology(IFaceNeighborIndexer indexer) { var vertexNeighborCounts = new ushort[indexer.vertexCount]; var vertexFirstEdgeIndices = new int[indexer.vertexCount]; var edgeData = new Topology.EdgeData[indexer.edgeCount]; var faceNeighborCounts = new ushort[indexer.faceCount]; var faceFirstEdgeIndices = new int[indexer.faceCount]; // Initialize the face roots and neighbor counts, and the next vertex, fNext, vNext, and wrap // fields of the internal edges. The vNext fields will result in linked lists that contain the // correct members (aside from external edges), but in an unspecified order. int edgeIndex = 0; for (int faceIndex = 0; faceIndex < indexer.internalFaceCount; ++faceIndex) { var neighborCount = indexer.GetNeighborCount(faceIndex); faceNeighborCounts[faceIndex] = neighborCount; faceFirstEdgeIndices[faceIndex] = edgeIndex; var priorVertexIndex = indexer.GetNeighborVertexIndex(faceIndex, neighborCount - 1); for (int neighborIndex = 0; neighborIndex < neighborCount; ++neighborIndex) { var vertexIndex = indexer.GetNeighborVertexIndex(faceIndex, neighborIndex); edgeData[edgeIndex].vertex = vertexIndex; edgeData[edgeIndex].fNext = edgeIndex + 1; edgeData[edgeIndex].face = -1; edgeData[edgeIndex].twin = -1; edgeData[edgeIndex].wrap = indexer.GetEdgeWrap(faceIndex, neighborIndex); AddEdgeToVertexUnordered(edgeIndex, priorVertexIndex, vertexNeighborCounts, vertexFirstEdgeIndices, edgeData); priorVertexIndex = vertexIndex; ++edgeIndex; } // Correct the face's last edge to refer back to the face's first edge. edgeData[edgeIndex - 1].fNext = edgeIndex - neighborCount; } var internalEdgeCount = edgeIndex; // Use the partial information constructed in the prior loop to determine edge twins, and set // the vertex, fNext, vNext, and wrap fields for external edges too. edgeIndex = 0; var externalEdgeIndex = internalEdgeCount; for (int faceIndex = 0; faceIndex < indexer.internalFaceCount; ++faceIndex) { var neighborCount = indexer.GetNeighborCount(faceIndex); var priorVertexIndex = indexer.GetNeighborVertexIndex(faceIndex, neighborCount - 1); for (int neighborIndex = 0; neighborIndex < neighborCount; ++neighborIndex) { var vertexIndex = indexer.GetNeighborVertexIndex(faceIndex, neighborIndex); if (edgeData[edgeIndex].twin == -1) { // The current edge is pointing at the current vertex index. Its twin will be one // of the edges pointing out from the current vertex, and will be pointing at the // prior vertex. Search for this edge. var vertexFirstEdgeIndex = vertexFirstEdgeIndices[vertexIndex]; var vertexEdgeIndex = vertexFirstEdgeIndex; while (edgeData[vertexEdgeIndex].vertex != priorVertexIndex) { vertexEdgeIndex = edgeData[vertexEdgeIndex].vNext; if (vertexEdgeIndex == vertexFirstEdgeIndex) { // This edge's twin is an external edge which needs to be initialized. edgeData[externalEdgeIndex].vertex = priorVertexIndex; edgeData[externalEdgeIndex].fNext = -1; // Cannot be determined until the vNext linked lists are in the correct order. edgeData[externalEdgeIndex].face = -1; edgeData[externalEdgeIndex].wrap = EdgeWrapUtility.Invert(edgeData[edgeIndex].wrap); AddEdgeToVertexUnordered(externalEdgeIndex, vertexIndex, vertexNeighborCounts, vertexFirstEdgeIndices, edgeData); vertexEdgeIndex = externalEdgeIndex; ++externalEdgeIndex; break; } } edgeData[vertexEdgeIndex].twin = edgeIndex; edgeData[edgeIndex].twin = vertexEdgeIndex; } ++edgeIndex; priorVertexIndex = vertexIndex; } } // Correct the order of the vNext linked lists, and set the face indices of the twins of all internal edges. edgeIndex = 0; for (int faceIndex = 0; faceIndex < indexer.internalFaceCount; ++faceIndex) { var neighborCount = indexer.GetNeighborCount(faceIndex); var priorEdgeIndex = edgeIndex + neighborCount - 1; for (int neighborIndex = 0; neighborIndex < neighborCount; ++neighborIndex) { var twinEdgeIndex = edgeData[priorEdgeIndex].twin; PutEdgesInSequence(edgeIndex, twinEdgeIndex, edgeData); edgeData[twinEdgeIndex].face = faceIndex; priorEdgeIndex = edgeIndex; ++edgeIndex; } } if (indexer.externalFaceCount > 0) { // Now that all vertex and edge relationships are established, locate all edges that don't have a // face relationship specified (those which are pointing out toward external faces) and spin around // each face to establish those edge -> face relationships and count external face neighbors. var faceIndex = indexer.internalFaceCount; for (edgeIndex = 0; edgeIndex < indexer.edgeCount; ++edgeIndex) { if (edgeData[edgeIndex].face == -1) { // Starting with the current edge, follow the edge links appropriately to wind around the // implicit face counter-clockwise and link the edges to the face. var neighborCount = 0; var twinEdgeIndex = edgeIndex; var faceEdgeIndex = edgeData[edgeIndex].twin; var faceFirstEdgeIndex = faceEdgeIndex; do { edgeData[twinEdgeIndex].face = faceIndex; twinEdgeIndex = edgeData[faceEdgeIndex].vNext; var nextFaceEdgeIndex = edgeData[twinEdgeIndex].twin; edgeData[nextFaceEdgeIndex].fNext = faceEdgeIndex; faceEdgeIndex = nextFaceEdgeIndex; ++neighborCount; if (neighborCount > vertexFirstEdgeIndices.Length) { throw new InvalidOperationException("Face neighbors were specified such that an external face was misconfigured."); } } while (twinEdgeIndex != edgeIndex); faceNeighborCounts[faceIndex] = (ushort)neighborCount; faceFirstEdgeIndices[faceIndex] = faceFirstEdgeIndex; ++faceIndex; } } } // Fill out the remainder of the edge wrap data, based on the possibly limited info supplied. for (edgeIndex = 0; edgeIndex < indexer.edgeCount; ++edgeIndex) { var twinIndex = edgeData[edgeIndex].twin; if (edgeIndex < twinIndex) { EdgeWrapUtility.CrossMergeTwins(ref edgeData[edgeIndex].wrap, ref edgeData[twinIndex].wrap); } } return(Topology.Create(vertexNeighborCounts, vertexFirstEdgeIndices, edgeData, faceNeighborCounts, faceFirstEdgeIndices, indexer.internalFaceCount)); }
/// <inheritdoc/> public override Vector3 ReverseOffsetFaceToFaceAttribute(Vector3 position, EdgeWrap edgeWrap) { return(ReverseOffsetAttribute(position, EdgeWrapUtility.FaceToFaceAsGeneric(edgeWrap))); }
/// <inheritdoc/> public override Vector3 ReverseOffsetVertToVertAttribute(Vector3 position, EdgeWrap edgeWrap) { return(ReverseOffsetAttribute(position, EdgeWrapUtility.VertToVertAsGeneric(edgeWrap))); }
/// <inheritdoc/> public override Vector3 OffsetFaceToVertAttribute(Vector3 position, EdgeWrap edgeWrap) { return(OffsetAttribute(position, EdgeWrapUtility.FaceToVertAsGeneric(edgeWrap))); }
/// <summary> /// Exports the given manifold to SVG format with the given style, flattening it onto a plane if necessary. /// </summary> /// <param name="writer">The writer to which the SVG-formatted manifold will be written.</param> /// <param name="surface">The surface describing the manifold.</param> /// <param name="topology">The topology of the manifold.</param> /// <param name="orientation">The orientation of the plane to which the manifold will be flattened.</param> /// <param name="scale">The scale which will be applied to the manifold while writing out SVG positions.</param> /// <param name="vertexPositions">The vertex positions of the manifold.</param> /// <param name="numericFormat">The format specifier to use for each number written to <paramref name="writer"/>.</param> /// <param name="style">The style details to apply to the SVG content.</param> public static void ExportToSVG(System.IO.TextWriter writer, ISurface surface, Topology topology, Quaternion orientation, Vector3 scale, IVertexAttribute <Vector3> vertexPositions, string numericFormat, SVGStyle style) { var inverseOrientation = Quaternion.Inverse(orientation); var plane = new Plane(orientation * Vector3.back, 0f); var transform = Matrix4x4.TRS(Vector3.zero, inverseOrientation, scale); Vector2 transformedVertexDotRadius = new Vector2(style.vertexCircleRadius * transform.m00, style.vertexCircleRadius * transform.m11); Func <Vector3, Vector2> flatten = (Vector3 point) => transform *plane.ClosestPoint(point); Vector2 min = new Vector2(float.PositiveInfinity, float.PositiveInfinity); Vector2 max = new Vector2(float.NegativeInfinity, float.NegativeInfinity); foreach (var face in topology.faces) { foreach (var edge in face.edges) { var p = flatten(vertexPositions[edge]); min = Geometry.AxisAlignedMin(min, p); max = Geometry.AxisAlignedMax(max, p); } } var range = max - min; flatten = (Vector3 point) => { Vector2 p = transform * plane.ClosestPoint(point); p.y = max.y - p.y + min.y; return(p); }; writer.WriteLine("<svg viewBox=\"{0} {1} {2} {3}\" xmlns=\"http://www.w3.org/2000/svg\">", (min.x - style.padding.x).ToString(numericFormat), (min.y - style.padding.y).ToString(numericFormat), (range.x + style.padding.x * 2f).ToString(numericFormat), (range.y + style.padding.y * 2f).ToString(numericFormat)); writer.WriteLine("\t<style>"); writer.WriteLine("\t\tsvg"); writer.WriteLine("\t\t{"); if (!style.svg.ContainsKey("width")) { writer.WriteLine("\t\t\twidth: 100%;"); } if (!style.svg.ContainsKey("height")) { writer.WriteLine("\t\t\theight: 100%;"); } foreach (var keyValue in style.svg) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.index"); writer.WriteLine("\t\t{"); foreach (var keyValue in style.index) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\tellipse.vertex"); writer.WriteLine("\t\t{"); if (!style.vertexCircle.ContainsKey("stroke")) { writer.WriteLine("\t\t\tstroke: black;"); } if (!style.vertexCircle.ContainsKey("stroke-width")) { writer.WriteLine("\t\t\tstroke-width: 1;"); } if (!style.vertexCircle.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: none;"); } foreach (var keyValue in style.vertexCircle) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.vertex.index"); writer.WriteLine("\t\t{"); if (!style.vertexIndex.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: black;"); } if (!style.vertexIndex.ContainsKey("font-size")) { writer.WriteLine("\t\t\tfont-size: {0:F0}px;", transformedVertexDotRadius.x); } if (!style.vertexIndex.ContainsKey("text-anchor")) { writer.WriteLine("\t\t\ttext-anchor: middle;"); } if (!style.vertexIndex.ContainsKey("dominant-baseline")) { writer.WriteLine("\t\t\tdominant-baseline: central;"); } foreach (var keyValue in style.vertexIndex) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\tpolygon.face"); writer.WriteLine("\t\t{"); if (!style.facePolygon.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: #CCC;"); } if (!style.facePolygon.ContainsKey("stroke")) { writer.WriteLine("\t\t\tstroke: none;"); } foreach (var keyValue in style.facePolygon) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.face.index"); writer.WriteLine("\t\t{"); if (!style.faceIndex.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: black;"); } if (!style.faceIndex.ContainsKey("font-size")) { writer.WriteLine("\t\t\tfont-size: {0:F0}px;", transformedVertexDotRadius.x * 2f); } if (!style.faceIndex.ContainsKey("text-anchor")) { writer.WriteLine("\t\t\ttext-anchor: middle;"); } if (!style.faceIndex.ContainsKey("dominant-baseline")) { writer.WriteLine("\t\t\tdominant-baseline: central;"); } foreach (var keyValue in style.faceIndex) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\tpolyline.edge"); writer.WriteLine("\t\t{"); if (!style.edgePath.ContainsKey("stroke")) { writer.WriteLine("\t\t\tstroke: black;"); } if (!style.edgePath.ContainsKey("stroke-width")) { writer.WriteLine("\t\t\tstroke-width: 1;"); } if (!style.edgePath.ContainsKey("stroke-linecap")) { writer.WriteLine("\t\t\tstroke-linecap: square;"); } if (!style.edgePath.ContainsKey("stroke-linejoin")) { writer.WriteLine("\t\t\tstroke-linejoin: miter;"); } if (!style.edgePath.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: none;"); } foreach (var keyValue in style.edgePath) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t\ttext.edge.index"); writer.WriteLine("\t\t{"); if (!style.edgeIndex.ContainsKey("fill")) { writer.WriteLine("\t\t\tfill: black;"); } if (!style.edgeIndex.ContainsKey("font-size")) { writer.WriteLine("\t\t\tfont-size: {0:F0}px;", style.edgeIndexOffset * Mathf.Sqrt(transform.m00 * transform.m11)); } if (!style.edgeIndex.ContainsKey("text-anchor")) { writer.WriteLine("\t\t\ttext-anchor: middle;"); } if (!style.edgeIndex.ContainsKey("dominant-baseline")) { writer.WriteLine("\t\t\tdominant-baseline: central;"); } foreach (var keyValue in style.edgeIndex) { writer.WriteLine("\t\t\t{0}: {1};", keyValue.Key, keyValue.Value); } writer.WriteLine("\t\t}"); writer.WriteLine("\t</style>"); var facePointFormat = string.Format("{{0:{0}}},{{1:{0}}}", numericFormat); var faceIndexFormat = string.Format("\t<text class=\"face index\" x=\"{{0:{0}}}\" y=\"{{1:{0}}}\">{{2}}</text>", numericFormat); var arrowFormat = string.Format("\t<polyline class=\"edge{{6}}\" points=\"{{0:{0}}},{{1:{0}}} {{2:{0}}},{{3:{0}}} {{4:{0}}},{{5:{0}}}\" />", numericFormat); var edgeIndexFormat = string.Format("\t<text class=\"edge index{{3}}\" x=\"{{0:{0}}}\" y=\"{{1:{0}}}\">{{2}}</text>", numericFormat); var vertexFormat = string.Format("\t<ellipse class=\"vertex{{4}}\" cx=\"{{0:{0}}}\" cy=\"{{1:{0}}}\" rx=\"{{2:{0}}}\" ry=\"{{3:{0}}}\" />", numericFormat); var vertexIndexFormat = string.Format("\t<text class=\"vertex index{{3}}\" x=\"{{0:{0}}}\" y=\"{{1:{0}}}\">{{2}}</text>", numericFormat); var centroids = FaceAttributeUtility.CalculateFaceCentroidsFromVertexPositions(topology.internalFaces, vertexPositions); var bisectors = EdgeAttributeUtility.CalculateFaceEdgeBisectorsFromVertexPositions(topology.internalFaces, surface, vertexPositions, centroids); writer.WriteLine(); writer.WriteLine("\t<!-- Faces -->"); foreach (var face in topology.internalFaces) { writer.Write("\t<polygon class=\"face\" points=\""); foreach (var edge in face.edges) { if (edge != face.firstEdge) { writer.Write(" "); } var p = flatten(vertexPositions[edge] + bisectors[edge] * style.faceInset); writer.Write(facePointFormat, p.x, p.y); } writer.WriteLine("\" />"); if (style.showFaceIndices) { var p = flatten(centroids[face]); writer.WriteLine(faceIndexFormat, p.x, p.y, face.index); } } writer.WriteLine(); writer.WriteLine("\t<!-- Edges -->"); foreach (var face in topology.faces) { foreach (var edge in face.edges) { ExportEdgeToSVG(writer, surface, edge, edge.prev, edge, flatten, vertexPositions, arrowFormat, edgeIndexFormat, style, " not-wrapped"); if ((edge.wrap & EdgeWrap.FaceToFace) != EdgeWrap.None) { ExportEdgeToSVG(writer, surface, edge, edge.twin, edge.twin.vertexEdge.next.twin.faceEdge, flatten, vertexPositions, arrowFormat, edgeIndexFormat, style, " wrapped"); } } } writer.WriteLine(); writer.WriteLine("\t<!-- Vertices -->"); foreach (var vertex in topology.vertices) { bool neitherAxis = false; bool axis0 = false; bool axis1 = false; bool bothAxes = false; foreach (var edge in vertex.edges) { var faceEdge = edge.twin.faceEdge; var wrap = faceEdge.wrap & EdgeWrap.FaceToVert; if (EdgeWrapUtility.WrapsOnNeitherAxis(wrap)) { if (!neitherAxis) { neitherAxis = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-neither"); } } else if (EdgeWrapUtility.WrapsOnOnlyAxis0(wrap)) { if (!axis0) { axis0 = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-axis-0"); } } else if (EdgeWrapUtility.WrapsOnOnlyAxis1(wrap)) { if (!axis1) { axis1 = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-axis-1"); } } else { if (!bothAxes) { bothAxes = true; ExportVertexToSVG(writer, vertex, faceEdge, flatten, vertexPositions, transformedVertexDotRadius, vertexFormat, vertexIndexFormat, style, " wrapped-both"); } } } } writer.WriteLine("</svg>"); }
/// <inheritdoc/> public EdgeWrap GetEdgeWrap(int faceIndex, int neighborIndex) { if (faceIndex < 0 || faceIndex >= _internalFaceCount) { throw new ArgumentOutOfRangeException("faceIndex"); } if (!_wrapRows && !_wrapCols) { return(EdgeWrap.None); } EdgeWrap wrap; switch (!_reverseWindingOrder ? neighborIndex : (6 - neighborIndex) % 6) { case 0: wrap = (WrapsOnSidePosRow(faceIndex) && !IsBackIndentedLower(faceIndex) ? EdgeWrap.NegVertToEdgeAxis0 : EdgeWrap.None); break; case 1: wrap = (WrapsOnSidePosCol(faceIndex) ? EdgeWrap.PosEdgeToVertAxis1 : EdgeWrap.None); break; case 2: wrap = (WrapsOnSidePosCol(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis1 : EdgeWrap.None) | (WrapsOnSidePosRow(faceIndex) && !IsBackIndentedUpper(faceIndex) ? EdgeWrap.PosEdgeToVertAxis0 : EdgeWrap.None); break; case 3: wrap = (WrapsOnSidePosCol(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis1 : EdgeWrap.None) | (WrapsOnSidePosRow(faceIndex) && !IsBackIndentedUpper(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis0 : EdgeWrap.None) | (WrapsOnSidePosRow(faceIndex) && IsBackIndentedUpper(faceIndex) ? EdgeWrap.PosEdgeToVertAxis0 : EdgeWrap.None); break; case 4: wrap = (WrapsOnSidePosRow(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis0 : EdgeWrap.None) | (WrapsOnSidePosCol(faceIndex) ? EdgeWrap.NegVertToEdgeAxis1 : EdgeWrap.None); break; case 5: wrap = (WrapsOnSidePosRow(faceIndex) && !IsBackIndentedLower(faceIndex) ? EdgeWrap.PosFaceToEdgeAxis0 : EdgeWrap.None) | (WrapsOnSidePosRow(faceIndex) && IsBackIndentedLower(faceIndex) ? EdgeWrap.NegVertToEdgeAxis0 : EdgeWrap.None); break; default: throw new ArgumentOutOfRangeException("neighborIndex"); } if (_reverseWindingOrder) { wrap = EdgeWrapUtility.InvertVertexEdgeRelations(wrap); } if (_reverseColumnsRows) { wrap = EdgeWrapUtility.SwapAxes(wrap); } return(wrap); }