| #include "rive/shapes/path_composer.hpp" | |
| #include "rive/artboard.hpp" | |
| #include "rive/renderer.hpp" | |
| #include "rive/shapes/path.hpp" | |
| #include "rive/shapes/shape.hpp" | |
| #include "rive/factory.hpp" | |
| #include "rive/shapes/points_path.hpp" | |
| using namespace rive; | |
| PathComposer::PathComposer(Shape* shape) : | |
| m_shape(shape), | |
| m_localPath(true), | |
| m_worldPath(false), | |
| m_localClockwisePath(true), | |
| m_deferredPathDirt(false) | |
| {} | |
| void PathComposer::buildDependencies() | |
| { | |
| assert(m_shape != nullptr); | |
| m_shape->addDependent(this); | |
| for (auto path : m_shape->paths()) | |
| { | |
| path->addDependent(this); | |
| } | |
| } | |
| void PathComposer::onDirty(ComponentDirt dirt) | |
| { | |
| if (m_deferredPathDirt) | |
| { | |
| // We'd deferred the update, let's make sure the rest of our | |
| // dependencies update too. Constraints need to update too, stroke | |
| // effects, etc. | |
| m_shape->pathChanged(); | |
| } | |
| } | |
| void PathComposer::update(ComponentDirt value) | |
| { | |
| if (hasDirt(value, ComponentDirt::Path | ComponentDirt::NSlicer)) | |
| { | |
| if (m_shape->canDeferPathUpdate()) | |
| { | |
| m_deferredPathDirt = true; | |
| return; | |
| } | |
| m_deferredPathDirt = false; | |
| if (m_shape->isFlagged(PathFlags::local)) | |
| { | |
| m_localPath.rewind(); | |
| auto world = m_shape->worldTransform(); | |
| Mat2D inverseWorld = world.invertOrIdentity(); | |
| // Get all the paths into local shape space. | |
| for (auto path : m_shape->paths()) | |
| { | |
| if (!path->isHidden() && !path->isCollapsed()) | |
| { | |
| const auto localTransform = | |
| inverseWorld * path->pathTransform(); | |
| m_localPath.addPath(path->rawPath(), &localTransform); | |
| } | |
| } | |
| } | |
| if (m_shape->isFlagged(PathFlags::localClockwise)) | |
| { | |
| m_localClockwisePath.rewind(); | |
| auto world = m_shape->worldTransform(); | |
| Mat2D inverseWorld = world.invertOrIdentity(); | |
| // Get all the paths into local shape space. | |
| for (auto path : m_shape->paths()) | |
| { | |
| if (path->isHidden() || path->isCollapsed()) | |
| { | |
| continue; | |
| } | |
| const auto localTransform = | |
| inverseWorld * path->pathTransform(); | |
| bool isNotClockwise = | |
| path->is<PointsPath>() && | |
| (localTransform.determinant() * | |
| (path->as<PointsPath>()->isClockwise() ? 1.0f | |
| : -1.0f) < | |
| 0); | |
| bool isHole = path->isHole(); | |
| // Only draw backwards if values are different | |
| if (isNotClockwise != isHole) | |
| { | |
| m_localClockwisePath.addPathBackwards(path->rawPath(), | |
| &localTransform); | |
| } | |
| else | |
| { | |
| m_localClockwisePath.addPath(path->rawPath(), | |
| &localTransform); | |
| } | |
| } | |
| } | |
| if (m_shape->isFlagged(PathFlags::world)) | |
| { | |
| m_worldPath.rewind(); | |
| for (auto path : m_shape->paths()) | |
| { | |
| if (!path->isHidden() && !path->isCollapsed()) | |
| { | |
| const Mat2D& transform = path->pathTransform(); | |
| m_worldPath.addPath(path->rawPath(), &transform); | |
| } | |
| } | |
| } | |
| m_shape->markBoundsDirty(); | |
| } | |
| } | |
| // Instead of adding dirt and rely on the recursive behavior of the addDirt | |
| // method, we need to explicitly add dirt to the dependents. The reason is that | |
| // a collapsed shape will not clear its dirty path flag in the current frame | |
| // since it is collapsed. So in a future frame if it is uncollapsed, we mark its | |
| // path flag as dirty again, but since it was already dirty, the recursive part | |
| // will not kick in and the dependents won't update. This scenario is not | |
| // common, but it can happen when a solo toggles between an empty group and a | |
| // path for example. | |
| void PathComposer::pathCollapseChanged() | |
| { | |
| addDirt(ComponentDirt::Path); | |
| for (auto d : dependents()) | |
| { | |
| d->addDirt(ComponentDirt::Path, true); | |
| } | |
| } |