blob: 0b11b30a9c23d9c94da27821af18b08713d8c774 [file] [log] [blame]
#include "rive/shapes/path.hpp"
#include "rive/math/circle_constant.hpp"
#include "rive/renderer.hpp"
#include "rive/shapes/cubic_vertex.hpp"
#include "rive/shapes/cubic_detached_vertex.hpp"
#include "rive/shapes/path_vertex.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/shapes/straight_vertex.hpp"
#include <cassert>
using namespace rive;
StatusCode Path::onAddedClean(CoreContext* context) {
StatusCode code = Super::onAddedClean(context);
if (code != StatusCode::Ok) {
return code;
}
// Find the shape.
for (auto currentParent = parent(); currentParent != nullptr;
currentParent = currentParent->parent())
{
if (currentParent->is<Shape>()) {
m_Shape = currentParent->as<Shape>();
m_Shape->addPath(this);
return StatusCode::Ok;
}
}
return StatusCode::MissingObject;
}
void Path::buildDependencies() {
Super::buildDependencies();
// Make sure this is called once the shape has all of the paints added
// (paints get added during the added cycle so buildDependencies is a good
// time to do this.)
m_CommandPath = m_Shape->makeCommandPath(PathSpace::Neither);
}
void Path::addVertex(PathVertex* vertex) { m_Vertices.push_back(vertex); }
const Mat2D& Path::pathTransform() const { return worldTransform(); }
void Path::buildPath(CommandPath& commandPath) const {
const bool isClosed = isPathClosed();
const std::vector<PathVertex*>& vertices = m_Vertices;
auto length = vertices.size();
if (length < 2) {
return;
}
auto firstPoint = vertices[0];
// Init out to translation
Vec2D out;
bool prevIsCubic;
Vec2D start, startIn;
bool startIsCubic;
if (firstPoint->is<CubicVertex>()) {
auto cubic = firstPoint->as<CubicVertex>();
startIsCubic = prevIsCubic = true;
startIn = cubic->renderIn();
out = cubic->renderOut();
start = cubic->renderTranslation();
commandPath.move(start);
} else {
startIsCubic = prevIsCubic = false;
auto point = *firstPoint->as<StraightVertex>();
if (auto radius = point.radius(); radius > 0.0f) {
auto prev = vertices[length - 1];
Vec2D pos = point.renderTranslation();
Vec2D toPrev = (prev->is<CubicVertex>() ? prev->as<CubicVertex>()->renderOut()
: prev->renderTranslation()) -
pos;
auto toPrevLength = toPrev.length();
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
auto next = vertices[1];
Vec2D toNext = (next->is<CubicVertex>() ? next->as<CubicVertex>()->renderIn()
: next->renderTranslation()) -
pos;
auto toNextLength = toNext.length();
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
float renderRadius = std::min(toPrevLength, std::min(toNextLength, radius));
startIn = start = Vec2D::scaleAndAdd(pos, toPrev, renderRadius);
commandPath.move(startIn);
Vec2D outPoint = Vec2D::scaleAndAdd(pos, toPrev, icircleConstant * renderRadius);
Vec2D inPoint = Vec2D::scaleAndAdd(pos, toNext, icircleConstant * renderRadius);
out = Vec2D::scaleAndAdd(pos, toNext, renderRadius);
commandPath.cubic(outPoint, inPoint, out);
prevIsCubic = false;
} else {
startIn = start = out = point.renderTranslation();
commandPath.move(out);
}
}
for (size_t i = 1; i < length; i++) {
auto vertex = vertices[i];
if (vertex->is<CubicVertex>()) {
auto cubic = vertex->as<CubicVertex>();
auto inPoint = cubic->renderIn();
auto translation = cubic->renderTranslation();
commandPath.cubic(out, inPoint, translation);
prevIsCubic = true;
out = cubic->renderOut();
} else {
auto point = *vertex->as<StraightVertex>();
Vec2D pos = point.renderTranslation();
if (auto radius = point.radius(); radius > 0.0f) {
Vec2D toPrev = out - pos;
auto toPrevLength = toPrev.length();
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
auto next = vertices[(i + 1) % length];
Vec2D toNext = (next->is<CubicVertex>() ? next->as<CubicVertex>()->renderIn()
: next->renderTranslation()) -
pos;
auto toNextLength = toNext.length();
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
float renderRadius = std::min(toPrevLength, std::min(toNextLength, radius));
Vec2D translation = Vec2D::scaleAndAdd(pos, toPrev, renderRadius);
if (prevIsCubic) {
commandPath.cubic(out, translation, translation);
} else {
commandPath.line(translation);
}
Vec2D outPoint = Vec2D::scaleAndAdd(pos, toPrev, icircleConstant * renderRadius);
Vec2D inPoint = Vec2D::scaleAndAdd(pos, toNext, icircleConstant * renderRadius);
out = Vec2D::scaleAndAdd(pos, toNext, renderRadius);
commandPath.cubic(outPoint, inPoint, out);
prevIsCubic = false;
} else if (prevIsCubic) {
commandPath.cubic(out, pos, pos);
prevIsCubic = false;
out = pos;
} else {
out = pos;
commandPath.line(out);
}
}
}
if (isClosed) {
if (prevIsCubic || startIsCubic) {
commandPath.cubic(out, startIn, start);
} else {
commandPath.line(start);
}
commandPath.close();
}
}
void Path::markPathDirty() {
addDirt(ComponentDirt::Path);
if (m_Shape != nullptr) {
m_Shape->pathChanged();
}
}
void Path::onDirty(ComponentDirt value) {
if (hasDirt(value, ComponentDirt::WorldTransform) && m_Shape != nullptr) {
m_Shape->pathChanged();
}
}
void Path::update(ComponentDirt value) {
Super::update(value);
assert(m_CommandPath != nullptr);
if (hasDirt(value, ComponentDirt::Path)) {
// Build path doesn't explicitly reset because we use it to concatenate
// multiple built paths into a single command path (like the hit
// tester).
m_CommandPath->reset();
buildPath(*m_CommandPath);
}
// if (hasDirt(value, ComponentDirt::WorldTransform) && m_Shape != nullptr)
// {
// // Make sure the path composer has an opportunity to rebuild the path
// // (this is why the composer depends on the shape and all its paths,
// // ascertaning it updates after both)
// m_Shape->pathChanged();
// }
}
#ifdef ENABLE_QUERY_FLAT_VERTICES
class DisplayCubicVertex : public CubicVertex {
public:
DisplayCubicVertex(const Vec2D& in, const Vec2D& out, const Vec2D& translation)
{
m_InPoint = in;
m_OutPoint = out;
m_InValid = true;
m_OutValid = true;
x(translation[0]);
y(translation[1]);
}
void computeIn() override {}
void computeOut() override {}
};
FlattenedPath* Path::makeFlat(bool transformToParent) {
if (m_Vertices.empty()) {
return nullptr;
}
// Path transform always puts the path into world space.
auto transform = pathTransform();
if (transformToParent && parent()->is<TransformComponent>()) {
// Put the transform in parent space.
auto world = parent()->as<TransformComponent>()->worldTransform();
transform = world.invertOrIdentity() * transform;
}
FlattenedPath* flat = new FlattenedPath();
auto length = m_Vertices.size();
PathVertex* previous = isPathClosed() ? m_Vertices[length - 1] : nullptr;
bool deletePrevious = false;
for (size_t i = 0; i < length; i++) {
auto vertex = m_Vertices[i];
switch (vertex->coreType()) {
case StraightVertex::typeKey: {
auto point = *vertex->as<StraightVertex>();
if (point.radius() > 0.0f && (isPathClosed() || (i != 0 && i != length - 1))) {
auto next = m_Vertices[(i + 1) % length];
Vec2D prevPoint = previous->is<CubicVertex>()
? previous->as<CubicVertex>()->renderOut()
: previous->renderTranslation();
Vec2D nextPoint = next->is<CubicVertex>() ? next->as<CubicVertex>()->renderIn()
: next->renderTranslation();
Vec2D pos = point.renderTranslation();
Vec2D toPrev = prevPoint - pos;
auto toPrevLength = toPrev.length();
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
Vec2D toNext = nextPoint - pos;
auto toNextLength = toNext.length();
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
auto renderRadius =
std::min(toPrevLength, std::min(toNextLength, point.radius()));
Vec2D translation = Vec2D::scaleAndAdd(pos, toPrev, renderRadius);
Vec2D out = Vec2D::scaleAndAdd(pos, toPrev, icircleConstant * renderRadius);
{
auto v1 = new DisplayCubicVertex(translation, out, translation);
flat->addVertex(v1, transform);
delete v1;
}
translation = Vec2D::scaleAndAdd(pos, toNext, renderRadius);
Vec2D in = Vec2D::scaleAndAdd(pos, toNext, icircleConstant * renderRadius);
auto v2 = new DisplayCubicVertex(in, translation, translation);
flat->addVertex(v2, transform);
if (deletePrevious) {
delete previous;
}
previous = v2;
deletePrevious = true;
break;
}
}
default:
if (deletePrevious) {
delete previous;
}
previous = vertex;
deletePrevious = false;
flat->addVertex(previous, transform);
break;
}
}
if (deletePrevious) {
delete previous;
}
return flat;
}
void FlattenedPath::addVertex(PathVertex* vertex, const Mat2D& transform) {
// To make this easy and relatively clean we just transform the vertices.
// Requires the vertex to be passed in as a clone.
if (vertex->is<CubicVertex>()) {
auto cubic = vertex->as<CubicVertex>();
// Cubics need to be transformed so we create a Display version which
// has set in/out values.
const auto in = transform * cubic->renderIn();
const auto out = transform * cubic->renderOut();
const auto translation = transform * cubic->renderTranslation();
auto displayCubic = new DisplayCubicVertex(in, out, translation);
m_Vertices.push_back(displayCubic);
} else {
auto point = new PathVertex();
Vec2D translation = transform * vertex->renderTranslation();
point->x(translation[0]);
point->y(translation[1]);
m_Vertices.push_back(point);
}
}
FlattenedPath::~FlattenedPath() {
for (auto vertex : m_Vertices) {
delete vertex;
}
}
#endif