| #include "rive/math/math_types.hpp" | |
| #include "rive/tess/contour_stroke.hpp" | |
| #include "rive/tess/segmented_contour.hpp" | |
| #include "rive/math/vec2d.hpp" | |
| #include <assert.h> | |
| #include <algorithm> | |
| using namespace rive; | |
| static const int subdivisionArcLength = 4.0f; | |
| void ContourStroke::reset() | |
| { | |
| m_TriangleStrip.clear(); | |
| m_Offsets.clear(); | |
| } | |
| void ContourStroke::resetRenderOffset() { m_RenderOffset = 0; } | |
| bool ContourStroke::nextRenderOffset(std::size_t& start, std::size_t& end) | |
| { | |
| if (m_RenderOffset == m_Offsets.size()) | |
| { | |
| return false; | |
| } | |
| start = m_RenderOffset == 0 ? 0 : m_Offsets[m_RenderOffset - 1]; | |
| end = m_Offsets[m_RenderOffset++]; | |
| return true; | |
| } | |
| void ContourStroke::extrude(const SegmentedContour* contour, | |
| bool isClosed, | |
| StrokeJoin join, | |
| StrokeCap cap, | |
| float strokeWidth) | |
| { | |
| auto contourPoints = contour->contourPoints(); | |
| std::vector<Vec2D> points(contourPoints.begin(), contourPoints.end()); | |
| auto pointCount = points.size(); | |
| if (pointCount < 2) | |
| { | |
| return; | |
| } | |
| auto startOffset = m_TriangleStrip.size(); | |
| Vec2D lastPoint = points[0]; | |
| Vec2D lastDiff = points[1] - lastPoint; | |
| float lastLength = lastDiff.length(); | |
| Vec2D lastDiffNormalized = lastDiff / lastLength; | |
| Vec2D perpendicularStrokeDiff = | |
| Vec2D(lastDiffNormalized.y * -strokeWidth, lastDiffNormalized.x * strokeWidth); | |
| Vec2D lastA = lastPoint + perpendicularStrokeDiff; | |
| Vec2D lastB = lastPoint - perpendicularStrokeDiff; | |
| if (!isClosed) | |
| { | |
| switch (cap) | |
| { | |
| case StrokeCap::square: | |
| { | |
| Vec2D strokeDiff = lastDiffNormalized * strokeWidth; | |
| Vec2D squareA = lastA - strokeDiff; | |
| Vec2D squareB = lastB - strokeDiff; | |
| m_TriangleStrip.push_back(squareA); | |
| m_TriangleStrip.push_back(squareB); | |
| break; | |
| } | |
| case StrokeCap::round: | |
| { | |
| Vec2D capDirection = Vec2D(-lastDiffNormalized.y, lastDiffNormalized.x); | |
| float arcLength = std::abs(math::PI * strokeWidth); | |
| int steps = (int)std::ceil(arcLength / subdivisionArcLength); | |
| float angleTo = std::atan2(capDirection.y, capDirection.x); | |
| float inc = math::PI / steps; | |
| float angle = angleTo; | |
| // make sure to draw the full cap due triangle strip | |
| for (int j = 0; j <= steps; j++) | |
| { | |
| m_TriangleStrip.push_back(lastPoint); | |
| m_TriangleStrip.push_back(Vec2D(lastPoint.x + std::cos(angle) * strokeWidth, | |
| lastPoint.y + std::sin(angle) * strokeWidth)); | |
| angle += inc; | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| m_TriangleStrip.push_back(lastA); | |
| m_TriangleStrip.push_back(lastB); | |
| pointCount -= isClosed ? 1 : 0; | |
| std::size_t adjustedPointCount = isClosed ? pointCount + 1 : pointCount; | |
| for (std::size_t i = 1; i < adjustedPointCount; i++) | |
| { | |
| const Vec2D& point = points[i % pointCount]; | |
| Vec2D diff, diffNormalized, next; | |
| float length; | |
| if (i < adjustedPointCount - 1 || isClosed) | |
| { | |
| diff = (next = points[(i + 1) % pointCount]) - point; | |
| length = diff.length(); | |
| diffNormalized = diff / length; | |
| } | |
| else | |
| { | |
| diff = lastDiff; | |
| next = point; | |
| length = lastLength; | |
| diffNormalized = lastDiffNormalized; | |
| } | |
| // perpendicular dx | |
| float pdx0 = -lastDiffNormalized.y; | |
| float pdy0 = lastDiffNormalized.x; | |
| float pdx1 = -diffNormalized.y; | |
| float pdy1 = diffNormalized.x; | |
| // Compute bisector without a normalization by averaging perpendicular | |
| // diffs. | |
| Vec2D bisector((pdx0 + pdx1) * 0.5f, (pdy0 + pdy1) * 0.5f); | |
| float cross = Vec2D::cross(diff, lastDiff); | |
| float dot = Vec2D::dot(bisector, bisector); | |
| float lengthLimit = std::min(length, lastLength); | |
| bool bevelInner = false; | |
| bool bevel = join == StrokeJoin::miter ? dot < 0.1f : dot < 0.999f; | |
| // Scale bisector to match stroke size. | |
| if (dot > 0.000001f) | |
| { | |
| float scale = 1.0f / dot * strokeWidth; | |
| float limit = lengthLimit / strokeWidth; | |
| if (dot * limit * limit < 1.0f) | |
| { | |
| bevelInner = true; | |
| } | |
| bisector *= scale; | |
| } | |
| else | |
| { | |
| bisector *= strokeWidth; | |
| } | |
| if (!bevel) | |
| { | |
| Vec2D c = point + bisector; | |
| Vec2D d = point - bisector; | |
| if (!bevelInner) | |
| { | |
| // Normal mitered edge. | |
| m_TriangleStrip.push_back(c); | |
| m_TriangleStrip.push_back(d); | |
| } | |
| else if (cross <= 0) | |
| { | |
| // Overlap the inner (in this case right) edge (sometimes called | |
| // miter inner). | |
| Vec2D c1 = point + Vec2D(lastDiffNormalized.y * -strokeWidth, | |
| lastDiffNormalized.x * strokeWidth); | |
| Vec2D c2 = | |
| point + Vec2D(diffNormalized.y * -strokeWidth, diffNormalized.x * strokeWidth); | |
| m_TriangleStrip.push_back(c1); | |
| m_TriangleStrip.push_back(d); | |
| m_TriangleStrip.push_back(c2); | |
| m_TriangleStrip.push_back(d); | |
| } | |
| else | |
| { | |
| // Overlap the inner (in this case left) edge (sometimes called | |
| // miter inner). | |
| Vec2D d1 = point - Vec2D(lastDiffNormalized.y * -strokeWidth, | |
| lastDiffNormalized.x * strokeWidth); | |
| Vec2D d2 = | |
| point - Vec2D(diffNormalized.y * -strokeWidth, diffNormalized.x * strokeWidth); | |
| m_TriangleStrip.push_back(c); | |
| m_TriangleStrip.push_back(d1); | |
| m_TriangleStrip.push_back(c); | |
| m_TriangleStrip.push_back(d2); | |
| } | |
| } | |
| else | |
| { | |
| Vec2D ldPStroke = | |
| Vec2D(lastDiffNormalized.y * -strokeWidth, lastDiffNormalized.x * strokeWidth); | |
| Vec2D dPStroke = Vec2D(diffNormalized.y * -strokeWidth, diffNormalized.x * strokeWidth); | |
| if (cross <= 0) | |
| { | |
| // Bevel the outer (left in this case) edge. | |
| Vec2D a1; | |
| Vec2D a2; | |
| if (bevelInner) | |
| { | |
| a1 = point + ldPStroke; | |
| a2 = point + dPStroke; | |
| } | |
| else | |
| { | |
| a1 = point + bisector; | |
| a2 = a1; | |
| } | |
| Vec2D b = point - ldPStroke; | |
| Vec2D bn = point - dPStroke; | |
| m_TriangleStrip.push_back(a1); | |
| m_TriangleStrip.push_back(b); | |
| if (join == StrokeJoin::round) | |
| { | |
| const Vec2D& pivot = bevelInner ? point : a1; | |
| Vec2D toPrev = bn - point; | |
| Vec2D toNext = b - point; | |
| float angleFrom = std::atan2(toPrev.y, toPrev.x); | |
| float angleTo = std::atan2(toNext.y, toNext.x); | |
| if (angleTo > angleFrom) | |
| { | |
| angleTo -= math::PI * 2.0f; | |
| } | |
| float range = angleTo - angleFrom; | |
| float arcLength = std::abs(range * strokeWidth); | |
| int steps = std::ceil(arcLength / subdivisionArcLength); | |
| float inc = range / steps; | |
| float angle = angleTo - inc; | |
| for (int j = 0; j < steps - 1; j++) | |
| { | |
| m_TriangleStrip.push_back(pivot); | |
| m_TriangleStrip.emplace_back( | |
| Vec2D(point.x + std::cos(angle) * strokeWidth, | |
| point.y + std::sin(angle) * strokeWidth)); | |
| angle -= inc; | |
| } | |
| } | |
| m_TriangleStrip.push_back(a2); | |
| m_TriangleStrip.push_back(bn); | |
| } | |
| else | |
| { | |
| // Bevel the outer (right in this case) edge. | |
| Vec2D b1; | |
| Vec2D b2; | |
| if (bevelInner) | |
| { | |
| b1 = point - ldPStroke; | |
| b2 = point - dPStroke; | |
| } | |
| else | |
| { | |
| b1 = point - bisector; | |
| b2 = b1; | |
| } | |
| Vec2D a = point + ldPStroke; | |
| Vec2D an = point + dPStroke; | |
| m_TriangleStrip.push_back(a); | |
| m_TriangleStrip.push_back(b1); | |
| if (join == StrokeJoin::round) | |
| { | |
| const Vec2D& pivot = bevelInner ? point : b1; | |
| Vec2D toPrev = a - point; | |
| Vec2D toNext = an - point; | |
| float angleFrom = std::atan2(toPrev.y, toPrev.x); | |
| float angleTo = std::atan2(toNext.y, toNext.x); | |
| if (angleTo > angleFrom) | |
| { | |
| angleTo -= math::PI * 2.0f; | |
| } | |
| float range = angleTo - angleFrom; | |
| float arcLength = std::abs(range * strokeWidth); | |
| int steps = std::ceil(arcLength / subdivisionArcLength); | |
| float inc = range / steps; | |
| float angle = angleFrom + inc; | |
| for (int j = 0; j < steps - 1; j++) | |
| { | |
| m_TriangleStrip.emplace_back( | |
| Vec2D(point.x + std::cos(angle) * strokeWidth, | |
| point.y + std::sin(angle) * strokeWidth)); | |
| m_TriangleStrip.push_back(pivot); | |
| angle += inc; | |
| } | |
| } | |
| m_TriangleStrip.push_back(an); | |
| m_TriangleStrip.push_back(b2); | |
| } | |
| } | |
| lastPoint = point; | |
| lastDiff = diff; | |
| lastDiffNormalized = diffNormalized; | |
| } | |
| if (isClosed) | |
| { | |
| auto last = m_TriangleStrip.size() - 1; | |
| m_TriangleStrip[startOffset] = m_TriangleStrip[last - 1]; | |
| m_TriangleStrip[startOffset + 1] = m_TriangleStrip[last]; | |
| } | |
| else | |
| { | |
| switch (cap) | |
| { | |
| case StrokeCap::square: | |
| { | |
| auto l = m_TriangleStrip.size(); | |
| Vec2D strokeDiff = lastDiffNormalized * strokeWidth; | |
| Vec2D squareA = m_TriangleStrip[l - 2] + strokeDiff; | |
| Vec2D squareB = m_TriangleStrip[l - 1] + strokeDiff; | |
| m_TriangleStrip.push_back(squareA); | |
| m_TriangleStrip.push_back(squareB); | |
| break; | |
| } | |
| case StrokeCap::round: | |
| { | |
| Vec2D capDirection = Vec2D(-lastDiffNormalized.y, lastDiffNormalized.x); | |
| float arcLength = std::abs(math::PI * strokeWidth); | |
| int steps = (int)std::ceil(arcLength / subdivisionArcLength); | |
| float angleTo = std::atan2(capDirection.y, capDirection.x); | |
| float inc = math::PI / steps; | |
| float angle = angleTo; | |
| // make sure to draw the full cap due triangle strip | |
| for (int j = 0; j <= steps; j++) | |
| { | |
| m_TriangleStrip.push_back(lastPoint); | |
| m_TriangleStrip.push_back(Vec2D(lastPoint.x + std::cos(angle) * strokeWidth, | |
| lastPoint.y + std::sin(angle) * strokeWidth)); | |
| angle -= inc; | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| m_Offsets.push_back(m_TriangleStrip.size()); | |
| } |