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(); }
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); } }