#pragma warning restore 612, 618 private static void TessellateShape(Shape vectorShape, List <Geometry> geoms, TessellationOptions tessellationOptions, bool isConvex) { UnityEngine.Profiling.Profiler.BeginSample("TessellateShape"); // Don't generate any geometry for pattern fills since these are generated from another SceneNode if (vectorShape.Fill != null && !(vectorShape.Fill is PatternFill)) { Color shapeColor = Color.white; if (vectorShape.Fill is SolidFill) { shapeColor = ((SolidFill)vectorShape.Fill).Color; } shapeColor.a *= vectorShape.Fill.Opacity; if (isConvex && vectorShape.Contours.Length == 1) { TessellateConvexContour(vectorShape, vectorShape.PathProps.Stroke, shapeColor, geoms, tessellationOptions); } else { TessellateShapeLibTess(vectorShape, shapeColor, geoms, tessellationOptions); } } var stroke = vectorShape.PathProps.Stroke; if (stroke != null && stroke.HalfThickness > VectorUtils.Epsilon) { foreach (var c in vectorShape.Contours) { Vector2[] strokeVerts; UInt16[] strokeIndices; VectorUtils.TessellatePath(c, vectorShape.PathProps, tessellationOptions, out strokeVerts, out strokeIndices); if (strokeIndices.Length > 0) { geoms.Add(new Geometry() { Vertices = strokeVerts, Indices = strokeIndices, Color = vectorShape.PathProps.Stroke.Color }); } } } UnityEngine.Profiling.Profiler.EndSample(); }
static PartTessellation GetBodyTessellation(Body body, Func<Face, Color> faceColor, double surfaceDeviation, double angleDeviation) { var tessellationOptions = new TessellationOptions(surfaceDeviation, angleDeviation); var tessellation = body.GetTessellation(null, tessellationOptions); var vertices = new Dictionary<PositionNormalTextured, int>(); var vertexList = new List<Point>(); var normalList = new List<Direction>(); var colors = new Dictionary<Color, int>(); var colorList = new List<Color>(); var faces = new List<FaceStruct>(); foreach (var pair in tessellation) { var color = faceColor(pair.Key); int colorIndex; if (!colors.TryGetValue(color, out colorIndex)) { colorList.Add(color); colorIndex = colorList.Count - 1; colors[color] = colorIndex; } var vertexIndices = new Dictionary<int, int>(); var i = 0; foreach (var vertex in pair.Value.Vertices) { int index; if (!vertices.TryGetValue(vertex, out index)) { vertexList.Add(vertex.Position); normalList.Add(vertex.Normal); index = vertexList.Count - 1; vertices[vertex] = index; } vertexIndices[i] = index; i++; } foreach (var facet in pair.Value.Facets) { faces.Add(new FaceStruct { Vertex1 = vertexIndices[facet.Vertex0], Vertex2 = vertexIndices[facet.Vertex1], Vertex3 = vertexIndices[facet.Vertex2], Color = colorIndex }); } } var faceTessellation = new BodyTessellation { VertexPositions = vertexList, VertexNormals = normalList, FaceColors = colorList, Faces = faces }; List<BodyTessellation> edges = new List<BodyTessellation>(); foreach (var edge in body.Edges) { edges.Add(new BodyTessellation { VertexPositions = new List<Point>(edge.GetPolyline()) }); } return new PartTessellation { Lines = edges, Meshes = { faceTessellation } }; }
#pragma warning disable 612, 618 // Silence use of deprecated IDrawable private static List <Geometry> TessellateNodeHierarchyRecursive(SceneNode node, TessellationOptions tessellationOptions, Matrix2D worldTransform, float worldOpacity, Dictionary <SceneNode, float> nodeOpacities) { if (node.Clipper != null) { VectorClip.PushClip(TraceNodeHierarchyShapes(node.Clipper, tessellationOptions), worldTransform); } var geoms = new List <Geometry>(); if (node.Shapes != null) { foreach (var shape in node.Shapes) { bool isConvex = shape.IsConvex && shape.Contours.Length == 1; TessellateShape(shape, geoms, tessellationOptions, isConvex); } } foreach (var g in geoms) { g.Color.a *= worldOpacity; g.WorldTransform = worldTransform; g.UnclippedBounds = Bounds(g.Vertices); VectorClip.ClipGeometry(g); } if (node.Children != null) { foreach (var child in node.Children) { var childOpacity = 1.0f; if (nodeOpacities == null || !nodeOpacities.TryGetValue(child, out childOpacity)) { childOpacity = 1.0f; } var transform = worldTransform * child.Transform; var opacity = worldOpacity * childOpacity; var childGeoms = TessellateNodeHierarchyRecursive(child, tessellationOptions, transform, opacity, nodeOpacities); geoms.AddRange(childGeoms); } } if (node.Clipper != null) { VectorClip.PopClip(); } return(geoms); }
internal static List <Vector2[]> TraceNodeHierarchyShapes(SceneNode root, TessellationOptions tessellationOptions) { var shapes = new List <Vector2[]>(); foreach (var nodeInfo in WorldTransformedSceneNodes(root, null)) { var node = nodeInfo.Node; // We process the drawables even though they are obsolete, until we remove the IDrawable interface entirely if (node.Drawables != null) { foreach (var drawable in node.Drawables) { var vectorShape = drawable as Shape; if (vectorShape != null) { foreach (var c in vectorShape.Contours) { var shape = VectorUtils.TraceShape(c, vectorShape.PathProps.Stroke, tessellationOptions); if (shape.Length > 0) { shapes.Add(shape.Select(v => nodeInfo.WorldTransform * v).ToArray()); } } continue; } var vectorPath = drawable as Path; if (vectorPath != null) { var shape = VectorUtils.TraceShape(vectorPath.Contour, vectorPath.PathProps.Stroke, tessellationOptions); if (shape.Length > 0) { shapes.Add(shape.Select(v => nodeInfo.WorldTransform * v).ToArray()); } continue; } var vectorRect = drawable as Rectangle; if (vectorRect != null) { var shape = VectorUtils.TraceRectangle(vectorRect, vectorRect.PathProps.Stroke, tessellationOptions); if (shape.Length > 0) { shapes.Add(shape.Select(v => nodeInfo.WorldTransform * v).ToArray()); } continue; } } } if (node.Shapes != null) { foreach (var shape in node.Shapes) { foreach (var c in shape.Contours) { var tracedShape = VectorUtils.TraceShape(c, shape.PathProps.Stroke, tessellationOptions); if (tracedShape.Length > 0) { shapes.Add(tracedShape.Select(v => nodeInfo.WorldTransform * v).ToArray()); } } } } } return(shapes); }
private static void TessellateConvexContour(Shape shape, Stroke stroke, Color color, List <Geometry> geoms, TessellationOptions tessellationOptions) { if (shape.Contours.Length != 1 || shape.Contours[0].Segments.Length == 0) { return; } UnityEngine.Profiling.Profiler.BeginSample("TessellateConvexContour"); // Compute geometric mean var contour = shape.Contours[0]; var mean = Vector2.zero; foreach (var seg in contour.Segments) { mean += seg.P0; } mean /= contour.Segments.Length; // Trace the shape and build triangle fan var tracedShape = VectorUtils.TraceShape(contour, stroke, tessellationOptions); var vertices = new Vector2[tracedShape.Length + 1]; var indices = new UInt16[tracedShape.Length * 3]; vertices[0] = mean; for (int i = 0; i < tracedShape.Length; ++i) { vertices[i + 1] = tracedShape[i]; indices[i * 3] = 0; indices[i * 3 + 1] = (UInt16)(i + 1); indices[i * 3 + 2] = ((i + 2) >= vertices.Length) ? (UInt16)1 : (UInt16)(i + 2); } geoms.Add(new Geometry() { Vertices = vertices, Indices = indices, Color = color, Fill = shape.Fill, FillTransform = shape.FillTransform }); UnityEngine.Profiling.Profiler.EndSample(); }
private static void TessellateShapeLibTess(Shape vectorShape, Color color, List <Geometry> geoms, TessellationOptions tessellationOptions) { UnityEngine.Profiling.Profiler.BeginSample("LibTess"); var tess = new Tess(); var angle = 45.0f * Mathf.Deg2Rad; var mat = Matrix2D.RotateLH(angle); var invMat = Matrix2D.RotateLH(-angle); foreach (var c in vectorShape.Contours) { var contour = new List <Vector2>(100); foreach (var v in VectorUtils.TraceShape(c, vectorShape.PathProps.Stroke, tessellationOptions)) { contour.Add(mat.MultiplyPoint(v)); } tess.AddContour(contour.Select(v => new ContourVertex() { Position = new Vec3() { X = v.x, Y = v.y } }).ToArray(), ContourOrientation.Original); } var windingRule = (vectorShape.Fill.Mode == FillMode.OddEven) ? WindingRule.EvenOdd : WindingRule.NonZero; try { tess.Tessellate(windingRule, ElementType.Polygons, 3); } catch (System.Exception) { Debug.LogWarning("Shape tessellation failed, skipping..."); UnityEngine.Profiling.Profiler.EndSample(); return; } var indices = tess.Elements.Select(i => (UInt16)i); var vertices = tess.Vertices.Select(v => invMat.MultiplyPoint(new Vector2(v.Position.X, v.Position.Y))); if (indices.Count() > 0) { geoms.Add(new Geometry() { Vertices = vertices.ToArray(), Indices = indices.ToArray(), Color = color, Fill = vectorShape.Fill, FillTransform = vectorShape.FillTransform }); } UnityEngine.Profiling.Profiler.EndSample(); }
private static void TessellatePath(BezierContour contour, PathProperties pathProps, List <Geometry> geoms, TessellationOptions tessellationOptions) { UnityEngine.Profiling.Profiler.BeginSample("TessellatePath"); if (pathProps.Stroke != null) { Vector2[] vertices; UInt16[] indices; VectorUtils.TessellatePath(contour, pathProps, tessellationOptions, out vertices, out indices); var color = pathProps.Stroke.Color; if (indices.Length > 0) { geoms.Add(new Geometry() { Vertices = vertices, Indices = indices, Color = color }); } } UnityEngine.Profiling.Profiler.EndSample(); }
#pragma warning disable 612, 618 // Silence use of deprecated IDrawable private static List <Geometry> TessellateNodeHierarchyRecursive(SceneNode node, TessellationOptions tessellationOptions, Matrix2D worldTransform, float worldOpacity, Dictionary <SceneNode, float> nodeOpacities) { if (node.Clipper != null) { VectorClip.PushClip(TraceNodeHierarchyShapes(node.Clipper, tessellationOptions), worldTransform); } var geoms = new List <Geometry>(); if (node.Drawables != null) { // We process the drawables even though they are obsolete, until we remove the IDrawable interface entirely foreach (var drawable in node.Drawables) { var vectorShape = drawable as Shape; if (vectorShape != null) { bool isConvex = vectorShape.IsConvex && vectorShape.Contours.Length == 1; TessellateShape(vectorShape, geoms, tessellationOptions, isConvex); continue; } var vectorPath = drawable as Path; if (vectorPath != null) { TessellatePath(vectorPath.Contour, vectorPath.PathProps, geoms, tessellationOptions); continue; } var vectorRect = drawable as Rectangle; if (vectorRect != null) { TessellateRectangle(vectorRect, geoms, tessellationOptions); continue; } } } if (node.Shapes != null) { foreach (var shape in node.Shapes) { bool isConvex = shape.IsConvex && shape.Contours.Length == 1; TessellateShape(shape, geoms, tessellationOptions, isConvex); } } foreach (var g in geoms) { g.Color.a *= worldOpacity; g.WorldTransform = worldTransform; g.UnclippedBounds = Bounds(g.Vertices); VectorClip.ClipGeometry(g); } if (node.Children != null) { foreach (var child in node.Children) { var childOpacity = 1.0f; if (nodeOpacities == null || !nodeOpacities.TryGetValue(child, out childOpacity)) { childOpacity = 1.0f; } var transform = worldTransform * child.Transform; var opacity = worldOpacity * childOpacity; var childGeoms = TessellateNodeHierarchyRecursive(child, tessellationOptions, transform, opacity, nodeOpacities); geoms.AddRange(childGeoms); } } if (node.Clipper != null) { VectorClip.PopClip(); } return(geoms); }
private static void TessellateRectangleRoundedCorners(Rectangle rect, List <Geometry> geoms, TessellationOptions tessellationOptions) { var contour = BuildRectangleContour(rect); var shape = new Shape() { Contours = new BezierContour[] { contour }, PathProps = rect.PathProps, Fill = rect.Fill, FillTransform = rect.FillTransform }; ShapeUtils.TessellateShape(shape, geoms, tessellationOptions, true); }
internal static void TessellateRectangle(Rectangle rect, List <Geometry> geoms, TessellationOptions tessellationOptions) { var width = rect.Size.x; var height = rect.Size.y; if (width <= ShapeUtils.Epsilon || height <= ShapeUtils.Epsilon) { return; } if (IsSimpleRectangle(rect)) { // Fast path, square corners, no patterns TessellateRectangleSquareCorners(rect, geoms); } else { TessellateRectangleRoundedCorners(rect, geoms, tessellationOptions); } }
/// <summary> /// Tessellates a path. /// </summary> /// <param name="contour">The path to tessellate</param> /// <param name="pathProps">The path properties</param> /// <param name="tessellateOptions">The tessellation options</param> /// <param name="vertices">The resulting vertices</param> /// <param name="indices">The resulting triangles</param> /// <remarks> /// The individual line segments generated during tessellation are made out of a set of ordered vertices. It is important /// to honor this ordering so joining and and capping connect properly with the existing vertices without generating dupes. /// The ordering assumed is as follows: /// The last two vertices of a piece must be such that the first is generated at the end with a positive half-thickness /// while the second vertex is at the end too but at a negative half-thickness. /// No assumptions are enforced for other vertices before the two last vertices. /// </remarks> public static void TessellatePath(BezierContour contour, PathProperties pathProps, TessellationOptions tessellateOptions, out Vector2[] vertices, out UInt16[] indices) { if (tessellateOptions.StepDistance < Epsilon) { throw new Exception("stepDistance too small"); } if (contour.Segments.Length < 2) { vertices = new Vector2[0]; indices = new UInt16[0]; return; } tessellateOptions.MaxCordDeviation = Mathf.Max(0.0001f, tessellateOptions.MaxCordDeviation); tessellateOptions.MaxTanAngleDeviation = Mathf.Max(0.0001f, tessellateOptions.MaxTanAngleDeviation); UnityEngine.Profiling.Profiler.BeginSample("TessellatePath"); float[] segmentLengths = VectorUtils.SegmentsLengths(contour.Segments, contour.Closed); // Approximate the number of vertices/indices we need to store the results so we reduce memory reallocations during work float approxTotalLength = 0.0f; foreach (var s in segmentLengths) { approxTotalLength += s; } int approxStepCount = Math.Max((int)(approxTotalLength / tessellateOptions.StepDistance + 0.5f), 2); if (pathProps.Stroke.Pattern != null) { approxStepCount += pathProps.Stroke.Pattern.Length * 2; } List <Vector2> verts = new List <Vector2>(approxStepCount * 2 + 32); // A little bit possibly for the endings List <UInt16> inds = new List <UInt16>((int)(verts.Capacity * 1.5f)); // Usually every 4 verts represent a quad that uses 6 indices var patternIt = new PathPatternIterator(pathProps.Stroke.Pattern, pathProps.Stroke.PatternOffset); var pathIt = new PathDistanceForwardIterator(contour.Segments, contour.Closed, tessellateOptions.MaxCordDeviationSquared, tessellateOptions.MaxTanAngleDeviationCosine, tessellateOptions.SamplingStepSize); JoiningInfo[] joiningInfo = new JoiningInfo[2]; HandleNewSegmentJoining(pathIt, patternIt, joiningInfo, pathProps.Stroke.HalfThickness, segmentLengths); int rangeIndex = 0; while (!pathIt.Ended) { if (patternIt.IsSolid) { TessellateRange(patternIt.SegmentLength, pathIt, patternIt, pathProps, tessellateOptions, joiningInfo, segmentLengths, approxTotalLength, rangeIndex++, verts, inds); } else { SkipRange(patternIt.SegmentLength, pathIt, patternIt, pathProps, joiningInfo, segmentLengths); } patternIt.Advance(); } vertices = verts.ToArray(); indices = inds.ToArray(); UnityEngine.Profiling.Profiler.EndSample(); }
static void GenerateTip(BezierSegment segment, bool atStart, float t, PathEnding ending, float halfThickness, TessellationOptions tessellateOptions, List <Vector2> verts, List <UInt16> inds) { // The tip includes the vertices at the end itself Vector2 tan, nrm; var pos = VectorUtils.EvalFull(segment, t, out tan, out nrm); int indexStart = verts.Count; switch (ending) { case PathEnding.Chop: if (atStart) { verts.Add(pos + nrm * halfThickness); verts.Add(pos - nrm * halfThickness); } else { // Not much, path segments are always expected to be generated perpendicular to the path // at the segment point location, so we don't have to do anything for the ending } break; case PathEnding.Square: if (atStart) { verts.Add(pos + nrm * halfThickness - tan * halfThickness); verts.Add(pos - nrm * halfThickness - tan * halfThickness); verts.Add(pos + nrm * halfThickness); verts.Add(pos - nrm * halfThickness); inds.Add((UInt16)(indexStart + 0)); inds.Add((UInt16)(indexStart + 3)); inds.Add((UInt16)(indexStart + 1)); inds.Add((UInt16)(indexStart + 0)); inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + 3)); } else { // Relying on the last two vertices, and just adding two of our own here verts.Add(pos + nrm * halfThickness + tan * halfThickness); verts.Add(pos - nrm * halfThickness + tan * halfThickness); inds.Add((UInt16)(indexStart + 0 - 2)); inds.Add((UInt16)(indexStart + 3 - 2)); inds.Add((UInt16)(indexStart + 1 - 2)); inds.Add((UInt16)(indexStart + 0 - 2)); inds.Add((UInt16)(indexStart + 2 - 2)); inds.Add((UInt16)(indexStart + 3 - 2)); } break; case PathEnding.Round: float arcSign = atStart ? -1 : 1; int arcSegments = CalculateArcSteps(halfThickness, 0, Mathf.PI, tessellateOptions); for (int i = 1; i < arcSegments; i++) { float angle = Mathf.PI * (i / (float)arcSegments); verts.Add(pos + Matrix2D.RotateLH(angle) * nrm * halfThickness * arcSign); } if (atStart) { // Note how we maintain the last two vertices being setup for connection by the rest of the path vertices int indexTipStart = verts.Count; verts.Add(pos + nrm * halfThickness); verts.Add(pos - nrm * halfThickness); for (int i = 1; i < arcSegments; i++) { inds.Add((UInt16)(indexTipStart + 1)); inds.Add((UInt16)(indexStart + i - 1)); inds.Add((UInt16)(indexStart + i)); } } else { inds.Add((UInt16)(indexStart - 1)); inds.Add((UInt16)(indexStart - 2)); inds.Add((UInt16)(indexStart + 0)); for (int i = 1; i < arcSegments - 1; i++) { inds.Add((UInt16)(indexStart - 1)); inds.Add((UInt16)(indexStart + i - 1)); inds.Add((UInt16)(indexStart + i)); } } break; default: System.Diagnostics.Debug.Assert(false); // Joining has its own function break; } }
static void GenerateJoining(JoiningInfo joinInfo, PathCorner corner, float halfThickness, float tippedCornerLimit, TessellationOptions tessellateOptions, List <Vector2> verts, List <UInt16> inds) { // The joining generates the vertices at both ends as well as the joining itself if (verts.Count == 0) { // Starting a path with a joining (meaning a loop) verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessEnd : joinInfo.InnerCornerVertex); verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessEnd); } System.Diagnostics.Debug.Assert(verts.Count >= 2); int indexStart = verts.Count - 2; // Using the last two vertices // Convert a tipped corner to a beveled one if tippedCornerLimit ratio is reached if (corner == PathCorner.Tipped && tippedCornerLimit >= 1.0f) { var theta = Vector2.Angle(-joinInfo.TanAtEnd, joinInfo.TanAtStart) * Mathf.Deg2Rad; var ratio = 1.0f / Mathf.Sin(theta / 2.0f); if (ratio > tippedCornerLimit) { corner = PathCorner.Beveled; } } if (joinInfo.SimpleJoin) { // TODO } else if (corner == PathCorner.Tipped) { verts.Add(joinInfo.PosThicknessClosingPoint); verts.Add(joinInfo.NegThicknessClosingPoint); verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessStart : joinInfo.InnerCornerVertex); verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessStart); // Ending to tip inds.Add((UInt16)(indexStart + 0)); inds.Add((UInt16)(indexStart + 3)); inds.Add((UInt16)(indexStart + 1)); inds.Add((UInt16)(indexStart + 0)); inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + 3)); // Tip to starting inds.Add((UInt16)(indexStart + 4)); inds.Add((UInt16)(indexStart + 3)); inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + 4)); inds.Add((UInt16)(indexStart + 5)); inds.Add((UInt16)(indexStart + 3)); return; } else if (corner == PathCorner.Beveled) { verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessEnd : joinInfo.InnerCornerVertex); // 2 verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessEnd); // 3 verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessStart : joinInfo.InnerCornerVertex); // 4 verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessStart); // 5 // Ending to tip inds.Add((UInt16)(indexStart + 0)); inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + 1)); inds.Add((UInt16)(indexStart + 1)); inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + 3)); // Bevel if (joinInfo.RoundPosThickness) { inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + 4)); inds.Add((UInt16)(indexStart + 3)); } else { inds.Add((UInt16)(indexStart + 3)); inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + 5)); } return; } if (corner == PathCorner.Round) { float sweepAngle = Mathf.Acos(Vector2.Dot(joinInfo.NormAtEnd, joinInfo.NormAtStart)); bool flipArc = false; if (!PointOnTheLeftOfLine(Vector2.zero, joinInfo.NormAtEnd, joinInfo.NormAtStart)) { sweepAngle = -sweepAngle; flipArc = true; } UInt16 innerCornerVertexIndex = (UInt16)verts.Count; verts.Add(joinInfo.InnerCornerVertex); int arcSegments = CalculateArcSteps(halfThickness, 0, sweepAngle, tessellateOptions); for (int i = 0; i <= arcSegments; i++) { float angle = sweepAngle * (i / (float)arcSegments); Vector2 nrm = Matrix2D.RotateLH(angle) * joinInfo.NormAtEnd; if (flipArc) { nrm = -nrm; } verts.Add(nrm * halfThickness + joinInfo.JoinPos); if (i == 0) { inds.Add((UInt16)(indexStart + 0)); inds.Add((UInt16)(indexStart + 3)); inds.Add((UInt16)(indexStart + (joinInfo.RoundPosThickness ? 2 : 1))); inds.Add((UInt16)(indexStart + 0)); inds.Add((UInt16)(indexStart + 2)); inds.Add((UInt16)(indexStart + (joinInfo.RoundPosThickness ? 1 : 3))); } else { if (joinInfo.RoundPosThickness) { inds.Add((UInt16)(indexStart + i + (flipArc ? 3 : 2))); inds.Add((UInt16)(indexStart + i + (flipArc ? 2 : 3))); inds.Add(innerCornerVertexIndex); } else { inds.Add((UInt16)(indexStart + i + (flipArc ? 3 : 2))); inds.Add((UInt16)(indexStart + i + (flipArc ? 2 : 3))); inds.Add(innerCornerVertexIndex); } } } // Manually add the last segment, maintain the expected vertex positioning int endingVerticesIndex = verts.Count; if (joinInfo.RoundPosThickness) { verts.Add(joinInfo.PosThicknessStart); verts.Add(joinInfo.InnerCornerVertex); } else { verts.Add(joinInfo.InnerCornerVertex); verts.Add(joinInfo.NegThicknessStart); } inds.Add((UInt16)(endingVerticesIndex - 1)); inds.Add((UInt16)(endingVerticesIndex + 0)); inds.Add(innerCornerVertexIndex); } }
static void TessellateRange( float distance, PathDistanceForwardIterator pathIt, PathPatternIterator patternIt, PathProperties pathProps, TessellationOptions tessellateOptions, JoiningInfo[] joiningInfo, float[] segmentLengths, float totalLength, int rangeIndex, List <Vector2> verts, List <UInt16> inds) { bool startOfLoop = pathIt.Closed && (pathIt.CurrentSegment == 0) && (pathIt.CurrentT == 0.0f); if (startOfLoop && (joiningInfo[0] != null)) { GenerateJoining(joiningInfo[0], pathProps.Corners, pathProps.Stroke.HalfThickness, pathProps.Stroke.TippedCornerLimit, tessellateOptions, verts, inds); } else { var pathEnding = pathProps.Head; // If pattern at the end will overlap with beginning, use a chopped ending to allow merging if (pathIt.Closed && rangeIndex == 0 && patternIt.IsSolidAt(pathIt.CurrentT) && patternIt.IsSolidAt(totalLength)) { pathEnding = PathEnding.Chop; } GenerateTip(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), true, pathIt.CurrentT, pathEnding, pathProps.Stroke.HalfThickness, tessellateOptions, verts, inds); } float startingLength = pathIt.LengthSoFar; float unitsRemaining = Mathf.Min(tessellateOptions.StepDistance, distance); bool endedEntirePath = false; for (;;) { var result = pathIt.AdvanceBy(unitsRemaining, out unitsRemaining); if (result == PathDistanceForwardIterator.Result.Ended) { endedEntirePath = true; break; } else if (result == PathDistanceForwardIterator.Result.NewSegment) { if (joiningInfo[1] != null) { GenerateJoining(joiningInfo[1], pathProps.Corners, pathProps.Stroke.HalfThickness, pathProps.Stroke.TippedCornerLimit, tessellateOptions, verts, inds); } else { AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, null, pathIt.SegmentLengthSoFar, verts, inds); } HandleNewSegmentJoining(pathIt, patternIt, joiningInfo, pathProps.Stroke.HalfThickness, segmentLengths); } if ((unitsRemaining <= Epsilon) && !TryGetMoreRemainingUnits(ref unitsRemaining, pathIt, startingLength, distance, tessellateOptions.StepDistance)) { break; } if (result == PathDistanceForwardIterator.Result.Stepped) { AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, joiningInfo, pathIt.SegmentLengthSoFar, verts, inds); } } // Ending if (endedEntirePath && pathIt.Closed) { // No joining needed, the start and end of the path should just connect inds.Add(0); inds.Add(1); inds.Add((UInt16)(verts.Count - 2)); inds.Add((UInt16)(verts.Count - 1)); inds.Add((UInt16)(verts.Count - 2)); inds.Add(1); } else { AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, joiningInfo, pathIt.SegmentLengthSoFar, verts, inds); GenerateTip(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), false, pathIt.CurrentT, pathProps.Tail, pathProps.Stroke.HalfThickness, tessellateOptions, verts, inds); } }
static Vector2[] TraceShape(BezierContour contour, Stroke stroke, TessellationOptions tessellateOptions) { if (tessellateOptions.StepDistance < Epsilon) { throw new Exception("stepDistance too small"); } if (contour.Segments.Length < 2) { return(new Vector2[0]); } float[] segmentLengths = VectorUtils.SegmentsLengths(contour.Segments, contour.Closed); // Approximate the number of vertices/indices we need to store the results so we reduce memory reallocations during work float approxTotalLength = 0.0f; foreach (var s in segmentLengths) { approxTotalLength += s; } int approxStepCount = Math.Max((int)(approxTotalLength / tessellateOptions.StepDistance + 0.5f), 2); var strokePattern = stroke != null ? stroke.Pattern : null; var strokePatternOffset = stroke != null ? stroke.PatternOffset : 0.0f; if (strokePattern != null) { approxStepCount += strokePattern.Length * 2; } List <Vector2> verts = new List <Vector2>(approxStepCount); // A little bit possibly for the endings var patternIt = new PathPatternIterator(strokePattern, strokePatternOffset); var pathIt = new PathDistanceForwardIterator(contour.Segments, true, tessellateOptions.MaxCordDeviationSquared, tessellateOptions.MaxTanAngleDeviationCosine, tessellateOptions.SamplingStepSize); verts.Add(pathIt.EvalCurrent()); while (!pathIt.Ended) { float distance = patternIt.SegmentLength; float startingLength = pathIt.LengthSoFar; float unitsRemaining = Mathf.Min(tessellateOptions.StepDistance, distance); bool endedEntirePath = false; for (;;) { var result = pathIt.AdvanceBy(unitsRemaining, out unitsRemaining); if (result == PathDistanceForwardIterator.Result.Ended) { endedEntirePath = true; break; } else if (result == PathDistanceForwardIterator.Result.NewSegment) { verts.Add(pathIt.EvalCurrent()); } if ((unitsRemaining <= Epsilon) && !TryGetMoreRemainingUnits(ref unitsRemaining, pathIt, startingLength, distance, tessellateOptions.StepDistance)) { break; } if (result == PathDistanceForwardIterator.Result.Stepped) { verts.Add(pathIt.EvalCurrent()); } } // Ending if (endedEntirePath) { break; } else { verts.Add(pathIt.EvalCurrent()); } patternIt.Advance(); } if ((verts[0] - verts[verts.Count - 1]).sqrMagnitude < Epsilon) { verts.RemoveAt(verts.Count - 1); } return(verts.ToArray()); // Why not return verts itself? }