mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
Get rid of MetricsPath.
This lets us trim a raw path directly which means all the trim path logic from the editor can now be pushed down to the C++ runtime, even for the case where the rendering is still happening via Skia/Impeller, all our path operations can be done in C++ (without using RenderPath/MetricsPath/RenderMetricsPath/OnlyMetricsPath/etc). Basically, I needed to do this now to not have as many edge cases in the editor for the difference between using our runtime to render with PLS vs Flutter. It also quite greatly simplifies the code! More lines removed than added! Some tests will fail as I think we're using the MetricsPath in some other parts of the code, but that can be refactored following the patterns used here... It also means that most of our runtime now speaks RawPath which means we can optimize/reserve memory ahead of time in a lot of cases (not doing that yet). Diffs= 8486c3445 Get rid of MetricsPath. (#7371) Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
085f5bd2dce2d25561b00eebc5f7118a2c8769eb
|
||||
8486c344528d5f7f84de1984510064a7d0788e73
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "rive/text/text_value_run.hpp"
|
||||
#include "rive/event.hpp"
|
||||
#include "rive/audio/audio_engine.hpp"
|
||||
#include "rive/math/raw_path.hpp"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
@@ -56,6 +57,7 @@ private:
|
||||
bool m_HasChangedDrawOrderInLastUpdate = false;
|
||||
|
||||
unsigned int m_DirtDepth = 0;
|
||||
RawPath m_backgroundRawPath;
|
||||
rcp<RenderPath> m_BackgroundPath;
|
||||
rcp<RenderPath> m_ClipPath;
|
||||
Factory* m_Factory = nullptr;
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
#define _RIVE_FOLLOW_PATH_CONSTRAINT_HPP_
|
||||
#include "rive/generated/constraints/follow_path_constraint_base.hpp"
|
||||
#include "rive/math/transform_components.hpp"
|
||||
#include "rive/shapes/metrics_path.hpp"
|
||||
#include <stdio.h>
|
||||
#include "rive/math/contour_measure.hpp"
|
||||
namespace rive
|
||||
{
|
||||
class FollowPathConstraint : public FollowPathConstraintBase
|
||||
{
|
||||
|
||||
public:
|
||||
void distanceChanged() override;
|
||||
void orientChanged() override;
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#ifndef _RIVE_METRICS_PATH_HPP_
|
||||
#define _RIVE_METRICS_PATH_HPP_
|
||||
|
||||
#include "rive/command_path.hpp"
|
||||
#include "rive/math/contour_measure.hpp"
|
||||
#include "rive/math/vec2d.hpp"
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace rive
|
||||
{
|
||||
|
||||
class MetricsPath : public CommandPath
|
||||
{
|
||||
private:
|
||||
RawPath m_RawPath; // temporary, until we build m_Contour
|
||||
rcp<ContourMeasure> m_Contour;
|
||||
std::vector<MetricsPath*> m_Paths;
|
||||
Mat2D m_ComputedLengthTransform;
|
||||
float m_ComputedLength = 0;
|
||||
|
||||
public:
|
||||
const std::vector<MetricsPath*>& paths() const { return m_Paths; }
|
||||
rcp<ContourMeasure> contourMeasure() const { return m_Contour; }
|
||||
|
||||
void addPath(CommandPath* path, const Mat2D& transform) override;
|
||||
void rewind() override;
|
||||
void moveTo(float x, float y) override;
|
||||
void lineTo(float x, float y) override;
|
||||
void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override;
|
||||
void close() override;
|
||||
|
||||
float length() const { return m_ComputedLength; }
|
||||
|
||||
/// Add commands to the result RenderPath that will draw the segment
|
||||
/// from startLength to endLength of this MetricsPath. Requires
|
||||
/// computeLength be called prior to trimming.
|
||||
void trim(float startLength, float endLength, bool moveTo, RawPath* result);
|
||||
|
||||
/// Add this MetricsPath to a raw path with a transform.
|
||||
RawPath::Iter addToRawPath(RawPath& rawPath, const Mat2D& transform) const;
|
||||
~MetricsPath() override;
|
||||
|
||||
private:
|
||||
float computeLength(const Mat2D& transform);
|
||||
};
|
||||
|
||||
class OnlyMetricsPath : public MetricsPath
|
||||
{
|
||||
public:
|
||||
void fillRule(FillRule value) override {}
|
||||
|
||||
RenderPath* renderPath() override
|
||||
{
|
||||
// Should never be used for actual rendering.
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class RenderMetricsPath : public MetricsPath
|
||||
{
|
||||
private:
|
||||
rcp<RenderPath> m_RenderPath;
|
||||
|
||||
public:
|
||||
RenderMetricsPath(rcp<RenderPath>);
|
||||
RenderPath* renderPath() override { return m_RenderPath.get(); }
|
||||
void addPath(CommandPath* path, const Mat2D& transform) override;
|
||||
|
||||
void fillRule(FillRule value) override;
|
||||
void rewind() override;
|
||||
void moveTo(float x, float y) override;
|
||||
void lineTo(float x, float y) override;
|
||||
void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override;
|
||||
void close() override;
|
||||
};
|
||||
} // namespace rive
|
||||
#endif
|
||||
@@ -9,7 +9,10 @@ class Fill : public FillBase
|
||||
public:
|
||||
RenderPaint* initRenderPaint(ShapePaintMutator* mutator) override;
|
||||
PathSpace pathSpace() const override;
|
||||
void draw(Renderer* renderer, CommandPath* path, RenderPaint* paint) override;
|
||||
void draw(Renderer* renderer,
|
||||
CommandPath* path,
|
||||
const RawPath* rawPath,
|
||||
RenderPaint* paint) override;
|
||||
void applyTo(RenderPaint* renderPaint, float opacityModifier) const override;
|
||||
};
|
||||
} // namespace rive
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "rive/shapes/paint/blend_mode.hpp"
|
||||
#include "rive/shapes/paint/shape_paint_mutator.hpp"
|
||||
#include "rive/shapes/path_space.hpp"
|
||||
#include "rive/math/raw_path.hpp"
|
||||
|
||||
namespace rive
|
||||
{
|
||||
class RenderPaint;
|
||||
@@ -30,9 +32,17 @@ public:
|
||||
|
||||
virtual PathSpace pathSpace() const = 0;
|
||||
|
||||
void draw(Renderer* renderer, CommandPath* path) { draw(renderer, path, renderPaint()); }
|
||||
void draw(Renderer* renderer, CommandPath* path, const RawPath* rawPath = nullptr)
|
||||
{
|
||||
draw(renderer, path, rawPath, renderPaint());
|
||||
}
|
||||
|
||||
virtual void draw(Renderer* renderer, CommandPath* path, RenderPaint* paint) = 0;
|
||||
virtual void draw(Renderer* renderer,
|
||||
CommandPath* path,
|
||||
// When every CommandPath stores a RawPath we can get rid
|
||||
// of this argument.
|
||||
const RawPath* rawPath,
|
||||
RenderPaint* paint) = 0;
|
||||
|
||||
RenderPaint* renderPaint() { return m_RenderPaint.get(); }
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@ private:
|
||||
public:
|
||||
RenderPaint* initRenderPaint(ShapePaintMutator* mutator) override;
|
||||
PathSpace pathSpace() const override;
|
||||
void draw(Renderer* renderer, CommandPath* path, RenderPaint* paint) override;
|
||||
void draw(Renderer* renderer,
|
||||
CommandPath* path,
|
||||
const RawPath* rawPath,
|
||||
RenderPaint* paint) override;
|
||||
void addStrokeEffect(StrokeEffect* effect);
|
||||
bool hasStrokeEffect() { return m_Effect != nullptr; }
|
||||
void invalidateEffects();
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace rive
|
||||
{
|
||||
class Factory;
|
||||
class RenderPath;
|
||||
class MetricsPath;
|
||||
class RawPath;
|
||||
|
||||
class StrokeEffect
|
||||
{
|
||||
public:
|
||||
virtual ~StrokeEffect() {}
|
||||
virtual RenderPath* effectPath(MetricsPath* source, Factory*) = 0;
|
||||
virtual RenderPath* effectPath(const RawPath& source, Factory*) = 0;
|
||||
virtual void invalidateEffect() = 0;
|
||||
};
|
||||
} // namespace rive
|
||||
|
||||
@@ -3,24 +3,44 @@
|
||||
#include "rive/generated/shapes/paint/trim_path_base.hpp"
|
||||
#include "rive/shapes/paint/stroke_effect.hpp"
|
||||
#include "rive/renderer.hpp"
|
||||
#include "rive/math/raw_path.hpp"
|
||||
#include "rive/math/contour_measure.hpp"
|
||||
|
||||
namespace rive
|
||||
{
|
||||
enum class TrimPathMode : unsigned char
|
||||
{
|
||||
sequential = 1,
|
||||
synchronized = 2
|
||||
|
||||
};
|
||||
class ContourMeasure;
|
||||
|
||||
class TrimPath : public TrimPathBase, public StrokeEffect
|
||||
{
|
||||
private:
|
||||
rcp<RenderPath> m_TrimmedPath;
|
||||
RenderPath* m_RenderPath = nullptr;
|
||||
|
||||
public:
|
||||
StatusCode onAddedClean(CoreContext* context) override;
|
||||
RenderPath* effectPath(MetricsPath* source, Factory*) override;
|
||||
RenderPath* effectPath(const RawPath& source, Factory*) override;
|
||||
void invalidateEffect() override;
|
||||
|
||||
void startChanged() override;
|
||||
void endChanged() override;
|
||||
void offsetChanged() override;
|
||||
void modeValueChanged() override;
|
||||
|
||||
TrimPathMode mode() const { return (TrimPathMode)modeValue(); }
|
||||
|
||||
StatusCode onAddedDirty(CoreContext* context) override;
|
||||
|
||||
const RawPath& rawPath() const { return m_rawPath; }
|
||||
|
||||
private:
|
||||
void invalidateTrim();
|
||||
void trimRawPath(const RawPath& source);
|
||||
RawPath m_rawPath;
|
||||
std::vector<rcp<ContourMeasure>> m_contours;
|
||||
rcp<RenderPath> m_trimmedPath;
|
||||
RenderPath* m_renderPath = nullptr;
|
||||
};
|
||||
} // namespace rive
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "rive/command_path.hpp"
|
||||
#include "rive/generated/shapes/path_base.hpp"
|
||||
#include "rive/math/mat2d.hpp"
|
||||
#include "rive/math/raw_path.hpp"
|
||||
#include "rive/shapes/shape_paint_container.hpp"
|
||||
#include <vector>
|
||||
|
||||
@@ -37,10 +38,10 @@ class Path : public PathBase
|
||||
{
|
||||
protected:
|
||||
Shape* m_Shape = nullptr;
|
||||
rcp<CommandPath> m_CommandPath;
|
||||
std::vector<PathVertex*> m_Vertices;
|
||||
bool m_deferredPathDirt = false;
|
||||
PathSpace m_DefaultPathSpace = PathSpace::Neither;
|
||||
RawPath m_rawPath;
|
||||
|
||||
public:
|
||||
Shape* shape() const { return m_Shape; }
|
||||
@@ -48,7 +49,7 @@ public:
|
||||
void buildDependencies() override;
|
||||
virtual const Mat2D& pathTransform() const;
|
||||
bool collapse(bool value) override;
|
||||
CommandPath* commandPath() const { return m_CommandPath.get(); }
|
||||
const RawPath& rawPath() const { return m_rawPath; }
|
||||
void update(ComponentDirt value) override;
|
||||
|
||||
void addDefaultPathSpace(PathSpace space);
|
||||
@@ -67,8 +68,7 @@ public:
|
||||
std::vector<PathVertex*>& vertices() { return m_Vertices; }
|
||||
#endif
|
||||
|
||||
// pour ourselves into a command-path
|
||||
void buildPath(CommandPath&) const;
|
||||
void buildPath(RawPath&) const;
|
||||
};
|
||||
} // namespace rive
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define _RIVE_PATH_COMPOSER_HPP_
|
||||
#include "rive/component.hpp"
|
||||
#include "rive/refcnt.hpp"
|
||||
#include "rive/math/raw_path.hpp"
|
||||
|
||||
namespace rive
|
||||
{
|
||||
class Shape;
|
||||
@@ -9,23 +11,29 @@ class CommandPath;
|
||||
class RenderPath;
|
||||
class PathComposer : public Component
|
||||
{
|
||||
private:
|
||||
Shape* m_Shape;
|
||||
rcp<CommandPath> m_LocalPath;
|
||||
rcp<CommandPath> m_WorldPath;
|
||||
bool m_deferredPathDirt;
|
||||
|
||||
public:
|
||||
PathComposer(Shape* shape);
|
||||
Shape* shape() const { return m_Shape; }
|
||||
Shape* shape() const { return m_shape; }
|
||||
void buildDependencies() override;
|
||||
void onDirty(ComponentDirt dirt) override;
|
||||
void update(ComponentDirt value) override;
|
||||
|
||||
CommandPath* localPath() const { return m_LocalPath.get(); }
|
||||
CommandPath* worldPath() const { return m_WorldPath.get(); }
|
||||
CommandPath* localPath() const { return m_localPath.get(); }
|
||||
CommandPath* worldPath() const { return m_worldPath.get(); }
|
||||
|
||||
const RawPath& localRawPath() const { return m_localRawPath; }
|
||||
const RawPath& worldRawPath() const { return m_worldRawPath; }
|
||||
|
||||
void pathCollapseChanged();
|
||||
|
||||
private:
|
||||
Shape* m_shape;
|
||||
RawPath m_localRawPath;
|
||||
RawPath m_worldRawPath;
|
||||
rcp<CommandPath> m_localPath;
|
||||
rcp<CommandPath> m_worldPath;
|
||||
bool m_deferredPathDirt;
|
||||
};
|
||||
} // namespace rive
|
||||
#endif
|
||||
|
||||
@@ -35,8 +35,6 @@ public:
|
||||
|
||||
void invalidateStrokeEffects();
|
||||
|
||||
rcp<CommandPath> makeCommandPath(PathSpace space);
|
||||
|
||||
void propagateOpacity(float opacity);
|
||||
|
||||
#ifdef TESTING
|
||||
|
||||
@@ -454,7 +454,10 @@ void Artboard::update(ComponentDirt value)
|
||||
clip = bg;
|
||||
}
|
||||
m_ClipPath = factory()->makeRenderPath(clip);
|
||||
m_BackgroundPath = factory()->makeRenderPath(bg);
|
||||
|
||||
m_backgroundRawPath.addRect(bg);
|
||||
m_BackgroundPath->rewind();
|
||||
m_backgroundRawPath.addTo(m_BackgroundPath.get());
|
||||
}
|
||||
if (hasDirt(value, ComponentDirt::RenderOpacity))
|
||||
{
|
||||
@@ -602,7 +605,7 @@ void Artboard::draw(Renderer* renderer, DrawOption option)
|
||||
{
|
||||
for (auto shapePaint : m_ShapePaints)
|
||||
{
|
||||
shapePaint->draw(renderer, m_BackgroundPath.get());
|
||||
shapePaint->draw(renderer, m_BackgroundPath.get(), &m_backgroundRawPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "rive/math/contour_measure.hpp"
|
||||
#include "rive/math/mat2d.hpp"
|
||||
#include "rive/math/math_types.hpp"
|
||||
#include "rive/shapes/metrics_path.hpp"
|
||||
#include "rive/shapes/path.hpp"
|
||||
#include "rive/shapes/shape.hpp"
|
||||
#include "rive/transform_component.hpp"
|
||||
@@ -150,8 +149,7 @@ void FollowPathConstraint::update(ComponentDirt value)
|
||||
m_contours.clear();
|
||||
for (auto path : paths)
|
||||
{
|
||||
auto commandPath = static_cast<MetricsPath*>(path->commandPath());
|
||||
commandPath->addToRawPath(m_rawPath, path->pathTransform());
|
||||
m_rawPath.addPath(path->rawPath(), &path->pathTransform());
|
||||
}
|
||||
|
||||
auto measure = ContourMeasureIter(&m_rawPath);
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
#include "rive/shapes/metrics_path.hpp"
|
||||
#include "rive/renderer.hpp"
|
||||
#include "rive/math/raw_path.hpp"
|
||||
#include "rive/math/contour_measure.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace rive;
|
||||
|
||||
void MetricsPath::rewind()
|
||||
{
|
||||
for (auto ptr : m_Paths)
|
||||
{
|
||||
delete ptr;
|
||||
}
|
||||
m_Paths.clear();
|
||||
m_Contour.reset(nullptr);
|
||||
m_RawPath.rewind();
|
||||
m_ComputedLengthTransform = Mat2D();
|
||||
m_ComputedLength = 0;
|
||||
}
|
||||
|
||||
MetricsPath::~MetricsPath() { rewind(); }
|
||||
|
||||
void MetricsPath::addPath(CommandPath* path, const Mat2D& transform)
|
||||
{
|
||||
MetricsPath* metricsPath = static_cast<MetricsPath*>(path);
|
||||
m_ComputedLength += metricsPath->computeLength(transform);
|
||||
// We need to copy the data to avoid contention between multiple uses of the same path
|
||||
// for example when the same path is added as localPath and worldPath
|
||||
auto metricsPathCopy = new OnlyMetricsPath();
|
||||
metricsPathCopy->m_Contour = metricsPath->m_Contour;
|
||||
metricsPathCopy->m_RawPath = metricsPath->m_RawPath;
|
||||
metricsPathCopy->m_ComputedLength = metricsPath->m_ComputedLength;
|
||||
m_Paths.emplace_back(metricsPathCopy);
|
||||
}
|
||||
|
||||
RawPath::Iter MetricsPath::addToRawPath(RawPath& rawPath, const Mat2D& transform) const
|
||||
{
|
||||
return rawPath.addPath(m_RawPath, &transform);
|
||||
}
|
||||
|
||||
void MetricsPath::moveTo(float x, float y)
|
||||
{
|
||||
assert(m_RawPath.points().size() == 0);
|
||||
m_RawPath.move({x, y});
|
||||
}
|
||||
|
||||
void MetricsPath::lineTo(float x, float y) { m_RawPath.line({x, y}); }
|
||||
|
||||
void MetricsPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y)
|
||||
{
|
||||
m_RawPath.cubic({ox, oy}, {ix, iy}, {x, y});
|
||||
}
|
||||
|
||||
void MetricsPath::close()
|
||||
{
|
||||
// Should we pass the close() to our m_RawPath ???
|
||||
}
|
||||
|
||||
float MetricsPath::computeLength(const Mat2D& transform)
|
||||
{
|
||||
// Only compute if our pre-computed length is not valid
|
||||
if (!m_Contour || transform != m_ComputedLengthTransform)
|
||||
{
|
||||
m_ComputedLengthTransform = transform;
|
||||
RawPath transformedPath = m_RawPath.transform(transform);
|
||||
m_Contour = ContourMeasureIter(&transformedPath).next();
|
||||
m_ComputedLength = m_Contour ? m_Contour->length() : 0;
|
||||
}
|
||||
return m_ComputedLength;
|
||||
}
|
||||
|
||||
void MetricsPath::trim(float startLength, float endLength, bool moveTo, RawPath* result)
|
||||
{
|
||||
assert(endLength >= startLength);
|
||||
if (!m_Paths.empty())
|
||||
{
|
||||
m_Paths.front()->trim(startLength, endLength, moveTo, result);
|
||||
return;
|
||||
}
|
||||
if (!m_Contour)
|
||||
{
|
||||
// All the contours were 0 length, so there's nothing to segment.
|
||||
return;
|
||||
}
|
||||
// TODO: if we can change the signature of MetricsPath and/or trim() to speak native
|
||||
// rawpaths, we wouldn't need this temporary copy (since ContourMeasure speaks
|
||||
// native rawpaths).
|
||||
RawPath tmp;
|
||||
m_Contour->getSegment(startLength, endLength, result, moveTo);
|
||||
}
|
||||
|
||||
RenderMetricsPath::RenderMetricsPath(rcp<RenderPath> path) : m_RenderPath(std::move(path)) {}
|
||||
|
||||
void RenderMetricsPath::addPath(CommandPath* path, const Mat2D& transform)
|
||||
{
|
||||
MetricsPath::addPath(path, transform);
|
||||
m_RenderPath->addPath(path->renderPath(), transform);
|
||||
}
|
||||
|
||||
void RenderMetricsPath::rewind()
|
||||
{
|
||||
MetricsPath::rewind();
|
||||
m_RenderPath->rewind();
|
||||
}
|
||||
|
||||
void RenderMetricsPath::moveTo(float x, float y)
|
||||
{
|
||||
MetricsPath::moveTo(x, y);
|
||||
m_RenderPath->moveTo(x, y);
|
||||
}
|
||||
|
||||
void RenderMetricsPath::lineTo(float x, float y)
|
||||
{
|
||||
MetricsPath::lineTo(x, y);
|
||||
m_RenderPath->lineTo(x, y);
|
||||
}
|
||||
|
||||
void RenderMetricsPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y)
|
||||
{
|
||||
MetricsPath::cubicTo(ox, oy, ix, iy, x, y);
|
||||
m_RenderPath->cubicTo(ox, oy, ix, iy, x, y);
|
||||
}
|
||||
|
||||
void RenderMetricsPath::close()
|
||||
{
|
||||
MetricsPath::close();
|
||||
m_RenderPath->close();
|
||||
}
|
||||
|
||||
void RenderMetricsPath::fillRule(FillRule value) { m_RenderPath->fillRule(value); }
|
||||
@@ -18,7 +18,7 @@ void Fill::applyTo(RenderPaint* renderPaint, float opacityModifier) const
|
||||
m_PaintMutator->applyTo(renderPaint, opacityModifier);
|
||||
}
|
||||
|
||||
void Fill::draw(Renderer* renderer, CommandPath* path, RenderPaint* paint)
|
||||
void Fill::draw(Renderer* renderer, CommandPath* path, const RawPath* rawPath, RenderPaint* paint)
|
||||
{
|
||||
if (!isVisible())
|
||||
{
|
||||
|
||||
@@ -33,18 +33,17 @@ void Stroke::applyTo(RenderPaint* renderPaint, float opacityModifier) const
|
||||
|
||||
bool Stroke::isVisible() const { return Super::isVisible() && thickness() > 0.0f; }
|
||||
|
||||
void Stroke::draw(Renderer* renderer, CommandPath* path, RenderPaint* paint)
|
||||
void Stroke::draw(Renderer* renderer, CommandPath* path, const RawPath* rawPath, RenderPaint* paint)
|
||||
{
|
||||
if (!isVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Effect != nullptr)
|
||||
if (m_Effect != nullptr && rawPath != nullptr)
|
||||
{
|
||||
/// We're guaranteed to get a metrics path here if we have an effect.
|
||||
auto factory = artboard()->factory();
|
||||
path = m_Effect->effectPath(reinterpret_cast<MetricsPath*>(path), factory);
|
||||
path = m_Effect->effectPath(*rawPath, factory);
|
||||
}
|
||||
|
||||
renderer->drawPath(path->renderPath(), paint);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "rive/shapes/paint/trim_path.hpp"
|
||||
#include "rive/shapes/metrics_path.hpp"
|
||||
#include "rive/shapes/paint/stroke.hpp"
|
||||
#include "rive/factory.hpp"
|
||||
|
||||
@@ -17,33 +16,29 @@ StatusCode TrimPath::onAddedClean(CoreContext* context)
|
||||
return StatusCode::Ok;
|
||||
}
|
||||
|
||||
RenderPath* TrimPath::effectPath(MetricsPath* source, Factory* factory)
|
||||
void TrimPath::trimRawPath(const RawPath& source)
|
||||
{
|
||||
if (m_RenderPath != nullptr)
|
||||
{
|
||||
return m_RenderPath;
|
||||
}
|
||||
|
||||
// Source is always a containing (shape) path.
|
||||
const std::vector<MetricsPath*>& subPaths = source->paths();
|
||||
|
||||
RawPath rawTrimmed;
|
||||
|
||||
if (!m_TrimmedPath)
|
||||
{
|
||||
m_TrimmedPath = factory->makeEmptyRenderPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_TrimmedPath->rewind();
|
||||
}
|
||||
|
||||
m_rawPath.rewind();
|
||||
auto renderOffset = std::fmod(std::fmod(offset(), 1.0f) + 1.0f, 1.0f);
|
||||
switch (modeValue())
|
||||
|
||||
// Build up contours if they're empty.
|
||||
if (m_contours.empty())
|
||||
{
|
||||
case 1:
|
||||
ContourMeasureIter iter(&source);
|
||||
while (auto meas = iter.next())
|
||||
{
|
||||
float totalLength = source->length();
|
||||
m_contours.push_back(meas);
|
||||
}
|
||||
}
|
||||
switch (mode())
|
||||
{
|
||||
case TrimPathMode::sequential:
|
||||
{
|
||||
float totalLength = 0.0f;
|
||||
for (auto contour : m_contours)
|
||||
{
|
||||
totalLength += contour->length();
|
||||
}
|
||||
auto startLength = totalLength * (start() + renderOffset);
|
||||
auto endLength = totalLength * (end() + renderOffset);
|
||||
|
||||
@@ -60,35 +55,35 @@ RenderPath* TrimPath::effectPath(MetricsPath* source, Factory* factory)
|
||||
endLength -= totalLength;
|
||||
}
|
||||
|
||||
int i = 0, subPathCount = (int)subPaths.size();
|
||||
int i = 0, subPathCount = (int)m_contours.size();
|
||||
while (endLength > 0)
|
||||
{
|
||||
auto path = subPaths[i % subPathCount];
|
||||
auto pathLength = path->length();
|
||||
auto contour = m_contours[i % subPathCount];
|
||||
auto contourLength = contour->length();
|
||||
|
||||
if (startLength < pathLength)
|
||||
if (startLength < contourLength)
|
||||
{
|
||||
path->trim(startLength, endLength, true, &rawTrimmed);
|
||||
endLength -= pathLength;
|
||||
contour->getSegment(startLength, endLength, &m_rawPath, true);
|
||||
endLength -= contourLength;
|
||||
startLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
startLength -= pathLength;
|
||||
endLength -= pathLength;
|
||||
startLength -= contourLength;
|
||||
endLength -= contourLength;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case TrimPathMode::synchronized:
|
||||
{
|
||||
for (auto path : subPaths)
|
||||
for (auto contour : m_contours)
|
||||
{
|
||||
auto pathLength = path->length();
|
||||
auto startLength = pathLength * (start() + renderOffset);
|
||||
auto endLength = pathLength * (end() + renderOffset);
|
||||
auto contourLength = contour->length();
|
||||
auto startLength = contourLength * (start() + renderOffset);
|
||||
auto endLength = contourLength * (end() + renderOffset);
|
||||
if (endLength < startLength)
|
||||
{
|
||||
auto length = startLength;
|
||||
@@ -96,37 +91,83 @@ RenderPath* TrimPath::effectPath(MetricsPath* source, Factory* factory)
|
||||
endLength = length;
|
||||
}
|
||||
|
||||
if (startLength > pathLength)
|
||||
if (startLength > contourLength)
|
||||
{
|
||||
startLength -= pathLength;
|
||||
endLength -= pathLength;
|
||||
startLength -= contourLength;
|
||||
endLength -= contourLength;
|
||||
}
|
||||
path->trim(startLength, endLength, true, &rawTrimmed);
|
||||
while (endLength > pathLength)
|
||||
contour->getSegment(startLength, endLength, &m_rawPath, true);
|
||||
while (endLength > contourLength)
|
||||
{
|
||||
startLength = 0;
|
||||
endLength -= pathLength;
|
||||
path->trim(startLength, endLength, true, &rawTrimmed);
|
||||
endLength -= contourLength;
|
||||
contour->getSegment(startLength, endLength, &m_rawPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RIVE_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
RenderPath* TrimPath::effectPath(const RawPath& source, Factory* factory)
|
||||
{
|
||||
if (m_renderPath != nullptr)
|
||||
{
|
||||
// Previous result hasn't been invalidated, it's still good.
|
||||
return m_renderPath;
|
||||
}
|
||||
|
||||
m_RenderPath = m_TrimmedPath.get();
|
||||
rawTrimmed.addTo(m_RenderPath);
|
||||
return m_RenderPath;
|
||||
trimRawPath(source);
|
||||
|
||||
if (!m_trimmedPath)
|
||||
{
|
||||
m_trimmedPath = factory->makeEmptyRenderPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_trimmedPath->rewind();
|
||||
}
|
||||
|
||||
m_renderPath = m_trimmedPath.get();
|
||||
m_rawPath.addTo(m_renderPath);
|
||||
return m_renderPath;
|
||||
}
|
||||
|
||||
void TrimPath::invalidateEffect()
|
||||
{
|
||||
m_RenderPath = nullptr;
|
||||
invalidateTrim();
|
||||
// This is usually sent when the path is changed so we need to also
|
||||
// invalidate the contours, not just the trim effect.
|
||||
m_contours.clear();
|
||||
}
|
||||
|
||||
void TrimPath::invalidateTrim()
|
||||
{
|
||||
m_renderPath = nullptr;
|
||||
auto stroke = parent()->as<Stroke>();
|
||||
stroke->parent()->addDirt(ComponentDirt::Paint);
|
||||
stroke->invalidateRendering();
|
||||
}
|
||||
|
||||
void TrimPath::startChanged() { invalidateEffect(); }
|
||||
void TrimPath::endChanged() { invalidateEffect(); }
|
||||
void TrimPath::offsetChanged() { invalidateEffect(); }
|
||||
void TrimPath::modeValueChanged() { invalidateEffect(); }
|
||||
void TrimPath::startChanged() { invalidateTrim(); }
|
||||
void TrimPath::endChanged() { invalidateTrim(); }
|
||||
void TrimPath::offsetChanged() { invalidateTrim(); }
|
||||
void TrimPath::modeValueChanged() { invalidateTrim(); }
|
||||
|
||||
StatusCode TrimPath::onAddedDirty(CoreContext* context)
|
||||
{
|
||||
auto code = Super::onAddedDirty(context);
|
||||
if (code != StatusCode::Ok)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
switch (mode())
|
||||
{
|
||||
case TrimPathMode::sequential:
|
||||
case TrimPathMode::synchronized:
|
||||
return StatusCode::Ok;
|
||||
}
|
||||
return StatusCode::InvalidObject;
|
||||
}
|
||||
@@ -47,14 +47,7 @@ StatusCode Path::onAddedClean(CoreContext* context)
|
||||
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(m_DefaultPathSpace);
|
||||
}
|
||||
void Path::buildDependencies() { Super::buildDependencies(); }
|
||||
|
||||
void Path::addVertex(PathVertex* vertex) { m_Vertices.push_back(vertex); }
|
||||
|
||||
@@ -62,13 +55,20 @@ void Path::addDefaultPathSpace(PathSpace space) { m_DefaultPathSpace |= space; }
|
||||
|
||||
bool Path::canDeferPathUpdate()
|
||||
{
|
||||
return ((m_DefaultPathSpace & PathSpace::Clipping) != PathSpace::Clipping) &&
|
||||
// A path cannot defer its update if the shapes requires an update. Note the
|
||||
// nuance here where we track that the shape may be marked for follow path
|
||||
// (meaning all child paths need to follow path). This doesn't mean the
|
||||
// Shape is necessarily forced to update put the paths are, which is why we
|
||||
// explicitly also check the shape's path space.
|
||||
return m_Shape->canDeferPathUpdate() &&
|
||||
(m_Shape->pathSpace() & PathSpace::FollowPath) != PathSpace::FollowPath &&
|
||||
((m_DefaultPathSpace & PathSpace::Clipping) != PathSpace::Clipping) &&
|
||||
((m_DefaultPathSpace & PathSpace::FollowPath) != PathSpace::FollowPath);
|
||||
}
|
||||
|
||||
const Mat2D& Path::pathTransform() const { return worldTransform(); }
|
||||
|
||||
void Path::buildPath(CommandPath& commandPath) const
|
||||
void Path::buildPath(RawPath& rawPath) const
|
||||
{
|
||||
const bool isClosed = isPathClosed();
|
||||
const std::vector<PathVertex*>& vertices = m_Vertices;
|
||||
@@ -94,7 +94,7 @@ void Path::buildPath(CommandPath& commandPath) const
|
||||
startIn = cubic->renderIn();
|
||||
out = cubic->renderOut();
|
||||
start = cubic->renderTranslation();
|
||||
commandPath.move(start);
|
||||
rawPath.move(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,18 +125,18 @@ void Path::buildPath(CommandPath& commandPath) const
|
||||
float idealDistance = computeIdealControlPointDistance(toPrev, toNext, renderRadius);
|
||||
|
||||
startIn = start = Vec2D::scaleAndAdd(pos, toPrev, renderRadius);
|
||||
commandPath.move(startIn);
|
||||
rawPath.move(startIn);
|
||||
|
||||
Vec2D outPoint = Vec2D::scaleAndAdd(pos, toPrev, renderRadius - idealDistance);
|
||||
Vec2D inPoint = Vec2D::scaleAndAdd(pos, toNext, renderRadius - idealDistance);
|
||||
out = Vec2D::scaleAndAdd(pos, toNext, renderRadius);
|
||||
commandPath.cubic(outPoint, inPoint, out);
|
||||
rawPath.cubic(outPoint, inPoint, out);
|
||||
prevIsCubic = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
startIn = start = out = point.renderTranslation();
|
||||
commandPath.move(out);
|
||||
rawPath.move(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ void Path::buildPath(CommandPath& commandPath) const
|
||||
auto inPoint = cubic->renderIn();
|
||||
auto translation = cubic->renderTranslation();
|
||||
|
||||
commandPath.cubic(out, inPoint, translation);
|
||||
rawPath.cubic(out, inPoint, translation);
|
||||
|
||||
prevIsCubic = true;
|
||||
out = cubic->renderOut();
|
||||
@@ -183,22 +183,22 @@ void Path::buildPath(CommandPath& commandPath) const
|
||||
Vec2D translation = Vec2D::scaleAndAdd(pos, toPrev, renderRadius);
|
||||
if (prevIsCubic)
|
||||
{
|
||||
commandPath.cubic(out, translation, translation);
|
||||
rawPath.cubic(out, translation, translation);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath.line(translation);
|
||||
rawPath.line(translation);
|
||||
}
|
||||
|
||||
Vec2D outPoint = Vec2D::scaleAndAdd(pos, toPrev, renderRadius - idealDistance);
|
||||
Vec2D inPoint = Vec2D::scaleAndAdd(pos, toNext, renderRadius - idealDistance);
|
||||
out = Vec2D::scaleAndAdd(pos, toNext, renderRadius);
|
||||
commandPath.cubic(outPoint, inPoint, out);
|
||||
rawPath.cubic(outPoint, inPoint, out);
|
||||
prevIsCubic = false;
|
||||
}
|
||||
else if (prevIsCubic)
|
||||
{
|
||||
commandPath.cubic(out, pos, pos);
|
||||
rawPath.cubic(out, pos, pos);
|
||||
|
||||
prevIsCubic = false;
|
||||
out = pos;
|
||||
@@ -206,7 +206,7 @@ void Path::buildPath(CommandPath& commandPath) const
|
||||
else
|
||||
{
|
||||
out = pos;
|
||||
commandPath.line(out);
|
||||
rawPath.line(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,13 +214,13 @@ void Path::buildPath(CommandPath& commandPath) const
|
||||
{
|
||||
if (prevIsCubic || startIsCubic)
|
||||
{
|
||||
commandPath.cubic(out, startIn, start);
|
||||
rawPath.cubic(out, startIn, start);
|
||||
}
|
||||
else
|
||||
{
|
||||
commandPath.line(start);
|
||||
rawPath.line(start);
|
||||
}
|
||||
commandPath.close();
|
||||
rawPath.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,9 +249,9 @@ void Path::update(ComponentDirt value)
|
||||
{
|
||||
Super::update(value);
|
||||
|
||||
if (m_CommandPath != nullptr && hasDirt(value, ComponentDirt::Path))
|
||||
if (hasDirt(value, ComponentDirt::Path))
|
||||
{
|
||||
if (m_Shape->canDeferPathUpdate())
|
||||
if (canDeferPathUpdate())
|
||||
{
|
||||
m_deferredPathDirt = true;
|
||||
return;
|
||||
@@ -260,8 +260,8 @@ void Path::update(ComponentDirt value)
|
||||
// Build path doesn't explicitly rewind because we use it to concatenate
|
||||
// multiple built paths into a single command path (like the hit
|
||||
// tester).
|
||||
m_CommandPath->rewind();
|
||||
buildPath(*m_CommandPath);
|
||||
m_rawPath.rewind();
|
||||
buildPath(m_rawPath);
|
||||
}
|
||||
// if (hasDirt(value, ComponentDirt::WorldTransform) && m_Shape != nullptr)
|
||||
// {
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
#include "rive/renderer.hpp"
|
||||
#include "rive/shapes/path.hpp"
|
||||
#include "rive/shapes/shape.hpp"
|
||||
#include "rive/factory.hpp"
|
||||
|
||||
using namespace rive;
|
||||
|
||||
PathComposer::PathComposer(Shape* shape) : m_Shape(shape), m_deferredPathDirt(false) {}
|
||||
PathComposer::PathComposer(Shape* shape) : m_shape(shape), m_deferredPathDirt(false) {}
|
||||
|
||||
void PathComposer::buildDependencies()
|
||||
{
|
||||
assert(m_Shape != nullptr);
|
||||
m_Shape->addDependent(this);
|
||||
for (auto path : m_Shape->paths())
|
||||
assert(m_shape != nullptr);
|
||||
m_shape->addDependent(this);
|
||||
for (auto path : m_shape->paths())
|
||||
{
|
||||
path->addDependent(this);
|
||||
}
|
||||
@@ -25,7 +26,7 @@ void PathComposer::onDirty(ComponentDirt dirt)
|
||||
// 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();
|
||||
m_shape->pathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,61 +34,63 @@ void PathComposer::update(ComponentDirt value)
|
||||
{
|
||||
if (hasDirt(value, ComponentDirt::Path))
|
||||
{
|
||||
if (m_Shape->canDeferPathUpdate())
|
||||
if (m_shape->canDeferPathUpdate())
|
||||
{
|
||||
m_deferredPathDirt = true;
|
||||
return;
|
||||
}
|
||||
m_deferredPathDirt = false;
|
||||
|
||||
auto space = m_Shape->pathSpace();
|
||||
bool hasConstraint = (space & PathSpace::FollowPath) == PathSpace::FollowPath;
|
||||
auto space = m_shape->pathSpace();
|
||||
if ((space & PathSpace::Local) == PathSpace::Local)
|
||||
{
|
||||
if (m_LocalPath == nullptr)
|
||||
if (m_localPath == nullptr)
|
||||
{
|
||||
PathSpace localSpace =
|
||||
(hasConstraint) ? PathSpace::Local & PathSpace::FollowPath : PathSpace::Local;
|
||||
m_LocalPath = m_Shape->makeCommandPath(localSpace);
|
||||
m_localPath = artboard()->factory()->makeEmptyRenderPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_LocalPath->rewind();
|
||||
m_localPath->rewind();
|
||||
m_localRawPath.rewind();
|
||||
}
|
||||
auto world = m_Shape->worldTransform();
|
||||
auto world = m_shape->worldTransform();
|
||||
Mat2D inverseWorld = world.invertOrIdentity();
|
||||
// Get all the paths into local shape space.
|
||||
for (auto path : m_Shape->paths())
|
||||
for (auto path : m_shape->paths())
|
||||
{
|
||||
if (!path->isHidden() && !path->isCollapsed())
|
||||
{
|
||||
const auto localTransform = inverseWorld * path->pathTransform();
|
||||
m_LocalPath->addPath(path->commandPath(), localTransform);
|
||||
m_localRawPath.addPath(path->rawPath(), &localTransform);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add a CommandPath::copy(RawPath)
|
||||
m_localRawPath.addTo(m_localPath.get());
|
||||
}
|
||||
if ((space & PathSpace::World) == PathSpace::World)
|
||||
{
|
||||
if (m_WorldPath == nullptr)
|
||||
if (m_worldPath == nullptr)
|
||||
{
|
||||
PathSpace worldSpace =
|
||||
(hasConstraint) ? PathSpace::World & PathSpace::FollowPath : PathSpace::World;
|
||||
m_WorldPath = m_Shape->makeCommandPath(worldSpace);
|
||||
m_worldPath = artboard()->factory()->makeEmptyRenderPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_WorldPath->rewind();
|
||||
m_worldPath->rewind();
|
||||
m_worldRawPath.rewind();
|
||||
}
|
||||
for (auto path : m_Shape->paths())
|
||||
for (auto path : m_shape->paths())
|
||||
{
|
||||
if (!path->isHidden() && !path->isCollapsed())
|
||||
{
|
||||
const Mat2D& transform = path->pathTransform();
|
||||
m_WorldPath->addPath(path->commandPath(), transform);
|
||||
m_worldRawPath.addPath(path->rawPath(), &transform);
|
||||
}
|
||||
}
|
||||
// TODO: add a CommandPath::copy(RawPath)
|
||||
m_worldRawPath.addTo(m_worldPath.get());
|
||||
}
|
||||
m_Shape->markBoundsDirty();
|
||||
m_shape->markBoundsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,8 @@ void Shape::addPath(Path* path)
|
||||
|
||||
bool Shape::canDeferPathUpdate()
|
||||
{
|
||||
auto canDefer = renderOpacity() == 0 &&
|
||||
(pathSpace() & PathSpace::Clipping) != PathSpace::Clipping &&
|
||||
(pathSpace() & PathSpace::FollowPath) != PathSpace::FollowPath;
|
||||
auto canDefer =
|
||||
renderOpacity() == 0 && (pathSpace() & PathSpace::Clipping) != PathSpace::Clipping;
|
||||
if (canDefer)
|
||||
{
|
||||
// If we have a dependent Skin, don't defer the update
|
||||
@@ -37,13 +36,6 @@ bool Shape::canDeferPathUpdate()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto path : m_Paths)
|
||||
{
|
||||
if (!path->canDeferPathUpdate())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canDefer;
|
||||
}
|
||||
@@ -113,9 +105,10 @@ void Shape::draw(Renderer* renderer)
|
||||
{
|
||||
renderer->transform(worldTransform());
|
||||
}
|
||||
shapePaint->draw(renderer,
|
||||
paintsInLocal ? m_PathComposer.localPath()
|
||||
: m_PathComposer.worldPath());
|
||||
shapePaint->draw(
|
||||
renderer,
|
||||
paintsInLocal ? m_PathComposer.localPath() : m_PathComposer.worldPath(),
|
||||
paintsInLocal ? &m_PathComposer.localRawPath() : &m_PathComposer.worldRawPath());
|
||||
renderer->restore();
|
||||
}
|
||||
}
|
||||
@@ -135,7 +128,7 @@ bool Shape::hitTest(const IAABB& area) const
|
||||
if (!path->isCollapsed())
|
||||
{
|
||||
tester.setXform(path->pathTransform());
|
||||
path->buildPath(tester);
|
||||
path->rawPath().addTo(&tester);
|
||||
}
|
||||
}
|
||||
return tester.wasHit();
|
||||
@@ -184,7 +177,7 @@ Core* Shape::hitTest(HitInfo* hinfo, const Mat2D& xform)
|
||||
{
|
||||
tester.setXform(mx * path->pathTransform());
|
||||
}
|
||||
path->buildPath(tester);
|
||||
path->rawPath().addTo(&tester);
|
||||
}
|
||||
if (tester.wasHit())
|
||||
{
|
||||
@@ -284,8 +277,7 @@ AABB Shape::computeWorldBounds(const Mat2D* xform) const
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
path->buildPath(boundsCalculator);
|
||||
path->rawPath().addTo(&boundsCalculator);
|
||||
|
||||
AABB aabb = boundsCalculator.bounds(xform == nullptr ? path->pathTransform()
|
||||
: path->pathTransform() * *xform);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "rive/artboard.hpp"
|
||||
#include "rive/factory.hpp"
|
||||
#include "rive/component.hpp"
|
||||
#include "rive/shapes/metrics_path.hpp"
|
||||
#include "rive/shapes/paint/stroke.hpp"
|
||||
#include "rive/shapes/shape.hpp"
|
||||
#include "rive/text/text_style.hpp"
|
||||
@@ -46,53 +45,6 @@ void ShapePaintContainer::invalidateStrokeEffects()
|
||||
}
|
||||
}
|
||||
|
||||
rcp<CommandPath> ShapePaintContainer::makeCommandPath(PathSpace space)
|
||||
{
|
||||
// Force a render path if we specifically request to use it for clipping or
|
||||
// this shape is used for clipping.
|
||||
bool needForRender =
|
||||
((space | m_DefaultPathSpace) & PathSpace::Clipping) == PathSpace::Clipping;
|
||||
bool needForConstraint =
|
||||
((space | m_DefaultPathSpace) & PathSpace::FollowPath) == PathSpace::FollowPath;
|
||||
|
||||
bool needForEffects = false;
|
||||
|
||||
for (auto paint : m_ShapePaints)
|
||||
{
|
||||
if (space != PathSpace::Neither && (space & paint->pathSpace()) != space)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (paint->is<Stroke>() && paint->as<Stroke>()->hasStrokeEffect())
|
||||
{
|
||||
needForEffects = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
needForRender = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto factory = getArtboard()->factory();
|
||||
if (needForEffects && needForRender)
|
||||
{
|
||||
return make_rcp<RenderMetricsPath>(factory->makeEmptyRenderPath());
|
||||
}
|
||||
else if (needForConstraint)
|
||||
{
|
||||
return make_rcp<RenderMetricsPath>(factory->makeEmptyRenderPath());
|
||||
}
|
||||
else if (needForEffects)
|
||||
{
|
||||
return make_rcp<OnlyMetricsPath>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return factory->makeEmptyRenderPath();
|
||||
}
|
||||
}
|
||||
|
||||
void ShapePaintContainer::propagateOpacity(float opacity)
|
||||
{
|
||||
for (auto shapePaint : m_ShapePaints)
|
||||
|
||||
@@ -191,7 +191,7 @@ void TextStyle::draw(Renderer* renderer)
|
||||
{
|
||||
RenderPaint* renderPaint = m_paintPool[paintIndex++].get();
|
||||
shapePaint->applyTo(renderPaint, itr->first);
|
||||
shapePaint->draw(renderer, itr->second.get(), renderPaint);
|
||||
shapePaint->draw(renderer, itr->second.get(), nullptr, renderPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ TEST_CASE("simple triangle path triangulates as expected", "[file]")
|
||||
auto path = artboard->find<rive::Path>("triangle_path");
|
||||
REQUIRE(path != nullptr);
|
||||
TestRenderPath renderPath;
|
||||
path->buildPath(renderPath);
|
||||
path->rawPath().addTo(&renderPath);
|
||||
|
||||
rive::Mat2D identity;
|
||||
TestRenderPath shapeRenderPath;
|
||||
|
||||
@@ -177,3 +177,25 @@ TEST_CASE("nan-path", "[contourmeasure]")
|
||||
CHECK(iter.next() == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Regression test for a crash found by fuzzing.
|
||||
TEST_CASE("fuzz_issue_7295", "[MetricsPath]")
|
||||
{
|
||||
NoOpFactory factory;
|
||||
|
||||
RawPath innerPath;
|
||||
innerPath.moveTo(.0f, -20.5f);
|
||||
innerPath.cubicTo(11.3218384f, -20.5f, 20.5f, -11.3218384f, 20.5f, .0f);
|
||||
innerPath.cubicTo(20.5f, 11.3218384f, 11.3218384f, 20.5f, .0f, 20.5f);
|
||||
innerPath.cubicTo(-11.3218384f, 20.5f, -20.5f, 11.3218384f, -20.5f, .0f);
|
||||
innerPath.cubicTo(-20.5f, -11.3218384f, -11.3218384f, -20.5f, .0f, -20.5f);
|
||||
|
||||
RawPath outerPath;
|
||||
Mat2D transform(1.f, .0f, .0f, 1.f, -134217728.f, -134217728.f);
|
||||
outerPath.addPath(innerPath, &transform);
|
||||
|
||||
auto contour = ContourMeasureIter(&outerPath).next();
|
||||
RawPath result;
|
||||
contour->getSegment(.0f, 168.389008f, &result, true);
|
||||
CHECK(math::nearly_equal(contour->length(), 168.389008f));
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#include <catch.hpp>
|
||||
#include <rive/math/math_types.hpp>
|
||||
#include <rive/shapes/metrics_path.hpp>
|
||||
#include <rive/shapes/paint/stroke.hpp>
|
||||
#include <rive/shapes/paint/trim_path.hpp>
|
||||
#include <utils/no_op_factory.hpp>
|
||||
|
||||
using namespace rive;
|
||||
|
||||
TEST_CASE("path metrics compute correctly", "[bezier]")
|
||||
{
|
||||
// TODO: fix these based on new logic
|
||||
// Make a square with sides length 10.
|
||||
// rive::OnlyMetricsPath path;
|
||||
// path.moveTo(0, 0);
|
||||
// path.lineTo(10, 0);
|
||||
// path.lineTo(10, 10);
|
||||
// path.lineTo(0, 10);
|
||||
// path.close();
|
||||
|
||||
// // Total length should be 40.
|
||||
// rive::Mat2D identity;
|
||||
// float length = path.computeLength(identity);
|
||||
// REQUIRE(length == 40);
|
||||
|
||||
// // Make a path with a single cubic.
|
||||
// rive::OnlyMetricsPath cubicPath;
|
||||
// cubicPath.moveTo(102, 22);
|
||||
// cubicPath.cubicTo(10, 80, 120, 100, 150, 222);
|
||||
// cubicPath.close();
|
||||
|
||||
// float cubicLength = cubicPath.computeLength(identity);
|
||||
// REQUIRE(cubicLength == 238.38698f);
|
||||
}
|
||||
|
||||
// Regression test for a crash found by fuzzing.
|
||||
TEST_CASE("fuzz_issue_7295", "[MetricsPath]")
|
||||
{
|
||||
NoOpFactory factory;
|
||||
|
||||
OnlyMetricsPath innerPath;
|
||||
innerPath.moveTo(.0f, -20.5f);
|
||||
innerPath.cubicTo(11.3218384f, -20.5f, 20.5f, -11.3218384f, 20.5f, .0f);
|
||||
innerPath.cubicTo(20.5f, 11.3218384f, 11.3218384f, 20.5f, .0f, 20.5f);
|
||||
innerPath.cubicTo(-11.3218384f, 20.5f, -20.5f, 11.3218384f, -20.5f, .0f);
|
||||
innerPath.cubicTo(-20.5f, -11.3218384f, -11.3218384f, -20.5f, .0f, -20.5f);
|
||||
|
||||
OnlyMetricsPath outerPath;
|
||||
outerPath.addPath(&innerPath, Mat2D(1.f, .0f, .0f, 1.f, -134217728.f, -134217728.f));
|
||||
|
||||
RawPath result;
|
||||
outerPath.paths()[0]->trim(.0f, 168.389008f, true, &result);
|
||||
CHECK(math::nearly_equal(outerPath.paths()[0]->length(), 168.389008f));
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <rive/solo.hpp>
|
||||
#include <rive/animation/linear_animation_instance.hpp>
|
||||
#include "rive_file_reader.hpp"
|
||||
#include "rive/math/path_types.hpp"
|
||||
#include <catch.hpp>
|
||||
#include <cstdio>
|
||||
|
||||
@@ -111,18 +112,16 @@ TEST_CASE("rectangle path builds expected commands", "[path]")
|
||||
|
||||
artboard.advance(0.0f);
|
||||
|
||||
REQUIRE(rectangle->commandPath() != nullptr);
|
||||
auto rawPath = rectangle->rawPath();
|
||||
|
||||
auto path = static_cast<TestRenderPath*>(rectangle->commandPath());
|
||||
|
||||
REQUIRE(path->commands.size() == 7);
|
||||
REQUIRE(path->commands[0].command == TestPathCommandType::Reset);
|
||||
REQUIRE(path->commands[1].command == TestPathCommandType::MoveTo);
|
||||
REQUIRE(path->commands[2].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(path->commands[3].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(path->commands[4].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(path->commands[5].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(path->commands[6].command == TestPathCommandType::Close);
|
||||
auto verbs = rawPath.verbs();
|
||||
REQUIRE(verbs.size() == 6);
|
||||
REQUIRE(verbs[0] == rive::PathVerb::move);
|
||||
REQUIRE(verbs[1] == rive::PathVerb::line);
|
||||
REQUIRE(verbs[2] == rive::PathVerb::line);
|
||||
REQUIRE(verbs[3] == rive::PathVerb::line);
|
||||
REQUIRE(verbs[4] == rive::PathVerb::line);
|
||||
REQUIRE(verbs[5] == rive::PathVerb::close);
|
||||
}
|
||||
|
||||
TEST_CASE("rounded rectangle path builds expected commands", "[path]")
|
||||
@@ -148,9 +147,7 @@ TEST_CASE("rounded rectangle path builds expected commands", "[path]")
|
||||
|
||||
artboard.advance(0.0f);
|
||||
|
||||
REQUIRE(rectangle->commandPath() != nullptr);
|
||||
|
||||
auto path = static_cast<TestRenderPath*>(rectangle->commandPath());
|
||||
auto rawPath = rectangle->rawPath();
|
||||
|
||||
// rewind
|
||||
// moveTo
|
||||
@@ -161,31 +158,30 @@ TEST_CASE("rounded rectangle path builds expected commands", "[path]")
|
||||
// lineTo, cubicTo for 4th corner
|
||||
|
||||
// close
|
||||
|
||||
REQUIRE(path->commands.size() == 11);
|
||||
auto verbs = rawPath.verbs();
|
||||
REQUIRE(verbs.size() == 10);
|
||||
|
||||
// Init
|
||||
REQUIRE(path->commands[0].command == TestPathCommandType::Reset);
|
||||
REQUIRE(path->commands[1].command == TestPathCommandType::MoveTo);
|
||||
REQUIRE(verbs[0] == rive::PathVerb::move);
|
||||
|
||||
// 1st
|
||||
REQUIRE(path->commands[2].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(verbs[1] == rive::PathVerb::cubic);
|
||||
|
||||
// 2nd
|
||||
REQUIRE(path->commands[3].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(path->commands[4].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(verbs[2] == rive::PathVerb::line);
|
||||
REQUIRE(verbs[3] == rive::PathVerb::cubic);
|
||||
|
||||
// 3rd
|
||||
REQUIRE(path->commands[5].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(path->commands[6].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(verbs[4] == rive::PathVerb::line);
|
||||
REQUIRE(verbs[5] == rive::PathVerb::cubic);
|
||||
|
||||
// 4th
|
||||
REQUIRE(path->commands[7].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(path->commands[8].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(verbs[6] == rive::PathVerb::line);
|
||||
REQUIRE(verbs[7] == rive::PathVerb::cubic);
|
||||
|
||||
REQUIRE(path->commands[9].command == TestPathCommandType::LineTo);
|
||||
REQUIRE(verbs[8] == rive::PathVerb::line);
|
||||
|
||||
REQUIRE(path->commands[10].command == TestPathCommandType::Close);
|
||||
REQUIRE(verbs[9] == rive::PathVerb::close);
|
||||
}
|
||||
|
||||
TEST_CASE("ellipse path builds expected commands", "[path]")
|
||||
@@ -209,9 +205,7 @@ TEST_CASE("ellipse path builds expected commands", "[path]")
|
||||
|
||||
artboard.advance(0.0f);
|
||||
|
||||
REQUIRE(ellipse->commandPath() != nullptr);
|
||||
|
||||
auto path = static_cast<TestRenderPath*>(ellipse->commandPath());
|
||||
auto path = ellipse->rawPath();
|
||||
|
||||
// rewind
|
||||
// moveTo
|
||||
@@ -223,51 +217,52 @@ TEST_CASE("ellipse path builds expected commands", "[path]")
|
||||
|
||||
// close
|
||||
|
||||
REQUIRE(path->commands.size() == 7);
|
||||
auto verbs = path.verbs();
|
||||
auto points = path.points();
|
||||
REQUIRE(verbs.size() == 6);
|
||||
|
||||
// Init
|
||||
REQUIRE(path->commands[0].command == TestPathCommandType::Reset);
|
||||
REQUIRE(path->commands[1].command == TestPathCommandType::MoveTo);
|
||||
REQUIRE(path->commands[1].x == 0.0f);
|
||||
REQUIRE(path->commands[1].y == -100.0f);
|
||||
REQUIRE(verbs[0] == rive::PathVerb::move);
|
||||
REQUIRE(points[0].x == 0.0f);
|
||||
REQUIRE(points[0].y == -100.0f);
|
||||
|
||||
// 1st
|
||||
REQUIRE(path->commands[2].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(path->commands[2].outX == 50.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[2].outY == -100.0f);
|
||||
REQUIRE(path->commands[2].inX == 50.0f);
|
||||
REQUIRE(path->commands[2].inY == -100.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[2].x == 50.0f);
|
||||
REQUIRE(path->commands[2].y == 0.0f);
|
||||
REQUIRE(verbs[1] == rive::PathVerb::cubic);
|
||||
REQUIRE(points[1].x == 50.0f * rive::circleConstant);
|
||||
REQUIRE(points[1].y == -100.0f);
|
||||
REQUIRE(points[2].x == 50.0f);
|
||||
REQUIRE(points[2].y == -100.0f * rive::circleConstant);
|
||||
REQUIRE(points[3].x == 50.0f);
|
||||
REQUIRE(points[3].y == 0.0f);
|
||||
|
||||
// 2nd
|
||||
REQUIRE(path->commands[3].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(path->commands[3].outX == 50.0f);
|
||||
REQUIRE(path->commands[3].outY == 100.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[3].inX == 50.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[3].inY == 100.0f);
|
||||
REQUIRE(path->commands[3].x == 0.0f);
|
||||
REQUIRE(path->commands[3].y == 100.0f);
|
||||
REQUIRE(verbs[2] == rive::PathVerb::cubic);
|
||||
REQUIRE(points[4].x == 50.0f);
|
||||
REQUIRE(points[4].y == 100.0f * rive::circleConstant);
|
||||
REQUIRE(points[5].x == 50.0f * rive::circleConstant);
|
||||
REQUIRE(points[5].y == 100.0f);
|
||||
REQUIRE(points[6].x == 0.0f);
|
||||
REQUIRE(points[6].y == 100.0f);
|
||||
|
||||
// 3rd
|
||||
REQUIRE(path->commands[4].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(path->commands[4].outX == -50.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[4].outY == 100.0f);
|
||||
REQUIRE(path->commands[4].inX == -50.0f);
|
||||
REQUIRE(path->commands[4].inY == 100.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[4].x == -50.0f);
|
||||
REQUIRE(path->commands[4].y == 0.0f);
|
||||
REQUIRE(verbs[3] == rive::PathVerb::cubic);
|
||||
REQUIRE(points[7].x == -50.0f * rive::circleConstant);
|
||||
REQUIRE(points[7].y == 100.0f);
|
||||
REQUIRE(points[8].x == -50.0f);
|
||||
REQUIRE(points[8].y == 100.0f * rive::circleConstant);
|
||||
REQUIRE(points[9].x == -50.0f);
|
||||
REQUIRE(points[9].y == 0.0f);
|
||||
|
||||
// 4th
|
||||
REQUIRE(path->commands[5].command == TestPathCommandType::CubicTo);
|
||||
REQUIRE(path->commands[5].outX == -50.0f);
|
||||
REQUIRE(path->commands[5].outY == -100.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[5].inX == -50.0f * rive::circleConstant);
|
||||
REQUIRE(path->commands[5].inY == -100.0f);
|
||||
REQUIRE(path->commands[5].x == 0.0f);
|
||||
REQUIRE(path->commands[5].y == -100.0f);
|
||||
REQUIRE(verbs[4] == rive::PathVerb::cubic);
|
||||
REQUIRE(points[10].x == -50.0f);
|
||||
REQUIRE(points[10].y == -100.0f * rive::circleConstant);
|
||||
REQUIRE(points[11].x == -50.0f * rive::circleConstant);
|
||||
REQUIRE(points[11].y == -100.0f);
|
||||
REQUIRE(points[12].x == 0.0f);
|
||||
REQUIRE(points[12].y == -100.0f);
|
||||
|
||||
REQUIRE(path->commands[6].command == TestPathCommandType::Close);
|
||||
REQUIRE(verbs[5] == rive::PathVerb::close);
|
||||
}
|
||||
|
||||
TEST_CASE("nested solo with shape expanded and path collapsed", "[path]")
|
||||
@@ -295,7 +290,7 @@ TEST_CASE("nested solo with shape expanded and path collapsed", "[path]")
|
||||
auto path = solo->children()[1]->as<rive::Path>();
|
||||
REQUIRE(rectangleShape->isCollapsed() == false);
|
||||
REQUIRE(path->isCollapsed() == true);
|
||||
REQUIRE(path->commandPath() != nullptr);
|
||||
|
||||
auto pathComposer = rootShape->pathComposer();
|
||||
auto pathComposerPath = static_cast<TestRenderPath*>(pathComposer->localPath());
|
||||
// Path is skipped and the nested shape forms its own drawable, so size is 0
|
||||
@@ -327,7 +322,7 @@ TEST_CASE("nested solo clipping with shape collapsed and path expanded", "[path]
|
||||
auto path = solo->children()[1]->as<rive::Path>();
|
||||
REQUIRE(rectangleShape->isCollapsed() == true);
|
||||
REQUIRE(path->isCollapsed() == false);
|
||||
REQUIRE(path->commandPath() != nullptr);
|
||||
|
||||
auto clippingShape = rectangleClip->clippingShapes()[0];
|
||||
REQUIRE(clippingShape != nullptr);
|
||||
auto clippingPath = static_cast<TestRenderPath*>(clippingShape->renderPath());
|
||||
|
||||
Reference in New Issue
Block a user