| #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 |