/// <summary>Builds a BezierContour from a Rectangle.</summary> /// <param name="rect">The rectangle to build the contour from</param> /// <param name="radiusTL">The top-left radius of the rectangle</param> /// <param name="radiusTR">The top-right radius of the rectangle</param> /// <param name="radiusBR">The bottom-right radius of the rectangle</param> /// <param name="radiusBL">The bottom-left radius of the rectangle</param> /// <returns>A BezierContour that follows the rectangle contour</returns> public static BezierContour BuildRectangleContour(Rect rect, Vector2 radiusTL, Vector2 radiusTR, Vector2 radiusBR, Vector2 radiusBL) { var width = rect.size.x; var height = rect.size.y; var halfSize = new Vector2(width / 2.0f, height / 2.0f); radiusTL = Vector2.Max(Vector2.Min(radiusTL, halfSize), Vector2.zero); radiusTR = Vector2.Max(Vector2.Min(radiusTR, halfSize), Vector2.zero); radiusBR = Vector2.Max(Vector2.Min(radiusBR, halfSize), Vector2.zero); radiusBL = Vector2.Max(Vector2.Min(radiusBL, halfSize), Vector2.zero); var leftSegmentSize = height - (radiusBL.y + radiusTL.y); var topSegmentSize = width - (radiusTL.x + radiusTR.x); var rightSegmentSize = height - (radiusBR.y + radiusTR.y); var bottomSegmentSize = width - (radiusBL.x + radiusBR.x); var segments = new List <BezierPathSegment>(8); BezierPathSegment seg; if (leftSegmentSize > VectorUtils.Epsilon) { seg = MakePathLine(new Vector2(0.0f, radiusTL.y + leftSegmentSize), new Vector2(0.0f, radiusTL.y))[0]; segments.Add(seg); } if (radiusTL.magnitude > VectorUtils.Epsilon) { var circleArc = VectorUtils.MakeArc(Vector2.zero, -Mathf.PI, Mathf.PI / 2.0f, 1.0f); circleArc = VectorUtils.TransformBezierPath(circleArc, radiusTL, 0.0f, radiusTL); segments.Add(circleArc[0]); } if (topSegmentSize > VectorUtils.Epsilon) { seg = MakePathLine(new Vector2(radiusTL.x, 0.0f), new Vector2(radiusTL.x + topSegmentSize, 0.0f))[0]; segments.Add(seg); } if (radiusTR.magnitude > VectorUtils.Epsilon) { var topRight = new Vector2(width - radiusTR.x, radiusTR.y); var circleArc = VectorUtils.MakeArc(Vector2.zero, -Mathf.PI / 2.0f, Mathf.PI / 2.0f, 1.0f); circleArc = VectorUtils.TransformBezierPath(circleArc, topRight, 0.0f, radiusTR); segments.Add(circleArc[0]); } if (rightSegmentSize > VectorUtils.Epsilon) { seg = MakePathLine(new Vector2(width, radiusTR.y), new Vector2(width, radiusTR.y + rightSegmentSize))[0]; segments.Add(seg); } if (radiusBR.magnitude > VectorUtils.Epsilon) { var bottomRight = new Vector2(width - radiusBR.x, height - radiusBR.y); var circleArc = VectorUtils.MakeArc(Vector2.zero, 0.0f, Mathf.PI / 2.0f, 1.0f); circleArc = VectorUtils.TransformBezierPath(circleArc, bottomRight, 0.0f, radiusBR); segments.Add(circleArc[0]); } if (bottomSegmentSize > VectorUtils.Epsilon) { seg = MakePathLine(new Vector2(width - radiusBR.x, height), new Vector2(width - (radiusBR.x + bottomSegmentSize), height))[0]; segments.Add(seg); } if (radiusBL.magnitude > VectorUtils.Epsilon) { var bottomLeft = new Vector2(radiusBL.x, height - radiusBL.y); var circleArc = VectorUtils.MakeArc(Vector2.zero, Mathf.PI / 2.0f, Mathf.PI / 2.0f, 1.0f); circleArc = VectorUtils.TransformBezierPath(circleArc, bottomLeft, 0.0f, radiusBL); segments.Add(circleArc[0]); } // Offset segments to position for (int i = 0; i < segments.Count; ++i) { var s = segments[i]; s.P0 += rect.position; s.P1 += rect.position; s.P2 += rect.position; segments[i] = s; } return(new BezierContour() { Segments = segments.ToArray(), Closed = true }); }
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); }
internal static void MakeVectorImageAsset(IEnumerable <VectorUtils.Geometry> geoms, uint rasterSize, out UnityEngine.Object outAsset, out Texture2D outTexAtlas) { var atlas = VectorUtils.GenerateAtlas(geoms, rasterSize, false, false); if (atlas != null) { VectorUtils.FillUVs(geoms, atlas); } bool hasTexture = atlas != null && atlas.Texture != null; outTexAtlas = hasTexture ? atlas.Texture : null; var vertices = new List <InternalBridge.VectorImageVertexBridge>(100); var indices = new List <UInt16>(300); var settings = new List <InternalBridge.GradientSettingsBridge>(); var min = new Vector2(float.MaxValue, float.MaxValue); var max = new Vector2(float.MinValue, float.MinValue); foreach (var geom in geoms) { if (geom.Vertices.Length == 0) { continue; } var b = VectorUtils.Bounds(geom.Vertices.Select(v => geom.WorldTransform.MultiplyPoint(v))); min = Vector2.Min(min, b.min); max = Vector2.Max(max, b.max); } var bounds = Rect.zero; if (min.x != float.MaxValue) { bounds = new Rect(min, max - min); } // Save written settings to avoid duplicates var writtenSettings = new HashSet <int>(); writtenSettings.Add(0); // Create a map of filling -> atlas entry var fillEntries = new Dictionary <IFill, VectorUtils.PackRectItem>(); if (atlas != null && atlas.Entries != null) { foreach (var entry in atlas.Entries) { if (entry.Fill != null) { fillEntries[entry.Fill] = entry; } } } if (hasTexture && atlas != null && atlas.Entries != null && atlas.Entries.Count > 0) { // Write the 'white' texel info var entry = atlas.Entries[atlas.Entries.Count - 1]; settings.Add(new InternalBridge.GradientSettingsBridge() { gradientType = InternalBridge.GradientTypeBridge.Linear, addressMode = InternalBridge.AddressModeBridge.Wrap, radialFocus = Vector2.zero, location = new RectInt((int)entry.Position.x, (int)entry.Position.y, (int)entry.Size.x, (int)entry.Size.y) }); } foreach (var geom in geoms) { for (int i = 0; i < geom.Vertices.Length; ++i) { var v = geom.WorldTransform.MultiplyPoint(geom.Vertices[i]); v -= bounds.position; geom.Vertices[i] = v; } VectorUtils.AdjustWinding(geom.Vertices, geom.Indices, VectorUtils.WindingDir.CCW); var count = vertices.Count; for (int i = 0; i < geom.Vertices.Length; ++i) { Vector3 p = (Vector3)geom.Vertices[i]; p.z = Vertex.nearZ; vertices.Add(new InternalBridge.VectorImageVertexBridge() { position = p, uv = hasTexture ? geom.UVs[i] : Vector2.zero, tint = geom.Color, settingIndex = (uint)geom.SettingIndex }); } indices.AddRange(geom.Indices.Select(i => (UInt16)(i + count))); if (atlas != null && atlas.Entries != null && atlas.Entries.Count > 0) { VectorUtils.PackRectItem entry; if (geom.Fill == null || !fillEntries.TryGetValue(geom.Fill, out entry) || writtenSettings.Contains(entry.SettingIndex)) { continue; } writtenSettings.Add(entry.SettingIndex); var gradientType = GradientFillType.Linear; var radialFocus = Vector2.zero; var addressMode = AddressMode.Wrap; var gradientFill = geom.Fill as GradientFill; if (gradientFill != null) { gradientType = gradientFill.Type; radialFocus = gradientFill.RadialFocus; addressMode = gradientFill.Addressing; } var textureFill = geom.Fill as TextureFill; if (textureFill != null) { addressMode = textureFill.Addressing; } settings.Add(new InternalBridge.GradientSettingsBridge() { gradientType = (InternalBridge.GradientTypeBridge)gradientType, addressMode = (InternalBridge.AddressModeBridge)addressMode, radialFocus = radialFocus, location = new RectInt((int)entry.Position.x, (int)entry.Position.y, (int)entry.Size.x, (int)entry.Size.y) }); } } outAsset = InternalBridge.MakeVectorImageAsset(vertices, indices, outTexAtlas, settings, bounds.size); }
private static Texture2D BuildAtlasWithEncodedSettings(InternalBridge.GradientSettingsBridge[] settings, Texture2D atlas) { var oldActive = RenderTexture.active; int width = atlas.width + 3; int height = Math.Max(settings.Length, atlas.height); var desc = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 0) { sRGB = QualitySettings.activeColorSpace == ColorSpace.Linear }; var rt = RenderTexture.GetTemporary(desc); GL.Clear(false, true, Color.black, 1.0f); Graphics.Blit(atlas, rt, Vector2.one, new Vector2(-3.0f / width, 0.0f)); RenderTexture.active = rt; Texture2D copy = new Texture2D(width, height, TextureFormat.RGBA32, false); copy.hideFlags = HideFlags.HideAndDontSave; copy.ReadPixels(new Rect(0, 0, width, height), 0, 0); // This encoding procedure is duplicated a few times, do something about it var rawSettingsTex = new VectorUtils.RawTexture() { Width = 3, Height = settings.Length, Rgba = new Color32[3 * settings.Length] }; for (int i = 0; i < settings.Length; ++i) { var g = settings[i]; // There are 3 consecutive pixels to store the settings int destX = 0; int destY = i; if (g.gradientType == InternalBridge.GradientTypeBridge.Radial) { var focus = g.radialFocus; focus += Vector2.one; focus /= 2.0f; focus.y = 1.0f - focus.y; VectorUtils.WriteRawFloat4Packed(rawSettingsTex, ((float)g.gradientType) / 255, ((float)g.addressMode) / 255, focus.x, focus.y, destX++, destY); } else { VectorUtils.WriteRawFloat4Packed(rawSettingsTex, 0.0f, ((float)g.addressMode) / 255, 0.0f, 0.0f, destX++, destY); } var pos = g.location.position; var size = g.location.size; size.x -= 1; size.y -= 1; VectorUtils.WriteRawInt2Packed(rawSettingsTex, (int)pos.x + 3, (int)pos.y, destX++, destY); VectorUtils.WriteRawInt2Packed(rawSettingsTex, (int)size.x, (int)size.y, destX++, destY); } copy.SetPixels32(0, 0, 3, settings.Length, rawSettingsTex.Rgba, 0); copy.Apply(); RenderTexture.active = oldActive; RenderTexture.ReleaseTemporary(rt); return(copy); }
/// <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 JoiningInfo ForeseeJoining(BezierSegment end, BezierSegment start, float halfThickness, float endSegmentLength) { JoiningInfo joinInfo = new JoiningInfo(); // The joining generates the vertices at both ends as well as the joining itself joinInfo.JoinPos = end.P3; joinInfo.TanAtEnd = VectorUtils.EvalTangent(end, 1.0f); joinInfo.NormAtEnd = Vector2.Perpendicular(joinInfo.TanAtEnd); joinInfo.TanAtStart = VectorUtils.EvalTangent(start, 0.0f); joinInfo.NormAtStart = Vector2.Perpendicular(joinInfo.TanAtStart); // If the tangents are continuous at the join location, we don't have // to generate a corner, we do a "simple" join by just connecting the vertices // from the two segments directly float cosAngleBetweenTans = Vector2.Dot(joinInfo.TanAtEnd, joinInfo.TanAtStart); joinInfo.SimpleJoin = Mathf.Approximately(Mathf.Abs(cosAngleBetweenTans), 1.0f); if (joinInfo.SimpleJoin) { return(null); } joinInfo.PosThicknessEnd = joinInfo.JoinPos + joinInfo.NormAtEnd * halfThickness; joinInfo.NegThicknessEnd = joinInfo.JoinPos - joinInfo.NormAtEnd * halfThickness; joinInfo.PosThicknessStart = joinInfo.JoinPos + joinInfo.NormAtStart * halfThickness; joinInfo.NegThicknessStart = joinInfo.JoinPos - joinInfo.NormAtStart * halfThickness; if (joinInfo.SimpleJoin) { joinInfo.PosThicknessClosingPoint = Vector2.LerpUnclamped(joinInfo.PosThicknessEnd, joinInfo.PosThicknessStart, 0.5f); joinInfo.NegThicknessClosingPoint = Vector2.LerpUnclamped(joinInfo.NegThicknessEnd, joinInfo.NegThicknessStart, 0.5f); } else { joinInfo.PosThicknessClosingPoint = VectorUtils.IntersectLines(joinInfo.PosThicknessEnd, joinInfo.PosThicknessEnd + joinInfo.TanAtEnd, joinInfo.PosThicknessStart, joinInfo.PosThicknessStart + joinInfo.TanAtStart); joinInfo.NegThicknessClosingPoint = VectorUtils.IntersectLines(joinInfo.NegThicknessEnd, joinInfo.NegThicknessEnd + joinInfo.TanAtEnd, joinInfo.NegThicknessStart, joinInfo.NegThicknessStart + joinInfo.TanAtStart); if (float.IsInfinity(joinInfo.PosThicknessClosingPoint.x) || float.IsInfinity(joinInfo.PosThicknessClosingPoint.y)) { joinInfo.PosThicknessClosingPoint = joinInfo.JoinPos; } if (float.IsInfinity(joinInfo.NegThicknessClosingPoint.x) || float.IsInfinity(joinInfo.NegThicknessClosingPoint.y)) { joinInfo.NegThicknessClosingPoint = joinInfo.JoinPos; } } // Should we round the positive thickness side or the negative thickness side? joinInfo.RoundPosThickness = PointOnTheLeftOfLine(Vector2.zero, joinInfo.TanAtEnd, joinInfo.TanAtStart); // Inner corner vertex should be calculated by intersection of the inner segments Vector2[] startTrail = null, endTrail = null; Vector2 intersectionOnStart = Vector2.zero, intersectionOnEnd = Vector2.zero; if (!joinInfo.SimpleJoin) { BezierSegment endFlipped = VectorUtils.FlipSegment(end); Vector2 thicknessClosingPoint = joinInfo.RoundPosThickness ? joinInfo.PosThicknessClosingPoint : joinInfo.NegThicknessClosingPoint; Vector2 meetingPoint = end.P3; Vector2 thicknessDiagonalEnd = meetingPoint + (thicknessClosingPoint - meetingPoint) * 10.0f; startTrail = LineBezierThicknessIntersect( start, joinInfo.RoundPosThickness ? -halfThickness : halfThickness, meetingPoint, thicknessDiagonalEnd, out joinInfo.InnerCornerDistFromStart, out intersectionOnStart); endTrail = LineBezierThicknessIntersect( endFlipped, joinInfo.RoundPosThickness ? halfThickness : -halfThickness, meetingPoint, thicknessDiagonalEnd, out joinInfo.InnerCornerDistToEnd, out intersectionOnEnd); } bool intersectionFound = false; if ((startTrail != null) && (endTrail != null)) { var intersect = VectorUtils.IntersectLines(startTrail[0], startTrail[1], endTrail[0], endTrail[1]); var isOnStartTrail = PointOnLineIsWithinSegment(startTrail[0], startTrail[1], intersect); var isOnEndTrail = PointOnLineIsWithinSegment(endTrail[0], endTrail[1], intersect); if (!float.IsInfinity(intersect.x) && isOnStartTrail && isOnEndTrail) { var vStart = intersectionOnStart - intersect; var vEnd = intersectionOnEnd - intersect; joinInfo.InnerCornerDistFromStart += (vStart == Vector2.zero) ? 0.0f : vStart.magnitude; joinInfo.InnerCornerDistToEnd += (vEnd == Vector2.zero) ? 0.0f : vEnd.magnitude; joinInfo.InnerCornerDistToEnd = endSegmentLength - joinInfo.InnerCornerDistToEnd; joinInfo.InnerCornerVertex = intersect; // Found it! intersectionFound = true; } } if (!intersectionFound) { joinInfo.InnerCornerVertex = joinInfo.JoinPos + ((joinInfo.TanAtStart - joinInfo.TanAtEnd) / 2.0f).normalized * halfThickness; joinInfo.InnerCornerDistFromStart = 0; joinInfo.InnerCornerDistToEnd = endSegmentLength; } return(joinInfo); }
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? }