Add translation constraint + test.
diff --git a/include/rive/constraints/transform_constraint.hpp b/include/rive/constraints/transform_constraint.hpp index 20edc76..026c2f9 100644 --- a/include/rive/constraints/transform_constraint.hpp +++ b/include/rive/constraints/transform_constraint.hpp
@@ -1,7 +1,6 @@ #ifndef _RIVE_TRANSFORM_CONSTRAINT_HPP_ #define _RIVE_TRANSFORM_CONSTRAINT_HPP_ #include "rive/generated/constraints/transform_constraint_base.hpp" -#include "rive/transform_space.hpp" #include "rive/math/transform_components.hpp" #include <stdio.h> @@ -15,14 +14,6 @@ public: void constrain(TransformComponent* component) override; - TransformSpace sourceSpace() const - { - return (TransformSpace)sourceSpaceValue(); - } - TransformSpace destSpace() const - { - return (TransformSpace)destSpaceValue(); - } }; } // namespace rive
diff --git a/include/rive/constraints/transform_space_constraint.hpp b/include/rive/constraints/transform_space_constraint.hpp index 4897451..e538814 100644 --- a/include/rive/constraints/transform_space_constraint.hpp +++ b/include/rive/constraints/transform_space_constraint.hpp
@@ -1,12 +1,21 @@ #ifndef _RIVE_TRANSFORM_SPACE_CONSTRAINT_HPP_ #define _RIVE_TRANSFORM_SPACE_CONSTRAINT_HPP_ #include "rive/generated/constraints/transform_space_constraint_base.hpp" +#include "rive/transform_space.hpp" #include <stdio.h> namespace rive { class TransformSpaceConstraint : public TransformSpaceConstraintBase { public: + TransformSpace sourceSpace() const + { + return (TransformSpace)sourceSpaceValue(); + } + TransformSpace destSpace() const + { + return (TransformSpace)destSpaceValue(); + } }; } // namespace rive
diff --git a/src/constraints/translation_constraint.cpp b/src/constraints/translation_constraint.cpp index 3e7bc3f..18fa59f 100644 --- a/src/constraints/translation_constraint.cpp +++ b/src/constraints/translation_constraint.cpp
@@ -1,8 +1,114 @@ #include "rive/constraints/translation_constraint.hpp" #include "rive/transform_component.hpp" #include "rive/math/mat2d.hpp" +#include "rive/math/vec2d.hpp" #include <cmath> using namespace rive; -void TranslationConstraint::constrain(TransformComponent* component) {} +void TranslationConstraint::constrain(TransformComponent* component) +{ + Mat2D& transformA = component->mutableWorldTransform(); + Vec2D translationA(transformA[4], transformA[5]); + Vec2D translationB; + if (m_Target == nullptr) + { + Vec2D::copy(translationB, translationA); + } + else + { + 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); + } + translationB[0] = transformB[4]; + translationB[1] = transformB[5]; + + if (!doesCopy()) + { + translationB[0] = + destSpace() == TransformSpace::local ? 0.0f : translationA[0]; + } + else + { + translationB[0] *= copyFactor(); + if (offset()) + { + translationB[0] += component->x(); + } + } + + if (!doesCopyY()) + { + translationB[1] = + destSpace() == TransformSpace::local ? 0.0f : translationA[1]; + } + else + { + translationB[1] *= copyFactorY(); + + if (offset()) + { + translationB[1] += component->y(); + } + } + + if (destSpace() == TransformSpace::local) + { + // Destination space is in parent transform coordinates. + Vec2D::transform( + translationB, translationB, getParentWorld(*component)); + } + } + + bool clampLocal = minMaxSpace() == TransformSpace::local; + if (clampLocal) + { + // Apply min max in local space, so transform to local coordinates + // first. + Mat2D invert; + if (!Mat2D::invert(invert, getParentWorld(*component))) + { + return; + } + // Get our target world coordinates in parent local. + Vec2D::transform(translationB, translationB, invert); + } + if (max() && translationB[0] > maxValue()) + { + translationB[0] = maxValue(); + } + if (min() && translationB[0] < minValue()) + { + translationB[0] = minValue(); + } + if (maxY() && translationB[1] > maxValueY()) + { + translationB[1] = maxValueY(); + } + if (minY() && translationB[1] < minValueY()) + { + translationB[1] = minValueY(); + } + if (clampLocal) + { + // Transform back to world. + Vec2D::transform( + translationB, translationB, getParentWorld(*component)); + } + + float t = strength(); + float ti = 1.0f - t; + + // Just interpolate world translation + transformA[4] = translationA[0] * ti + translationB[0] * t; + transformA[5] = translationA[1] * ti + translationB[1] * t; +}
diff --git a/test/assets/translation_constraint.riv b/test/assets/translation_constraint.riv new file mode 100644 index 0000000..0070b0b --- /dev/null +++ b/test/assets/translation_constraint.riv Binary files differ
diff --git a/test/translation_constraint_test.cpp b/test/translation_constraint_test.cpp new file mode 100644 index 0000000..da4f98b --- /dev/null +++ b/test/translation_constraint_test.cpp
@@ -0,0 +1,49 @@ +#include <rive/core/binary_reader.hpp> +#include <rive/file.hpp> +#include <rive/node.hpp> +#include <rive/bones/bone.hpp> +#include <rive/shapes/shape.hpp> +#include <rive/math/transform_components.hpp> +#include "no_op_renderer.hpp" +#include "rive_testing.hpp" +#include <cstdio> + +TEST_CASE("translation constraint updates world transform", "[file]") +{ + FILE* fp = fopen("../../test/assets/translation_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>("rect") != nullptr); + auto rectangle = artboard->find<rive::TransformComponent>("rect"); + + rive::TransformComponents targetComponents; + rive::Mat2D::decompose(targetComponents, target->worldTransform()); + rive::TransformComponents rectComponents; + rive::Mat2D::decompose(rectComponents, rectangle->worldTransform()); + + artboard->advance(0.0f); + + REQUIRE(targetComponents.x() == rectComponents.x()); + REQUIRE(targetComponents.y() == rectComponents.y()); + + delete file; + delete[] bytes; +} \ No newline at end of file