Adding transform constraint.
diff --git a/dev/defs/constraints/transform_constraint.json b/dev/defs/constraints/transform_constraint.json new file mode 100644 index 0000000..add65a0 --- /dev/null +++ b/dev/defs/constraints/transform_constraint.json
@@ -0,0 +1,28 @@ +{ + "name": "TransformConstraint", + "key": { + "int": 83, + "string": "transformconstraint" + }, + "extends": "constraints/targeted_constraint.json", + "properties": { + "sourceSpaceValue": { + "type": "uint", + "initialValue": "0", + "key": { + "int": 179, + "string": "sourcespacevalue" + }, + "description": "The source transform space." + }, + "destSpaceValue": { + "type": "uint", + "initialValue": "0", + "key": { + "int": 180, + "string": "destspacevalue" + }, + "description": "The destination transform space." + } + } +} \ No newline at end of file
diff --git a/include/constraints/constraint.hpp b/include/constraints/constraint.hpp index e8427b1..093e11d 100644 --- a/include/constraints/constraint.hpp +++ b/include/constraints/constraint.hpp
@@ -5,6 +5,8 @@ namespace rive { class TransformComponent; + class Mat2D; + class Constraint : public ConstraintBase { public: @@ -15,6 +17,8 @@ void buildDependencies() override; void onDirty(ComponentDirt dirt) override; }; + + const Mat2D& getParentWorld(const TransformComponent& component); } // namespace rive #endif \ No newline at end of file
diff --git a/include/constraints/transform_constraint.hpp b/include/constraints/transform_constraint.hpp new file mode 100644 index 0000000..4100e7c --- /dev/null +++ b/include/constraints/transform_constraint.hpp
@@ -0,0 +1,29 @@ +#ifndef _RIVE_TRANSFORM_CONSTRAINT_HPP_ +#define _RIVE_TRANSFORM_CONSTRAINT_HPP_ +#include "generated/constraints/transform_constraint_base.hpp" +#include "transform_space.hpp" +#include "math/transform_components.hpp" + +#include <stdio.h> +namespace rive +{ + class TransformConstraint : public TransformConstraintBase + { + private: + TransformComponents m_ComponentsA; + TransformComponents m_ComponentsB; + + public: + void constrain(TransformComponent* component) override; + TransformSpace sourceSpace() const + { + return (TransformSpace)sourceSpaceValue(); + } + TransformSpace destSpace() const + { + return (TransformSpace)destSpaceValue(); + } + }; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/generated/constraints/transform_constraint_base.hpp b/include/generated/constraints/transform_constraint_base.hpp new file mode 100644 index 0000000..aa85a2a --- /dev/null +++ b/include/generated/constraints/transform_constraint_base.hpp
@@ -0,0 +1,90 @@ +#ifndef _RIVE_TRANSFORM_CONSTRAINT_BASE_HPP_ +#define _RIVE_TRANSFORM_CONSTRAINT_BASE_HPP_ +#include "constraints/targeted_constraint.hpp" +#include "core/field_types/core_uint_type.hpp" +namespace rive +{ + class TransformConstraintBase : public TargetedConstraint + { + protected: + typedef TargetedConstraint Super; + + public: + static const uint16_t typeKey = 83; + + /// Helper to quickly determine if a core object extends another without + /// RTTI at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case TransformConstraintBase::typeKey: + case TargetedConstraintBase::typeKey: + case ConstraintBase::typeKey: + case ComponentBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t sourceSpaceValuePropertyKey = 179; + static const uint16_t destSpaceValuePropertyKey = 180; + + private: + int m_SourceSpaceValue = 0; + int m_DestSpaceValue = 0; + public: + inline int sourceSpaceValue() const { return m_SourceSpaceValue; } + void sourceSpaceValue(int value) + { + if (m_SourceSpaceValue == value) + { + return; + } + m_SourceSpaceValue = value; + sourceSpaceValueChanged(); + } + + inline int destSpaceValue() const { return m_DestSpaceValue; } + void destSpaceValue(int value) + { + if (m_DestSpaceValue == value) + { + return; + } + m_DestSpaceValue = value; + destSpaceValueChanged(); + } + + Core* clone() const override; + void copy(const TransformConstraintBase& object) + { + m_SourceSpaceValue = object.m_SourceSpaceValue; + m_DestSpaceValue = object.m_DestSpaceValue; + TargetedConstraint::copy(object); + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case sourceSpaceValuePropertyKey: + m_SourceSpaceValue = CoreUintType::deserialize(reader); + return true; + case destSpaceValuePropertyKey: + m_DestSpaceValue = CoreUintType::deserialize(reader); + return true; + } + return TargetedConstraint::deserialize(propertyKey, reader); + } + + protected: + virtual void sourceSpaceValueChanged() {} + virtual void destSpaceValueChanged() {} + }; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/generated/core_registry.hpp b/include/generated/core_registry.hpp index a4128fc..cd04bc5 100644 --- a/include/generated/core_registry.hpp +++ b/include/generated/core_registry.hpp
@@ -49,6 +49,7 @@ #include "constraints/distance_constraint.hpp" #include "constraints/ik_constraint.hpp" #include "constraints/targeted_constraint.hpp" +#include "constraints/transform_constraint.hpp" #include "container_component.hpp" #include "draw_rules.hpp" #include "draw_target.hpp" @@ -94,6 +95,8 @@ return new DistanceConstraint(); case IKConstraintBase::typeKey: return new IKConstraint(); + case TransformConstraintBase::typeKey: + return new TransformConstraint(); case AnimationStateBase::typeKey: return new AnimationState(); case KeyedObjectBase::typeKey: @@ -244,6 +247,14 @@ case IKConstraintBase::parentBoneCountPropertyKey: object->as<IKConstraintBase>()->parentBoneCount(value); break; + case TransformConstraintBase::sourceSpaceValuePropertyKey: + object->as<TransformConstraintBase>()->sourceSpaceValue( + value); + break; + case TransformConstraintBase::destSpaceValuePropertyKey: + object->as<TransformConstraintBase>()->destSpaceValue( + value); + break; case AnimationStateBase::animationIdPropertyKey: object->as<AnimationStateBase>()->animationId(value); break; @@ -657,6 +668,12 @@ return object->as<DistanceConstraintBase>()->modeValue(); case IKConstraintBase::parentBoneCountPropertyKey: return object->as<IKConstraintBase>()->parentBoneCount(); + case TransformConstraintBase::sourceSpaceValuePropertyKey: + return object->as<TransformConstraintBase>() + ->sourceSpaceValue(); + case TransformConstraintBase::destSpaceValuePropertyKey: + return object->as<TransformConstraintBase>() + ->destSpaceValue(); case AnimationStateBase::animationIdPropertyKey: return object->as<AnimationStateBase>()->animationId(); case KeyedObjectBase::objectIdPropertyKey: @@ -941,6 +958,8 @@ case TargetedConstraintBase::targetIdPropertyKey: case DistanceConstraintBase::modeValuePropertyKey: case IKConstraintBase::parentBoneCountPropertyKey: + case TransformConstraintBase::sourceSpaceValuePropertyKey: + case TransformConstraintBase::destSpaceValuePropertyKey: case AnimationStateBase::animationIdPropertyKey: case KeyedObjectBase::objectIdPropertyKey: case BlendAnimationBase::animationIdPropertyKey:
diff --git a/include/transform_space.hpp b/include/transform_space.hpp new file mode 100644 index 0000000..0bfc44d --- /dev/null +++ b/include/transform_space.hpp
@@ -0,0 +1,11 @@ +#ifndef _RIVE_TRANSFORM_SPACE_HPP_ +#define _RIVE_TRANSFORM_SPACE_HPP_ +namespace rive +{ + enum class TransformSpace : unsigned int + { + world = 0, + local = 1 + }; +} +#endif \ No newline at end of file
diff --git a/src/constraints/constraint.cpp b/src/constraints/constraint.cpp index cd32ec6..503f024 100644 --- a/src/constraints/constraint.cpp +++ b/src/constraints/constraint.cpp
@@ -2,6 +2,8 @@ #include "container_component.hpp" #include "transform_component.hpp" #include "core_context.hpp" +#include "artboard.hpp" +#include "math/mat2d.hpp" using namespace rive; @@ -35,4 +37,20 @@ // Whenever the constraint gets any dirt, make sure to mark the constrained // component dirty. markConstraintDirty(); +} + +static Mat2D identity; +const Mat2D& rive::getParentWorld(const TransformComponent& component) +{ + auto parent = component.parent(); + if (parent->is<Artboard>()) + { + // TODO: when we have symbols working artboards will need to store their + // world transform (probably should just become TransformComponent). + return identity; + } + else + { + return parent->as<TransformComponent>()->worldTransform(); + } } \ No newline at end of file
diff --git a/src/constraints/ik_constraint.cpp b/src/constraints/ik_constraint.cpp index 3e4f5a0..2f74b92 100644 --- a/src/constraints/ik_constraint.cpp +++ b/src/constraints/ik_constraint.cpp
@@ -5,22 +5,6 @@ using namespace rive; -static Mat2D identity; -static const Mat2D& getParentWorld(const TransformComponent& component) -{ - auto parent = component.parent(); - if (parent->is<Artboard>()) - { - // TODO: when we have symbols working artboards will need to store their - // world transform (probably should just become TransformComponents). - return identity; - } - else - { - return parent->as<TransformComponent>()->worldTransform(); - } -} - StatusCode IKConstraint::onAddedClean(CoreContext* context) { if (!parent()->is<Bone>()) @@ -221,6 +205,7 @@ { Mat2D::fromRotation(transform, rotation); } + // Translate transform[4] = c.x(); transform[5] = c.y();
diff --git a/src/constraints/transform_constraint.cpp b/src/constraints/transform_constraint.cpp new file mode 100644 index 0000000..36a9ab3 --- /dev/null +++ b/src/constraints/transform_constraint.cpp
@@ -0,0 +1,62 @@ +#include "constraints/transform_constraint.hpp" +#include "transform_component.hpp" +#include "math/mat2d.hpp" +#include <cmath> + +using namespace rive; + +void TransformConstraint::constrain(TransformComponent* component) +{ + if (m_Target == nullptr) + { + return; + } + + const Mat2D& transformA = component->worldTransform(); + Mat2D transformB(m_Target->worldTransform()); + if (sourceSpace() == TransformSpace::local) + { + const Mat2D& targetParentWorld = getParentWorld(*m_Target); + + Mat2D inverse; + if (!Mat2D::invert(inverse, targetParentWorld)) + { + return; + } + Mat2D::multiply(transformB, inverse, transformB); + } + if (destSpace() == TransformSpace::local) + { + const Mat2D& targetParentWorld = getParentWorld(*component); + Mat2D::multiply(transformB, targetParentWorld, transformB); + } + + Mat2D::decompose(m_ComponentsA, transformA); + Mat2D::decompose(m_ComponentsB, transformB); + + float angleA = std::fmod(m_ComponentsA.rotation(), (float)M_PI_2); + float angleB = std::fmod(m_ComponentsB.rotation(), (float)M_PI_2); + float diff = angleB - angleA; + if (diff > M_PI) + { + diff -= M_PI_2; + } + else if (diff < -M_PI) + { + diff += M_PI_2; + } + + float t = strength(); + float ti = 1.0f - t; + + m_ComponentsB.rotation(angleA + diff * t); + m_ComponentsB.x(m_ComponentsA.x() * ti + m_ComponentsB.x() * t); + m_ComponentsB.y(m_ComponentsA.y() * ti + m_ComponentsB.y() * t); + m_ComponentsB.scaleX(m_ComponentsA.scaleX() * ti + + m_ComponentsB.scaleX() * t); + m_ComponentsB.scaleY(m_ComponentsA.scaleY() * ti + + m_ComponentsB.scaleY() * t); + m_ComponentsB.skew(m_ComponentsA.skew() * ti + m_ComponentsB.skew() * t); + + Mat2D::compose(component->mutableWorldTransform(), m_ComponentsB); +}
diff --git a/src/generated/constraints/transform_constraint_base.cpp b/src/generated/constraints/transform_constraint_base.cpp new file mode 100644 index 0000000..8214dae --- /dev/null +++ b/src/generated/constraints/transform_constraint_base.cpp
@@ -0,0 +1,11 @@ +#include "generated/constraints/transform_constraint_base.hpp" +#include "constraints/transform_constraint.hpp" + +using namespace rive; + +Core* TransformConstraintBase::clone() const +{ + auto cloned = new TransformConstraint(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/math/mat2d.cpp b/src/math/mat2d.cpp index 617ebfe..f2d96b5 100644 --- a/src/math/mat2d.cpp +++ b/src/math/mat2d.cpp
@@ -104,7 +104,7 @@ { float r = components.rotation(); - if (r != 0.0) + if (r != 0.0f) { Mat2D::fromRotation(result, r); } @@ -119,7 +119,7 @@ Mat2D::scale(result, result, scale); float sk = components.skew(); - if (sk != 0.0) + if (sk != 0.0f) { result[2] = result[0] * sk + result[2]; result[3] = result[1] * sk + result[3];
diff --git a/test/assets/transform_constraint.riv b/test/assets/transform_constraint.riv new file mode 100644 index 0000000..30f765f --- /dev/null +++ b/test/assets/transform_constraint.riv Binary files differ
diff --git a/test/rive_testing.cpp b/test/rive_testing.cpp new file mode 100644 index 0000000..0191903 --- /dev/null +++ b/test/rive_testing.cpp
@@ -0,0 +1,14 @@ +#include "rive_testing.hpp" + +bool aboutEqual(const rive::Mat2D& a, const rive::Mat2D& b) +{ + const float epsilon = 0.0001f; + for (int i = 0; i < 6; i++) + { + if (std::fabs(a[i] - b[i]) > epsilon) + { + return false; + } + } + return true; +} \ No newline at end of file
diff --git a/test/rive_testing.hpp b/test/rive_testing.hpp index 8518891..c617cd6 100644 --- a/test/rive_testing.hpp +++ b/test/rive_testing.hpp
@@ -5,18 +5,7 @@ #include <sstream> #include "math/mat2d.hpp" -static bool aboutEqual(const rive::Mat2D& a, const rive::Mat2D& b) -{ - const float epsilon = 0.0001f; - for (int i = 0; i < 6; i++) - { - if (std::fabs(a[i] - b[i]) > epsilon) - { - return false; - } - } - return true; -} +bool aboutEqual(const rive::Mat2D& a, const rive::Mat2D& b); namespace Catch {
diff --git a/test/transform_constraint_test.cpp b/test/transform_constraint_test.cpp new file mode 100644 index 0000000..eb077eb --- /dev/null +++ b/test/transform_constraint_test.cpp
@@ -0,0 +1,44 @@ +#include "rive_testing.hpp" +#include "core/binary_reader.hpp" +#include "file.hpp" +#include "no_op_renderer.hpp" +#include "node.hpp" +#include "bones/bone.hpp" +#include "shapes/shape.hpp" +#include <cstdio> + +TEST_CASE("transform constraint updates world transform", "[file]") +{ + FILE* fp = fopen("../../test/assets/transform_constraint.riv", "r"); + REQUIRE(fp != nullptr); + + fseek(fp, 0, SEEK_END); + auto length = ftell(fp); + fseek(fp, 0, SEEK_SET); + uint8_t* bytes = new uint8_t[length]; + REQUIRE(fread(bytes, 1, length, fp) == length); + auto reader = rive::BinaryReader(bytes, length); + rive::File* file = nullptr; + auto result = rive::File::import(reader, &file); + + REQUIRE(result == rive::ImportResult::success); + REQUIRE(file != nullptr); + REQUIRE(file->artboard() != nullptr); + + auto artboard = file->artboard(); + + REQUIRE(artboard->find<rive::TransformComponent>("Target") != nullptr); + auto target = artboard->find<rive::TransformComponent>("Target"); + + REQUIRE(artboard->find<rive::TransformComponent>("Rectangle") != nullptr); + auto rectangle = artboard->find<rive::TransformComponent>("Rectangle"); + + artboard->advance(0.0f); + + // Expect the transform constraint to have placed the shape in the same + // exact world transform as the target. + REQUIRE(aboutEqual(target->worldTransform(), rectangle->worldTransform())); + + delete file; + delete[] bytes; +} \ No newline at end of file