示例#1
0
        /// <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>");
        }