blob: 09d88a4d60dcafba7bcd532504167b4a929501c5 [file] [log] [blame]
#define _USE_MATH_DEFINES
#include <cmath>
#include <algorithm>
#ifdef LOW_LEVEL_RENDERING
#include "rive/contour_stroke.hpp"
#include "rive/contour_render_path.hpp"
#include "rive/math/vec2d.hpp"
#include <assert.h>
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 ContourRenderPath* renderPath,
bool isClosed,
StrokeJoin join,
StrokeCap cap,
float strokeWidth,
const Mat2D& transform)
{
// TODO: if transform is identity, no need to copy and transform
// contourPoints->points.
const std::vector<Vec2D>& contourPoints = renderPath->contourVertices();
std::vector<Vec2D> points(contourPoints);
auto pointCount = points.size();
if (pointCount < 6)
{
return;
}
for (int i = 4; i < pointCount; i++)
{
Vec2D& point = points[i];
Vec2D::transform(point, point, transform);
}
auto startOffset = m_TriangleStrip.size();
Vec2D lastPoint = points[4];
Vec2D lastDiff;
Vec2D::subtract(lastDiff, points[5], lastPoint);
float lastLength = Vec2D::length(lastDiff);
Vec2D lastDiffNormalized;
Vec2D::scale(lastDiffNormalized, lastDiff, 1.0f / lastLength);
Vec2D lastA, lastB;
Vec2D perpendicularStrokeDiff = Vec2D(lastDiffNormalized[1] * -strokeWidth,
lastDiffNormalized[0] * strokeWidth);
Vec2D::add(lastA, lastPoint, perpendicularStrokeDiff);
Vec2D::subtract(lastB, lastPoint, perpendicularStrokeDiff);
if (!isClosed)
{
switch (cap)
{
case StrokeCap::square:
{
Vec2D squareA, squareB;
Vec2D strokeDiff = Vec2D(lastDiffNormalized[0] * strokeWidth,
lastDiffNormalized[1] * strokeWidth);
Vec2D::subtract(squareA, lastA, strokeDiff);
Vec2D::subtract(squareB, lastB, strokeDiff);
m_TriangleStrip.push_back(squareA);
m_TriangleStrip.push_back(squareB);
break;
}
case StrokeCap::round:
{
Vec2D capDirection =
Vec2D(-lastDiffNormalized[1], lastDiffNormalized[0]);
float arcLength = std::abs(M_PI * strokeWidth);
int steps = (int)std::ceil(arcLength / subdivisionArcLength);
float angleTo = std::atan2(capDirection[1], capDirection[0]);
float inc = M_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[0] + std::cos(angle) * strokeWidth,
lastPoint[1] + 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)
{
Vec2D::subtract(
diff, (next = points[((i + 1) % pointCount) + 4]), point);
length = Vec2D::length(diff);
Vec2D::scale(diffNormalized, diff, 1.0f / length);
}
else
{
diff = lastDiff;
next = point;
length = lastLength;
diffNormalized = lastDiffNormalized;
}
// perpendicular dx
float pdx0 = -lastDiffNormalized[1];
float pdy0 = lastDiffNormalized[0];
float pdx1 = -diffNormalized[1];
float pdy1 = diffNormalized[0];
// Compute bisector without a normalization by averaging perpendicular
// diffs.
Vec2D bisector((pdx0 + pdx1) * 0.5f, (pdy0 + pdy1) * 0.5f);
float cross = diff[0] * lastDiff[1] - lastDiff[0] * diff[1];
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[0] *= scale;
bisector[1] *= scale;
}
else
{
bisector[0] *= strokeWidth;
bisector[1] *= strokeWidth;
}
if (!bevel)
{
Vec2D c, d;
Vec2D::add(c, point, bisector);
Vec2D::subtract(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, c2;
Vec2D::add(c1,
point,
Vec2D(lastDiffNormalized[1] * -strokeWidth,
lastDiffNormalized[0] * strokeWidth));
Vec2D::add(c2,
point,
Vec2D(diffNormalized[1] * -strokeWidth,
diffNormalized[0] * 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, d2;
Vec2D::subtract(d1,
point,
Vec2D(lastDiffNormalized[1] * -strokeWidth,
lastDiffNormalized[0] * strokeWidth));
Vec2D::subtract(d2,
point,
Vec2D(diffNormalized[1] * -strokeWidth,
diffNormalized[0] * 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[1] * -strokeWidth,
lastDiffNormalized[0] * strokeWidth);
Vec2D dPStroke = Vec2D(diffNormalized[1] * -strokeWidth,
diffNormalized[0] * strokeWidth);
if (cross <= 0)
{
// Bevel the outer (left in this case) edge.
Vec2D a1;
Vec2D a2;
if (bevelInner)
{
Vec2D::add(a1, point, ldPStroke);
Vec2D::add(a2, point, dPStroke);
}
else
{
Vec2D::add(a1, point, bisector);
a2 = a1;
}
Vec2D b;
Vec2D::subtract(b, point, ldPStroke);
Vec2D bn;
Vec2D::subtract(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;
Vec2D::subtract(toPrev, bn, point);
Vec2D toNext;
Vec2D::subtract(toNext, b, point);
float angleFrom = std::atan2(toPrev[1], toPrev[0]);
float angleTo = std::atan2(toNext[1], toNext[0]);
if (angleTo > angleFrom)
{
angleTo -= M_2_PI;
}
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[0] + std::cos(angle) * strokeWidth,
point[1] + 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)
{
Vec2D::subtract(b1, point, ldPStroke);
Vec2D::subtract(b2, point, dPStroke);
}
else
{
Vec2D::subtract(b1, point, bisector);
b2 = b1;
}
Vec2D a;
Vec2D::add(a, point, ldPStroke);
Vec2D an;
Vec2D::add(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;
Vec2D::subtract(toPrev, a, point);
Vec2D toNext;
Vec2D::subtract(toNext, an, point);
float angleFrom = std::atan2(toPrev[1], toPrev[0]);
float angleTo = std::atan2(toNext[1], toNext[0]);
if (angleTo > angleFrom)
{
angleTo -= M_2_PI;
}
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[0] + std::cos(angle) * strokeWidth,
point[1] + 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 squareA, squareB;
Vec2D strokeDiff = Vec2D(lastDiffNormalized[0] * strokeWidth,
lastDiffNormalized[1] * strokeWidth);
Vec2D::add(squareA, m_TriangleStrip[l - 2], strokeDiff);
Vec2D::add(squareB, m_TriangleStrip[l - 1], strokeDiff);
m_TriangleStrip.push_back(squareA);
m_TriangleStrip.push_back(squareB);
break;
}
case StrokeCap::round:
{
Vec2D capDirection =
Vec2D(-lastDiffNormalized[1], lastDiffNormalized[0]);
float arcLength = std::abs(M_PI * strokeWidth);
int steps = (int)std::ceil(arcLength / subdivisionArcLength);
float angleTo = std::atan2(capDirection[1], capDirection[0]);
float inc = M_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[0] + std::cos(angle) * strokeWidth,
lastPoint[1] + std::sin(angle) * strokeWidth));
angle -= inc;
}
break;
}
default:
break;
}
}
m_Offsets.push_back(m_TriangleStrip.size());
}
#endif