use contour_measure Diffs= ad4ff71df use contour_measure
diff --git a/.rive_head b/.rive_head index a3be2d6..5e47c21 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -f9a5698be92024c4c4a1b7337575f85341d89211 +ad4ff71dfd6362883bcab2d30227d4209228f1f6
diff --git a/include/rive/math/raw_path.hpp b/include/rive/math/raw_path.hpp index 7a89329..de32874 100644 --- a/include/rive/math/raw_path.hpp +++ b/include/rive/math/raw_path.hpp
@@ -18,6 +18,8 @@ namespace rive { +class CommandPath; + class RawPath { public: std::vector<Vec2D> m_Points; @@ -130,6 +132,9 @@ } return dst; } + + // Utility for pouring a RawPath into a CommandPath + void addTo(CommandPath*) const; }; } // namespace rive
diff --git a/include/rive/shapes/metrics_path.hpp b/include/rive/shapes/metrics_path.hpp index 9748824..4ff43a7 100644 --- a/include/rive/shapes/metrics_path.hpp +++ b/include/rive/shapes/metrics_path.hpp
@@ -2,49 +2,24 @@ #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 { -struct CubicSegment { - float t; - float length; - CubicSegment(float tValue, float lengthValue) : t(tValue), length(lengthValue) {} -}; - -struct PathPart { - static const unsigned char line = 0; - /// Type is 0 when this is a line segment, it's 1 or greater when it's a - /// cubic. When it's a cubic it also represents the index in - /// CubicSegments-1. - unsigned char type; - - /// Offset is the offset in original path points (which get transformed - /// when they're added to another path). - unsigned char offset; - - // Only used by the cubic to count the number of cubic segments used by - // this part. - unsigned char numSegments; - - PathPart(unsigned char t, unsigned char l) : type(t), offset(l), numSegments(0) {} -}; - class MetricsPath : public CommandPath { private: - std::vector<Vec2D> m_Points; - std::vector<Vec2D> m_TransformedPoints; - std::vector<CubicSegment> m_CubicSegments; - std::vector<PathPart> m_Parts; - std::vector<float> m_Lengths; + RawPath m_RawPath; // temporary, until we build m_Contour + rcp<ContourMeasure> m_Contour; std::vector<MetricsPath*> m_Paths; - float m_ComputedLength = 0.0f; Mat2D m_ComputedLengthTransform; + float m_ComputedLength = 0; public: const std::vector<MetricsPath*>& paths() const { return m_Paths; } + void addPath(CommandPath* path, const Mat2D& transform) override; void reset() override; void moveTo(float x, float y) override; @@ -61,10 +36,6 @@ private: float computeLength(const Mat2D& transform); - - /// Extract a single segment from startT to endT as render commands - /// added to result. - void extractSubPart(int index, float startT, float endT, bool moveTo, RenderPath* result); }; class OnlyMetricsPath : public MetricsPath {
diff --git a/src/math/raw_path.cpp b/src/math/raw_path.cpp index 5f45a8c..fd4419a 100644 --- a/src/math/raw_path.cpp +++ b/src/math/raw_path.cpp
@@ -236,4 +236,21 @@ void RawPath::rewind() { m_Points.clear(); m_Verbs.clear(); -} \ No newline at end of file +} + +/////////////////////////////////// + +#include "rive/command_path.hpp" + +void RawPath::addTo(CommandPath* result) const { + RawPath::Iter iter(*this); + while (auto rec = iter.next()) { + switch (rec.verb) { + case PathVerb::move: result->move(rec.pts[0]); break; + case PathVerb::line: result->line(rec.pts[0]); break; + case PathVerb::quad: assert(false); break; + case PathVerb::cubic: result->cubic(rec.pts[0], rec.pts[1], rec.pts[2]); break; + case PathVerb::close: result->close(); break; + } + } +}
diff --git a/src/shapes/metrics_path.cpp b/src/shapes/metrics_path.cpp index 38f77a9..5082f6d 100644 --- a/src/shapes/metrics_path.cpp +++ b/src/shapes/metrics_path.cpp
@@ -2,21 +2,17 @@ #include "rive/shapes/metrics_path.hpp" #include "rive/renderer.hpp" #include "rive/math/cubic_utilities.hpp" +#include "rive/math/raw_path.hpp" +#include "rive/math/contour_measure.hpp" using namespace rive; -static float clamp(float v, float lo, float hi) { return std::min(std::max(v, lo), hi); } - -// Less exact, but faster, than std::lerp -static float lerp(float from, float to, float f) { return from + f * (to - from); } - void MetricsPath::reset() { - m_ComputedLength = 0.0f; - m_CubicSegments.clear(); - m_Points.clear(); - m_Parts.clear(); - m_Lengths.clear(); m_Paths.clear(); + m_Contour.reset(nullptr); + m_RawPath = RawPath(); + m_ComputedLengthTransform = Mat2D(); + m_ComputedLength = 0; } void MetricsPath::addPath(CommandPath* path, const Mat2D& transform) { @@ -26,122 +22,28 @@ } void MetricsPath::moveTo(float x, float y) { - assert(m_Points.size() == 0); - m_Points.emplace_back(Vec2D(x, y)); + assert(m_RawPath.points().size() == 0); + m_RawPath.move({x, y}); } -void MetricsPath::lineTo(float x, float y) { - // TODO: resize PathPart to allow for larger offsets - auto offset = castTo<uint8_t>(m_Points.size()); - m_Parts.push_back(PathPart(0, offset)); - m_Points.emplace_back(Vec2D(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) { - auto offset = castTo<uint8_t>(m_Points.size()); - m_Parts.push_back(PathPart(1, offset)); - m_Points.emplace_back(Vec2D(ox, oy)); - m_Points.emplace_back(Vec2D(ix, iy)); - m_Points.emplace_back(Vec2D(x, y)); + m_RawPath.cubic({ox, oy}, {ix, iy}, {x, y}); } -void MetricsPath::close() {} - -static const float minSegmentLength = 0.05f; -static const float distTooFar = 1.0f; - -static float segmentCubic(const Vec2D& from, - const Vec2D& fromOut, - const Vec2D& toIn, - const Vec2D& to, - float runningLength, - float t1, - float t2, - std::vector<CubicSegment>& segments) { - - if (CubicUtilities::shouldSplitCubic(from, fromOut, toIn, to, distTooFar)) { - float halfT = (t1 + t2) / 2.0f; - - Vec2D hull[6]; - CubicUtilities::computeHull(from, fromOut, toIn, to, 0.5f, hull); - - runningLength = - segmentCubic(from, hull[0], hull[3], hull[5], runningLength, t1, halfT, segments); - runningLength = - segmentCubic(hull[5], hull[4], hull[2], to, runningLength, halfT, t2, segments); - } else { - float length = Vec2D::distance(from, to); - runningLength += length; - if (length > minSegmentLength) { - segments.emplace_back(CubicSegment(t2, runningLength)); - } - } - return runningLength; +void MetricsPath::close() { + // Should we pass the close() to our m_RawPath ??? } float MetricsPath::computeLength(const Mat2D& transform) { - // If the pre-computed length is still valid (transformed with the same - // transform) just return that. - if (!m_Lengths.empty() && transform == m_ComputedLengthTransform) { - return m_ComputedLength; + // Only compute if our pre-computed length is not valid + if (!m_Contour || transform != m_ComputedLengthTransform) { + m_ComputedLengthTransform = transform; + m_Contour = ContourMeasureIter(m_RawPath * transform).next(); + m_ComputedLength = m_Contour ? m_Contour->length() : 0; } - m_ComputedLengthTransform = transform; - m_Lengths.clear(); - m_CubicSegments.clear(); - - // We have to dupe the transformed points as we're not sure whether just the - // transform is changing (path may not have been reset but got added with - // another transform). - m_TransformedPoints.resize(m_Points.size()); - for (size_t i = 0, l = m_Points.size(); i < l; i++) { - m_TransformedPoints[i] = transform * m_Points[i]; - } - - // Should never have subPaths with more subPaths (Skia allows this but for - // Rive this isn't necessary and it keeps things simpler). - assert(m_Paths.empty()); - const Vec2D* pen = &m_TransformedPoints[0]; - int idx = 1; - float length = 0.0f; - - for (PathPart& part : m_Parts) { - switch (part.type) { - case PathPart::line: { - const Vec2D& point = m_TransformedPoints[idx++]; - - float partLength = Vec2D::distance(*pen, point); - m_Lengths.push_back(partLength); - pen = &point; - length += partLength; - break; - } - // Anything above 0 is the number of cubic parts... - default: { - // Subdivide as necessary... - - // push in the parts - - const Vec2D& from = pen[0]; - const Vec2D& fromOut = pen[1]; - const Vec2D& toIn = pen[2]; - const Vec2D& to = pen[3]; - - idx += 3; - pen = &to; - - int index = (int)m_CubicSegments.size(); - part.type = castTo<uint8_t>(index + 1); - float partLength = - segmentCubic(from, fromOut, toIn, to, 0.0f, 0.0f, 1.0f, m_CubicSegments); - m_Lengths.push_back(partLength); - length += partLength; - part.numSegments = castTo<uint8_t>(m_CubicSegments.size() - index); - break; - } - } - } - m_ComputedLength = length; - return length; + return m_ComputedLength; } void MetricsPath::trim(float startLength, float endLength, bool moveTo, RenderPath* result) { @@ -150,179 +52,13 @@ m_Paths.front()->trim(startLength, endLength, moveTo, result); return; } - if (startLength == endLength) { - // nothing to trim. - return; - } - // We need to find the first part to trim. - float length = 0.0f; - int partCount = (int)m_Parts.size(); - int firstPartIndex = -1, lastPartIndex = partCount - 1; - float startT = 0.0f, endT = 1.0f; - // Find first part. - for (int i = 0; i < partCount; i++) { - float partLength = m_Lengths[i]; - if (length + partLength > startLength) { - firstPartIndex = i; - startT = (startLength - length) / partLength; - break; - } - length += partLength; - } - if (firstPartIndex == -1) { - // Couldn't find it. - return; - } - - // Find last part. - for (int i = firstPartIndex; i < partCount; i++) { - float partLength = m_Lengths[i]; - if (length + partLength >= endLength) { - lastPartIndex = i; - endT = (endLength - length) / partLength; - break; - } - length += partLength; - } - - // Lets make sur we're between 0 & 1f on both start & end. - startT = clamp(startT, 0.0f, 1.0f); - endT = clamp(endT, 0.0f, 1.0f); - - if (firstPartIndex == lastPartIndex) { - extractSubPart(firstPartIndex, startT, endT, moveTo, result); - } else { - extractSubPart(firstPartIndex, startT, 1.0f, moveTo, result); - for (int i = firstPartIndex + 1; i < lastPartIndex; i++) { - // add entire part... - const PathPart& part = m_Parts[i]; - switch (part.type) { - case PathPart::line: { - result->line(m_TransformedPoints[part.offset]); - break; - } - default: { - result->cubic(m_TransformedPoints[part.offset + 0], - m_TransformedPoints[part.offset + 1], - m_TransformedPoints[part.offset + 2]); - break; - } - } - } - extractSubPart(lastPartIndex, 0.0f, endT, false, result); - } -} - -void MetricsPath::extractSubPart(int index, - float startT, - float endT, - bool moveTo, - RenderPath* result) { - assert(startT >= 0.0f && startT <= 1.0f && endT >= 0.0f && endT <= 1.0f); - const PathPart& part = m_Parts[index]; - switch (part.type) { - case PathPart::line: { - const Vec2D from = m_TransformedPoints[part.offset - 1]; - const Vec2D to = m_TransformedPoints[part.offset]; - const Vec2D dir = to - from; - if (moveTo) { - result->move(from + dir * startT); - } - result->line(from + dir * endT); - - break; - } - default: { - auto startingSegmentIndex = part.type - 1; - auto startEndSegmentIndex = startingSegmentIndex; - auto endingSegmentIndex = startingSegmentIndex + part.numSegments; - - // Find cubicStartT and cubicEndT - float length = m_Lengths[index]; - if (startT != 0.0f) { - float startLength = startT * length; - for (int si = startingSegmentIndex; si < endingSegmentIndex; si++) { - const CubicSegment& segment = m_CubicSegments[si]; - if (segment.length >= startLength) { - if (si == startingSegmentIndex) { - startT = segment.t * (startLength / segment.length); - } else { - float previousLength = m_CubicSegments[si - 1].length; - - float t = - (startLength - previousLength) / (segment.length - previousLength); - startT = lerp(m_CubicSegments[si - 1].t, segment.t, t); - } - // Help out the ending segment finder by setting its - // start to where we landed while finding the first - // segment, that way it can skip a bunch of work. - startEndSegmentIndex = si; - break; - } - } - } - - if (endT != 1.0f) { - float endLength = endT * length; - for (int si = startEndSegmentIndex; si < endingSegmentIndex; si++) { - const CubicSegment& segment = m_CubicSegments[si]; - if (segment.length >= endLength) { - if (si == startingSegmentIndex) { - endT = segment.t * (endLength / segment.length); - } else { - float previousLength = m_CubicSegments[si - 1].length; - - float t = - (endLength - previousLength) / (segment.length - previousLength); - endT = lerp(m_CubicSegments[si - 1].t, segment.t, t); - } - break; - } - } - } - - Vec2D hull[6]; - - const Vec2D& from = m_TransformedPoints[part.offset - 1]; - const Vec2D& fromOut = m_TransformedPoints[part.offset]; - const Vec2D& toIn = m_TransformedPoints[part.offset + 1]; - const Vec2D& to = m_TransformedPoints[part.offset + 2]; - - if (startT == 0.0f) { - // Start is 0, so split at end and keep the left side. - CubicUtilities::computeHull(from, fromOut, toIn, to, endT, hull); - if (moveTo) { - result->move(from); - } - result->cubic(hull[0], hull[3], hull[5]); - } else { - // Split at start since it's non 0. - CubicUtilities::computeHull(from, fromOut, toIn, to, startT, hull); - if (moveTo) { - // Move to first point on the right side. - result->move(hull[5]); - } - if (endT == 1.0f) { - // End is 1, so no further split is necessary just cubicTo - // the remaining right side. - result->cubic(hull[4], hull[2], to); - } else { - // End is not 1, so split again and cubic to the left side - // of the split and remap endT to the new curve range - CubicUtilities::computeHull(hull[5], - hull[4], - hull[2], - to, - (endT - startT) / (1.0f - startT), - hull); - - result->cubic(hull[0], hull[3], hull[5]); - } - } - break; - } - } + // 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, &tmp, moveTo); + tmp.addTo(result); } RenderMetricsPath::RenderMetricsPath(std::unique_ptr<RenderPath> path) :