PlayRho  2.0.0
An interactive physics engine & library.
MotorJoint.cpp

This is the googletest based unit testing file for the interfaces to playrho::d2::MotorJointConf.

/*
* Copyright (c) 2023 Louis Langholtz https://github.com/louis-langholtz/PlayRho
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "UnitTests.hpp"
using namespace playrho;
using namespace playrho::d2;
TEST(MotorJointConf, DefaultConstruction)
{
MotorJointConf def{};
EXPECT_EQ(def.bodyA, InvalidBodyID);
EXPECT_EQ(def.bodyB, InvalidBodyID);
EXPECT_EQ(def.collideConnected, false);
EXPECT_EQ(def.linearOffset, (Length2{}));
EXPECT_EQ(def.angularOffset, 0_deg);
EXPECT_EQ(def.maxForce, MotorJointConf::DefaultMaxForce);
EXPECT_EQ(def.maxTorque, MotorJointConf::DefaultMaxTorque);
EXPECT_EQ(def.maxForce, 1_N);
EXPECT_EQ(def.maxTorque, 1_Nm);
EXPECT_EQ(def.correctionFactor, MotorJointConf::DefaultCorrectionFactor);
}
TEST(MotorJointConf, BuilderConstruction)
{
const auto bodyA = static_cast<BodyID>(0x1);
const auto bodyB = static_cast<BodyID>(0x2);
const auto collideConnected = true;
const auto linearOffset = Length2{2_m, 3_m};
const auto angularOffset = 33_rad;
const auto maxForce = 22_N;
const auto maxTorque = 31_Nm;
const auto correctionFactor = Real(0.44f);
const auto def = MotorJointConf{}
.UseBodyA(bodyA)
.UseBodyB(bodyB)
.UseCollideConnected(collideConnected)
.UseLinearOffset(linearOffset)
.UseAngularOffset(angularOffset)
.UseMaxForce(maxForce)
.UseMaxTorque(maxTorque)
.UseCorrectionFactor(correctionFactor);
EXPECT_EQ(def.bodyA, bodyA);
EXPECT_EQ(def.bodyB, bodyB);
EXPECT_EQ(def.collideConnected, collideConnected);
EXPECT_EQ(def.linearOffset, linearOffset);
EXPECT_EQ(def.angularOffset, angularOffset);
EXPECT_EQ(def.maxForce, maxForce);
EXPECT_EQ(def.maxTorque, maxTorque);
EXPECT_EQ(def.correctionFactor, correctionFactor);
}
TEST(MotorJoint, Construction)
{
auto world = World{};
const auto b0 = CreateBody(world);
const auto b1 = CreateBody(world);
auto def = GetMotorJointConf(world, b0, b1);
const auto jointID = CreateJoint(world, def);
EXPECT_EQ(GetType(world, jointID), GetTypeID<MotorJointConf>());
EXPECT_EQ(GetBodyA(world, jointID), def.bodyA);
EXPECT_EQ(GetBodyB(world, jointID), def.bodyB);
EXPECT_EQ(GetCollideConnected(world, jointID), def.collideConnected);
EXPECT_EQ(GetLinearReaction(world, jointID), Momentum2{});
EXPECT_EQ(GetAngularReaction(world, jointID), AngularMomentum{0});
EXPECT_EQ(GetLinearOffset(world, jointID), def.linearOffset);
EXPECT_EQ(GetAngularOffset(world, jointID), def.angularOffset);
const auto conf = TypeCast<MotorJointConf>(GetJoint(world, jointID));
EXPECT_EQ(GetMaxForce(conf), def.maxForce);
EXPECT_EQ(GetMaxTorque(conf), def.maxTorque);
EXPECT_EQ(GetCorrectionFactor(conf), def.correctionFactor);
EXPECT_EQ(GetMaxForce(Joint{conf}), def.maxForce);
EXPECT_EQ(GetMaxTorque(Joint{conf}), def.maxTorque);
}
TEST(MotorJoint, ShiftOrigin)
{
auto world = World{};
const auto b0 = CreateBody(world);
const auto b1 = CreateBody(world);
auto def = GetMotorJointConf(world, b0, b1);
const auto joint = CreateJoint(world, def);
const auto newOrigin = Length2{1_m, 1_m};
EXPECT_FALSE(ShiftOrigin(world, joint, newOrigin));
}
TEST(MotorJoint, SetCorrectionFactor)
{
auto world = World{};
const auto b0 = CreateBody(world);
const auto b1 = CreateBody(world);
auto def = GetMotorJointConf(world, b0, b1);
const auto jointID = CreateJoint(world, def);
auto conf = TypeCast<MotorJointConf>(GetJoint(world, jointID));
ASSERT_EQ(GetCorrectionFactor(conf), def.correctionFactor);
ASSERT_EQ(Real(0.3), def.correctionFactor);
EXPECT_NO_THROW(SetCorrectionFactor(conf, Real(0.9)));
EXPECT_EQ(GetCorrectionFactor(conf), Real(0.9));
EXPECT_NO_THROW(SetJoint(world, jointID, conf));
auto conf2 = TypeCast<MotorJointConf>(GetJoint(world, jointID));
EXPECT_EQ(GetCorrectionFactor(conf2), Real(0.9));
}
TEST(MotorJoint, GetMotorJointConfThrows)
{
EXPECT_THROW(GetMotorJointConf(Joint{}), std::bad_cast);
}
TEST(MotorJoint, GetMotorJointConf)
{
auto world = World{};
const auto b0 = CreateBody(world);
const auto b1 = CreateBody(world);
auto def = GetMotorJointConf(world, b0, b1);
const auto jointID = CreateJoint(world, def);
ASSERT_EQ(GetType(world, jointID), GetTypeID<MotorJointConf>());
ASSERT_EQ(GetBodyA(world, jointID), def.bodyA);
ASSERT_EQ(GetBodyB(world, jointID), def.bodyB);
ASSERT_EQ(GetCollideConnected(world, jointID), def.collideConnected);
ASSERT_EQ(GetLinearOffset(world, jointID), def.linearOffset);
ASSERT_EQ(GetAngularOffset(world, jointID), def.angularOffset);
const auto conf = TypeCast<MotorJointConf>(GetJoint(world, jointID));
ASSERT_EQ(GetMaxForce(conf), def.maxForce);
ASSERT_EQ(GetMaxTorque(conf), def.maxTorque);
ASSERT_EQ(GetCorrectionFactor(conf), def.correctionFactor);
const auto cdef = GetMotorJointConf(GetJoint(world, jointID));
EXPECT_EQ(cdef.bodyA, b0);
EXPECT_EQ(cdef.bodyB, b1);
EXPECT_EQ(cdef.collideConnected, false);
EXPECT_EQ(cdef.linearOffset, (Length2{}));
EXPECT_EQ(cdef.angularOffset, 0_deg);
EXPECT_EQ(cdef.maxForce, 1_N);
EXPECT_EQ(cdef.maxTorque, 1_Nm);
EXPECT_EQ(cdef.correctionFactor, Real(0.3));
}
TEST(MotorJoint, WithDynamicCircles)
{
auto world = World{};
const auto p1 = Length2{-1_m, 0_m};
const auto p2 = Length2{+1_m, 0_m};
const auto b1 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(p1));
const auto b2 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(p2));
const auto circle = CreateShape(world, DiskShapeConf{}.UseRadius(0.2_m).UseDensity(2_kgpm2));
ASSERT_NO_THROW(Attach(world, b1, circle));
ASSERT_NO_THROW(Attach(world, b2, circle));
// const auto anchor = Length2(2_m, 1_m);
const auto jd = GetMotorJointConf(world, b1, b2).UseMaxForce(1000_N).UseMaxTorque(1000_Nm);
const auto joint = CreateJoint(world, jd);
ASSERT_NE(joint, InvalidJointID);
EXPECT_EQ(GetAnchorA(world, joint), p1);
EXPECT_EQ(GetAnchorB(world, joint), p2);
auto stepConf = StepConf{};
Step(world, stepConf);
EXPECT_NEAR(double(Real{GetX(GetLocation(world, b1)) / Meter}), -1.0, 0.001);
EXPECT_NEAR(double(Real{GetY(GetLocation(world, b1)) / Meter}), 0.0, 0.001);
EXPECT_NEAR(double(Real{GetX(GetLocation(world, b2)) / Meter}), +1.0, 0.01);
EXPECT_NEAR(double(Real{GetY(GetLocation(world, b2)) / Meter}), 0.0, 0.01);
EXPECT_EQ(GetAngle(world, b1), 0_deg);
EXPECT_EQ(GetAngle(world, b2), 0_deg);
const auto linearOffset = Length2{static_cast<Real>(2.6 * std::sin(2 * stepConf.deltaTime / 1_s)) * 1_m,
static_cast<Real>(2.0 * std::sin(1 * stepConf.deltaTime / 1_s)) * 1_m};
const auto angularOffset = static_cast<Real>(4 * stepConf.deltaTime / 1_s) * 1_rad;
SetLinearOffset(world, joint, linearOffset);
SetAngularOffset(world, joint, angularOffset);
stepConf.doWarmStart = false;
Step(world, stepConf);
EXPECT_NEAR(double(Real{GetX(GetLocation(world, b1)) / Meter}), -0.71690911054611206, 0.001);
EXPECT_NEAR(double(Real{GetY(GetLocation(world, b1)) / Meter}), 0.0051687383092939854, 0.001);
EXPECT_NEAR(double(Real{GetX(GetLocation(world, b2)) / Meter}), +0.71690911054611206, 0.01);
EXPECT_NEAR(double(Real{GetY(GetLocation(world, b2)) / Meter}), 0.0, 0.01);
EXPECT_NEAR(double(Real(GetAngle(world, b1) / 1_rad)), -0.23470129, 0.0001);
EXPECT_NEAR(double(Real(GetAngle(world, b2) / 1_rad)), -0.21470131, 0.0001);
}
TEST(MotorJoint, SetLinearOffset)
{
auto world = World{};
const auto p1 = Length2{-1_m, 0_m};
const auto p2 = Length2{+1_m, 0_m};
const auto b1 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(p1));
const auto b2 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(p2));
const auto circle = CreateShape(world, DiskShapeConf{}.UseRadius(0.2_m));
Attach(world, b1, circle);
Attach(world, b2, circle);
// const auto anchor = Length2(2_m, 1_m);
const auto jd = GetMotorJointConf(world, b1, b2);
const auto joint = CreateJoint(world, jd);
ASSERT_NE(joint, InvalidJointID);
EXPECT_EQ(GetAnchorA(world, joint), p1);
EXPECT_EQ(GetAnchorB(world, joint), p2);
const auto linearOffset = Length2{2_m, 1_m};
ASSERT_EQ(GetLinearOffset(world, joint), jd.linearOffset);
ASSERT_NE(jd.linearOffset, linearOffset);
SetLinearOffset(world, joint, linearOffset);
EXPECT_EQ(GetLinearOffset(world, joint), linearOffset);
}
TEST(MotorJoint, SetAngularOffset)
{
auto world = World{};
const auto p1 = Length2{-1_m, 0_m};
const auto p2 = Length2{+1_m, 0_m};
const auto b1 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(p1));
const auto b2 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(p2));
const auto circle = CreateShape(world, DiskShapeConf{}.UseRadius(0.2_m));
Attach(world, b1, circle);
Attach(world, b2, circle);
// const auto anchor = Length2(2_m, 1_m);
const auto jd = GetMotorJointConf(world, b1, b2);
const auto joint = CreateJoint(world, jd);
ASSERT_NE(joint, InvalidJointID);
EXPECT_EQ(GetAnchorA(world, joint), p1);
EXPECT_EQ(GetAnchorB(world, joint), p2);
ASSERT_EQ(GetAngularOffset(world, joint), 0_deg);
SetAngularOffset(world, joint, 45_deg);
EXPECT_EQ(GetAngularOffset(world, joint), 45_deg);
}
TEST(MotorJointConf, GetAngularMass)
{
auto conf = MotorJointConf{};
conf.angularMass = RotInertia{2_m2 * 3_kg / SquareRadian}; // L^2 M QP^-2
auto rotInertia = RotInertia{};
EXPECT_NO_THROW(rotInertia = GetAngularMass(Joint{conf}));
EXPECT_EQ(conf.angularMass, rotInertia);
}
TEST(MotorJointConf, EqualsOperator)
{
EXPECT_TRUE(MotorJointConf() == MotorJointConf());
{
auto conf = MotorJointConf{};
conf.linearOffset = Length2{1.2_m, -3_m};
EXPECT_TRUE(conf == conf);
EXPECT_FALSE(MotorJointConf() == conf);
}
{
auto conf = MotorJointConf{};
conf.angularOffset = 33_deg;
EXPECT_TRUE(conf == conf);
EXPECT_FALSE(MotorJointConf() == conf);
}
{
auto conf = MotorJointConf{};
conf.correctionFactor = Real(3.4);
EXPECT_TRUE(conf == conf);
EXPECT_FALSE(MotorJointConf() == conf);
}
{
auto conf = MotorJointConf{};
conf.angularError = 19_deg;
EXPECT_TRUE(conf == conf);
EXPECT_FALSE(MotorJointConf() == conf);
}
// TODO: test remaining fields.
}
TEST(MotorJointConf, NotEqualsOperator)
{
EXPECT_FALSE(MotorJointConf() != MotorJointConf());
{
auto conf = MotorJointConf{};
conf.maxForce = 2.5_N;
EXPECT_FALSE(conf != conf);
EXPECT_TRUE(MotorJointConf() != conf);
}
// TODO: test remaining fields.
}
TEST(MotorJointConf, GetName)
{
EXPECT_STREQ(GetName(GetTypeID<MotorJointConf>()), "d2::MotorJointConf");
}
TEST(MotorJointConf, InitVelocity)
{
auto conf = MotorJointConf{};
std::vector<BodyConstraint> bodies;
EXPECT_NO_THROW(InitVelocity(conf, bodies, StepConf{}, ConstraintSolverConf{}));
conf.bodyA = BodyID(0);
conf.bodyB = BodyID(0);
EXPECT_THROW(InitVelocity(conf, bodies, StepConf{}, ConstraintSolverConf{}), std::out_of_range);
const auto posA = Position{Length2{-5_m, 0_m}, 0_deg};
bodies.push_back(BodyConstraint{Real(1) / 4_kg, InvRotInertia{}, Length2{}, posA, Velocity{}});
EXPECT_NO_THROW(InitVelocity(conf, bodies, StepConf{}, ConstraintSolverConf{}));
}
TEST(MotorJointConf, SolveVelocity)
{
auto conf = MotorJointConf{};
std::vector<BodyConstraint> bodies;
auto result = false;
EXPECT_NO_THROW(result = SolveVelocity(conf, bodies, StepConf{}));
EXPECT_TRUE(result);
conf.bodyA = BodyID(0);
conf.bodyB = BodyID(0);
EXPECT_THROW(SolveVelocity(conf, bodies, StepConf{}), std::out_of_range);
const auto posA = Position{Length2{-5_m, 0_m}, 0_deg};
bodies.push_back(BodyConstraint{Real(1) / 4_kg, InvRotInertia{}, Length2{}, posA, Velocity{}});
EXPECT_NO_THROW(result = SolveVelocity(conf, bodies, StepConf{}));
}
TEST(MotorJointConf, SolvePosition)
{
auto conf = MotorJointConf{};
std::vector<BodyConstraint> bodies;
auto result = false;
EXPECT_NO_THROW(result = SolvePosition(conf, bodies, ConstraintSolverConf{}));
EXPECT_TRUE(result);
}
Declarations of BodyConf class & free functions associated with it.
Definition of the BodyConstraint class and closely related code.
Definition of the ConstraintSolverConf class and closely related code.
Definition of the DiskShapeConf class and closely related code.
Definition of the Joint class and closely related code.
Definition of the MotorJointConf class and closely related code.
Declarations of the StepConf class, and free functions associated with it.
Declarations of free functions of World for bodies identified by BodyID.
Declarations of free functions of World for joints identified by JointID.
Declarations of free functions of World for unidentified information.
Declarations of free functions of World for shapes identified by ShapeID.
Definitions of the World class and closely related code.
detail::angular_momentum AngularMomentum
Angular momentum quantity.
Definition: Units.hpp:390
detail::moment_of_inertia RotInertia
Rotational inertia quantity.
Definition: Units.hpp:360
detail::inverse_moment_of_inertia InvRotInertia
Inverse rotational inertia quantity.
Definition: Units.hpp:368
constexpr auto SquareRadian
Square radian unit type.
Definition: Units.hpp:463
constexpr auto Meter
Meter unit of Length.
Definition: Units.hpp:423
Definition: AABB.hpp:48
constexpr auto GetMaxTorque(const FrictionJointConf &object) noexcept
Free function for getting the max torque value of the given configuration.
Definition: FrictionJointConf.hpp:227
bool SolveVelocity(DistanceJointConf &object, const Span< BodyConstraint > &bodies, const StepConf &step)
Solves velocity constraint.
Definition: DistanceJointConf.cpp:169
Angle GetAngularOffset(const Joint &object)
Gets the angular offset property of the specified joint if its type has one.
Definition: Joint.cpp:615
constexpr auto GetY(const UnitVec &value) -> decltype(get< 1 >(value))
Gets the "Y" element of the given value - i.e. the second element.
Definition: UnitVec.hpp:499
constexpr Momentum2 GetLinearReaction(const DistanceJointConf &object) noexcept
Gets the current linear reaction for the given configuration.
Definition: DistanceJointConf.hpp:175
Angle GetAngle(const Body &body) noexcept
Gets the body's angle.
Definition: Body.cpp:280
BodyID CreateBody(AabbTreeWorld &world, Body body=Body{})
Creates a rigid body that's a copy of the given one.
Definition: AabbTreeWorld.cpp:1019
BodyID GetBodyB(const Joint &object) noexcept
Gets the second body attached to this joint.
Definition: Joint.hpp:295
BodyType GetType(const Body &body) noexcept
Gets the type of this body.
Definition: Body.hpp:748
void ShiftOrigin(AabbTreeWorld &world, const Length2 &newOrigin)
Shifts the world origin.
Definition: AabbTreeWorld.cpp:2221
bool SolvePosition(const DistanceJointConf &object, const Span< BodyConstraint > &bodies, const ConstraintSolverConf &conf)
Solves the position constraint.
Definition: DistanceJointConf.cpp:203
ShapeID CreateShape(AabbTreeWorld &world, Shape def)
Creates an identifiable copy of the given shape within this world.
Definition: AabbTreeWorld.cpp:1234
constexpr auto SetCorrectionFactor(MotorJointConf &object, Real value) noexcept
Free function for setting the correction factor value of the given configuration.
Definition: MotorJointConf.hpp:311
void SetLinearOffset(Joint &object, const Length2 &value)
Sets the linear offset property of the specified joint if its type has one.
Definition: Joint.cpp:605
const Joint & GetJoint(const AabbTreeWorld &world, JointID id)
Gets the identified joint.
Definition: AabbTreeWorld.cpp:2855
BodyID GetBodyA(const Joint &object) noexcept
Gets the first body attached to this joint.
Definition: Joint.hpp:290
void Attach(AabbTreeWorld &world, BodyID id, ShapeID shapeID)
Associates a validly identified shape with the validly identified body.
Definition: AabbTreeWorld.cpp:2896
Length2 GetLocation(const Body &body) noexcept
Gets the body's origin location.
Definition: Body.hpp:930
MotorJointConf GetMotorJointConf(const Joint &joint)
Gets the definition data for the given joint.
Definition: MotorJointConf.cpp:68
Length2 GetLinearOffset(const Joint &object)
Gets the linear offset property of the specified joint if its type has one.
Definition: Joint.cpp:596
constexpr auto GetCorrectionFactor(const MotorJointConf &object) noexcept
Free function for getting the correction factor value of the given configuration.
Definition: MotorJointConf.hpp:304
constexpr auto GetMaxForce(const FrictionJointConf &object) noexcept
Free function for getting the max force value of the given configuration.
Definition: FrictionJointConf.hpp:213
Length2 GetAnchorB(const World &world, JointID id)
Definition: WorldJoint.cpp:179
void SetAngularOffset(Joint &object, Angle value)
Sets the angular offset property of the specified joint if its type has one.
Definition: Joint.cpp:624
RotInertia GetAngularMass(const Joint &object)
Gets the given joint's angular mass.
Definition: Joint.cpp:290
JointID CreateJoint(AabbTreeWorld &world, Joint def)
Creates a joint to constrain one or more bodies.
Definition: AabbTreeWorld.cpp:1132
Length2 GetAnchorA(const World &world, JointID id)
Definition: WorldJoint.cpp:171
bool GetCollideConnected(const Joint &object) noexcept
Gets collide connected.
Definition: Joint.hpp:300
constexpr auto GetX(const UnitVec &value)
Gets the "X" element of the given value - i.e. the first element.
Definition: UnitVec.hpp:493
void SetJoint(AabbTreeWorld &world, JointID id, Joint def)
Sets the identified joint.
Definition: AabbTreeWorld.cpp:1108
StepStats Step(AabbTreeWorld &world, const StepConf &conf)
Steps the world simulation according to the given configuration.
Definition: AabbTreeWorld.cpp:2144
constexpr AngularMomentum GetAngularReaction(const DistanceJointConf &) noexcept
Gets the current angular reaction for the given configuration.
Definition: DistanceJointConf.hpp:182
void InitVelocity(DistanceJointConf &object, const Span< BodyConstraint > &bodies, const StepConf &step, const ConstraintSolverConf &conf)
Initializes velocity constraint data based on the given solver data.
Definition: DistanceJointConf.cpp:81
const char * GetName(Manifold::Type type) noexcept
Gets a unique name for the given manifold type.
Definition: Manifold.cpp:633
Definition: ArrayList.hpp:43
Vector2< Momentum > Momentum2
2-element vector of Momentum quantities.
Definition: Vector2.hpp:76
float Real
Real-number type.
Definition: Real.hpp:69
constexpr auto InvalidBodyID
Invalid body ID value.
Definition: BodyID.hpp:50
constexpr auto InvalidJointID
Invalid joint ID value.
Definition: JointID.hpp:50
Vector2< Length > Length2
2-element vector of Length quantities.
Definition: Vector2.hpp:51
detail::IndexingNamedType< BodyCounter, struct BodyIdentifier > BodyID
Body identifier.
Definition: BodyID.hpp:44
static constexpr auto DefaultCorrectionFactor
Default correction factor.
Definition: MotorJointConf.hpp:69
constexpr auto & UseMaxForce(NonNegative< Force > v) noexcept
Uses the given maximum force value.
Definition: MotorJointConf.hpp:93
static constexpr auto DefaultMaxForce
Default max force.
Definition: MotorJointConf.hpp:63
static constexpr auto DefaultMaxTorque
Default max torque.
Definition: MotorJointConf.hpp:66