blob: 653cd1996a434d5ad5e1c2bce4876a5c475b6fef [file] [log] [blame]
#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; }
void ContourStroke::nextRenderOffset(std::size_t& start, std::size_t& end) {
assert(m_RenderOffset < m_Offsets.size());
start = m_RenderOffset == 0 ? 0 : m_Offsets[m_RenderOffset - 1];
end = m_Offsets[m_RenderOffset++];
}
void ContourStroke::extrude(const SegmentedContour* contour,
bool isClosed,
StrokeJoin join,
StrokeCap cap,
float strokeWidth,
const Mat2D& transform) {
// TODO: if transform is identity, no need to copy and transform
// contourPoints->points.
auto contourPoints = contour->contourPoints();
std::vector<Vec2D> points(contourPoints.begin(), contourPoints.end());
auto pointCount = points.size();
if (pointCount < 6) {
return;
}
for (int i = 4; i < pointCount; i++) {
Vec2D& point = points[i];
point = transform * point; // point, transform);
}
auto startOffset = m_TriangleStrip.size();
Vec2D lastPoint = points[4];
Vec2D lastDiff = points[5] - 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 ? 6 : 5;
std::size_t adjustedPointCount = isClosed ? pointCount + 1 : pointCount;
for (std::size_t i = 1; i < adjustedPointCount; i++) {
const Vec2D& point = points[(i % pointCount) + 4];
Vec2D diff, diffNormalized, next;
float length;
if (i < adjustedPointCount - 1 || isClosed) {
diff = (next = points[((i + 1) % pointCount) + 4]) - 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());
}