blob: 892b9eb35573d65638d3eebc38ad804ae978a866 [file] [log] [blame] [edit]
#include "rive/artboard.hpp"
#include "rive/command_path.hpp"
#include "rive/constraints/constrainable_list.hpp"
#include "rive/constraints/follow_path_constraint.hpp"
#include "rive/factory.hpp"
#include "rive/math/contour_measure.hpp"
#include "rive/math/mat2d.hpp"
#include "rive/math/math_types.hpp"
#include "rive/shapes/path.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/transform_component.hpp"
#include <algorithm>
#include <iostream>
#include <typeinfo>
using namespace rive;
void FollowPathConstraint::distanceChanged() { markConstraintDirty(); }
void FollowPathConstraint::orientChanged() { markConstraintDirty(); }
const Mat2D FollowPathConstraint::targetTransform(float distanceOffset) const
{
if (m_Target->is<Shape>() || m_Target->is<Path>())
{
auto result = m_pathMeasure.atPercentage(distance() * distanceOffset);
Vec2D position = result.pos;
Mat2D transformB = Mat2D(m_Target->worldTransform());
if (orient())
{
transformB =
Mat2D::fromRotation(std::atan2(result.tan.y, result.tan.x));
}
Vec2D offsetPosition = Vec2D();
if (offset())
{
if (parent()->is<TransformComponent>())
{
Mat2D components =
parent()->as<TransformComponent>()->transform();
offsetPosition.x = components[4];
offsetPosition.y = components[5];
}
}
transformB[4] = position.x + offsetPosition.x;
transformB[5] = position.y + offsetPosition.y;
return transformB;
}
else
{
return m_Target->worldTransform();
}
}
void FollowPathConstraint::constrainList(ConstrainableList* list)
{
auto listTransform = list->listTransform();
std::vector<Mat2D*> transforms;
list->listItemTransforms(transforms);
auto count = transforms.size();
float offsetDistance = count <= 1 ? 0 : 1 / ((float)count - 1);
for (int i = 0; i < count; i++)
{
auto transform = transforms[i];
auto transformComponents =
constrainAtOffset(*transform, listTransform, i * offsetDistance);
auto transformB = Mat2D::compose(transformComponents);
transform->xx(transformB.xx());
transform->xy(transformB.xy());
transform->yx(transformB.yx());
transform->yy(transformB.yy());
transform->tx(transformB.tx());
transform->ty(transformB.ty());
}
}
TransformComponents FollowPathConstraint::constrainAtOffset(
const Mat2D& componentTransform,
const Mat2D& parentTransform,
float componentOffset)
{
if (m_Target == nullptr || m_Target->isCollapsed())
{
return TransformComponents();
}
Mat2D transformB(targetTransform(componentOffset));
auto transformComponents =
constrainHelper(componentTransform, transformB, parentTransform);
return transformComponents;
}
void FollowPathConstraint::constrain(TransformComponent* component)
{
if (m_Target == nullptr || m_Target->isCollapsed())
{
return;
}
Mat2D transformB(targetTransform());
const Mat2D& targetParentWorld = getParentWorld(*component);
auto transformComponents = constrainHelper(component->worldTransform(),
transformB,
targetParentWorld);
component->mutableWorldTransform() = Mat2D::compose(transformComponents);
}
TransformComponents FollowPathConstraint::constrainHelper(
const Mat2D& componentTransform,
Mat2D& transformB,
const Mat2D& componentParentWorld)
{
const Mat2D& transformA = componentTransform;
if (sourceSpace() == TransformSpace::local)
{
const Mat2D& targetParentWorld = getParentWorld(*m_Target);
Mat2D inverse;
if (!targetParentWorld.invert(&inverse))
{
TransformComponents result;
return result;
}
transformB = inverse * transformB;
}
if (destSpace() == TransformSpace::local)
{
transformB = componentParentWorld * transformB;
}
auto componentsA = transformA.decompose();
auto componentsB = transformB.decompose();
float t = strength();
float ti = 1.0f - t;
if (!orient())
{
float angleA = std::fmod(componentsA.rotation(), math::PI * 2);
componentsB.rotation(angleA);
}
componentsB.x(componentsA.x() * ti + componentsB.x() * t);
componentsB.y(componentsA.y() * ti + componentsB.y() * t);
componentsB.scaleX(componentsA.scaleX());
componentsB.scaleY(componentsA.scaleY());
componentsB.skew(componentsA.skew());
return componentsB;
}
void FollowPathConstraint::update(ComponentDirt value)
{
std::vector<Path*> paths;
if (m_Target->is<Shape>())
{
auto shape = m_Target->as<Shape>();
for (auto path : shape->paths())
{
paths.push_back(path);
}
}
else if (m_Target->is<Path>())
{
paths.push_back(m_Target->as<Path>());
}
if (paths.size() > 0)
{
m_rawPath.rewind();
for (auto path : paths)
{
m_rawPath.addPath(path->rawPath(), &path->pathTransform());
}
m_pathMeasure = PathMeasure(&m_rawPath);
}
}
StatusCode FollowPathConstraint::onAddedClean(CoreContext* context)
{
if (m_Target != nullptr)
{
if (m_Target->is<Shape>())
{
Shape* shape = static_cast<Shape*>(m_Target);
shape->addFlags(PathFlags::followPath);
}
else if (m_Target->is<Path>())
{
Path* path = static_cast<Path*>(m_Target);
path->addFlags(PathFlags::followPath);
}
}
return Super::onAddedClean(context);
}
void FollowPathConstraint::buildDependencies()
{
if (m_Target != nullptr &&
m_Target->is<Shape>()) // which should never happen
{
// Follow path should update after the target's path composer
Shape* shape = static_cast<Shape*>(m_Target);
shape->pathComposer()->addDependent(this);
}
// ok this appears to be enough to get the inital layout & animations to be
// working.
else if (m_Target != nullptr &&
m_Target->is<Path>()) // which should never happen
{
// or do we need to be dependent on the shape still???
Path* path = static_cast<Path*>(m_Target);
path->addDependent(this);
}
// The constrained component should update after follow path
addDependent(parent());
auto constrainableList = ConstrainableList::from(parent());
if (constrainableList != nullptr)
{
constrainableList->addListConstraint(this);
}
}