blob: 651eb79d62deb80372f53b8be61f59eb4dedd1c9 [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "rive/math/raw_path.hpp"
#include <cmath>
using namespace rive;
AABB RawPath::bounds() const {
if (this->empty()) {
return {0, 0, 0, 0};
}
float l, t, r, b;
l = r = m_Points[0].x();
t = b = m_Points[0].y();
for (size_t i = 1; i < m_Points.size(); ++i) {
const float x = m_Points[i].x();
const float y = m_Points[i].y();
l = std::min(l, x);
r = std::max(r, x);
t = std::min(t, y);
b = std::max(b, y);
}
return {l, t, r, b};
}
void RawPath::move(Vec2D a) {
const auto n = m_Verbs.size();
if (n > 0 && m_Verbs[n - 1] == PathVerb::move) {
m_Points[n - 1] = a; // replace previous move position
} else {
m_Points.push_back(a);
m_Verbs.push_back(PathVerb::move);
}
}
void RawPath::line(Vec2D a) {
m_Points.push_back(a);
m_Verbs.push_back(PathVerb::line);
}
void RawPath::quad(Vec2D a, Vec2D b) {
m_Points.push_back(a);
m_Points.push_back(b);
m_Verbs.push_back(PathVerb::quad);
}
void RawPath::cubic(Vec2D a, Vec2D b, Vec2D c) {
m_Points.push_back(a);
m_Points.push_back(b);
m_Points.push_back(c);
m_Verbs.push_back(PathVerb::cubic);
}
void RawPath::close() {
const auto n = m_Verbs.size();
if (n > 0 && m_Verbs[n - 1] != PathVerb::close) {
m_Verbs.push_back(PathVerb::close);
}
}
RawPath RawPath::transform(const Mat2D& m) const {
RawPath path;
path.m_Verbs = m_Verbs;
path.m_Points.resize(m_Points.size());
for (size_t i = 0; i < m_Points.size(); ++i) {
const float x = m_Points[i].x();
const float y = m_Points[i].y();
path.m_Points[i] = {
m[0] * x + m[2] * y + m[4],
m[1] * x + m[3] * y + m[5],
};
}
return path;
}
void RawPath::transformInPlace(const Mat2D& m) {
for (auto& p : m_Points) {
const float x = p.x();
const float y = p.y();
p = {
m[0] * x + m[2] * y + m[4],
m[1] * x + m[3] * y + m[5],
};
}
}
void RawPath::addRect(const AABB& r, PathDirection dir) {
// We manually close the rectangle, in case we want to stroke
// this path. We also call close() so we get proper joins
// (and not caps).
m_Points.reserve(5);
m_Verbs.reserve(6);
moveTo(r.left(), r.top());
if (dir == PathDirection::clockwise) {
lineTo(r.right(), r.top());
lineTo(r.right(), r.bottom());
lineTo(r.left(), r.bottom());
} else {
lineTo(r.left(), r.bottom());
lineTo(r.right(), r.bottom());
lineTo(r.right(), r.top());
}
close();
}
void RawPath::addOval(const AABB& r, PathDirection dir) {
// see https://spencermortensen.com/articles/bezier-circle/
constexpr float C = 0.5519150244935105707435627f;
// precompute clockwise unit circle, starting and ending at {1, 0}
constexpr rive::Vec2D unit[] = {
{1, 0},
{1, C},
{C, 1}, // quadrant 1 ( 4:30)
{0, 1},
{-C, 1},
{-1, C}, // quadrant 2 ( 7:30)
{-1, 0},
{-1, -C},
{-C, -1}, // quadrant 3 (10:30)
{0, -1},
{C, -1},
{1, -C}, // quadrant 4 ( 1:30)
{1, 0},
};
const auto center = r.center();
const float dx = center.x();
const float dy = center.y();
const float sx = r.width() * 0.5f;
const float sy = r.height() * 0.5f;
auto map = [dx, dy, sx, sy](rive::Vec2D p) {
return rive::Vec2D(p.x() * sx + dx, p.y() * sy + dy);
};
m_Points.reserve(13);
m_Verbs.reserve(6);
if (dir == PathDirection::clockwise) {
move(map(unit[0]));
for (int i = 1; i <= 12; i += 3) {
cubic(map(unit[i + 0]), map(unit[i + 1]), map(unit[i + 2]));
}
} else {
move(map(unit[12]));
for (int i = 11; i >= 0; i -= 3) {
cubic(map(unit[i - 0]), map(unit[i - 1]), map(unit[i - 2]));
}
}
close();
}
void RawPath::addPoly(Span<const Vec2D> span, bool isClosed) {
if (span.size() == 0) {
return;
}
// should we permit must moveTo() or just moveTo()/close() ?
m_Points.reserve(span.size() + isClosed);
m_Verbs.reserve(span.size() + isClosed);
move(span[0]);
for (size_t i = 1; i < span.size(); ++i) {
line(span[i]);
}
if (isClosed) {
close();
}
}