| #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 |
| 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 = (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)); |
| |
| Vec2D translation = Vec2D::scaleAndAdd(pos, toPrev, renderRadius); |
| commandPath.moveTo(startInX = startX = translation[0], |
| startInY = startY = translation[1]); |
| |
| Vec2D outPoint = Vec2D::scaleAndAdd(pos, toPrev, icircleConstant * renderRadius); |
| Vec2D inPoint = Vec2D::scaleAndAdd(pos, toNext, icircleConstant * renderRadius); |
| Vec2D posNext = Vec2D::scaleAndAdd(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(outX, outY) - 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.cubicTo( |
| outX, outY, translation[0], translation[1], translation[0], translation[1]); |
| } else { |
| commandPath.lineTo(translation[0], translation[1]); |
| } |
| |
| Vec2D outPoint = Vec2D::scaleAndAdd(pos, toPrev, icircleConstant * renderRadius); |
| Vec2D inPoint = Vec2D::scaleAndAdd(pos, toNext, icircleConstant * renderRadius); |
| Vec2D posNext = Vec2D::scaleAndAdd(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)) { |
| // 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 |