Skip to content

Commit

Permalink
Add contour measure to Rive Native
Browse files Browse the repository at this point in the history
Addresses second item in https://github.com/rive-app/rive/issues/7946

~~Draft as I need to add WASM side.~~

Diffs=
5719c5662 Add contour measure to Rive Native (#8145)

Co-authored-by: Luigi Rosso <[email protected]>
  • Loading branch information
luigi-rosso and luigi-rosso committed Sep 16, 2024
1 parent 28e7d45 commit 6e9a2a5
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8441098ec49bc6dcf6022db2af677d7dc7b590b6
5719c566261148150e788c80a9672e940baa0542
4 changes: 2 additions & 2 deletions include/rive/constraints/follow_path_constraint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#define _RIVE_FOLLOW_PATH_CONSTRAINT_HPP_
#include "rive/generated/constraints/follow_path_constraint_base.hpp"
#include "rive/math/transform_components.hpp"
#include "rive/math/contour_measure.hpp"
#include "rive/math/path_measure.hpp"
namespace rive
{
class FollowPathConstraint : public FollowPathConstraintBase
Expand All @@ -18,7 +18,7 @@ class FollowPathConstraint : public FollowPathConstraintBase

private:
RawPath m_rawPath;
std::vector<rcp<ContourMeasure>> m_contours;
PathMeasure m_pathMeasure;
TransformComponents m_ComponentsA;
TransformComponents m_ComponentsB;
};
Expand Down
14 changes: 14 additions & 0 deletions include/rive/math/contour_measure.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ class ContourMeasure : public RefCnt<ContourMeasure>
{
Vec2D pos, tan;
};
struct PosTanDistance
{
Vec2D pos, tan;
/// Distance along the curve.
float distance;

/// Squared distance to point projected to curve.
float sqDistanceToPoint;

PosTanDistance() : distance(0.0f), sqDistanceToPoint(0.0f) {}
PosTanDistance(PosTan posTan, float dist) :
pos(posTan.pos), tan(posTan.tan), distance(dist), sqDistanceToPoint(0.0f)
{}
};
PosTan getPosTan(float distance) const;

void getSegment(float startDistance, float endDistance, RawPath* dst, bool startWithMove) const;
Expand Down
25 changes: 25 additions & 0 deletions include/rive/math/path_measure.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef _RIVE_PATH_MEASURE_HPP_
#define _RIVE_PATH_MEASURE_HPP_

#include "rive/math/contour_measure.hpp"
#include <vector>

namespace rive
{
class PathMeasure
{
public:
PathMeasure();
PathMeasure(const RawPath* path);
ContourMeasure::PosTanDistance atDistance(float distance) const;
ContourMeasure::PosTanDistance atPercentage(float percentageDistance) const;

float length() const { return m_length; }

private:
float m_length;
std::vector<rcp<ContourMeasure>> m_contours;
};
} // namespace rive

#endif
47 changes: 4 additions & 43 deletions src/constraints/follow_path_constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,20 @@

using namespace rive;

static float positiveMod(float value, float range)
{
assert(range > 0.0f);
float v = fmodf(value, range);
if (v < 0.0f)
{
v += range;
}
return v;
}

void FollowPathConstraint::distanceChanged() { markConstraintDirty(); }
void FollowPathConstraint::orientChanged() { markConstraintDirty(); }

const Mat2D FollowPathConstraint::targetTransform() const
{
if (m_Target->is<Shape>() || m_Target->is<Path>())
{
float totalLength = 0.0f;
for (auto contour : m_contours)
{
totalLength += contour->length();
}

float actualDistance = positiveMod(distance(), 1.0f);
if (distance() != 0 && actualDistance == 0)
{
actualDistance = 1;
}
float distanceUnits = totalLength * std::min(1.0f, std::max(0.0f, actualDistance));
float runningLength = 0;
ContourMeasure::PosTan posTan = ContourMeasure::PosTan();
for (auto contour : m_contours)
{
float pathLength = contour->length();
if (distanceUnits <= pathLength + runningLength)
{
posTan = contour->getPosTan(distanceUnits - runningLength);
break;
}
runningLength += pathLength;
}
Vec2D position = Vec2D(posTan.pos.x, posTan.pos.y);
auto result = m_pathMeasure.atPercentage(distance());
Vec2D position = result.pos;
Mat2D transformB = Mat2D(m_Target->worldTransform());

if (orient())
{
transformB = Mat2D::fromRotation(std::atan2(posTan.tan.y, posTan.tan.x));
transformB = Mat2D::fromRotation(std::atan2(result.tan.y, result.tan.x));
}
Vec2D offsetPosition = Vec2D();
if (offset())
Expand Down Expand Up @@ -146,17 +112,12 @@ void FollowPathConstraint::update(ComponentDirt value)
if (paths.size() > 0)
{
m_rawPath.rewind();
m_contours.clear();
for (auto path : paths)
{
m_rawPath.addPath(path->rawPath(), &path->pathTransform());
}

auto measure = ContourMeasureIter(&m_rawPath);
for (auto contour = measure.next(); contour != nullptr; contour = measure.next())
{
m_contours.push_back(contour);
}
m_pathMeasure = PathMeasure(&m_rawPath);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/math/contour_measure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "rive/math/math_types.hpp"
#include "rive/math/wangs_formula.hpp"
#include <cmath>
#include <limits>

using namespace rive;

Expand Down
48 changes: 48 additions & 0 deletions src/math/path_measure.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "rive/math/path_measure.hpp"

using namespace rive;

PathMeasure::PathMeasure() : m_length(0.0f) {}

PathMeasure::PathMeasure(const RawPath* path) : m_length(0.0f)
{
auto measure = ContourMeasureIter(path);
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);
}

0 comments on commit 6e9a2a5

Please sign in to comment.