#include "gtest/gtest.h"
template <typename T>
struct PushBackListener
{
std::vector<T> ids;
void operator()(T id)
{
ids.push_back(id);
}
};
TEST(World, WorldLockedError)
{
const auto value = WrongState{"world is locked"};
EXPECT_STREQ(value.what(), "world is locked");
}
TEST(World, DefaultInit)
{
World world;
{
EXPECT_TRUE(bodies.empty());
EXPECT_EQ(bodies.begin(), bodies.end());
}
{
const auto& w = static_cast<const World&>(world);
EXPECT_TRUE(bodies.empty());
EXPECT_EQ(bodies.begin(), bodies.end());
}
EXPECT_TRUE(world == world);
EXPECT_TRUE(world == World());
EXPECT_TRUE(World() == world);
EXPECT_TRUE(World() == World());
EXPECT_FALSE(world != world);
EXPECT_FALSE(world != World());
EXPECT_FALSE(World() != world);
EXPECT_FALSE(World() != World());
}
TEST(World, Init)
{
World world{};
{
auto calls = 0;
++calls;
return true;
});
EXPECT_EQ(calls, 0);
}
{
const auto p2 =
Length2{100_m, 0_m};
auto calls = 0;
++calls;
});
EXPECT_EQ(calls, 0);
}
}
{
auto jointListener = PushBackListener<JointID>{};
auto shapeListener = PushBackListener<ShapeID>{};
auto associationListener = PushBackListener<std::pair<BodyID, ShapeID>>{};
auto world = World{};
const auto f1 =
CreateShape(world, Shape{DiskShapeConf{}});
const auto j0 =
CreateJoint(world, Joint{DistanceJointConf{b0, b1}});
EXPECT_FALSE(World() == world);
EXPECT_TRUE(World() != world);
EXPECT_NO_THROW(
Clear(world));
EXPECT_TRUE(World() == world);
EXPECT_FALSE(World() != world);
ASSERT_EQ(shapeListener.ids.size(), std::size_t(2));
EXPECT_EQ(shapeListener.ids.at(0), f0);
EXPECT_EQ(shapeListener.ids.at(1), f1);
ASSERT_EQ(jointListener.ids.size(), std::size_t(1));
EXPECT_EQ(jointListener.ids.at(0), j0);
EXPECT_LE(b2, b1);
const auto f2 =
CreateShape(world, Shape{DiskShapeConf{}});
}
TEST(World, SetSubSteppingFreeFunction)
{
World world;
auto stepConf = StepConf{};
stepConf.deltaTime =
Real(1) / 100_Hz;
EXPECT_NO_THROW(
Step(world, stepConf));
}
{
auto world = World{};
auto stepConf = StepConf{};
stepConf.deltaTime =
Real(1) / 100_Hz;
const auto shapeId0 =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRadius(1_m));
const auto shapeId1 =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRadius(1_m));
Attach(world, stabody, shapeId2);
auto i = 0ull;
const auto max = 100000ull;
EXPECT_NO_THROW(
Step(world, stepConf));
++i;
}
EXPECT_LT(i, max);
EXPECT_NO_THROW(
Step(world, stepConf));
EXPECT_NO_THROW(
Step(world, stepConf));
}
TEST(World, CopyConstruction)
{
auto world = World{};
{
const auto copy = World{world};
EXPECT_EQ(
GetTree(world).GetLeafCount(),
GetTree(copy).GetLeafCount());
}
const auto shapeId =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRadius(1_m));
const auto rj1 =
CreateJoint(world, Joint{RevoluteJointConf{b1, b2}});
const auto rj2 =
CreateJoint(world, Joint{RevoluteJointConf{b3, b4}});
auto stepConf = StepConf{};
{
const auto copy = World{world};
auto worldJointIter = worldJoints.begin();
auto copyJointIter = copyJoints.begin();
for (auto i = decltype(minJoints){0}; i < minJoints; ++i)
{
EXPECT_EQ(
GetType(world, *worldJointIter),
GetType(copy, *copyJointIter));
++worldJointIter;
++copyJointIter;
}
EXPECT_EQ(
GetTree(world).GetLeafCount(),
GetTree(copy).GetLeafCount());
for (
auto i =
static_cast<decltype(
GetContactRange(world))
>(0u); i < max; ++i) {
}
}
}
}
TEST(World, CopyAssignment)
{
auto world = World{};
{
auto copy = World{};
copy = world;
EXPECT_EQ(
GetTree(world).GetLeafCount(),
GetTree(copy).GetLeafCount());
}
const auto shapeId =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRadius(1_m));
auto stepConf = StepConf{};
{
auto copy = World{};
copy = world;
auto worldJointIter = worldJoints.begin();
auto copyJointIter = copyJoints.begin();
for (auto i = decltype(minJoints){0}; i < minJoints; ++i)
{
EXPECT_EQ(
GetType(world, *worldJointIter),
GetType(copy, *copyJointIter));
++worldJointIter;
++copyJointIter;
}
EXPECT_EQ(
GetTree(world).GetLeafCount(),
GetTree(copy).GetLeafCount());
for (
auto i =
static_cast<decltype(
GetContactRange(world))
>(0u); i < max; ++i) {
}
}
}
}
TEST(World, MoveConstruction)
{
auto world = World{};
const auto shapeId =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRadius(1_m));
auto stepConf = StepConf{};
{
auto other = World{std::move(world)};
}
}
TEST(World, MoveAssignment)
{
auto world = World{};
const auto shapeId =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRadius(1_m));
auto stepConf = StepConf{};
{
auto other = World{};
other = std::move(world);
}
}
{
EXPECT_NE(
GetType(World{}), GetTypeID<void>());
EXPECT_EQ(
GetType(World{}), GetTypeID<AabbTreeWorld>());
}
TEST(WorldModel, GetData_)
{
EXPECT_NE(model.GetData_(), nullptr);
}
{
{
auto world = World{};
EXPECT_EQ(TypeCast<int>(&world), nullptr);
EXPECT_NE(TypeCast<AabbTreeWorld>(&world), nullptr);
EXPECT_THROW(TypeCast<int>(world), std::bad_cast);
}
{
const auto world = World{};
EXPECT_EQ(TypeCast<const int>(&world), nullptr);
EXPECT_NE(TypeCast<AabbTreeWorld>(&world), nullptr);
EXPECT_THROW(TypeCast<int>(world), std::bad_cast);
}
}
TEST(World, CreateDestroyEmptyDynamicBody)
{
auto world = World{};
EXPECT_EQ(
GetAngle(world, bodyId), 0_deg);
EXPECT_FALSE(bodies1.empty());
EXPECT_NE(bodies1.begin(), bodies1.end());
const auto first = bodies1.begin();
EXPECT_EQ(bodyId, *first);
EXPECT_TRUE(bodies2.empty());
}
TEST(World, CreateDestroyDynamicBodyAndFixture)
{
auto world = World{};
EXPECT_TRUE(
IsAwake(world, bodyId));
EXPECT_FALSE(bodies1.empty());
EXPECT_NE(bodies1.begin(), bodies1.end());
const auto first = bodies1.begin();
EXPECT_EQ(bodyId, *first);
EXPECT_NO_THROW(
Destroy(world, bodyId));
EXPECT_FALSE(
IsAwake(world, bodyId));
EXPECT_TRUE(bodies2.empty());
}
TEST(World, CreateDestroyJoinedBodies)
{
auto jointListener = PushBackListener<JointID>{};
auto shapeListener = PushBackListener<ShapeID>{};
auto associationListener = PushBackListener<std::pair<BodyID, ShapeID>>{};
auto world = World{};
EXPECT_FALSE(bodies1.empty());
EXPECT_NE(bodies1.begin(), bodies1.end());
EXPECT_EQ(bodyId1, *bodies1.begin());
const auto shapeId1 =
CreateShape(world, DiskShapeConf{1_m});
Attach(world, bodyId1, shapeId1);
const auto shapeId2 =
CreateShape(world, DiskShapeConf{1_m});
Attach(world, bodyId2, shapeId2);
auto stepConf = StepConf{};
const auto c0 = worldContacts.begin();
auto cid0 = std::get<ContactID>(*c0);
const auto contactBodyA =
GetBodyA(world, cid0);
const auto contactBodyB =
GetBodyB(world, cid0);
EXPECT_EQ(contactBodyA, bodyId1);
EXPECT_EQ(contactBodyB, bodyId2);
if (contact0.HasValidToi()) {
contact0.SetToi({});
}
else {
contact0.SetToi(
Real(0.5f));
}
EXPECT_THROW(
SetContact(world, cid0, contact0), InvalidArgument);
contact0.SetToiCount(contact0.GetToiCount() + 1u);
EXPECT_THROW(
SetContact(world, cid0, contact0), InvalidArgument);
const auto joint =
CreateJoint(world, Joint{DistanceJointConf{bodyId1, bodyId2}});
EXPECT_FALSE(bodies0.empty());
EXPECT_NE(bodies0.begin(), bodies0.end());
ASSERT_EQ(associationListener.ids.size(), std::size_t(2));
EXPECT_EQ(associationListener.ids.at(0), std::make_pair(bodyId1, shapeId1));
EXPECT_EQ(associationListener.ids.at(1), std::make_pair(bodyId2, shapeId2));
ASSERT_EQ(jointListener.ids.size(), std::size_t(1));
EXPECT_EQ(jointListener.ids.at(0), joint);
}
TEST(World, CreateDestroyContactingBodies)
{
auto world = World{};
ASSERT_EQ(
GetTree(world).GetNodeCount(), 0u);
ASSERT_TRUE(contacts.empty());
EXPECT_EQ(
GetTree(world).GetNodeCount(), 0u);
EXPECT_EQ(
GetTree(world).GetNodeCount(), 0u);
const auto stepConf = StepConf{};
const auto stats0 =
Step(world, stepConf);
EXPECT_EQ(
GetTree(world).GetNodeCount(), 3u);
EXPECT_EQ(stats0.pre.proxiesCreated, 2u);
EXPECT_EQ(stats0.pre.proxiesMoved, 0u);
EXPECT_EQ(stats0.pre.contactsDestroyed, 0u);
EXPECT_EQ(stats0.pre.contactsAdded, 1u);
EXPECT_EQ(stats0.pre.contactsUpdated, 0u);
EXPECT_EQ(stats0.pre.contactsSkipped, 0u);
EXPECT_EQ(stats0.reg.minSeparation, -2.0_m);
EXPECT_EQ(stats0.reg.maxIncImpulse, 0.0_Ns);
EXPECT_EQ(stats0.reg.islandsFound, 1u);
EXPECT_EQ(stats0.reg.islandsSolved, 0u);
EXPECT_EQ(stats0.reg.contactsAdded, 0u);
EXPECT_EQ(stats0.reg.bodiesSlept, 0u);
EXPECT_EQ(stats0.reg.proxiesMoved, 0u);
EXPECT_EQ(stats0.reg.sumPosIters, 3u);
EXPECT_EQ(stats0.reg.sumVelIters, 1u);
EXPECT_EQ(stats0.toi.minSeparation, std::numeric_limits<Length>::infinity());
EXPECT_EQ(stats0.toi.maxIncImpulse, 0.0_Ns);
EXPECT_EQ(stats0.toi.islandsFound, 0u);
EXPECT_EQ(stats0.toi.islandsSolved, 0u);
EXPECT_EQ(stats0.toi.contactsFound, 0u);
EXPECT_EQ(stats0.toi.contactsAtMaxSubSteps, 0u);
EXPECT_EQ(stats0.toi.contactsUpdatedToi, 0u);
EXPECT_EQ(stats0.toi.contactsUpdatedTouching, 0u);
EXPECT_EQ(stats0.toi.contactsSkippedTouching, 1u);
EXPECT_EQ(stats0.toi.contactsAdded, 0u);
EXPECT_EQ(stats0.toi.proxiesMoved, 0u);
EXPECT_EQ(stats0.toi.sumPosIters, 0u);
EXPECT_EQ(stats0.toi.sumVelIters, 0u);
EXPECT_EQ(stats0.toi.maxDistIters, 0u);
EXPECT_EQ(stats0.toi.maxToiIters, 0u);
EXPECT_EQ(stats0.toi.maxRootIters, 0u);
EXPECT_FALSE(contacts.empty());
if (contacts.size() == 1u) {
EXPECT_EQ(contacts.begin()->first.GetMin(),
static_cast<decltype(contacts.
begin()-
>first.GetMin())>(0));
EXPECT_EQ(contacts.begin()->first.GetMax(),
static_cast<decltype(contacts.
begin()-
>first.GetMax())>(1));
EXPECT_EQ(
GetShapeA(world, contacts.begin()->second),
EXPECT_EQ(
GetShapeB(world, contacts.begin()->second),
}
EXPECT_EQ(
GetTree(world).GetNodeCount(),
static_cast<decltype(
GetTree(world).GetNodeCount())
>(1));
EXPECT_EQ(
GetTree(world).GetNodeCount(),
static_cast<decltype(
GetTree(world).GetNodeCount())
>(1));
EXPECT_TRUE(
empty(contacts));
EXPECT_EQ(
GetTree(world).GetNodeCount(),
static_cast<decltype(
GetTree(world).GetNodeCount())
>(0));
EXPECT_TRUE(contacts.empty());
}
TEST(World, SetUnsetSetImpenetrable)
{
auto world = World{};
}
{
auto world = World{};
}
TEST(World, GetSetAngle)
{
auto world = World{};
ASSERT_EQ(
GetAngle(world, body), 0_deg);
EXPECT_NO_THROW(
SetAngle(world, body,
Pi * 0.5_rad));
EXPECT_NO_THROW(
SetAngle(world, body,
Pi * 2.1_rad));
EXPECT_NO_THROW(
SetAngle(world, body,
Pi * 0_rad));
for (auto i = 0; i < 105; ++i) {
EXPECT_NO_THROW(
Step(world));
}
0.001);
}
{
auto world = World{};
auto value = 1_Hz;
value = 2_Hz;
value = 23_Hz;
}
{
auto world = World{};
auto value = 1_Hz;
value = 2_Hz;
value = 23_Hz;
}
TEST(World, SynchronizeProxies)
{
auto world = World{};
const auto stepConf = StepConf{};
}
TEST(World, SetTypeOfBody)
{
auto world = World{};
auto other = World{};
}
{
auto world = World{};
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto conf = EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).Set(v1, v2);
auto stepConf = StepConf{};
stepConf.deltaTime = 0_s;
{
auto foundOurs = 0;
auto foundOthers = 0;
if (f == shapeId && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
return true;
});
EXPECT_EQ(foundOurs, 1);
EXPECT_EQ(foundOthers, 0);
}
}
{
World world{};
const auto p0 =
Length2{-10_m, +3_m};
const auto p1 =
Length2{+1_m, 0_m};
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto conf = EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).Set(v1, v2);
const auto shape = Shape{conf};
auto stepConf = StepConf{};
stepConf.deltaTime = 0_s;
{
const auto p2 =
Length2{-2_m, 0_m};
const auto p3 =
Length2{+2_m, 0_m};
auto foundOurs = 0;
auto foundOthers = 0;
if (b == body && f == shapeId && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
});
EXPECT_FALSE(retval);
EXPECT_EQ(foundOurs, 1);
EXPECT_EQ(foundOthers, 1);
}
{
const auto p2 =
Length2{-2_m, 0_m};
const auto p3 =
Length2{+2_m, 0_m};
auto foundOurs = 0;
auto foundOthers = 0;
if (b == body && f == shapeId && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
});
EXPECT_TRUE(retval);
EXPECT_EQ(foundOurs, 1);
EXPECT_EQ(foundOthers, 0);
}
{
const auto p2 =
Length2{-2_m, 0_m};
const auto p3 =
Length2{+2_m, 0_m};
auto foundOurs = 0;
auto foundOthers = 0;
if (b == body && f == shapeId && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
});
EXPECT_FALSE(retval);
EXPECT_EQ(foundOurs, 1);
EXPECT_EQ(foundOthers, 1);
}
{
const auto p2 =
Length2{ +5_m, 0_m};
const auto p3 =
Length2{+10_m, 0_m};
auto foundOurs = 0;
auto foundOthers = 0;
if (b == body && f == shapeId && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
});
EXPECT_FALSE(retval);
EXPECT_EQ(foundOurs, 0);
EXPECT_EQ(foundOthers, 0);
}
{
const auto p2 =
Length2{-2_m, 0_m};
const auto p3 =
Length2{+2_m, 0_m};
auto foundOurs = 0;
auto foundOthers = 0;
if (b == body && f == shapeId && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
});
EXPECT_TRUE(retval);
EXPECT_EQ(foundOurs, 1);
EXPECT_EQ(foundOthers, 0);
}
{
const auto p2 =
Length2{-3_m, 0_m};
const auto p3 =
Length2{+2_m, 10_m};
auto foundOurs = 0;
auto foundOthers = 0;
if (b == body && f == shapeId && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
});
EXPECT_FALSE(retval);
EXPECT_EQ(foundOurs, 0);
EXPECT_EQ(foundOthers, 0);
}
{
auto found = 0;
const auto retval =
RayCast(world, rci,
++found;
});
EXPECT_FALSE(retval);
EXPECT_EQ(found, 0);
}
}
TEST(World, ClearForcesFreeFunction)
{
World world;
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto conf = EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).Set(v1, v2);
}
TEST(World, SetAccelerationsFunctionalFF)
{
World world;
const auto a1 = Acceleration{
};
const auto a2 = a1 * 2;
ASSERT_EQ(a1.linear * 2, a2.linear);
ASSERT_EQ(a1.angular * 2, a2.angular);
});
}
TEST(World, SetLinearAccelerationsFF)
{
World world;
const auto a1 = Acceleration{
};
const auto a2 = a1 * 2;
ASSERT_EQ(a1.linear * 2, a2.linear);
ASSERT_EQ(a1.angular * 2, a2.angular);
EXPECT_EQ(
GetAcceleration(world, b1), (Acceleration{a1.linear * 2, a1.angular}));
EXPECT_EQ(
GetAcceleration(world, b2), (Acceleration{a1.linear * 2, a2.angular}));
}
TEST(World, FindClosestBodyFF)
{
World world;
}
TEST(World, CreateAndDestroyShape)
{
World world{};
const auto shapeId =
CreateShape(world, EdgeShapeConf{});
EXPECT_NO_THROW(
Destroy(world, shapeId));
}
TEST(World, GetAssociationCountFreeFunction)
{
World world{};
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto shapeConf = EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).Set(v1, v2);
ASSERT_NO_THROW(
Attach(world, body, shapeId1));
ASSERT_NO_THROW(
Attach(world, body, shapeId1));
ASSERT_NO_THROW(
Attach(world, body, shapeId2));
}
TEST(World, GetUsedShapesCountFreeFunction)
{
World world{};
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto shapeConf = EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).Set(v1, v2);
ASSERT_NO_THROW(
Attach(world, body, shapeId1));
ASSERT_NO_THROW(
Attach(world, body, shapeId1));
ASSERT_NO_THROW(
Attach(world, body, shapeId2));
}
{
World world{};
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto shapeId =
CreateShape(world, EdgeShapeConf{}
.UseVertexRadius(1_m).UseDensity(1_kgpm2).
Set(v1, v2));
EXPECT_NO_THROW(
Attach(world, body, shapeId));
EXPECT_NO_THROW(
Attach(world, body, shapeId));
EXPECT_NO_THROW(
Attach(world, body, shapeId));
}
TEST(World, AwakenFreeFunction)
{
World world{};
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto shapeId =
CreateShape(world, EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).
Set(v1, v2));
ASSERT_NO_THROW(
Attach(world, body, shapeId));
auto stepConf = StepConf{};
ASSERT_FALSE(
IsAwake(world, body));
}
TEST(World, GetTouchingCountFreeFunction)
{
World world;
auto stepConf = StepConf{};
stepConf.deltaTime =
Real(1) / 100_Hz;
const auto groundConf = EdgeShapeConf{}
const auto diskConf = DiskShapeConf{}.UseDensity(10_kgpm2);
const auto smallerDiskConf = DiskShapeConf(diskConf).UseRadius(0.5_m);
const auto lowerBody =
CreateBody(world, lowerBodyConf);
{
}
}
TEST(World, ShiftOriginFreeFunction)
{
const auto origin =
Length2{0_m, 0_m};
const auto location =
Length2{1_m, 1_m};
ASSERT_NE(origin, location);
World world;
auto bodyConf = BodyConf{};
bodyConf.UseLocation(location);
}
TEST(World, DynamicEdgeBodyHasCorrectMass)
{
World world;
auto bodyConf = BodyConf{};
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto conf = EdgeShapeConf{}.UseVertexRadius(1_m).UseDensity(1_kgpm2).Set(v1, v2);
const auto shape = Shape{conf};
const auto totalMass =
Mass{circleMass + rectMass};
EXPECT_NEAR(
static_cast<double>(
Real{
GetInvMass(world, body) * 1_kg}),
static_cast<double>(
Real{1_kg / totalMass}),
0.000001);
}
TEST(World, CreateAndDestroyJoint)
{
World world;
const auto anchorA =
Length2{+0.4_m, -1.2_m};
const auto anchorB =
Length2{-2.3_m, +0.7_m};
anchorA, anchorB)});
const auto first = *joints.begin();
EXPECT_EQ(jointId, first);
EXPECT_EQ(
GetType(world, jointId), GetTypeID<DistanceJointConf>());
EXPECT_EQ(
GetBodyA(world, jointId), body1);
EXPECT_EQ(
GetBodyB(world, jointId), body2);
EXPECT_NO_THROW(
Destroy(world, jointId));
}
{
World world;
{
}
{
}
}
{
World world;
{
const auto joint =
CreateJoint(world, Joint{RopeJointConf{body1, body2}});
}
{
EXPECT_THROW(
CreateJoint(world, Joint{RopeJointConf{body1, body2}}), LengthError);
}
}
TEST(World, StepZeroTimeDoesNothing)
{
World world{};
BodyConf def;
def.UseLocation(
Length2{31.9_m, -19.24_m});
EXPECT_EQ(
GetLocation(world, body), def.sweep.pos0.linear);
const auto time_inc = 0_s;
for (auto i = 0; i < 100; ++i)
{
}
}
TEST(World, GravitationalBodyMovement)
{
const auto a =
Real(-10);
auto body_def = BodyConf{};
body_def.UseLocation(p0);
const auto t = .01_s;
auto world = World{};
double(
Real{a * (t *
Real{3}) / 1_s}), 0.00001);
}
{
auto world = World{};
EXPECT_EQ(massData.center,
Length2{});
EXPECT_EQ(massData.mass, 0_kg);
Attach(world, body,
CreateShape(world, PolygonShapeConf{2_m, 1_m}.UseDensity(1_kgpm2)));
EXPECT_EQ(massData.center,
Length2{});
EXPECT_EQ(massData.mass, 8_kg);
EXPECT_NEAR(
static_cast<double>(
StripUnit(massData.I)), 13.3333, 0.0001);
}
#if defined(BODY_DOESNT_GROW_UNBOUNDED)
TEST(World, BodyAngleDoesntGrowUnbounded)
{
auto world = World{};
.UseAngularVelocity(10_rad /
Second));
ASSERT_EQ(
GetAngle(world, body), 0_rad);
auto stepConf = StepConf{};
auto lastAngle = 0_rad;
auto maxAngle = 0_rad;
for (auto i = 0; i < 1000000; ++i)
{
const auto angle =
GetAngle(world, body);
EXPECT_NE(angle, lastAngle);
ASSERT_LE(angle, 360_deg);
maxAngle = std::max(maxAngle, angle);
lastAngle = angle;
}
}
#endif
TEST(World, BodyAccelPerSpecWithNoVelOrPosIterations)
{
auto world = World{};
BodyConf def;
def.UseLocation(
Length2{31.9_m, -19.24_m});
EXPECT_EQ(
GetLocation(world, body), def.sweep.pos0.linear);
const auto time_inc = 0.01_s;
for (auto i = 0; i < 100; ++i)
{
Step(world, time_inc, 0, 0);
}
}
TEST(World, BodyAccelRevPerSpecWithNegativeTimeAndNoVelOrPosIterations)
{
World world{};
BodyConf def;
def.UseLocation(
Length2{31.9_m, -19.24_m});
EXPECT_EQ(
GetLocation(world, body), def.sweep.pos0.linear);
const auto time_inc = -0.01_s;
auto stepConf = StepConf{};
stepConf.deltaTime = time_inc;
stepConf.dtRatio = -1;
stepConf.regPositionIters = 0;
stepConf.regVelocityIters = 0;
stepConf.toiPositionIters = 0;
stepConf.toiVelocityIters = 0;
for (auto i = 0; i < 99; ++i)
{
}
}
struct MyContactListener
{
using PreSolver = std::function<void(
ContactID,
const Manifold&)>;
using PostSolver = std::function<void(
ContactID,
const ContactImpulsesList&,
unsigned)>;
using Ender = std::function<void(
ContactID)>;
MyContactListener(World& w, PreSolver&& pre, PostSolver&& post, Ender&&
end):
world(w), presolver(pre), postsolver(post), ender(
end) {}
{
++begin_contacts;
contacting = true;
const auto bA =
GetBodyA(world, contact);
const auto bB =
GetBodyB(world, contact);
EXPECT_THROW(
SetShape(world, sA, Shape()), WrongState);
const auto typeA =
GetType(world, bA);
{
EXPECT_NO_THROW(
SetType(world, bA, typeA));
}
EXPECT_THROW(
Destroy(world, bA), WrongState);
EXPECT_THROW(
Destroy(world, sA), WrongState);
EXPECT_THROW(
CreateJoint(world, Joint{DistanceJointConf{bA, bB}}), WrongState);
EXPECT_THROW(
Step(world, stepConf), WrongState);
EXPECT_THROW(
CreateShape(world, Shape{DiskShapeConf{}}), WrongState);
EXPECT_THROW(
Attach(world, bA, sA), WrongState);
EXPECT_THROW(
Detach(world, bA, sA), WrongState);
EXPECT_THROW(
Detach(world, bB, sB), WrongState);
}
{
++end_contacts;
contacting = false;
if (ender)
{
ender(contact);
}
}
void PreSolve(
ContactID id,
const Manifold& oldManifold)
{
++pre_solves;
presolver(id, oldManifold);
}
void PostSolve(
ContactID id,
const ContactImpulsesList& impulses,
unsigned solved)
{
++post_solves;
postsolver(id, impulses, solved);
}
World& world;
unsigned begin_contacts = 0;
unsigned end_contacts = 0;
unsigned pre_solves = 0;
unsigned post_solves = 0;
bool contacting = false;
bool touching = false;
PreSolver presolver;
PostSolver postsolver;
Ender ender;
const StepConf stepConf{};
};
TEST(World, NoCorrectionsWithNoVelOrPosIterations)
{
auto presolved = unsigned{0};
auto postsolved = unsigned{0};
World world{};
MyContactListener listener{
world,
[&](
ContactID,
const Manifold&) { ++presolved; },
[&](
ContactID,
const ContactImpulsesList&, unsigned) { ++postsolved; },
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
const ContactImpulsesList& impulses,
unsigned count){
listener.PostSolve(id, impulses, count);
});
ASSERT_EQ(listener.begin_contacts, unsigned(0));
ASSERT_EQ(listener.end_contacts, unsigned(0));
auto body_def = BodyConf{};
body_def.bullet = true;
const auto shapeId =
CreateShape(world, DiskShapeConf{}.UseRadius(1_m).UseRestitution(
Real(1)).UseDensity(1_kgpm2));
Attach(world, body_a, shapeId);
Attach(world, body_b, shapeId);
const auto time_inc = .01_s;
auto conf = StepConf{};
conf.deltaTime = time_inc;
conf.regPositionIters = 0;
conf.regVelocityIters = 0;
conf.toiPositionIters = 0;
conf.toiVelocityIters = 0;
auto steps = unsigned{0};
{
++steps;
}
EXPECT_GE(steps, 199u);
EXPECT_LE(steps, 201u);
}
TEST(World, HeavyOnLight)
{
constexpr
auto AngularSlop = (
Pi *
Real{2} * 1_rad) /
Real{180};
const auto VertexRadius = Interval<Positive<Length>>{SmallerLinearSlop, MaxVertexRadius};
const auto upperBodyConf = BodyConf(bd).UseLocation(
Vec2(0.0f, 6.0f) *
Meter);
const auto lowerBodyConf = BodyConf(bd).UseLocation(
Vec2(0.0f, 0.5f) *
Meter);
const auto groundConf = EdgeShapeConf{}
const auto diskConf = DiskShapeConf{}.UseDensity(10_kgpm2);
const auto smallerDiskConf = DiskShapeConf(diskConf).UseRadius(0.5_m);
const auto biggerDiskConf = DiskShapeConf(diskConf).UseRadius(5.0_m);
const auto baseStepConf = []() {
auto step = StepConf{};
step.deltaTime =
Real(1) / 60_Hz;
return step;
}();
const auto largerStepConf = [=](StepConf step) {
step.linearSlop = LargerLinearSlop;
step.regMinSeparation = -LargerLinearSlop *
Real(3);
step.toiMinSeparation = -LargerLinearSlop *
Real(1.5f);
step.targetDepth = LargerLinearSlop *
Real(3);
step.tolerance = LargerLinearSlop /
Real(4);
step.maxLinearCorrection = LargerLinearSlop *
Real(40);
step.maxAngularCorrection = AngularSlop *
Real{4};
step.aabbExtension = LargerLinearSlop *
Real(20);
step.velocityThreshold = (
Real{8} /
Real{10}) * 1_mps;
step.maxSubSteps = std::uint8_t{48};
return step;
}(baseStepConf);
const auto smallerStepConf = [=](StepConf step) {
step.linearSlop = SmallerLinearSlop;
step.regMinSeparation = -SmallerLinearSlop *
Real(3);
step.toiMinSeparation = -SmallerLinearSlop *
Real(1.5f);
step.targetDepth = SmallerLinearSlop *
Real(3);
step.tolerance = SmallerLinearSlop /
Real(4);
step.maxLinearCorrection = SmallerLinearSlop *
Real(40);
step.maxAngularCorrection = AngularSlop *
Real{4};
step.aabbExtension = SmallerLinearSlop *
Real(20);
step.velocityThreshold = (
Real{8} /
Real{10}) * 1_mps;
step.maxSubSteps = std::uint8_t{48};
return step;
}(baseStepConf);
{
auto world = World{WorldConf{}.UseVertexRadius(VertexRadius)};
const auto lowerBody =
CreateBody(world, lowerBodyConf);
const auto upperBody =
CreateBody(world, upperBodyConf);
auto numSteps = 0ul;
{
Step(world, largerStepConf);
upperBodysLowestPoint = std::min(upperBodysLowestPoint,
GetY(
GetLocation(world, upperBody)));
++numSteps;
}
{
case 4: EXPECT_EQ(numSteps, 145ul); break;
case 8: EXPECT_EQ(numSteps, 145ul); break;
case 16: EXPECT_EQ(numSteps, 145ul); break;
}
EXPECT_NEAR(
static_cast<double>(
Real(upperBodysLowestPoint /
Meter)), 5.9475154876708984, 0.001);
}
{
auto world = World{WorldConf{}.UseVertexRadius(VertexRadius)};
const auto upperBody =
CreateBody(world, upperBodyConf);
const auto lowerBody =
CreateBody(world, lowerBodyConf);
auto numSteps = 0ul;
{
Step(world, largerStepConf);
upperBodysLowestPoint = std::min(upperBodysLowestPoint,
GetY(
GetLocation(world, upperBody)));
++numSteps;
}
EXPECT_EQ(numSteps, 152ul);
EXPECT_NEAR(
static_cast<double>(
Real(upperBodysLowestPoint /
Meter)), 5.9470911026000977, 0.001);
}
{
auto world = World{WorldConf{}.UseVertexRadius(VertexRadius)};
const auto lowerBody =
CreateBody(world, lowerBodyConf);
const auto upperBody =
CreateBody(world, upperBodyConf);
auto numSteps = 0ul;
{
Step(world, smallerStepConf);
upperBodysLowestPoint = std::min(upperBodysLowestPoint,
GetY(
GetLocation(world, upperBody)));
++numSteps;
}
{
case 4: EXPECT_EQ(numSteps, 736ul); break;
case 8: EXPECT_EQ(numSteps, 736ul); break;
case 16: EXPECT_EQ(numSteps, 736ul); break;
}
EXPECT_NEAR(
static_cast<double>(
Real(upperBodysLowestPoint /
Meter)), 5.9473052024841309, 0.001);
}
{
auto world = World{WorldConf{}.UseVertexRadius(VertexRadius)};
const auto upperBody =
CreateBody(world, upperBodyConf);
const auto lowerBody =
CreateBody(world, lowerBodyConf);
auto numSteps = 0ul;
{
Step(world, smallerStepConf);
upperBodysLowestPoint = std::min(upperBodysLowestPoint,
GetY(
GetLocation(world, upperBody)));
++numSteps;
}
{
case 4: EXPECT_EQ(numSteps, 724ul); break;
case 8: EXPECT_EQ(numSteps, 724ul); break;
case 16: EXPECT_EQ(numSteps, 724ul); break;
}
EXPECT_NEAR(
static_cast<double>(
Real(upperBodysLowestPoint /
Meter)), 5.9476470947265625, 0.001);
}
{
auto world = World{WorldConf{}.UseVertexRadius(VertexRadius)};
const auto upperBody =
CreateBody(world, upperBodyConf);
const auto lowerBody =
CreateBody(world, lowerBodyConf);
Attach(world, lowerBody,
CreateShape(world, DiskShapeConf{smallerDiskConf}.UseIsSensor(
true)));
Attach(world, upperBody,
CreateShape(world, DiskShapeConf{biggerDiskConf}.UseIsSensor(
true)));
Step(world, smallerStepConf);
}
}
TEST(World, PerfectlyOverlappedSameCirclesStayPut)
{
auto world = World{};
const auto radius = 1_m;
const auto shapeId =
CreateShape(world, DiskShapeConf{}.UseRadius(radius).UseDensity(1_kgpm2).UseRestitution(
Real(1)));
auto body_def = BodyConf{};
body_def.bullet = false;
body_def.UseLocation(
Length2{0_m, 0_m});
Attach(world, body1, shapeId);
ASSERT_EQ(
GetLocation(world, body1), body_def.sweep.pos0.linear);
Attach(world, body2, shapeId);
ASSERT_EQ(
GetLocation(world, body2), body_def.sweep.pos0.linear);
const auto time_inc =
Real(.01);
for (auto i = 0; i < 100; ++i)
{
Step(world, 1_s * time_inc);
EXPECT_EQ(
GetLocation(world, body1), body_def.sweep.pos0.linear);
EXPECT_EQ(
GetLocation(world, body2), body_def.sweep.pos0.linear);
}
}
TEST(World, PerfectlyOverlappedConcentricCirclesStayPut)
{
World world{};
const auto radius1 = 1_m;
const auto radius2 = 0.6_m;
const auto shapeId1 =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).UseRadius(radius1));
const auto shapeId2 =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).UseRadius(radius2));
auto body_def = BodyConf{};
body_def.bullet = false;
Attach(world, body1, shapeId1);
ASSERT_EQ(
GetLocation(world, body1), body_def.sweep.pos0.linear);
Attach(world, body2, shapeId2);
ASSERT_EQ(
GetLocation(world, body2), body_def.sweep.pos0.linear);
const auto time_inc =
Real(.01);
for (auto i = 0; i < 100; ++i)
{
Step(world, 1_s * time_inc);
EXPECT_EQ(
GetLocation(world, body1), body_def.sweep.pos0.linear);
EXPECT_EQ(
GetLocation(world, body2), body_def.sweep.pos0.linear);
}
}
TEST(World, ListenerCalledForCircleBodyWithinCircleBody)
{
World world{};
MyContactListener listener{
world,
[&](
ContactID,
const ContactImpulsesList&, unsigned) {},
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
const ContactImpulsesList& impulses,
unsigned count){
listener.PostSolve(id, impulses, count);
});
auto body_def = BodyConf{};
const auto shapeId =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).UseRadius(1_m));
for (auto i = 0; i < 2; ++i)
{
}
ASSERT_EQ(listener.begin_contacts, 0u);
ASSERT_EQ(listener.end_contacts, 0u);
ASSERT_EQ(listener.pre_solves, 0u);
ASSERT_EQ(listener.post_solves, 0u);
EXPECT_NE(listener.begin_contacts, 0u);
EXPECT_EQ(listener.end_contacts, 0u);
EXPECT_NE(listener.pre_solves, 0u);
EXPECT_NE(listener.post_solves, 0u);
}
TEST(World, ListenerCalledForSquareBodyWithinSquareBody)
{
World world{};
MyContactListener listener{
world,
[&](
ContactID,
const ContactImpulsesList&, unsigned) {},
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
const ContactImpulsesList& impulses,
unsigned count){
listener.PostSolve(id, impulses, count);
});
auto body_def = BodyConf{};
auto conf = PolygonShapeConf{};
conf.UseVertexRadius(1_m);
conf.SetAsBox(2_m, 2_m);
conf.UseDensity(1_kgpm2);
conf.UseRestitution(
Real(1));
for (auto i = 0; i < 2; ++i)
{
}
ASSERT_EQ(listener.begin_contacts, 0u);
ASSERT_EQ(listener.end_contacts, 0u);
ASSERT_EQ(listener.pre_solves, 0u);
ASSERT_EQ(listener.post_solves, 0u);
EXPECT_NE(listener.begin_contacts, 0u);
EXPECT_EQ(listener.end_contacts, 0u);
EXPECT_NE(listener.pre_solves, 0u);
EXPECT_NE(listener.post_solves, 0u);
}
TEST(World, DropDisks)
{
const auto diskRadius = 0.5_m;
const auto numDisks = static_cast<std::uint16_t>(10000u);
auto world = World{};
ASSERT_NO_THROW(shapeId =
CreateShape(world, Shape{DiskShapeConf{}.UseRadius(diskRadius)}));
for (auto i = decltype(numDisks){0}; i < numDisks; ++i) {
const auto x = i * diskRadius * 4;
const auto location =
Length2{x, 0_m};
.UseLocation(location)
ASSERT_NO_THROW(
Attach(world, body, shapeId));
}
ASSERT_EQ(
GetTree(world).GetNodeCount(), 0u);
ASSERT_EQ(
GetTree(world).GetNodeCapacity(), 4096u);
auto stats = StepStats{};
ASSERT_NO_THROW(stats =
Step(world, stepConf));
EXPECT_EQ(
GetTree(world).GetNodeCount(), 19999u);
EXPECT_EQ(
GetTree(world).GetNodeCapacity(), 32768u);
EXPECT_EQ(stats.reg.islandsFound, numDisks);
EXPECT_EQ(stats.reg.islandsSolved, numDisks);
EXPECT_EQ(stats.reg.maxIslandBodies, 1u);
EXPECT_EQ(stats.reg.proxiesMoved, 0u);
EXPECT_EQ(stats.reg.contactsAdded, 0u);
EXPECT_EQ(stats.reg.maxIncImpulse,
Momentum(0));
EXPECT_EQ(stats.reg.minSeparation, std::numeric_limits<Length>::infinity());
EXPECT_EQ(stats.reg.bodiesSlept, 0u);
EXPECT_EQ(stats.reg.sumPosIters, numDisks);
EXPECT_EQ(stats.reg.sumVelIters, numDisks);
EXPECT_EQ(stats.toi.minSeparation, std::numeric_limits<Length>::infinity());
EXPECT_EQ(stats.toi.maxIncImpulse,
Momentum(0));
EXPECT_EQ(stats.toi.islandsFound, 0u);
EXPECT_EQ(stats.toi.islandsSolved, 0u);
EXPECT_EQ(stats.toi.contactsFound, 0u);
EXPECT_EQ(stats.toi.contactsAdded, 0u);
EXPECT_EQ(stats.toi.proxiesMoved, 0u);
EXPECT_EQ(stats.toi.sumPosIters, 0u);
EXPECT_EQ(stats.toi.sumVelIters, 0u);
for (auto i = decltype(numDisks){0}; i < numDisks; ++i) {
EXPECT_EQ(
GetX(body.GetVelocity().linear), 0_mps);
EXPECT_LT(
GetY(body.GetVelocity().linear), 0_mps);
}
for (auto i = 1; i < 8; ++i) {
EXPECT_NO_THROW(stats =
Step(world, stepConf));
SCOPED_TRACE(std::string("#-steps is ") + std::to_string(i));
EXPECT_EQ(stats.reg.proxiesMoved, 0u);
}
EXPECT_NO_THROW(stats =
Step(world, stepConf));
EXPECT_EQ(stats.reg.proxiesMoved, numDisks);
EXPECT_EQ(stats.toi.islandsFound, 0u);
EXPECT_EQ(stats.toi.islandsSolved, 0u);
EXPECT_EQ(stats.toi.contactsFound, 0u);
EXPECT_EQ(stats.toi.contactsAdded, 0u);
EXPECT_EQ(stats.toi.proxiesMoved, 0u);
EXPECT_EQ(stats.toi.sumPosIters, 0u);
EXPECT_EQ(stats.toi.sumVelIters, 0u);
EXPECT_EQ(
GetTree(world).GetNodeCount(), 19999u);
EXPECT_EQ(
GetTree(world).GetNodeCapacity(), 32768u);
}
TEST(World, PartiallyOverlappedSameCirclesSeparate)
{
const auto radius =
Real(1);
World world{};
auto body_def = BodyConf{};
body_def.bullet = false;
const auto shape =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).UseRadius(radius *
Meter));
body_def.UseLocation(body1pos);
ASSERT_EQ(
GetLocation(world, body1), body_def.sweep.pos0.linear);
body_def.UseLocation(body2pos);
ASSERT_EQ(
GetLocation(world, body2), body_def.sweep.pos0.linear);
auto position_diff = body2pos - body1pos;
const auto angle =
GetAngle(position_diff);
ASSERT_EQ(angle, 0_deg);
const auto time_inc = .01_s;
StepConf step;
step.deltaTime = time_inc;
const auto full_separation = radius * 2_m -
Length{step.linearSlop};
for (auto i = 0; i < 100; ++i)
{
new_distance > full_separation)
{
break;
}
ASSERT_GE(new_distance, distance);
if (new_distance == distance)
{
ASSERT_GE(new_distance, radius * 2_m -
Length{step.linearSlop} *
Real{4});
break;
}
else
{
if (cos(angle) != 0)
{
}
if (sin(angle) != 0)
{
}
}
ASSERT_NE(new_pos_diff, position_diff);
position_diff = new_pos_diff;
ASSERT_NE(new_distance, distance);
distance = new_distance;
const auto new_angle =
GetAngle(new_pos_diff);
EXPECT_EQ(angle, new_angle);
}
}
TEST(World, PerfectlyOverlappedSameSquaresSeparateHorizontally)
{
World world{};
const auto shape =
CreateShape(world, PolygonShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).SetAsBox(1_m, 1_m));
auto body_def = BodyConf{};
body_def.bullet = false;
ASSERT_EQ(
GetLocation(world, body1), body_def.sweep.pos0.linear);
ASSERT_EQ(
GetLocation(world, body2), body_def.sweep.pos0.linear);
auto stepConf = StepConf{};
const auto time_inc = .01_s;
stepConf.deltaTime = time_inc;
stepConf.maxLinearCorrection =
Real{0.0001f * 40} *
Meter;
for (auto i = 0; i < 100; ++i)
{
}
}
TEST(World, PartiallyOverlappedSquaresSeparateProperly)
{
World world{};
auto body_def = BodyConf{};
body_def.bullet = false;
const auto half_dim =
Real(64);
const auto shape =
CreateShape(world, PolygonShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).SetAsBox(half_dim *
Meter, half_dim *
Meter));
body_def.UseLocation(body1pos);
body_def.UseLocation(body2pos);
ASSERT_EQ(
GetAngle(world, body1), 0_deg);
ASSERT_EQ(
GetAngle(world, body2), 0_deg);
auto last_angle_1 =
GetAngle(world, body1);
auto last_angle_2 =
GetAngle(world, body2);
auto position_diff = body1pos - body2pos;
const auto velocity_iters = 10u;
const auto position_iters = 10u;
const auto time_inc =
Real(.01);
StepConf step;
step.deltaTime = 1_s * time_inc;
const auto full_separation = half_dim * 2_m -
Length{step.linearSlop};
for (auto i = 0; i < 100; ++i)
{
Step(world, 1_s * time_inc, velocity_iters, position_iters);
for (auto&& contact: contacts)
{
++count;
const auto cid = contact.second;
const auto body_a =
GetBodyA(world, cid);
const auto body_b =
GetBodyB(world, cid);
EXPECT_EQ(body_a, body1);
EXPECT_EQ(body_b, body2);
}
EXPECT_EQ(v1.angular, 0_deg / 1_s);
EXPECT_EQ(
GetX(v1.linear), 0_mps);
EXPECT_EQ(
GetY(v1.linear), 0_mps);
EXPECT_EQ(v2.angular, 0_deg / 1_s);
EXPECT_EQ(
GetX(v2.linear), 0_mps);
EXPECT_EQ(
GetY(v2.linear), 0_mps);
{
break;
}
if (new_distance == distance)
{
if (cos(angle) != 0)
{
}
if (sin(angle) != 0)
{
}
ASSERT_GE(new_distance, 2_m);
break;
}
ASSERT_NE(new_pos_diff, position_diff);
position_diff = new_pos_diff;
ASSERT_NE(new_distance, distance);
distance = new_distance;
const auto new_angle =
GetAngle(new_pos_diff);
angle = new_angle;
}
}
TEST(World, CollidingDynamicBodies)
{
const auto radius = 1_m;
auto body_def = BodyConf{};
World world{};
MyContactListener listener{
world,
[&](
ContactID,
const ContactImpulsesList&, unsigned) {},
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
const ContactImpulsesList& impulses,
unsigned count){
listener.PostSolve(id, impulses, count);
});
const auto shape =
CreateShape(world, DiskShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).UseRadius(radius));
const auto time_collision = 1.0099994_s;
const auto time_inc = 0.01_s;
auto elapsed_time = 0_s;
for (;;) {
elapsed_time += time_inc;
if (listener.contacting || elapsed_time >= 600_s) {
break;
}
}
EXPECT_LT(elapsed_time, 600_s);
EXPECT_TRUE(listener.contacting);
{
}
filter.categoryBits = ~filter.categoryBits;
{
}
const auto time_contacting = elapsed_time;
EXPECT_TRUE(listener.touching);
EXPECT_NEAR(
double(
Real(time_contacting / 1_s)),
double(
Real(time_collision / 1_s)), 0.02);
const auto tolerance = x / 100;
EXPECT_GE(
GetX(listener.body_a[0]), -1_m);
EXPECT_LE(
GetX(listener.body_b[0]), +1_m);
for (;;)
{
elapsed_time += time_inc;
if (!listener.contacting && !listener.touching)
{
break;
}
}
EXPECT_FALSE(listener.touching);
EXPECT_TRUE(
AlmostEqual(
double(
Real(elapsed_time / 1_s)),
double(
Real((time_contacting + time_inc) / 1_s))));
EXPECT_LT(
GetX(listener.body_a[1]), -1_m);
EXPECT_GT(
GetX(listener.body_b[1]), +1_m);
double(-x), 0.0001);
double(+x), 0.0001);
}
TEST(World_Longer, TilesComesToRest)
{
const auto ExpectedFirstBodiesSlept = []() -> unsigned long
{
if constexpr (std::is_same_v<Real, float>) {
#if defined(__k8__) || defined(__core2__) || defined(_WIN64) || (defined(__arm64__) && !defined(NDEBUG))
return 76u;
#elif defined(__arm64__)
return 110u;
#elif defined(_WIN32)
return 111u;
#else
return 0u;
#endif
}
if constexpr (std::is_same_v<Real, double>) {
return 110u;
}
return 0u;
};
const auto ExpectedFirstAllSlept = []() -> unsigned long
{
if constexpr (std::is_same_v<Real, float>) {
#if defined(__k8__)
return 1828u;
#elif defined(__core2__)
return 1798u;
#elif defined(__arm64__) && defined(NDEBUG)
return 1796u;
#elif defined(__arm64__) && !defined(NDBUG)
return 1766u;
#elif defined(_WIN64)
return 1803u;
#elif defined(_WIN32)
return 1769u;
#else
return 0u;
#endif
}
if constexpr (std::is_same_v<Real, double>) {
return 1791u;
}
return 0u;
};
constexpr auto LinearSlop = 1_m / 1000;
constexpr
auto AngularSlop = (
Pi * 2_rad) / 180;
constexpr auto MinVertexRadius = LinearSlop * 2;
const auto VertexRadius = Interval<Positive<Length>>{MinVertexRadius, MaxVertexRadius};
auto conf = PolygonShapeConf{}.UseVertexRadius(MinVertexRadius);
auto world = World(WorldConf{}.UseVertexRadius(VertexRadius));
constexpr auto e_count = 36;
auto createdBodyCount = 0ul;
{
const auto a =
Real{0.5f};
auto ground = Body(BodyConf{}.UseLocation(
Length2{0_m, -a * 1_m}));
constexpr auto N = 200;
constexpr auto M = 10;
for (auto j = 0; j < M; ++j) {
GetX(position) = -N * a * 1_m;
for (auto i = 0; i < N; ++i) {
conf.SetAsBox(a * 1_m, a * 1_m, position, 0_deg);
GetX(position) += 2_m * a;
}
GetY(position) -= 2_m * a;
}
ASSERT_EQ(
size(ground.GetShapes()), 2000u);
++createdBodyCount;
}
{
const auto a =
Real{0.5f};
conf.UseDensity(5_kgpm2);
conf.SetAsBox(a * 1_m, a * 1_m);
const auto deltaX =
Length2(0.5625_m, 1.25_m);
const auto deltaY =
Length2(1.125_m, 0.0_m);
for (auto i = 0; i < e_count; ++i) {
y = x;
for (auto j = i; j < e_count; ++j) {
ASSERT_NO_THROW(body.Attach(shapeId));
++createdBodyCount;
y += deltaY;
}
x += deltaX;
}
}
EXPECT_EQ(createdBodyCount, 667u);
StepConf step;
step.deltaTime = 1_s / 60;
step.linearSlop = LinearSlop;
step.regMinSeparation = -LinearSlop *
Real(3);
step.toiMinSeparation = -LinearSlop *
Real(1.5f);
step.targetDepth = LinearSlop *
Real(3);
step.tolerance = LinearSlop /
Real(4);
step.maxLinearCorrection = LinearSlop *
Real(40);
step.maxAngularCorrection = AngularSlop *
Real{4};
step.aabbExtension = LinearSlop *
Real(20);
step.maxTranslation = 4_m;
step.velocityThreshold = (
Real{8} /
Real{10}) * 1_mps;
step.maxSubSteps = std::uint8_t{48};
auto numSteps = 0ul;
auto sumRegPosIters = 0ul;
auto sumRegVelIters = 0ul;
auto sumToiPosIters = 0ul;
auto sumToiVelIters = 0ul;
auto lastStats = StepStats{};
auto firstWithContacts = std::optional<unsigned long>{};
auto firstWithDestroyed = std::optional<unsigned long>{};
auto firstWithIslandSolved = std::optional<unsigned long>{};
auto firstWithOneIsland = std::optional<unsigned long>{};
auto firstWithBodiesSlept = std::optional<unsigned long>{};
auto firstWithAllSlept = std::optional<unsigned long>{};
auto totalBodiesSlept = 0ul;
auto awakeCount = 0ul;
constexpr auto maxSteps = 3000ul;
while ((awakeCount =
GetAwakeCount(world)) > 0 && (numSteps < maxSteps)) {
const auto stats =
Step(world, step);
sumRegPosIters += stats.reg.sumPosIters;
sumRegVelIters += stats.reg.sumVelIters;
sumToiPosIters += stats.toi.sumPosIters;
sumToiVelIters += stats.toi.sumVelIters;
lastStats = stats;
if (!firstWithContacts &&
((stats.pre.contactsAdded > 0) || (stats.reg.contactsAdded > 0) || (stats.toi.contactsAdded > 0))) {
firstWithContacts = numSteps;
}
if (!firstWithIslandSolved &&
(stats.reg.islandsSolved > 0u) && (stats.reg.maxIslandBodies > 1u)) {
firstWithIslandSolved = numSteps;
}
if (!firstWithDestroyed && (stats.pre.contactsDestroyed > 0)) {
firstWithDestroyed = numSteps;
}
if (!firstWithOneIsland && (stats.reg.islandsFound == 1u)) {
firstWithOneIsland = numSteps;
}
if (!firstWithBodiesSlept && (stats.reg.bodiesSlept > 0u)) {
firstWithBodiesSlept = numSteps;
}
EXPECT_GT(stats.reg.islandsFound, 0u);
totalBodiesSlept += stats.reg.bodiesSlept;
if (!firstWithAllSlept && (totalBodiesSlept >= createdBodyCount)) {
firstWithAllSlept = numSteps;
}
++numSteps;
}
ASSERT_LT(numSteps, maxSteps);
EXPECT_EQ(firstWithContacts.value_or(0), 12u);
EXPECT_EQ(firstWithIslandSolved.value_or(0), 13u);
EXPECT_EQ(firstWithDestroyed.value_or(0), 51u);
EXPECT_EQ(firstWithOneIsland.value_or(0), 87u);
EXPECT_EQ(firstWithBodiesSlept.value_or(0), ExpectedFirstBodiesSlept());
EXPECT_EQ(firstWithAllSlept.value_or(0), ExpectedFirstAllSlept());
case 4u:
#if defined(__core2__)
EXPECT_EQ(totalBodiesSlept, 670u);
#elif defined(_WIN64)
EXPECT_EQ(totalBodiesSlept, 671u);
#elif defined(_WIN32)
EXPECT_EQ(totalBodiesSlept, 669u);
#elif defined(__amd64__)
EXPECT_EQ(totalBodiesSlept, 672u);
#elif defined(__arm64__)
EXPECT_GE(totalBodiesSlept, 667u);
EXPECT_LE(totalBodiesSlept, 671u);
EXPECT_TRUE(firstWithOneIsland);
#else
EXPECT_GE(totalBodiesSlept, 667u);
EXPECT_LE(totalBodiesSlept, 668u);
#endif
break;
case 8u:
#if defined(__GNUC__) && defined(__clang__) && !defined(__core2__) && !defined(__arm64__)
EXPECT_EQ(totalBodiesSlept, createdBodyCount);
#else
EXPECT_EQ(totalBodiesSlept, createdBodyCount + 3u);
#endif
break;
}
EXPECT_EQ(
GetTree(world).GetNodeCount(), 5331u);
EXPECT_EQ(awakeCount, 0u);
if (awakeCount == 0u) {
#if defined(PLAYRHO_USE_BOOST_UNITS) || defined(__core2__)
EXPECT_EQ(lastStats.reg.proxiesMoved, 1u);
#else
EXPECT_EQ(lastStats.reg.proxiesMoved, 0u);
#endif
EXPECT_EQ(lastStats.toi.proxiesMoved, 0u);
}
#if defined(__core2__)
{
case 4:
{
#ifndef __FAST_MATH__
EXPECT_EQ(numSteps, 1799ul);
EXPECT_EQ(sumRegPosIters, 36514ul);
EXPECT_EQ(sumRegVelIters, 46940ul);
EXPECT_EQ(sumToiPosIters, 43860ul);
EXPECT_EQ(sumToiVelIters, 113355ul);
#else
EXPECT_EQ(numSteps, 1003ul);
EXPECT_EQ(sumRegPosIters, 52909ul);
EXPECT_EQ(sumRegVelIters, 103896ul);
EXPECT_EQ(sumToiPosIters, 20616ul);
EXPECT_EQ(sumToiVelIters, 30175ul);
#endif
break;
}
case 8:
{
EXPECT_EQ(numSteps, 1828ul);
EXPECT_EQ(sumRegPosIters, 36540ul);
EXPECT_EQ(sumRegVelIters, 47173ul);
EXPECT_EQ(sumToiPosIters, 44005ul);
EXPECT_EQ(sumToiVelIters, 114357ul);
break;
}
case 16:
{
break;
}
default:
{
FAIL(); break;
}
}
#elif defined(__k8__)
{
case 4:
{
EXPECT_EQ(numSteps, 1829ul);
EXPECT_EQ(sumRegPosIters, 36551ul);
EXPECT_EQ(sumRegVelIters, 47211ul);
EXPECT_EQ(sumToiPosIters, 43751ul);
EXPECT_EQ(sumToiVelIters, 114349ul);
break;
}
case 8:
{
EXPECT_EQ(numSteps, 1792ul);
EXPECT_EQ(sumRegPosIters, 36494ul);
EXPECT_EQ(sumRegVelIters, 46885ul);
EXPECT_EQ(sumToiPosIters, 44086ul);
#ifdef NDEBUG
EXPECT_EQ(sumToiVelIters, 112731ul);
#else
EXPECT_EQ(sumToiVelIters, 112836ul);
#endif
break;
}
}
#elif defined(_WIN64)
EXPECT_EQ(numSteps, 1804ul);
EXPECT_EQ(sumRegPosIters, 36529ul);
EXPECT_EQ(sumRegVelIters, 46988ul);
EXPECT_EQ(sumToiPosIters, 43779ul);
EXPECT_EQ(sumToiVelIters, 113106ul);
#elif defined(_WIN32)
EXPECT_EQ(numSteps, 1770ul);
EXPECT_EQ(sumRegPosIters, 36429ul);
EXPECT_EQ(sumRegVelIters, 46732ul);
EXPECT_EQ(sumToiPosIters, 44141ul);
EXPECT_EQ(sumToiVelIters, 114445ul);
#elif defined(__arm64__)
{
case 4u:
#if defined(__clang_major__) && (__clang_major__ >= 14)
#if defined(PLAYRHO_USE_BOOST_UNITS)
EXPECT_EQ(numSteps, 1800ul);
EXPECT_EQ(sumRegPosIters, 36516ul);
EXPECT_EQ(sumRegVelIters, 46948ul);
EXPECT_EQ(sumToiPosIters, 44022ul);
EXPECT_EQ(sumToiVelIters, 113284ul);
#elif defined(__arm64__) && !defined(NDEBUG)
EXPECT_EQ(numSteps, 1767ul);
EXPECT_EQ(sumRegPosIters, 36414ul);
EXPECT_EQ(sumRegVelIters, 46675ul);
EXPECT_EQ(sumToiPosIters, 44239ul);
EXPECT_EQ(sumToiVelIters, 114126ul);
#else
EXPECT_EQ(numSteps, 1797ul);
EXPECT_EQ(sumRegPosIters, 36508ul);
EXPECT_EQ(sumRegVelIters, 46932ul);
EXPECT_EQ(sumToiPosIters, 44054ul);
EXPECT_EQ(sumToiVelIters, 114380ul);
#endif
#else
EXPECT_EQ(numSteps, 1799ul);
EXPECT_EQ(sumRegPosIters, 36512ul);
EXPECT_EQ(sumRegVelIters, 46940ul);
EXPECT_EQ(sumToiPosIters, 44021ul);
EXPECT_EQ(sumToiVelIters, 113137ul);
#endif
break;
case 8u:
EXPECT_EQ(numSteps, 1792ul);
EXPECT_EQ(sumRegPosIters, 36494ul);
EXPECT_EQ(sumRegVelIters, 46885ul);
EXPECT_EQ(sumToiPosIters, 44086ul);
#if defined(__clang_major__) && (__clang_major__ >= 14) && !defined(PLAYRHO_USE_BOOST_UNITS)
#if defined(__arm64__) && !defined(NDEBUG)
EXPECT_EQ(sumToiVelIters, 112885ul);
#else
EXPECT_EQ(sumToiVelIters, 112990ul);
#endif
#else
EXPECT_EQ(sumToiVelIters, 114196ul);
#endif
break;
}
#else
EXPECT_EQ(numSteps, 1814ul);
EXPECT_EQ(sumRegPosIters, 36600ul);
EXPECT_EQ(sumRegVelIters, 264096ul);
EXPECT_EQ(sumToiPosIters, 45022ul);
EXPECT_EQ(sumToiVelIters, 148560ul);
#endif
}
TEST(World, SpeedingBulletBallWontTunnel)
{
constexpr
auto AngularSlop = (
Pi *
Real{2} * 1_rad) /
Real{180};
const auto RadiusRange = Interval<Positive<Length>>{VertexRadius, MaxVertexRadius};
World world{WorldConf{}.UseVertexRadius(RadiusRange)};
MyContactListener listener{
world,
[&](
ContactID,
const ContactImpulsesList&, unsigned) {},
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
const ContactImpulsesList& impulses,
unsigned count){
listener.PostSolve(id, impulses, count);
});
ASSERT_EQ(listener.begin_contacts, unsigned{0});
const auto left_edge_x = -0.1_m;
const auto right_edge_x = +0.1_m;
const auto edgeConf = EdgeShapeConf{}
.UseVertexRadius(VertexRadius)
BodyConf body_def;
body_def.UseLocation(
Length2{left_edge_x, 0_m});
const auto left_wall_body =
CreateBody(world, body_def);
Attach(world, left_wall_body, edge_shape);
body_def.UseLocation(
Length2{right_edge_x, 0_m});
const auto right_wall_body =
CreateBody(world, body_def);
Attach(world, right_wall_body, edge_shape);
const auto begin_x =
Real(0);
body_def.bullet = false;
const auto ball_body =
CreateBody(world, body_def);
const auto ball_radius = 0.01_m;
const auto circle_shape =
Shape(DiskShapeConf{}.UseDensity(1_kgpm2).UseRestitution(
Real(1)).UseRadius(ball_radius));
const auto time_inc = .01_s;
auto step = StepConf{};
step.deltaTime = time_inc;
step.linearSlop = LinearSlop;
step.regMinSeparation = -LinearSlop *
Real(3);
step.toiMinSeparation = -LinearSlop *
Real(1.5f);
step.targetDepth = LinearSlop *
Real(3);
step.tolerance = LinearSlop /
Real(4);
step.maxLinearCorrection = LinearSlop *
Real(40);
step.maxAngularCorrection = AngularSlop *
Real{4};
step.aabbExtension = LinearSlop *
Real(20);
step.velocityThreshold = (
Real{8} /
Real{10}) * 1_mps;
step.maxSubSteps = std::uint8_t{48};
const auto max_velocity = step.maxTranslation / time_inc;
ASSERT_EQ(listener.begin_contacts, unsigned{0});
const auto max_travel = unsigned{10000};
auto increments = int{1};
for (auto laps = int{1}; laps < 100; ++laps) {
SCOPED_TRACE(std::string("lap=") + std::to_string(laps));
listener.begin_contacts = 0;
listener.end_contacts = 0;
for (auto travel_r = unsigned{0}; ; ++travel_r) {
SCOPED_TRACE(std::string("travel_r=") + std::to_string(travel_r));
if (travel_r == max_travel) {
std::cout << "begin_contacts=" << listener.begin_contacts << std::endl;
ASSERT_LT(travel_r, max_travel);
}
const auto lastEndCount = listener.end_contacts;
return;
}
if (listener.end_contacts == lastEndCount) {
continue;
}
if (listener.end_contacts % 2 != 0) {
break;
}
++increments;
}
listener.begin_contacts = 0;
listener.end_contacts = 0;
for (auto travel_l = unsigned{0}; ; ++travel_l)
{
if (travel_l == max_travel) {
std::cout << "begin_contacts=" << listener.begin_contacts << std::endl;
ASSERT_LT(travel_l, max_travel);
}
const auto lastEndCount = listener.end_contacts;
return;
}
if (listener.end_contacts == lastEndCount) {
continue;
}
if (listener.end_contacts % 2 != 0) {
break;
}
++increments;
}
}
}
TEST(World_Longer, TargetJointWontCauseTunnelling)
{
World world{};
const auto half_box_width =
Real(0.2);
const auto left_edge_x = -half_box_width;
const auto right_edge_x = +half_box_width;
const auto half_box_height =
Real(0.2);
const auto btm_edge_y = -half_box_height;
const auto top_edge_y = +half_box_height;
BodyConf body_def;
auto edgeConf = EdgeShapeConf{};
edgeConf.UseFriction(
Real(0.4f));
edgeConf.UseRestitution(
Real(0.94f));
edgeConf.Set(
Length2{0, +half_box_height * 2_m},
Length2{0, -half_box_height * 2_m});
{
const auto left_wall_body =
CreateBody(world, body_def);
}
{
const auto right_wall_body =
CreateBody(world, body_def);
}
edgeConf.Set(
Length2{-half_box_width * 2_m, 0_m},
Length2{+half_box_width * 2_m, 0_m});
{
const auto btm_wall_body =
CreateBody(world, body_def);
}
{
const auto top_wall_body =
CreateBody(world, body_def);
}
body_def.bullet = true;
const auto ball_body =
CreateBody(world, body_def);
const auto ball_radius =
Real(half_box_width / 4) *
Meter;
const auto object_shape =
CreateShape(world, PolygonShapeConf{}.UseDensity(10_kgpm2).SetAsBox(ball_radius, ball_radius));
Attach(world, ball_body, object_shape);
constexpr auto numBodies = 1u;
for (auto i = decltype(numBodies){0}; i < numBodies; ++i)
{
const auto angle = i * 2 *
Pi / numBodies;
const auto x = ball_radius *
Real(2.1) * cos(angle);
const auto y = ball_radius *
Real(2.1) * sin(angle);
body_def.UseLocation(
Length2{x, y});
Attach(world, bodies[i], object_shape);
}
const auto spare_body = [&](){
BodyConf bodyConf;
bodyConf.UseEnabled(false);
bodyConf.UseLocation(
Length2{-ball_radius /
Real{2}, +ball_radius /
Real{2}});
}();
const auto target_joint = [&]() {
TargetJointConf mjd;
mjd.bodyA = spare_body;
mjd.bodyB = ball_body;
const auto ball_body_pos =
GetLocation(world, ball_body);
GetX(ball_body_pos) - ball_radius /
Real{2},
GetY(ball_body_pos) + ball_radius /
Real{2}
};
}();
auto max_velocity =
Real(0);
const auto time_inc =
Real(.00367281295);
auto anglular_speed =
Real(0.01);
const auto anglular_accel =
Real(1.002);
auto distance = half_box_width / 2;
auto distance_speed =
Real(0.003);
const auto distance_accel =
Real(1.001);
MyContactListener listener{world,
[&](
ContactID contact,
const Manifold& old_manifold)
{
const auto oldPointCount = old_manifold.GetPointCount();
switch (oldPointCount)
{
case 0:
break;
case 1:
break;
case 2:
break;
default:
ASSERT_LE(oldPointCount, 2);
break;
}
const auto newPointCount = new_manifold.GetPointCount();
switch (newPointCount)
{
case 0:
break;
case 1:
break;
case 2:
break;
default:
ASSERT_LE(newPointCount, 2);
break;
}
ASSERT_THROW(
Destroy(world, target_joint), WrongState);
},
[&](
ContactID contact,
const ContactImpulsesList& impulse,
unsigned solved)
{
const auto body_a =
GetBodyA(world, contact);
const auto body_b =
GetBodyB(world, contact);
auto fail_count = unsigned{0};
for (auto&& body: {body_a, body_b})
{
{
continue;
}
{
{
++fail_count;
}
}
}
if (fail_count > 0)
{
std::cout << " angl=" << angle;
std::cout <<
" ctoi=" << 0 +
GetToiCount(world, contact);
std::cout << " solv=" << 0 + solved;
std::cout << " targ=(" << distance * cos(angle) << "," << distance * sin(angle) << ")";
std::cout << " maxv=" << max_velocity;
std::cout << " rang=(" << min_x << "," << min_y << ")-(" << max_x << "," << max_y << ")";
std::cout << std::endl;
for (auto i = decltype(impulse.GetCount()){0}; i < impulse.GetCount(); ++i)
{
std::cout << " i#" << (0 + i) << "={n" << impulse.GetEntryNormal(i) << ",t" << impulse.GetEntryTanget(i) << "}";
}
std::cout << std::endl;
if (body_a == ball_body) std::cout << " ball";
std::cout << std::endl;
if (body_b == ball_body) std::cout << " ball";
std::cout << std::endl;
}
},
const auto body_a =
GetBodyA(world, contact);
const auto body_b =
GetBodyB(world, contact);
auto escaped = false;
for (auto&& body: {body_a, body_b})
{
{
continue;
}
{
escaped = true;
}
{
escaped = true;
}
{
escaped = true;
}
{
escaped = true;
}
}
{
std::cout << "Escaped at EndContact[" << &contact << "]:";
std::cout <<
" toiSteps=" <<
static_cast<unsigned>(
GetToiCount(world, contact));
std::cout <<
" toiValid=" <<
HasValidToi(world, contact);
std::cout << std::endl;
}
},
};
ASSERT_EQ(listener.begin_contacts, unsigned{0});
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
const ContactImpulsesList& impulses,
unsigned count){
listener.PostSolve(id, impulses, count);
});
for (auto outer = unsigned{0}; outer < 2000; ++outer)
{
for (auto loops = unsigned{0};; ++loops)
{
angle += anglular_speed;
distance += distance_speed;
Step(world, 1_s * time_inc, 8, 3);
for (auto i = decltype(numBodies){0}; i < numBodies; ++i)
{
}
if (loops > 50)
{
{
break;
}
else
{
break;
}
{
break;
}
else
{
break;
}
}
}
anglular_speed *= anglular_accel;
distance_speed *= distance_accel;
#if 0
if (outer > 100)
{
for (auto i = decltype(numBodies){0}; i < numBodies; ++i)
{
EXPECT_NE(last_opos[i],
GetLocation(world, bodies[i]));
}
}
#endif
}
#if 0
std::cout << "angle=" << angle;
std::cout << " target=(" << distance * cos(angle) << "," << distance * sin(angle) << ")";
std::cout << " maxvel=" << max_velocity;
std::cout << " range=(" << min_x << "," << min_y << ")-(" << max_x << "," << max_y << ")";
std::cout << std::endl;
#endif
const auto target0 =
GetTarget(world, target_joint);
const auto shift =
Length2{2_m, 2_m};
const auto target1 =
GetTarget(world, target_joint);
EXPECT_EQ(target0 - shift, target1);
}
#if 0
class VerticalStackTest: public ::testing::TestWithParam<Real>
{
public:
virtual void SetUp()
{
const auto hw_ground = 40.0_m;
const auto numboxes = boxes.size();
original_x = GetParam();
const auto boxShape =
CreateShape(world, PolygonShapeConf{}.UseDensity(1_kgpm2).UseFriction(
Real(0.3f)).SetAsBox(hdim, hdim));
for (auto i = decltype(numboxes){0}; i < numboxes; ++i)
{
boxes[i] = box;
}
auto stepConf = StepConf{};
stepConf.deltaTime = 1_s / 60;
while (loopsTillSleeping < maxLoops)
{
{
break;
}
++loopsTillSleeping;
}
}
protected:
World world{};
std::size_t loopsTillSleeping = 0;
const std::size_t maxLoops = 10000;
std::vector<BodyID> boxes{10};
};
TEST_P(VerticalStackTest, EndsBeforeMaxLoops)
{
EXPECT_LT(loopsTillSleeping, maxLoops);
}
TEST_P(VerticalStackTest, BoxesAtOriginalX)
{
for (auto&& box: boxes)
{
}
}
TEST_P(VerticalStackTest, EachBoxAboveLast)
{
auto lasty = 0_m;
for (auto&& box: boxes)
{
}
}
TEST_P(VerticalStackTest, EachBodyLevel)
{
for (auto&& box: boxes)
{
}
}
static std::string test_suffix_generator(::testing::TestParamInfo<Real> param_info)
{
std::stringstream strbuf;
strbuf << param_info.index;
return strbuf.str();
}
static ::testing::internal::ParamGenerator<VerticalStackTest::ParamType> gtest_WorldVerticalStackTest_EvalGenerator_();
static ::std::string gtest_WorldVerticalStackTest_EvalGenerateName_(const ::testing::TestParamInfo<VerticalStackTest::ParamType>& info);
INSTANTIATE_TEST_CASE_P(World, VerticalStackTest, ::testing::Values(
Real(0),
Real(5)), test_suffix_generator);
#endif
TEST(World, GetResourceStatsWhenOff)
{
auto conf = WorldConf();
conf.doStats = false;
conf.reserveBuffers = 0;
conf.reserveBodyStack = 0u;
conf.reserveBodyConstraints = 0u;
conf.reserveDistanceConstraints = 0u;
conf.reserveContactKeys = 0u;
auto world = World{conf};
}
TEST(World, GetResourceStatsWhenOn)
{
auto conf = WorldConf();
conf.doStats = true;
conf.reserveBuffers = 0;
conf.reserveBodyStack = 0u;
conf.reserveBodyConstraints = 0u;
conf.reserveDistanceConstraints = 0u;
conf.reserveContactKeys = 0u;
auto world = World{conf};
auto stats = std::optional<pmr::StatsResource::Stats>{};
ASSERT_TRUE(stats.has_value());
const auto oldstats = stats;
EXPECT_EQ(stats->blocksAllocated, 0u);
EXPECT_EQ(stats->bytesAllocated, 0u);
EXPECT_EQ(stats->maxBlocksAllocated, 0u);
EXPECT_EQ(stats->maxBytesAllocated, 0u);
EXPECT_EQ(stats->maxBytes, 0u);
EXPECT_EQ(stats->maxAlignment, 0u);
const auto stepConf = StepConf{};
ASSERT_TRUE(stats.has_value());
#if defined(_WIN64) && !defined(NDEBUG)
EXPECT_EQ(stats->blocksAllocated, 1u);
EXPECT_EQ(stats->bytesAllocated, 16u);
EXPECT_EQ(stats->maxBlocksAllocated, 1u);
EXPECT_EQ(stats->maxBytesAllocated, 16u);
EXPECT_EQ(stats->maxBytes, 16u);
EXPECT_EQ(stats->maxAlignment, 8u);
#elif defined(_WIN32) && !defined(NDEBUG)
EXPECT_EQ(stats->blocksAllocated, 1u);
EXPECT_EQ(stats->bytesAllocated, 8u);
EXPECT_EQ(stats->maxBlocksAllocated, 1u);
EXPECT_EQ(stats->maxBytesAllocated, 8u);
EXPECT_EQ(stats->maxBytes, 8u);
EXPECT_EQ(stats->maxAlignment, 4u);
#else
EXPECT_EQ(stats->blocksAllocated, oldstats->blocksAllocated);
EXPECT_EQ(stats->bytesAllocated, oldstats->bytesAllocated);
EXPECT_EQ(stats->maxBlocksAllocated, oldstats->maxBlocksAllocated);
EXPECT_EQ(stats->maxBytesAllocated, oldstats->maxBytesAllocated);
EXPECT_EQ(stats->maxBytes, oldstats->maxBytes);
EXPECT_EQ(stats->maxAlignment, oldstats->maxAlignment);
#endif
}
TEST(World, TouchingAwakeBodiesWithZeroDeltaTime)
{
auto world = World{};
const auto shapeId0 =
CreateShape(world, Shape{DiskShapeConf{}.UseRadius(1_m)});
ASSERT_GT(next0, -1_m);
.UseAwake(true)
.UseLocation({-1_m, 0_m})
.Use(shapeId0));
.UseAwake(true)
.UseLocation({+1_m, 0_m})
.Use(shapeId0));
const auto stats =
Step(world, 0_s);
EXPECT_EQ(stats.pre.proxiesCreated, 2u);
EXPECT_EQ(stats.pre.proxiesMoved, 0u);
EXPECT_EQ(stats.pre.contactsDestroyed, 0u);
EXPECT_EQ(stats.pre.contactsAdded, 1u);
EXPECT_EQ(stats.pre.contactsUpdated, 0u);
EXPECT_EQ(stats.pre.contactsSkipped, 0u);
EXPECT_NE(stats.pre, PreStepStats());
EXPECT_EQ(stats.reg.minSeparation, std::numeric_limits<Length>::infinity());
EXPECT_EQ(stats.reg.maxIncImpulse, 0_Ns);
EXPECT_EQ(stats.reg.islandsFound, 0u);
EXPECT_EQ(stats.reg.islandsSolved, 0u);
EXPECT_EQ(stats.reg.bodiesSlept, 0u);
EXPECT_EQ(stats.reg.maxIslandBodies, 0u);
EXPECT_EQ(stats.reg.contactsAdded, 0u);
EXPECT_EQ(stats.reg.proxiesMoved, 0u);
EXPECT_EQ(stats.reg.sumPosIters, 0u);
EXPECT_EQ(stats.reg.sumVelIters, 0u);
EXPECT_EQ(stats.reg, RegStepStats());
EXPECT_EQ(stats.toi, ToiStepStats());
EXPECT_EQ(manifold0.GetPointCount(), 1u);
EXPECT_FALSE(contact0.NeedsUpdating());
EXPECT_TRUE(contact0.IsTouching());
EXPECT_EQ(contact0.GetContactableA(), (Contactable{bodyId0, shapeId0, 0u}));
EXPECT_EQ(contact0.GetContactableB(), (Contactable{bodyId1, shapeId0, 0u}));
EXPECT_FALSE(contact0.HasValidToi());
}
TEST(World, TouchingAsleepBodiesWithZeroDeltaTime)
{
auto world = World{};
const auto shapeId0 =
CreateShape(world, Shape{DiskShapeConf{}.UseRadius(1_m)});
ASSERT_GT(next0, -1_m);
.UseAwake(false)
.UseLocation({-1_m, 0_m})
.Use(shapeId0));
.UseAwake(false)
.UseLocation({+1_m, 0_m})
.Use(shapeId0));
const auto stats =
Step(world, 0_s);
EXPECT_EQ(stats.pre.proxiesCreated, 2u);
EXPECT_EQ(stats.pre.proxiesMoved, 0u);
EXPECT_EQ(stats.pre.contactsDestroyed, 0u);
EXPECT_EQ(stats.pre.contactsAdded, 1u);
EXPECT_EQ(stats.pre.contactsUpdated, 0u);
EXPECT_EQ(stats.pre.contactsSkipped, 0u);
EXPECT_NE(stats.pre, PreStepStats());
EXPECT_EQ(stats.reg.minSeparation, std::numeric_limits<Length>::infinity());
EXPECT_EQ(stats.reg.maxIncImpulse, 0_Ns);
EXPECT_EQ(stats.reg.islandsFound, 0u);
EXPECT_EQ(stats.reg.islandsSolved, 0u);
EXPECT_EQ(stats.reg.bodiesSlept, 0u);
EXPECT_EQ(stats.reg.maxIslandBodies, 0u);
EXPECT_EQ(stats.reg.contactsAdded, 0u);
EXPECT_EQ(stats.reg.proxiesMoved, 0u);
EXPECT_EQ(stats.reg.sumPosIters, 0u);
EXPECT_EQ(stats.reg.sumVelIters, 0u);
EXPECT_EQ(stats.reg, RegStepStats());
EXPECT_EQ(stats.toi, ToiStepStats());
EXPECT_EQ(manifold0.GetPointCount(), 1u);
EXPECT_FALSE(contact0.NeedsUpdating());
EXPECT_TRUE(contact0.IsTouching());
EXPECT_EQ(contact0.GetContactableA(), (Contactable{bodyId0, shapeId0, 0u}));
EXPECT_EQ(contact0.GetContactableB(), (Contactable{bodyId1, shapeId0, 0u}));
EXPECT_FALSE(contact0.HasValidToi());
}
TEST(World, Recreate)
{
constexpr auto LinearSlop = 1_m / 1000;
constexpr
auto AngularSlop = (
Pi * 2_rad) / 180;
constexpr auto MinVertexRadius = LinearSlop * 2;
const auto VertexRadius = Interval<Positive<Length>>{MinVertexRadius, MaxVertexRadius};
auto conf = PolygonShapeConf{}.UseVertexRadius(MinVertexRadius);
auto world = World{WorldConf{}.UseVertexRadius(VertexRadius)};
constexpr auto e_count = 20;
auto createdBodyCount = 0ul;
constexpr auto ExpectedFirstWithBodiesSlept = []() -> unsigned {
#if defined(__arm64__) && !defined(NDEBUG) && !defined(PLAYRHO_USE_BOOST_UNITS)
return 81u;
#else
if constexpr (std::is_same_v<Real, float>) {
return 82u;
}
if constexpr (std::is_same_v<Real, double>) {
return 81u;
}
return 0u;
#endif
};
constexpr auto ExpectedWorldContactRange = []() -> unsigned {
#if defined(__arm64__) && !defined(NDEBUG) && !defined(PLAYRHO_USE_BOOST_UNITS)
return 441u;
#else
if constexpr (std::is_same_v<Real, float>) {
return 442u;
}
if constexpr (std::is_same_v<Real, double>) {
return 441u;
}
return 0u;
#endif
};
constexpr auto ExpectedTotalContactsDestroyed = []() -> unsigned {
#if defined(__arm64__) && !defined(NDEBUG) && !defined(PLAYRHO_USE_BOOST_UNITS)
return 21u;
#else
if constexpr (std::is_same_v<Real, float>) {
return 20u;
}
if constexpr (std::is_same_v<Real, double>) {
return 21u;
}
return 0u;
#endif
};
{
const auto a =
Real{0.5f};
auto ground = Body(BodyConf{}.UseLocation(
Length2{0_m, -a * 1_m}));
constexpr auto N = 200;
constexpr auto M = 10;
for (auto j = 0; j < M; ++j) {
GetX(position) = -N * a * 1_m;
for (auto i = 0; i < N; ++i) {
conf.SetAsBox(a * 1_m, a * 1_m, position, 0_deg);
GetX(position) += 2_m * a;
}
GetY(position) -= 2_m * a;
}
++createdBodyCount;
}
{
const auto a =
Real{0.5f};
conf.UseDensity(5_kgpm2);
conf.SetAsBox(a * 1_m, a * 1_m);
const auto deltaX =
Length2(0.5625_m, 1.25_m);
const auto deltaY =
Length2(1.125_m, 0.0_m);
for (auto i = 0; i < e_count; ++i) {
y = x;
for (auto j = i; j < e_count; ++j) {
auto body =
ASSERT_NO_THROW(bodyId =
CreateBody(world, body));
++createdBodyCount;
y += deltaY;
}
x += deltaX;
}
}
ASSERT_EQ(createdBodyCount, 211u);
constexpr auto deltaTime = 1_s / 60;
StepConf stepConf;
stepConf.deltaTime = deltaTime;
stepConf.linearSlop = LinearSlop;
stepConf.regMinSeparation = -LinearSlop *
Real(3);
stepConf.toiMinSeparation = -LinearSlop *
Real(1.5f);
stepConf.targetDepth = LinearSlop *
Real(3);
stepConf.tolerance = LinearSlop /
Real(4);
stepConf.maxLinearCorrection = LinearSlop *
Real(40);
stepConf.maxAngularCorrection = AngularSlop *
Real{4};
stepConf.aabbExtension = LinearSlop *
Real(20);
stepConf.maxTranslation = 4_m;
stepConf.velocityThreshold = (
Real{8} /
Real{10}) * 1_mps;
stepConf.maxSubSteps = std::uint8_t{48};
auto lastStats = StepStats{};
auto firstWithContacts = std::optional<unsigned long>{};
auto firstHasContacts = std::optional<unsigned long>{};
auto firstWithDestroyed = std::optional<unsigned long>{};
auto firstWithIslandSolved = std::optional<unsigned long>{};
auto firstWithOneIsland = std::optional<unsigned long>{};
auto firstWithBodiesSlept = std::optional<unsigned long>{};
auto firstWithAllSlept = std::optional<unsigned long>{};
auto totalBodiesSlept = 0ul;
auto totalContactsDestroyed = 0ul;
constexpr auto maxSteps = 100ul;
auto numSteps = 0ul;
while (numSteps < maxSteps) {
const auto stats =
Step(world, stepConf);
lastStats = stats;
if (!firstWithContacts &&
((stats.pre.contactsAdded > 0) || (stats.reg.contactsAdded > 0) || (stats.toi.contactsAdded > 0))) {
firstWithContacts = numSteps;
}
firstHasContacts = numSteps;
}
if (!firstWithIslandSolved &&
(stats.reg.islandsSolved > 0u) && (stats.reg.maxIslandBodies > 1u)) {
firstWithIslandSolved = numSteps;
}
if (!firstWithDestroyed && (stats.pre.contactsDestroyed > 0)) {
firstWithDestroyed = numSteps;
}
totalContactsDestroyed += stats.pre.contactsDestroyed;
if (!firstWithOneIsland && (stats.reg.islandsFound == 1u)) {
firstWithOneIsland = numSteps;
}
if (!firstWithBodiesSlept && (stats.reg.bodiesSlept > 0u)) {
firstWithBodiesSlept = numSteps;
}
totalBodiesSlept += stats.reg.bodiesSlept;
if (!firstWithAllSlept && (totalBodiesSlept >= createdBodyCount)) {
firstWithAllSlept = numSteps;
}
++numSteps;
}
EXPECT_EQ(firstWithContacts.value_or(0), 12u);
EXPECT_EQ(firstWithContacts, firstHasContacts);
EXPECT_EQ(firstWithIslandSolved.value_or(0), 13u);
EXPECT_EQ(firstWithDestroyed.value_or(0), 51u);
EXPECT_EQ(firstWithOneIsland.value_or(0), 63u);
EXPECT_EQ(firstWithBodiesSlept.value_or(0), ExpectedFirstWithBodiesSlept());
EXPECT_EQ(firstWithAllSlept.value_or(0), 0u);
EXPECT_EQ(totalBodiesSlept, 1u);
EXPECT_EQ(awakeCount, (211u - totalBodiesSlept));
EXPECT_EQ(totalContactsDestroyed, ExpectedTotalContactsDestroyed());
EXPECT_EQ(
GetTree(world).GetNodeCount(), 4419u);
EXPECT_EQ(
GetTree(world).GetLeafCount(), 2210u);
stepConf.deltaTime = 0_s;
const auto copy = world;
ASSERT_TRUE(copy == world);
auto recreated = World{WorldConf{}.UseVertexRadius(VertexRadius)};
{
}
for (auto i = decltype(max)(0); i < max; ++i) {
}
}
{
for (
auto i = decltype(
GetBodyRange(world))(0); i < max; ++i) {
}
for (auto i = decltype(max)(0); i < max; ++i) {
SCOPED_TRACE(std::string("BodyID ") + std::to_string(i));
}
}
ASSERT_FALSE(recreated == world);
auto recreatedStats = StepStats{};
ASSERT_NO_THROW(recreatedStats =
Step(recreated, stepConf));
EXPECT_EQ(recreatedStats.pre.proxiesCreated, 2210u);
EXPECT_EQ(recreatedStats.pre.proxiesMoved, 0u);
EXPECT_EQ(recreatedStats.pre.contactsDestroyed, 0u);
EXPECT_EQ(recreatedStats.pre.contactsAdded, 436u);
EXPECT_EQ(recreatedStats.pre.contactsUpdated, 0u);
EXPECT_EQ(recreatedStats.pre.contactsSkipped, 0u);
EXPECT_EQ(
GetTree(world).GetNodeCount(),
GetTree(recreated).GetNodeCount());
EXPECT_EQ(
GetTree(world).GetLeafCount(),
GetTree(recreated).GetLeafCount());
auto worldContactMap = std::map<std::pair<Contactable, Contactable>,
ContactID>{};
auto worldContactsDestroyed = 0u;
{
SCOPED_TRACE(std::string("ContactID ") + std::to_string(i));
if (contact.IsDestroyed()) {
++worldContactsDestroyed;
continue;
}
if (!contact.IsTouching()) {
continue;
}
EXPECT_FALSE(contact.NeedsUpdating());
const auto [it, inserted] = worldContactMap.emplace(
std::minmax(contact.GetContactableA(), contact.GetContactableB()),
ContactID(i));
EXPECT_TRUE(inserted);
if (!inserted) {
ADD_FAILURE() << "insertion failed: older ID="
<< " cA={"
<< it->first.first.childId << "},"
<< "cB={"
<< it->first.second.childId << "},";
}
}
}
EXPECT_EQ(worldContactsDestroyed, 4u);
EXPECT_EQ(worldContactMap.size(), 420u);
auto recreatedContactMap = std::map<std::pair<Contactable, Contactable>,
ContactID>{};
{
SCOPED_TRACE(std::string("ContactID ") + std::to_string(i));
EXPECT_FALSE(contact.IsDestroyed());
if (contact.IsDestroyed()) {
continue;
}
if (!contact.IsTouching()) {
continue;
}
EXPECT_FALSE(contact.NeedsUpdating());
const auto [it, inserted] = recreatedContactMap.emplace(
std::minmax(contact.GetContactableA(), contact.GetContactableB()),
ContactID(i));
EXPECT_TRUE(inserted);
}
}
EXPECT_EQ(recreatedContactMap.size(), 420u);
EXPECT_EQ(worldContactMap.size(), recreatedContactMap.size());
for (const auto &entry: worldContactMap) {
std::ostringstream os;
os << "World ContactID ";
os << ": contactableA=" << entry.first.first;
os << ", contactableB=" << entry.first.second;
const auto it = recreatedContactMap.find(entry.first);
EXPECT_TRUE(it != recreatedContactMap.end()) << os.str();
if (it != recreatedContactMap.end()) {
const auto worldContact =
GetContact(world, entry.second);
const auto recreatedContact =
GetContact(recreated, it->second);
EXPECT_EQ(worldContact.GetContactableA(), recreatedContact.GetContactableA());
EXPECT_EQ(worldContact.GetContactableB(), recreatedContact.GetContactableB());
EXPECT_EQ(worldContact.GetFriction(), recreatedContact.GetFriction());
EXPECT_EQ(worldContact.GetRestitution(), recreatedContact.GetRestitution());
EXPECT_EQ(worldContact.GetTangentSpeed(), recreatedContact.GetTangentSpeed());
EXPECT_EQ(worldContact.IsTouching(), recreatedContact.IsTouching());
EXPECT_EQ(worldContact.IsEnabled(), recreatedContact.IsEnabled());
EXPECT_EQ(worldContact.IsSensor(), recreatedContact.IsSensor());
EXPECT_EQ(worldContact.IsImpenetrable(), recreatedContact.IsImpenetrable());
EXPECT_EQ(worldContact.IsDestroyed(), recreatedContact.IsDestroyed());
EXPECT_EQ(worldContact.NeedsFiltering(), recreatedContact.NeedsFiltering());
EXPECT_EQ(worldContact.NeedsUpdating(), recreatedContact.NeedsUpdating());
ASSERT_EQ(worldContact, recreatedContact);
const auto worldManifold =
GetManifold(world, entry.second);
const auto recreatedManifold =
GetManifold(recreated, it->second);
EXPECT_EQ(unsigned(worldManifold.GetType()), unsigned(recreatedManifold.GetType()));
EXPECT_EQ(unsigned(worldManifold.GetPointCount()), unsigned(recreatedManifold.GetPointCount()));
for (auto i = decltype(worldManifold.GetPointCount()){0u};
i < worldManifold.GetPointCount() && i < recreatedManifold.GetPointCount();
++i) {
EXPECT_EQ(worldManifold.GetPoint(i).localPoint, recreatedManifold.GetPoint(i).localPoint);
EXPECT_EQ(worldManifold.GetPoint(i).contactFeature, recreatedManifold.GetPoint(i).contactFeature);
}
EXPECT_NO_THROW(
SetManifold(recreated, it->second, worldManifold));
}
else {
const auto recreatedAabbA =
GetAABB(
GetTree(recreated).GetNode(recreatedIdxA));
const auto recreatedAabbB =
GetAABB(
GetTree(recreated).GetNode(recreatedIdxB));
EXPECT_NE(recreatedAabbA, worldAabbA);
EXPECT_NE(recreatedAabbB, worldAabbB);
}
}
for (const auto &entry: recreatedContactMap) {
std::ostringstream os;
os << "Recreated ContactID ";
os << " for: contactableA=" << entry.first.first;
os << ", contactableB=" << entry.first.second;
const auto it = worldContactMap.find(entry.first);
EXPECT_TRUE(it != worldContactMap.end()) << os.str();
if (it != worldContactMap.end()) {
const auto worldContact =
GetContact(world, it->second);
const auto recreatedContact =
GetContact(recreated, entry.second);
EXPECT_EQ(worldContact.GetContactableA(), recreatedContact.GetContactableA());
EXPECT_EQ(worldContact.GetContactableB(), recreatedContact.GetContactableB());
EXPECT_EQ(worldContact.GetFriction(), recreatedContact.GetFriction());
EXPECT_EQ(worldContact.GetRestitution(), recreatedContact.GetRestitution());
EXPECT_EQ(worldContact.GetTangentSpeed(), recreatedContact.GetTangentSpeed());
EXPECT_EQ(worldContact.IsTouching(), recreatedContact.IsTouching());
EXPECT_EQ(worldContact.IsEnabled(), recreatedContact.IsEnabled());
EXPECT_EQ(worldContact.IsSensor(), recreatedContact.IsSensor());
EXPECT_EQ(worldContact.IsImpenetrable(), recreatedContact.IsImpenetrable());
EXPECT_EQ(worldContact.IsDestroyed(), recreatedContact.IsDestroyed());
EXPECT_EQ(worldContact.NeedsFiltering(), recreatedContact.NeedsFiltering());
EXPECT_EQ(worldContact.NeedsUpdating(), recreatedContact.NeedsUpdating());
ASSERT_EQ(worldContact, recreatedContact);
}
else {
const auto recreatedAabbA =
GetAABB(
GetTree(recreated).GetNode(recreatedIdxA));
const auto recreatedAabbB =
GetAABB(
GetTree(recreated).GetNode(recreatedIdxB));
EXPECT_NE(recreatedAabbA, worldAabbA);
EXPECT_NE(recreatedAabbB, worldAabbB);
}
}
EXPECT_TRUE(recreated == world);
}
Declarations of the AabbTreeWorld class.
Declarations of BodyConf class & free functions associated with it.
Declarations of the Body class, and functions associated with it.
Definition of the DiskShapeConf class and closely related code.
Definition of the DistanceJointConf class and closely related code.
Declaration of the DynamicTree class.
Definition of the EdgeShapeConf class and closely related code.
Definition of the FrictionJointConf class and closely related code.
Definition of the GearJointConf class and closely related code.
Definition of the Joint class and closely related code.
Definition of the LengthError class.
Definition of the Manifold class and closely related code.
Definition of the MotorJointConf class and closely related code.
Structures and functions used for computing before and after like point oriented collision response s...
Definition of the PolygonShapeConf class and closely related code.
Definition of the PrismaticJointConf class and closely related code.
Definition of the PulleyJointConf class and closely related code.
Declaration of the RayCastOutput structure and related free functions.
Definition of the RevoluteJointConf class and closely related code.
Definition of the RopeJointConf class and closely related code.
Declarations of the StepConf class, and free functions associated with it.
Definition of the TargetJointConf class and closely related code.
Definition of the WeldJointConf class and closely related code.
Definition of the WheelJointConf class and closely related code.
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.
Definition of the WrongState class.
static constexpr auto InvalidSize
Invalid size constant value.
Definition: DynamicTree.hpp:83
@ e_circles
Definition: Manifold.hpp:107
@ e_faceA
Definition: Manifold.hpp:116
std::remove_const_t< decltype(MaxManifoldPoints)> size_type
Size type.
Definition: Manifold.hpp:85
static constexpr UnitVec GetRight() noexcept
Gets the right-ward oriented unit vector.
Definition: UnitVec.hpp:122
auto nextafter(const Checked< ValueType, Checker, NoExcept > &from, const Checked< ValueType, Checker, NoExcept > &to) -> decltype(Checked< ValueType, Checker, false >(nextafter(from.get(), to.get())))
Next after function.
Definition: CheckedMath.hpp:56
constexpr auto Square(T t) noexcept(noexcept(t *t)) -> decltype(t *t)
Squares the given value.
Definition: Math.hpp:120
constexpr auto AlmostEqual(T a, T b, int ulp=4) -> std::enable_if_t< IsArithmeticV< T >, bool >
Determines whether the given two values are "almost equal".
Definition: Math.hpp:187
auto GetMagnitude(const T &value) noexcept(noexcept(sqrt(GetMagnitudeSquared(value)))) -> decltype(sqrt(GetMagnitudeSquared(value)))
Gets the magnitude of the given value.
Definition: Math.hpp:289
detail::moment_of_inertia RotInertia
Rotational inertia quantity.
Definition: Units.hpp:360
detail::length Length
Length quantity.
Definition: Units.hpp:244
detail::mass Mass
Mass quantity.
Definition: Units.hpp:270
detail::plane_angle Angle
Angle quantity.
Definition: Units.hpp:301
detail::momentum Momentum
Momentum quantity.
Definition: Units.hpp:379
constexpr auto MeterPerSquareSecond
Meter per square second unit of linear acceleration.
Definition: Units.hpp:431
constexpr auto RadianPerSquareSecond
Radian per square second unit of angular acceleration.
Definition: Units.hpp:478
constexpr auto Second
Second unit of time.
Definition: Units.hpp:406
constexpr auto Meter
Meter unit of Length.
Definition: Units.hpp:423
RayCastOutput RayCast(Length radius, const Length2 &location, const RayCastInput &input) noexcept
Cast a ray against a circle of a given radius at the given location.
Definition: RayCastOutput.cpp:41
void SetAcceleration(Body &body, const Acceleration &value) noexcept
Sets the accelerations on the given body.
Definition: Body.hpp:1167
const BodyIDs & GetBodiesForProxies(const AabbTreeWorld &world) noexcept
Gets the bodies-for-proxies container for this world.
Definition: AabbTreeWorld.hpp:1050
NonNegative< Length > GetVertexRadius(const ChainShapeConf &arg) noexcept
Gets the vertex radius of the given shape configuration.
Definition: ChainShapeConf.hpp:234
bool NeedsFiltering(const World &world, ContactID id)
Whether or not the contact needs filtering.
Definition: WorldContact.cpp:94
void SetShape(AabbTreeWorld &world, ShapeID id, Shape def)
Sets the value of the identified shape.
Definition: AabbTreeWorld.cpp:1284
bool NeedsUpdating(const World &world, ContactID id)
Whether or not the contact needs updating.
Definition: WorldContact.cpp:99
auto FindIndex(const DynamicTree &tree, const Contactable &c) noexcept -> DynamicTree::Size
Finds index of node matching given contactble using a linear search.
Definition: DynamicTree.cpp:712
std::size_t size(const DynamicTree &tree) noexcept
Gets the "size" of the given tree.
Definition: DynamicTree.hpp:715
bool IsAwake(const Body &body) noexcept
Gets the awake/asleep state of this body.
Definition: Body.hpp:884
Velocity GetVelocity(const Body &body) noexcept
Gets the velocity.
Definition: Body.hpp:1292
ShapeCounter GetAssociationCount(const World &world)
Gets the count of body-shape associations in the given world.
Definition: WorldShape.cpp:53
BodyCounter GetBodyCount(const World &world) noexcept
Gets the body count in the given world.
Definition: WorldBody.cpp:594
NonNegative< Frequency > GetLinearDamping(const Body &body) noexcept
Gets the linear damping of the body.
Definition: Body.hpp:1122
void ClearForces(World &world)
Clears forces.
Definition: WorldBody.hpp:799
::playrho::detail::MassData< 2 > MassData
Mass data alias for 2-D objects.
Definition: MassData.hpp:88
bool IsSleepingAllowed(const Body &body) noexcept
Gets whether or not this body allowed to sleep.
Definition: Body.hpp:833
NonNegative< AreaDensity > GetDensity(const Shape &shape) noexcept
Gets the density of the given shape.
Definition: Shape.hpp:353
ContactCounter GetContactRange(const AabbTreeWorld &world) noexcept
Gets the extent of the currently valid contact range.
Definition: AabbTreeWorld.cpp:1014
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
Length2 GetLocalAnchorB(const GearJointConf &conf)
Gets the local anchor B property of the given joint.
Definition: GearJointConf.cpp:510
void SetType(Body &body, BodyType value) noexcept
Sets the type of this body.
Definition: Body.hpp:756
void UnsetImpenetrable(Body &body) noexcept
Unsets the impenetrable status of this body.
Definition: Body.hpp:825
Angle GetAngle(const Body &body) noexcept
Gets the body's angle.
Definition: Body.cpp:280
void SetEndContactListener(AabbTreeWorld &world, ContactFunction listener) noexcept
Register an end contact event listener.
Definition: AabbTreeWorld.hpp:1131
AngularVelocity GetAngularVelocity(const Body &body) noexcept
Gets the angular velocity.
Definition: Body.hpp:1322
void SetAngularDamping(Body &body, NonNegative< Frequency > value) noexcept
Sets the angular damping of the body.
Definition: Body.hpp:1146
BodyID CreateBody(AabbTreeWorld &world, Body body=Body{})
Creates a rigid body that's a copy of the given one.
Definition: AabbTreeWorld.cpp:1019
GearJointConf GetGearJointConf(const Joint &joint)
Gets the definition data for the given joint.
Definition: GearJointConf.cpp:87
BodyID GetBodyB(const Joint &object) noexcept
Gets the second body attached to this joint.
Definition: Joint.hpp:295
LinearVelocity2 GetLinearVelocity(const Body &body) noexcept
Gets the linear velocity of the center of mass.
Definition: Body.hpp:1312
Mass GetMass(const Body &body) noexcept
Gets the mass of the body.
Definition: Body.hpp:1217
BodyType GetType(const Body &body) noexcept
Gets the type of this body.
Definition: Body.hpp:748
Length2 GetTarget(const Joint &object)
Gets the given joint's target property if it has one.
Definition: Joint.cpp:462
void ShiftOrigin(AabbTreeWorld &world, const Length2 &newOrigin)
Shifts the world origin.
Definition: AabbTreeWorld.cpp:2221
const Manifold & GetManifold(const AabbTreeWorld &world, ContactID id)
Gets the identified manifold.
Definition: AabbTreeWorld.cpp:2865
bool Awaken(Body &body) noexcept
Awakens the body if it's asleep.
Definition: Body.hpp:1191
AABB ComputeAABB(const DistanceProxy &proxy, const Transformation &xf) noexcept
Computes the AABB.
Definition: AABB.cpp:41
const BodyIDs & GetBodies(const AabbTreeWorld &world) noexcept
Gets a container of valid world body identifiers for this constant world.
Definition: AabbTreeWorld.hpp:1045
PulleyJointConf GetPulleyJointConf(const Joint &joint)
Gets the definition data for the given joint.
Definition: PulleyJointConf.cpp:73
ShapeID CreateShape(AabbTreeWorld &world, Shape def)
Creates an identifiable copy of the given shape within this world.
Definition: AabbTreeWorld.cpp:1234
bool IsTouching(const World &world, ContactID id)
Is this contact touching?
Definition: WorldContact.cpp:41
void Query(const DynamicTree &tree, const AABB &aabb, const DynamicTreeSizeCB &callback)
Query the given dynamic tree and find nodes overlapping the given AABB.
Definition: DynamicTree.cpp:673
constexpr Vec2 GetVec2(const UnitVec &value)
Gets a Vec2 representation of the given value.
Definition: Math.hpp:55
void SetSleepingAllowed(Body &body, bool value) noexcept
Definition: Body.hpp:842
void SetDetachListener(AabbTreeWorld &world, BodyShapeFunction listener) noexcept
Registers a detach listener for shapes detaching from bodies.
Definition: AabbTreeWorld.hpp:1116
Position GetNormalized(const Position &val) noexcept
Gets the "normalized" position.
Definition: Math.hpp:150
void SetShapeDestructionListener(AabbTreeWorld &world, ShapeFunction listener) noexcept
Registers a destruction listener for shapes.
Definition: AabbTreeWorld.hpp:1111
void SetLocation(Body &body, const Length2 &value)
Sets the body's location.
Definition: Body.cpp:275
void SetAwake(Body &body) noexcept
Awakens this body.
Definition: Body.hpp:898
PrismaticJointConf GetPrismaticJointConf(const Joint &joint)
Gets the definition data for the given joint.
Definition: PrismaticJointConf.cpp:127
ShapeCounter GetUsedShapesCount(const World &world) noexcept
Gets the count of uniquely identified shapes that are in use - i.e. that are attached to bodies.
Definition: WorldShape.cpp:63
ShapeCounter GetShapeRange(const AabbTreeWorld &world) noexcept
Gets the extent of the currently valid shape range.
Definition: AabbTreeWorld.cpp:1229
void SetManifold(AabbTreeWorld &world, ContactID id, const Manifold &value)
Sets the identified manifold.
Definition: AabbTreeWorld.cpp:2834
void SetPostSolveContactListener(AabbTreeWorld &world, ContactImpulsesFunction listener) noexcept
Register a post-solve contact event listener.
Definition: AabbTreeWorld.hpp:1141
void Clear(AabbTreeWorld &world) noexcept
Clears this world.
Definition: AabbTreeWorld.cpp:954
std::add_pointer_t< std::add_const_t< T > > TypeCast(const Joint *value) noexcept
Converts the given joint into its current configuration value.
Definition: Joint.hpp:439
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
DynamicTree::Height GetHeight(const DynamicTree &tree) noexcept
Gets the height of the binary tree.
Definition: DynamicTree.hpp:622
JointCounter GetJointCount(const World &world)
Definition: World.hpp:435
NonNegative< Frequency > GetAngularDamping(const Body &body) noexcept
Gets the angular damping of the body.
Definition: Body.hpp:1138
bool IsSensor(const Shape &shape) noexcept
Gets whether or not the given shape is a sensor.
Definition: Shape.hpp:381
void SetTarget(Joint &object, const Length2 &value)
Sets the given joint's target property if it has one.
Definition: Joint.cpp:471
const Transformation & GetTransformation(const Body &body) noexcept
Gets the body's transformation.
Definition: Body.hpp:1014
BodyID FindClosestBody(const World &world, const Length2 &location)
Finds body in given world that's closest to the given location.
Definition: WorldBody.cpp:576
void SetSubStepping(AabbTreeWorld &world, bool flag) noexcept
Enables/disables single stepped continuous physics.
Definition: AabbTreeWorld.hpp:1086
bool HasValidToi(const World &world, ContactID id)
Whether or not the contact has a valid TOI.
Definition: WorldContact.cpp:104
const BodyContactIDs & GetContacts(const AabbTreeWorld &world, BodyID id)
Gets the contacts associated with the identified body.
Definition: AabbTreeWorld.cpp:2554
void SetJointDestructionListener(AabbTreeWorld &world, JointFunction listener) noexcept
Register a destruction listener for joints.
Definition: AabbTreeWorld.hpp:1121
NonNegativeFF< InvMass > GetInvMass(const Body &body) noexcept
Gets the inverse total mass of the body.
Definition: Body.hpp:1102
Real ComputePerimeterRatio(const DynamicTree &tree) noexcept
Gets the ratio of the sum of the perimeters of nodes to the root perimeter.
Definition: DynamicTree.cpp:736
void Destroy(AabbTreeWorld &world, BodyID id)
Destroys the identified body.
Definition: AabbTreeWorld.cpp:1062
void SetAngle(Body &body, Angle value)
Sets the body's angular orientation.
Definition: Body.cpp:285
bool IsSpeedable(const Body &body) noexcept
Is "speedable".
Definition: Body.hpp:786
ContactCounter GetTouchingCount(const World &world)
Gets the touching count for the given world.
Definition: WorldContact.cpp:186
WeldJointConf GetWeldJointConf(const Joint &joint)
Gets the definition data for the given joint.
Definition: WeldJointConf.cpp:103
Length2 GetLocation(const Body &body) noexcept
Gets the body's origin location.
Definition: Body.hpp:930
bool IsImpenetrable(const Body &body) noexcept
Is this body treated like a bullet for continuous collision detection?
Definition: Body.hpp:805
MotorJointConf GetMotorJointConf(const Joint &joint)
Gets the definition data for the given joint.
Definition: MotorJointConf.cpp:68
const DynamicTree & GetTree(const AabbTreeWorld &world) noexcept
Gets access to the broad-phase dynamic tree information.
Definition: AabbTreeWorld.hpp:1106
ShapeID GetShapeA(const World &world, ContactID id)
Gets shape A of the identified contact.
Definition: WorldContact.cpp:69
MassData ComputeMassData(const World &world, BodyID id)
Computes the identified body's mass data.
Definition: WorldBody.cpp:355
void SetContact(AabbTreeWorld &world, ContactID id, Contact value)
Sets the identified contact's state.
Definition: AabbTreeWorld.cpp:2797
void SetLinearDamping(Body &body, NonNegative< Frequency > value) noexcept
Sets the linear damping of the body.
Definition: Body.hpp:1130
const Shape & GetShape(const AabbTreeWorld &world, ShapeID id)
Gets the identified shape.
Definition: AabbTreeWorld.cpp:1279
void Set(JointConf &def, const Joint &joint) noexcept
Sets the joint definition data for the given joint.
Definition: JointConf.cpp:41
Filter GetFilterData(const World &world, ShapeID id)
Gets the filter data for the identified shape.
Definition: WorldShape.cpp:74
auto MakeTouchingMap(const World &world) -> std::map< std::pair< Contactable, Contactable >, ContactID >
Makes a map of contacts in the given world that are in the touching state.
Definition: WorldContact.cpp:195
Interval< Positive< Length > > GetVertexRadiusInterval(const AabbTreeWorld &world) noexcept
Gets the vertex radius interval allowable for the given world.
Definition: AabbTreeWorld.hpp:1096
bool Detach(AabbTreeWorld &world, BodyID id, ShapeID shapeID)
Disassociates a validly identified shape from the validly identified body.
Definition: AabbTreeWorld.cpp:2903
const Body & GetBody(const AabbTreeWorld &world, BodyID id)
Gets the identified body.
Definition: AabbTreeWorld.cpp:2850
bool IsStepComplete(const AabbTreeWorld &world) noexcept
Whether or not "step" is complete.
Definition: AabbTreeWorld.hpp:1076
void SetImpenetrable(Body &body) noexcept
Sets the impenetrable status of this body.
Definition: Body.hpp:815
DynamicTree::Height GetMaxImbalance(const DynamicTree &tree) noexcept
Gets the maximum imbalance.
Definition: DynamicTree.cpp:759
BodyCounter GetAwakeCount(const World &world)
Gets the count of awake bodies in the given world.
Definition: WorldBody.cpp:538
DistanceJointConf GetDistanceJointConf(const Joint &joint)
Gets the definition data for the given joint.
Definition: DistanceJointConf.cpp:69
const Contact & GetContact(const AabbTreeWorld &world, ContactID id)
Gets the identified contact.
Definition: AabbTreeWorld.cpp:2860
const BodyJointIDs & GetJoints(const AabbTreeWorld &world, BodyID id)
Definition: AabbTreeWorld.cpp:2559
constexpr auto EarthlyGravity
Earthly gravity in 2-dimensions.
Definition: Vector2.hpp:128
auto IsDestroyed(const AabbTreeWorld &world, BodyID id) -> bool
Gets whether the given identifier is to a body that's been destroyed.
Definition: AabbTreeWorld.hpp:341
::playrho::detail::AABB< 2 > AABB
2-Dimensional Axis Aligned Bounding Box.
Definition: AABB.hpp:63
void ApplyForceToCenter(World &world, BodyID id, const Force2 &force)
Applies a force to the center of mass of the given body.
Definition: WorldBody.hpp:665
ContactCounter GetContactCount(const World &world) noexcept
Gets the count of contacts in the given world.
Definition: World.hpp:613
Acceleration GetAcceleration(const Body &body) noexcept
Gets the given body's acceleration.
Definition: Body.hpp:1155
JointID CreateJoint(AabbTreeWorld &world, Joint def)
Creates a joint to constrain one or more bodies.
Definition: AabbTreeWorld.cpp:1132
bool IsAccelerable(const Body &body) noexcept
Is "accelerable".
Definition: Body.hpp:797
Length2 GetLocalAnchorA(const GearJointConf &conf)
Gets the local anchor A property of the given joint.
Definition: GearJointConf.cpp:502
Area TestOverlap(const DistanceProxy &proxyA, const Transformation &xfA, const DistanceProxy &proxyB, const Transformation &xfB, DistanceConf conf=DistanceConf{})
Determine if two generic shapes overlap.
Definition: Distance.cpp:205
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
AABB GetAABB(const playrho::detail::RayCastInput< 2 > &input) noexcept
Gets the AABB for the given ray cast input data. <2>
Definition: AABB.cpp:107
void SetVelocity(Body &body, const Velocity &value) noexcept
Sets the body's velocity (linear and angular velocity).
Definition: Body.hpp:1302
playrho::detail::RayCastInput< 2 > RayCastInput
Ray cast input data for 2-dimensions.
Definition: RayCastInput.hpp:36
JointCounter GetJointRange(const AabbTreeWorld &world) noexcept
Gets the extent of the currently valid joint range.
Definition: AabbTreeWorld.cpp:1009
void SetAccelerations(World &world, F fn)
Sets the accelerations of all the world's bodies.
Definition: World.hpp:411
void SetJoint(AabbTreeWorld &world, JointID id, Joint def)
Sets the identified joint.
Definition: AabbTreeWorld.cpp:1108
void SetSensor(Shape &shape, bool value)
Sets whether or not the given shape is a sensor.
Definition: Shape.hpp:386
PointStates GetPointStates(const Manifold &manifold1, const Manifold &manifold2) noexcept
Computes the before and after like point states given two manifolds.
Definition: PointStates.cpp:27
ShapeID GetShapeB(const World &world, ContactID id)
Gets shape B of the identified contact.
Definition: WorldContact.cpp:74
BodyCounter GetBodyRange(const AabbTreeWorld &world) noexcept
Gets the extent of the currently valid body range.
Definition: AabbTreeWorld.cpp:1004
TimestepIters GetToiCount(const World &world, ContactID id)
Gets the Time Of Impact (TOI) count.
Definition: WorldContact.cpp:89
LinearAcceleration2 GetLinearAcceleration(const Body &body) noexcept
Gets this body's linear acceleration.
Definition: Body.hpp:1175
StepStats Step(AabbTreeWorld &world, const StepConf &conf)
Steps the world simulation according to the given configuration.
Definition: AabbTreeWorld.cpp:2144
void SetFilterData(World &world, ShapeID id, const Filter &filter)
Convenience function for setting the contact filtering data.
Definition: WorldShape.cpp:103
void SetPreSolveContactListener(AabbTreeWorld &world, ContactManifoldFunction listener) noexcept
Register a pre-solve contact event listener.
Definition: AabbTreeWorld.hpp:1136
bool IsLocked(const AabbTreeWorld &world) noexcept
Is the world locked (in the middle of a time step).
Definition: AabbTreeWorld.hpp:1071
bool GetSubStepping(const AabbTreeWorld &world) noexcept
Gets whether or not sub-stepping is enabled.
Definition: AabbTreeWorld.hpp:1081
void SetBeginContactListener(AabbTreeWorld &world, ContactFunction listener) noexcept
Register a begin contact event listener.
Definition: AabbTreeWorld.hpp:1126
const std::vector< ShapeID > & GetShapes(const AabbTreeWorld &world, BodyID id)
Disassociates all of the associated shape from the validly identified body.
Definition: AabbTreeWorld.cpp:2913
ChildCounter GetChildCount(const ChainShapeConf &arg) noexcept
Gets the child count for a given chain shape configuration.
Definition: ChainShapeConf.hpp:203
std::optional< pmr::StatsResource::Stats > GetResourceStats(const AabbTreeWorld &world) noexcept
Gets the resource statistics of the specified world.
Definition: AabbTreeWorld.hpp:1034
Real velocity
Velocity quantity type.
Definition: Units.hpp:157
constexpr AABB< N > & Include(AABB< N > &var, const Vector< Length, N > &value) noexcept
Includes the given location into the given AABB.
Definition: AABB.hpp:258
Definition: ArrayList.hpp:43
@ Null
Point does not exist.
std::remove_const_t< decltype(MaxChildCount)> ChildCounter
Child counter type.
Definition: Settings.hpp:57
float Real
Real-number type.
Definition: Real.hpp:69
std::remove_const_t< decltype(MaxBodies)> BodyCounter
Count type for bodies.
Definition: Settings.hpp:187
Vector2< Force > Force2
2-element vector of Force quantities.
Definition: Vector2.hpp:66
Vector2< LinearVelocity > LinearVelocity2
2-element vector of linear velocity (LinearVelocity) quantities.
Definition: Vector2.hpp:58
auto end(ReversionWrapper< T > w)
End function for getting a reversed order iterator.
Definition: Templates.hpp:118
constexpr auto InvalidBodyID
Invalid body ID value.
Definition: BodyID.hpp:50
constexpr auto Pi
Pi.
Definition: RealConstants.hpp:57
std::remove_const_t< decltype(MaxJoints)> JointCounter
Counter type for joints.
Definition: Settings.hpp:215
auto begin(ReversionWrapper< T > w)
Begin function for getting a reversed order iterator.
Definition: Templates.hpp:111
Vector2< LinearAcceleration > LinearAcceleration2
2-element vector of linear acceleration (LinearAcceleration) quantities.
Definition: Vector2.hpp:62
WiderType< BodyCounter > ContactCounter
Count type for contacts.
Definition: Settings.hpp:193
@ Terminate
End the ray-cast search for fixtures.
@ ResetRay
Reset the ray end back to the second point.
@ ClipRay
Clip the ray end to the current point.
@ IgnoreFixture
Ignore the current fixture.
detail::IndexingNamedType< ShapeCounter, struct ShapeIdentifier > ShapeID
Shape identifier.
Definition: ShapeID.hpp:44
constexpr auto InvalidJointID
Invalid joint ID value.
Definition: JointID.hpp:50
constexpr auto MaxJoints
Maximum number of joints in a world.
Definition: Settings.hpp:209
constexpr auto MaxBodies
Maximum number of bodies in a world.
Definition: Settings.hpp:181
constexpr auto DefaultMaxVertexRadius
Default maximum vertex radius.
Definition: Settings.hpp:110
constexpr auto InvalidShapeID
Invalid shape ID value.
Definition: ShapeID.hpp:50
Vector2< Length > Length2
2-element vector of Length quantities.
Definition: Vector2.hpp:51
constexpr bool empty(IndexPair3 pairs) noexcept
Checks whether the given collection of index pairs is empty.
Definition: IndexPair.hpp:75
detail::IndexingNamedType< BodyCounter, struct BodyIdentifier > BodyID
Body identifier.
Definition: BodyID.hpp:44
constexpr auto StripUnit(const T &value) -> std::enable_if_t< IsArithmeticV< T > &&!detail::is_detected_v< detail::get_member_type, T >, T >
Strips the units off of the given value.
Definition: Units.hpp:926
constexpr auto to_underlying(T value) noexcept -> underlying_type_t< T >
Definition: to_underlying.hpp:42
detail::IndexingNamedType< ContactCounter, struct ContactIdentifier > ContactID
Contact identifier.
Definition: ContactID.hpp:45
d2::UnitVec abs(const d2::UnitVec &v) noexcept
Gets the absolute value of the given value.
Definition: UnitVec.hpp:519
Vector2< Real > Vec2
Vector with 2 Real elements.
Definition: Vector2.hpp:47
std::uint32_t counter_type
Counter type.
Definition: StepStats.hpp:43
Step configuration.
Definition: StepConf.hpp:49
Configuration for a body.
Definition: BodyConf.hpp:55
constexpr BodyConf & Use(BodyType t) noexcept
Use the given type.
Definition: BodyConf.hpp:264
AngularVelocity angular
Angular velocity.
Definition: Velocity.hpp:47
LinearVelocity2 linear
Linear velocity.
Definition: Velocity.hpp:46
Interface between type class template instantiated for and the WorldConcept class.
Definition: WorldModel.hpp:37
Definition of to_underlying function template.