| #include "rive/math/path_measure.hpp" |
| #include <algorithm> |
| |
| using namespace rive; |
| |
| PathMeasure::PathMeasure() : m_length(0.0f) {} |
| |
| PathMeasure::PathMeasure(const RawPath* path, float tol) : m_length(0.0f) |
| { |
| auto measure = ContourMeasureIter(path, tol); |
| for (auto contour = measure.next(); contour != nullptr; |
| contour = measure.next()) |
| { |
| m_length += contour->length(); |
| m_contours.push_back(contour); |
| } |
| } |
| |
| ContourMeasure::PosTanDistance PathMeasure::atDistance(float distance) const |
| { |
| float currentDistance = distance; |
| for (auto contour : m_contours) |
| { |
| float contourLength = contour->length(); |
| if (currentDistance - contourLength <= 0) |
| { |
| return ContourMeasure::PosTanDistance( |
| contour->getPosTan(currentDistance), |
| distance); |
| } |
| currentDistance -= contourLength; |
| } |
| return ContourMeasure::PosTanDistance(); |
| } |
| |
| ContourMeasure::PosTanDistance PathMeasure::atPercentage( |
| float percentageDistance) const |
| { |
| float inRangePercentage = fmodf(percentageDistance, 1.0f); |
| if (inRangePercentage < 0.0f) |
| { |
| inRangePercentage += 1.0f; |
| } |
| |
| // Mod to correct percentage (0-100%) and make sure we actually reach |
| // 100%. |
| if (percentageDistance != 0.0f && inRangePercentage == 0.0f) |
| { |
| inRangePercentage = 1.0f; |
| } |
| |
| return atDistance(m_length * inRangePercentage); |
| } |
| |
| void PathMeasure::getSegment(float startDistance, |
| float endDistance, |
| RawPath* dst, |
| bool startWithMove) const |
| { |
| if (dst == nullptr || m_contours.empty()) |
| { |
| return; |
| } |
| |
| // Clamp distances to valid range |
| startDistance = std::max(0.0f, std::min(startDistance, m_length)); |
| endDistance = std::max(0.0f, std::min(endDistance, m_length)); |
| |
| if (startDistance >= endDistance) |
| { |
| return; |
| } |
| |
| float currentDistance = 0.0f; |
| bool isFirstSegment = true; |
| |
| for (auto contour : m_contours) |
| { |
| float contourLength = contour->length(); |
| float contourStart = currentDistance; |
| float contourEnd = currentDistance + contourLength; |
| |
| // Check if this contour intersects with the requested range |
| if (contourEnd > startDistance && contourStart < endDistance) |
| { |
| // Calculate the local distances within this contour |
| float localStart = std::max(0.0f, startDistance - contourStart); |
| float localEnd = |
| std::min(contourLength, endDistance - contourStart); |
| |
| // Extract from this contour |
| contour->getSegment(localStart, |
| localEnd, |
| dst, |
| !isFirstSegment || startWithMove); |
| isFirstSegment = false; |
| } |
| |
| currentDistance += contourLength; |
| |
| // If we've passed the end distance, we're done |
| if (currentDistance >= endDistance) |
| { |
| break; |
| } |
| } |
| } |
| |
| bool PathMeasure::isClosed() const |
| { |
| // Return true only if there is exactly one contour and it is closed |
| return m_contours.size() == 1 && m_contours[0]->isClosed(); |
| } |