blob: 6ea34c1773e184b40c9f8c03901e7aab0ce8929e [file]
#include <rive/artboard.hpp>
#include <rive/constraints/rotation_constraint.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 <utils/no_op_factory.hpp>
#include <utils/no_op_renderer.hpp>
#include "rive_file_reader.hpp"
#include "rive_testing.hpp"
#include <cstdio>
TEST_CASE("rotation constraint updates world transform", "[file]")
{
auto file = ReadRiveFile("assets/rotation_constraint.riv");
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");
artboard->advance(0.0f);
auto targetComponents = target->worldTransform().decompose();
auto rectComponents = rectangle->worldTransform().decompose();
REQUIRE(targetComponents.rotation() == rectComponents.rotation());
}
static float worldRotation(rive::Artboard* artboard, const char* name)
{
auto component = artboard->find<rive::TransformComponent>(name);
REQUIRE(component != nullptr);
return component->worldTransform().decompose().rotation();
}
TEST_CASE("rotation constraint limits hold across the half turn", "[file]")
{
auto file = ReadRiveFile("assets/rotation_constraint_wrap.riv");
auto artboard = file->artboard();
artboard->advance(0.0f);
// A min of -180 degrees used to never clamp since decomposed angles are
// never below it.
CHECK(
std::abs(worldRotation(artboard, "min_at_half_turn")) ==
Approx(rive::math::PI).margin(0.0001f));
// 200 degrees is inside 150 to 210 even though it decomposes to -160.
CHECK(
worldRotation(artboard, "range_across_half_turn") ==
Approx(-2.7925268f).margin(0.0001f));
CHECK(
worldRotation(artboard, "max_zero_clamped") ==
Approx(0.0f).margin(0.0001f));
CHECK(
worldRotation(artboard, "max_zero_free") ==
Approx(-2.5f).margin(0.0001f));
}
namespace
{
struct LimitCase
{
float rotation;
bool min;
float minValue;
bool max;
float maxValue;
float expected;
};
float degrees(float value) { return value * rive::math::PI / 180.0f; }
// A node under the artboard with an untargeted rotation constraint, so the
// limits apply to its own world rotation.
float constrainedRotation(const LimitCase& limits)
{
rive::NoOpFactory factory;
rive::Artboard artboard(&factory);
artboard.addObject(&artboard);
auto node = new rive::Node();
node->rotation(degrees(limits.rotation));
artboard.addObject(node);
node->parentId(artboard.idOf(&artboard));
auto constraint = new rive::RotationConstraint();
constraint->min(limits.min);
constraint->minValue(degrees(limits.minValue));
constraint->max(limits.max);
constraint->maxValue(degrees(limits.maxValue));
artboard.addObject(constraint);
constraint->parentId(artboard.idOf(node));
REQUIRE(artboard.initialize() == rive::StatusCode::Ok);
artboard.advance(0.0f);
return node->worldTransform().decompose().rotation();
}
} // namespace
TEST_CASE("rotation constraint limits clamp off center angles", "[constraint]")
{
// Mirrored in rive_core's rotation_constraint_test.dart.
const LimitCase cases[] = {
{-120, false, 0, true, 90, 90},
{120, true, -90, false, 0, -90},
{-150, true, 0, true, 90, 90},
{-60, true, 0, true, 90, 0},
{45, true, 90, true, 0, 90},
{-170, true, 90, true, 0, 90},
{170, true, -180, false, 0, -180},
{200, true, 150, true, 210, -160},
};
for (const auto& limits : cases)
{
float rotation = constrainedRotation(limits);
float expected = degrees(limits.expected);
// The half turn decomposes to either sign.
if (std::abs(expected) == rive::math::PI)
{
rotation = std::abs(rotation);
expected = std::abs(expected);
}
CAPTURE(limits.rotation, limits.minValue, limits.maxValue);
CHECK(rotation == Approx(expected).margin(0.0001f));
}
}