#ifndef CompoundBezierPath_hpp #define CompoundBezierPath_hpp #include "Lottie/Private/Utility/Primitives/BezierPath.hpp" namespace lottie { /// A collection of BezierPath objects that can be trimmed and added. /// class CompoundBezierPath: public std::enable_shared_from_this { public: CompoundBezierPath() : paths({}) { } CompoundBezierPath(BezierPath const &path) : paths({ path }) { } CompoundBezierPath(std::vector paths_, std::optional length_) : paths(paths_), _length(length_) { } CompoundBezierPath(std::vector paths_) : paths(paths_) { } public: std::vector paths; double length() { if (_length.has_value()) { return _length.value(); } else { double l = 0.0; for (auto &path : paths) { l += path.length(); } _length = l; return l; } } private: std::optional _length; public: std::shared_ptr addingPath(BezierPath const &path) const { auto newPaths = paths; newPaths.push_back(path); return std::make_shared(newPaths); } void appendPath(BezierPath const &path) { paths.push_back(path); _length.reset(); } std::shared_ptr combine(std::shared_ptr compoundBezier) { auto newPaths = paths; for (const auto &path : compoundBezier->paths) { newPaths.push_back(path); } return std::make_shared(newPaths); } std::shared_ptr trim(double fromPosition, double toPosition, double offset) { if (fromPosition == toPosition) { return std::make_shared(); } /*bool trimSimultaneously = false; if (trimSimultaneously) { /// Trim each path individually. std::vector newPaths; for (auto &path : paths) { auto trimmedPaths = path.trim(fromPosition * path.length(), toPosition * path.length(), offset * path.length()); for (const auto &trimmedPath : trimmedPaths) { newPaths.push_back(trimmedPath); } } return std::make_shared(newPaths); }*/ double lengthValue = length(); /// Normalize lengths to the curve length. double startPosition = fmod(fromPosition + offset, 1.0); double endPosition = fmod(toPosition + offset, 1.0); if (startPosition < 0.0) { startPosition = 1.0 + startPosition; } if (endPosition < 0.0) { endPosition = 1.0 + endPosition; } if (startPosition == 1.0) { startPosition = 0.0; } if (endPosition == 0.0) { endPosition = 1.0; } if ((startPosition == 0.0 && endPosition == 1.0) || startPosition == endPosition || (startPosition == 1.0 && endPosition == 0.0)) { /// The trim encompasses the entire path. Return. return shared_from_this(); } std::vector positions; if (endPosition < startPosition) { positions = { BezierTrimPathPosition(0.0, endPosition * lengthValue), BezierTrimPathPosition(startPosition * lengthValue, lengthValue) }; } else { positions = { BezierTrimPathPosition(startPosition * lengthValue, endPosition * lengthValue) }; } auto compoundPath = std::make_shared(); auto trim = positions[0]; positions.erase(positions.begin()); double pathStartPosition = 0.0; bool finishedTrimming = false; int i = 0; while (!finishedTrimming) { if (paths.size() <= i) { /// Rounding errors finishedTrimming = true; continue; } auto path = paths[i]; auto pathEndPosition = pathStartPosition + path.length(); if (pathEndPosition < trim.start) { /// Path is not included in the trim, continue. pathStartPosition = pathEndPosition; i = i + 1; continue; } else if (trim.start <= pathStartPosition && pathEndPosition <= trim.end) { /// Full Path is inside of trim. Add full path. compoundPath = compoundPath->addingPath(path); } else { auto trimPaths = path.trim(trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length(), 0.0); if (!trimPaths.empty()) { compoundPath = compoundPath->addingPath(trimPaths[0]); } } if (trim.end <= pathEndPosition) { /// We are done with the current trim. /// Advance trim but remain on the same path in case the next trim overlaps it. if (positions.size() > 0) { trim = positions[0]; positions.erase(positions.begin()); } else { finishedTrimming = true; } } else { pathStartPosition = pathEndPosition; i = i + 1; } } return compoundPath; } }; } #endif /* CompoundBezierPath_hpp */