blob: e201c9813deeb6d50288594be24c005a57c31513 [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;
Path::~Path() { delete m_CommandPath; }
StatusCode Path::onAddedDirty(CoreContext* context)
{
StatusCode code = Super::onAddedDirty(context);
if (code != StatusCode::Ok)
{
return code;
}
return StatusCode::Ok;
}
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(); }
static void buildPath(CommandPath& commandPath,
bool isClosed,
const std::vector<PathVertex*>& vertices)
{
commandPath.reset();
auto length = vertices.size();
if (length < 2)
{
return;
}
auto firstPoint = vertices[0];
// Init out to translation
float outX, outY;
bool prevIsCubic;
float startX, startY;
float startInX, startInY;
bool startIsCubic;
if (firstPoint->is<CubicVertex>())
{
auto cubic = firstPoint->as<CubicVertex>();
startIsCubic = prevIsCubic = true;
auto inPoint = cubic->renderIn();
startInX = inPoint[0];
startInY = inPoint[1];
auto outPoint = cubic->renderOut();
outX = outPoint[0];
outY = outPoint[1];
auto translation = cubic->renderTranslation();
commandPath.moveTo(startX = translation[0], startY = translation[1]);
}
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;
Vec2D::subtract(toPrev,
prev->is<CubicVertex>()
? prev->as<CubicVertex>()->renderOut()
: prev->renderTranslation(),
pos);
auto toPrevLength = Vec2D::length(toPrev);
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
auto next = vertices[1];
Vec2D toNext;
Vec2D::subtract(toNext,
next->is<CubicVertex>()
? next->as<CubicVertex>()->renderIn()
: next->renderTranslation(),
pos);
auto toNextLength = Vec2D::length(toNext);
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
float renderRadius =
std::min(toPrevLength, std::min(toNextLength, radius));
Vec2D translation;
Vec2D::scaleAndAdd(translation, pos, toPrev, renderRadius);
commandPath.moveTo(startInX = startX = translation[0],
startInY = startY = translation[1]);
Vec2D outPoint;
Vec2D::scaleAndAdd(
outPoint, pos, toPrev, icircleConstant * renderRadius);
Vec2D inPoint;
Vec2D::scaleAndAdd(
inPoint, pos, toNext, icircleConstant * renderRadius);
Vec2D posNext;
Vec2D::scaleAndAdd(posNext, pos, toNext, renderRadius);
commandPath.cubicTo(outPoint[0],
outPoint[1],
inPoint[0],
inPoint[1],
outX = posNext[0],
outY = posNext[1]);
prevIsCubic = false;
}
else
{
auto translation = point.renderTranslation();
commandPath.moveTo(startInX = startX = outX = translation[0],
startInY = startY = outY = translation[1]);
}
}
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.cubicTo(outX,
outY,
inPoint[0],
inPoint[1],
translation[0],
translation[1]);
prevIsCubic = true;
auto outPoint = cubic->renderOut();
outX = outPoint[0];
outY = outPoint[1];
}
else
{
auto point = *vertex->as<StraightVertex>();
Vec2D pos = point.renderTranslation();
if (auto radius = point.radius(); radius > 0.0f)
{
Vec2D toPrev;
Vec2D::subtract(toPrev, Vec2D(outX, outY), pos);
auto toPrevLength = Vec2D::length(toPrev);
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
auto next = vertices[(i + 1) % length];
Vec2D toNext;
Vec2D::subtract(toNext,
next->is<CubicVertex>()
? next->as<CubicVertex>()->renderIn()
: next->renderTranslation(),
pos);
auto toNextLength = Vec2D::length(toNext);
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
float renderRadius =
std::min(toPrevLength, std::min(toNextLength, radius));
Vec2D translation;
Vec2D::scaleAndAdd(translation, pos, toPrev, renderRadius);
if (prevIsCubic)
{
commandPath.cubicTo(outX,
outY,
translation[0],
translation[1],
translation[0],
translation[1]);
}
else
{
commandPath.lineTo(translation[0], translation[1]);
}
Vec2D outPoint;
Vec2D::scaleAndAdd(
outPoint, pos, toPrev, icircleConstant * renderRadius);
Vec2D inPoint;
Vec2D::scaleAndAdd(
inPoint, pos, toNext, icircleConstant * renderRadius);
Vec2D posNext;
Vec2D::scaleAndAdd(posNext, pos, toNext, renderRadius);
commandPath.cubicTo(outPoint[0],
outPoint[1],
inPoint[0],
inPoint[1],
outX = posNext[0],
outY = posNext[1]);
prevIsCubic = false;
}
else if (prevIsCubic)
{
float x = pos[0];
float y = pos[1];
commandPath.cubicTo(outX, outY, x, y, x, y);
prevIsCubic = false;
outX = x;
outY = y;
}
else
{
commandPath.lineTo(outX = pos[0], outY = pos[1]);
}
}
}
if (isClosed)
{
if (prevIsCubic || startIsCubic)
{
commandPath.cubicTo(outX, outY, startInX, startInY, startX, startY);
}
else
{
commandPath.lineTo(startX, startY);
}
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))
{
buildPath(*m_CommandPath, isPathClosed(), m_Vertices);
}
// 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();
Mat2D inverseWorld;
if (!Mat2D::invert(inverseWorld, world))
{
Mat2D::identity(inverseWorld);
}
Mat2D::multiply(transform, inverseWorld, 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;
Vec2D::subtract(toPrev, prevPoint, pos);
auto toPrevLength = Vec2D::length(toPrev);
toPrev[0] /= toPrevLength;
toPrev[1] /= toPrevLength;
Vec2D toNext;
Vec2D::subtract(toNext, nextPoint, pos);
auto toNextLength = Vec2D::length(toNext);
toNext[0] /= toNextLength;
toNext[1] /= toNextLength;
auto renderRadius = std::min(
toPrevLength, std::min(toNextLength, point.radius()));
Vec2D translation;
Vec2D::scaleAndAdd(translation, pos, toPrev, renderRadius);
Vec2D out;
Vec2D::scaleAndAdd(
out, pos, toPrev, icircleConstant * renderRadius);
{
auto v1 = new DisplayCubicVertex(
translation, out, translation);
flat->addVertex(v1, transform);
delete v1;
}
Vec2D::scaleAndAdd(translation, pos, toNext, renderRadius);
Vec2D in;
Vec2D::scaleAndAdd(
in, 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.
Vec2D in, out, translation;
Vec2D::transform(in, cubic->renderIn(), transform);
Vec2D::transform(out, cubic->renderOut(), transform);
Vec2D::transform(translation, cubic->renderTranslation(), transform);
auto displayCubic = new DisplayCubicVertex(in, out, translation);
m_Vertices.push_back(displayCubic);
}
else
{
auto point = new PathVertex();
Vec2D translation;
Vec2D::transform(translation, vertex->renderTranslation(), transform);
point->x(translation[0]);
point->y(translation[1]);
m_Vertices.push_back(point);
}
}
FlattenedPath::~FlattenedPath()
{
for (auto vertex : m_Vertices)
{
delete vertex;
}
}
#endif