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