#include "UnitTests.hpp"
#include <PlayRho/Dynamics/World.hpp>
#include <PlayRho/Dynamics/WorldBody.hpp>
#include <PlayRho/Dynamics/WorldMisc.hpp>
#include <PlayRho/Dynamics/WorldJoint.hpp>
#include <PlayRho/Dynamics/WorldContact.hpp>
#include <PlayRho/Dynamics/WorldFixture.hpp>
#include <PlayRho/Dynamics/StepConf.hpp>
#include <PlayRho/Dynamics/BodyConf.hpp>
#include <PlayRho/Dynamics/Contacts/Contact.hpp>
#include <PlayRho/Dynamics/ContactImpulsesList.hpp>
#include <PlayRho/Collision/Shapes/DiskShapeConf.hpp>
#include <PlayRho/Collision/Shapes/PolygonShapeConf.hpp>
#include <PlayRho/Collision/Shapes/EdgeShapeConf.hpp>
#include <PlayRho/Collision/Collision.hpp>
#include <PlayRho/Collision/DynamicTree.hpp>
#include <PlayRho/Collision/RayCastInput.hpp>
#include <PlayRho/Collision/RayCastOutput.hpp>
#include <PlayRho/Collision/Manifold.hpp>
#include <PlayRho/Common/LengthError.hpp>
#include <PlayRho/Common/WrongState.hpp>
#include <PlayRho/Dynamics/Joints/Joint.hpp>
#include <PlayRho/Dynamics/Joints/TargetJointConf.hpp>
#include <PlayRho/Dynamics/Joints/RopeJointConf.hpp>
#include <PlayRho/Dynamics/Joints/RevoluteJointConf.hpp>
#include <PlayRho/Dynamics/Joints/PrismaticJointConf.hpp>
#include <PlayRho/Dynamics/Joints/DistanceJointConf.hpp>
#include <PlayRho/Dynamics/Joints/PulleyJointConf.hpp>
#include <PlayRho/Dynamics/Joints/WeldJointConf.hpp>
#include <PlayRho/Dynamics/Joints/FrictionJointConf.hpp>
#include <PlayRho/Dynamics/Joints/MotorJointConf.hpp>
#include <PlayRho/Dynamics/Joints/WheelJointConf.hpp>
#include <PlayRho/Dynamics/Joints/GearJointConf.hpp>
#include <chrono>
#include <type_traits>
template <typename T>
struct PushBackListener
{
std::vector<T> ids;
void operator()(T id)
{
ids.push_back(id);
}
};
{
EXPECT_EQ(
sizeof(
World),
sizeof(
void*));
}
{
EXPECT_EQ(defaultConf.maxVertexRadius, worldConf.maxVertexRadius);
EXPECT_EQ(defaultConf.minVertexRadius, worldConf.minVertexRadius);
const auto time_inc = (v - n) * 1_s;
ASSERT_GT(time_inc, 0_s);
ASSERT_LT(time_inc, 1_s);
const auto max_inc = time_inc * stepConf.maxTranslation;
EXPECT_GT(max_inc, 0_m * 1_s);
}
{
EXPECT_TRUE(std::is_default_constructible<World>::value);
EXPECT_FALSE(std::is_nothrow_default_constructible<World>::value);
EXPECT_FALSE(std::is_trivially_default_constructible<World>::value);
EXPECT_TRUE(std::is_constructible<World>::value);
EXPECT_FALSE(std::is_nothrow_constructible<World>::value);
EXPECT_FALSE(std::is_trivially_constructible<World>::value);
EXPECT_TRUE(std::is_copy_constructible<World>::value);
EXPECT_FALSE(std::is_nothrow_copy_constructible<World>::value);
EXPECT_FALSE(std::is_trivially_copy_constructible<World>::value);
EXPECT_TRUE(std::is_copy_assignable<World>::value);
EXPECT_FALSE(std::is_nothrow_copy_assignable<World>::value);
EXPECT_FALSE(std::is_trivially_copy_assignable<World>::value);
EXPECT_TRUE(std::is_destructible<World>::value);
EXPECT_TRUE(std::is_nothrow_destructible<World>::value);
EXPECT_FALSE(std::is_trivially_destructible<World>::value);
}
TEST(
World, WorldLockedError)
{
EXPECT_STREQ(value.what(), "world is locked");
}
{
{
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_EQ(w.GetBodies().begin(), w.GetBodies().end());
}
}
{
{
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);
}
}
TEST(
World, InvalidArgumentInit)
{
ASSERT_GT(max, min);
}
{
auto jointListener = PushBackListener<JointID>{};
auto fixtureListener = PushBackListener<FixtureID>{};
ASSERT_EQ(world.
GetBodies().size(), std::size_t(0));
ASSERT_EQ(world.
GetJoints().size(), std::size_t(0));
ASSERT_EQ(world.
GetFixtures(b0).size(), std::size_t(1));;
ASSERT_EQ(world.
GetFixtures(b1).size(), std::size_t(1));;
ASSERT_EQ(world.
GetBodies().size(), std::size_t(2));
ASSERT_EQ(world.
GetJoints().size(), std::size_t(1));
EXPECT_NO_THROW(world.
Clear());
EXPECT_EQ(world.
GetBodies().size(), std::size_t(0));
EXPECT_EQ(world.
GetJoints().size(), std::size_t(0));
ASSERT_EQ(fixtureListener.ids.size(), std::size_t(2));
EXPECT_EQ(fixtureListener.ids.at(0), f0);
EXPECT_EQ(fixtureListener.ids.at(1), f1);
ASSERT_EQ(jointListener.ids.size(), std::size_t(1));
EXPECT_EQ(jointListener.ids.at(0), j0);
EXPECT_LE(b2, b1);
EXPECT_LE(f2, f1);
}
TEST(
World, SetSubSteppingFreeFunction)
{
EXPECT_NO_THROW(
Step(world, stepConf));
}
{
{
}
}
TEST(
World, CopyConstruction)
{
{
const auto copy =
World{world};
EXPECT_EQ(world.
GetJoints().size(), copy.GetJoints().size());
EXPECT_EQ(world.
GetBodies().size(), copy.GetBodies().size());
EXPECT_EQ(world.
GetContacts().size(), copy.GetContacts().size());
}
{
const auto copy =
World{world};
EXPECT_EQ(world.
GetJoints().size(), copy.GetJoints().size());
const auto minJoints = std::min(world.
GetJoints().size(), copy.GetJoints().size());
auto worldJointIter = world.
GetJoints().begin();
auto copyJointIter = copy.GetJoints().begin();
for (auto i = decltype(minJoints){0}; i < minJoints; ++i)
{
EXPECT_EQ(
GetType(world, *worldJointIter),
GetType(copy, *copyJointIter));
++worldJointIter;
++copyJointIter;
}
EXPECT_EQ(world.
GetBodies().size(), copy.GetBodies().size());
EXPECT_EQ(world.
GetContacts().size(), copy.GetContacts().size());
}
}
TEST(
World, CopyAssignment)
{
{
copy = world;
EXPECT_EQ(world.
GetJoints().size(), copy.GetJoints().size());
EXPECT_EQ(world.
GetBodies().size(), copy.GetBodies().size());
EXPECT_EQ(world.
GetContacts().size(), copy.GetContacts().size());
}
{
copy = world;
EXPECT_EQ(world.
GetJoints().size(), copy.GetJoints().size());
const auto minJoints = std::min(world.
GetJoints().size(), copy.GetJoints().size());
auto worldJointIter = world.
GetJoints().begin();
auto copyJointIter = copy.GetJoints().begin();
for (auto i = decltype(minJoints){0}; i < minJoints; ++i)
{
EXPECT_EQ(
GetType(world, *worldJointIter),
GetType(copy, *copyJointIter));
++worldJointIter;
++copyJointIter;
}
EXPECT_EQ(world.
GetBodies().size(), copy.GetBodies().size());
EXPECT_EQ(world.
GetContacts().size(), copy.GetContacts().size());
}
}
TEST(
World, CreateDestroyEmptyDynamicBody)
{
EXPECT_FALSE(bodies1.empty());
EXPECT_NE(bodies1.begin(), bodies1.end());
const auto first = bodies1.begin();
EXPECT_EQ(body, *first);
EXPECT_TRUE(bodies2.empty());
}
TEST(
World, CreateDestroyDynamicBodyAndFixture)
{
EXPECT_FALSE(bodies1.empty());
EXPECT_NE(bodies1.begin(), bodies1.end());
const auto first = bodies1.begin();
EXPECT_EQ(body, *first);
EXPECT_TRUE(bodies2.empty());
}
TEST(
World, CreateDestroyJoinedBodies)
{
auto jointListener = PushBackListener<JointID>{};
auto fixtureListener = PushBackListener<FixtureID>{};
EXPECT_FALSE(bodies1.empty());
EXPECT_NE(bodies1.begin(), bodies1.end());
EXPECT_EQ(body, *bodies1.begin());
const auto contact0 = std::get<ContactID>(*world.
GetContacts().begin());
const auto contactBodyA =
GetBodyA(world, contact0);
const auto contactBodyB =
GetBodyB(world, contact0);
EXPECT_EQ(contactBodyA, body);
EXPECT_EQ(contactBodyB, body2);
EXPECT_FALSE(bodies0.empty());
EXPECT_NE(bodies0.begin(), bodies0.end());
ASSERT_EQ(fixtureListener.ids.size(), std::size_t(2));
EXPECT_EQ(fixtureListener.ids.at(0), f0);
EXPECT_EQ(fixtureListener.ids.at(1), f1);
ASSERT_EQ(jointListener.ids.size(), std::size_t(1));
EXPECT_EQ(jointListener.ids.at(0), joint);
}
TEST(
World, CreateDestroyContactingBodies)
{
ASSERT_TRUE(contacts.empty());
const auto stats0 = world.
Step(stepConf);
EXPECT_EQ(stats0.pre.proxiesMoved, static_cast<decltype(stats0.pre.proxiesMoved)>(0));
EXPECT_EQ(stats0.pre.destroyed, static_cast<decltype(stats0.pre.destroyed)>(0));
EXPECT_EQ(stats0.pre.added, static_cast<decltype(stats0.pre.added)>(1));
EXPECT_EQ(stats0.pre.ignored, static_cast<decltype(stats0.pre.ignored)>(0));
EXPECT_EQ(stats0.pre.updated, static_cast<decltype(stats0.pre.updated)>(1));
EXPECT_EQ(stats0.pre.skipped, static_cast<decltype(stats0.pre.skipped)>(0));
EXPECT_EQ(stats0.reg.minSeparation, -2.0_m);
EXPECT_EQ(stats0.reg.maxIncImpulse, 0.0_Ns);
EXPECT_EQ(stats0.reg.islandsFound, static_cast<decltype(stats0.reg.islandsFound)>(1));
EXPECT_EQ(stats0.reg.islandsSolved, static_cast<decltype(stats0.reg.islandsSolved)>(0));
EXPECT_EQ(stats0.reg.contactsAdded, static_cast<decltype(stats0.reg.contactsAdded)>(0));
EXPECT_EQ(stats0.reg.bodiesSlept, static_cast<decltype(stats0.reg.bodiesSlept)>(0));
EXPECT_EQ(stats0.reg.proxiesMoved, static_cast<decltype(stats0.reg.proxiesMoved)>(0));
EXPECT_EQ(stats0.reg.sumPosIters, static_cast<decltype(stats0.reg.sumPosIters)>(3));
EXPECT_EQ(stats0.reg.sumVelIters, static_cast<decltype(stats0.reg.sumVelIters)>(1));
EXPECT_EQ(stats0.toi.minSeparation, std::numeric_limits<Length>::infinity());
EXPECT_EQ(stats0.toi.maxIncImpulse, 0.0_Ns);
EXPECT_EQ(stats0.toi.islandsFound, static_cast<decltype(stats0.toi.islandsFound)>(0));
EXPECT_EQ(stats0.toi.islandsSolved, static_cast<decltype(stats0.toi.islandsSolved)>(0));
EXPECT_EQ(stats0.toi.contactsFound, static_cast<decltype(stats0.toi.contactsFound)>(0));
EXPECT_EQ(stats0.toi.contactsAtMaxSubSteps, static_cast<decltype(stats0.toi.contactsAtMaxSubSteps)>(0));
EXPECT_EQ(stats0.toi.contactsUpdatedToi, static_cast<decltype(stats0.toi.contactsUpdatedToi)>(0));
EXPECT_EQ(stats0.toi.contactsUpdatedTouching, static_cast<decltype(stats0.toi.contactsUpdatedTouching)>(0));
EXPECT_EQ(stats0.toi.contactsSkippedTouching, static_cast<decltype(stats0.toi.contactsSkippedTouching)>(0));
EXPECT_EQ(stats0.toi.contactsAdded, static_cast<decltype(stats0.toi.contactsAdded)>(0));
EXPECT_EQ(stats0.toi.proxiesMoved, static_cast<decltype(stats0.toi.proxiesMoved)>(0));
EXPECT_EQ(stats0.toi.sumPosIters, static_cast<decltype(stats0.toi.sumPosIters)>(0));
EXPECT_EQ(stats0.toi.sumVelIters, static_cast<decltype(stats0.toi.sumVelIters)>(0));
EXPECT_EQ(stats0.toi.maxSimulContacts, static_cast<decltype(stats0.toi.maxSimulContacts)>(0));
EXPECT_EQ(stats0.toi.maxDistIters, static_cast<decltype(stats0.toi.maxDistIters)>(0));
EXPECT_EQ(stats0.toi.maxToiIters, static_cast<decltype(stats0.toi.maxToiIters)>(0));
EXPECT_EQ(stats0.toi.maxRootIters, static_cast<decltype(stats0.toi.maxRootIters)>(0));
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(
GetFixtureA(world, contacts.begin()->second),
EXPECT_EQ(
GetFixtureB(world, contacts.begin()->second),
}
EXPECT_TRUE(contacts.empty());
EXPECT_TRUE(contacts.empty());
}
TEST(
World, SetUnsetSetImpenetrable)
{
}
{
SetType(world, body, BodyType::Static);
}
{
auto value = 1_Hz;
value = 2_Hz;
value = 23_Hz;
}
{
auto value = 1_Hz;
value = 2_Hz;
value = 23_Hz;
}
TEST(
World, SynchronizeProxies)
{
}
TEST(
World, SetTypeOfBody)
{
EXPECT_THROW(
SetType(other, body, BodyType::Static), std::out_of_range);
SetType(world, body, BodyType::Static);
EXPECT_EQ(
GetType(world, body), BodyType::Static);
}
{
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
{
auto foundOurs = 0;
auto foundOthers = 0;
if (f == fixture && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
return true;
});
EXPECT_EQ(foundOurs, 1);
EXPECT_EQ(foundOthers, 0);
}
}
{
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 shape =
Shape{conf};
{
const auto p2 =
Length2{-2_m, 0_m};
const auto p3 =
Length2{+2_m, 0_m};
auto foundOurs = 0;
auto foundOthers = 0;
if (f == fixture && 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 (f == fixture && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
return RayCastOpcode::Terminate;
});
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 (f == fixture && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
return RayCastOpcode::IgnoreFixture;
});
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 (f == fixture && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
return RayCastOpcode::IgnoreFixture;
});
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 (f == fixture && i == 0)
{
++foundOurs;
}
else
{
++foundOthers;
}
return RayCastOpcode::ClipRay;
});
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 (f == fixture && 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;
return RayCastOpcode::Terminate;
});
EXPECT_FALSE(retval);
EXPECT_EQ(found, 0);
}
}
TEST(
World, ClearForcesFreeFunction)
{
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto shape =
Shape{conf};
}
TEST(
World, SetAccelerationsFunctionalFF)
{
};
const auto a2 = a1 * 2;
ASSERT_EQ(a1.linear * 2, a2.linear);
ASSERT_EQ(a1.angular * 2, a2.angular);
});
}
TEST(
World, SetLinearAccelerationsFF)
{
};
const auto a2 = a1 * 2;
ASSERT_EQ(a1.linear * 2, a2.linear);
ASSERT_EQ(a1.angular * 2, a2.angular);
}
TEST(
World, FindClosestBodyFF)
{
}
TEST(
World, GetShapeCountFreeFunction)
{
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto shape1 =
Shape{shapeConf};
const auto shape2 =
Shape{shapeConf};
}
TEST(
World, GetFixtureCountFreeFunction)
{
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
const auto shape =
Shape{shapeConf};
}
TEST(
World, AwakenFreeFunction)
{
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
ASSERT_FALSE(
IsAwake(world, body));
}
TEST(
World, GetTouchingCountFreeFunction)
{
stepConf.deltaTime =
Real(1) / 100_Hz;
const auto lowerBody = world.
CreateBody(lowerBodyConf);
{
}
}
TEST(
World, ShiftOriginFreeFunction)
{
const auto origin =
Length2{0_m, 0_m};
const auto location =
Length2{1_m, 1_m};
ASSERT_NE(origin, location);
}
TEST(
World, DynamicEdgeBodyHasCorrectMass)
{
const auto v1 =
Length2{-1_m, 0_m};
const auto v2 =
Length2{+1_m, 0_m};
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)
{
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 = *world.
GetJoints().begin();
EXPECT_EQ(joint, first);
EXPECT_EQ(
GetType(world, joint), GetTypeID<DistanceJointConf>());
EXPECT_EQ(
GetBodyA(world, joint), body1);
EXPECT_EQ(
GetBodyB(world, joint), body2);
}
{
{
}
{
}
}
{
{
}
{
}
}
TEST(
World, StepZeroTimeDoesNothing)
{
def.
type = BodyType::Dynamic;
ASSERT_NE(body, InvalidBodyID);
const auto time_inc = 0_s;
for (auto i = 0; i < 100; ++i)
{
}
}
TEST(
World, GravitationalBodyMovement)
{
const auto a =
Real(-10);
body_def.
type = BodyType::Dynamic;
body_def.location = p0;
const auto t = .01_s;
ASSERT_NE(body, InvalidBodyID);
EXPECT_EQ(
GetType(world, body), BodyType::Dynamic);
double(
Real{a * (t *
Real{3}) / 1_s}), 0.00001);
}
{
EXPECT_THROW(massData =
ComputeMassData(world, InvalidBodyID), std::out_of_range);
EXPECT_EQ(massData.center,
Length2{});
EXPECT_EQ(massData.mass, 0_kg);
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)
{
.UseAngularVelocity(10_rad /
Second));
ASSERT_EQ(
GetAngle(world, body), 0_rad);
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)
{
def.
type = BodyType::Dynamic;
ASSERT_NE(body, InvalidBodyID);
const auto time_inc = 0.01_s;
for (auto i = 0; i < 100; ++i)
{
Step(world, time_inc, 0, 0);
Real{(
GetY(vel) +
GetY(EarthlyGravity) * time_inc) / 1_mps}));
}
}
TEST(
World, BodyAccelRevPerSpecWithNegativeTimeAndNoVelOrPosIterations)
{
def.
type = BodyType::Dynamic;
ASSERT_NE(body, InvalidBodyID);
const auto time_inc = -0.01_s;
stepConf.dtRatio = -1;
stepConf.regPositionIterations = 0;
stepConf.regVelocityIterations = 0;
stepConf.toiPositionIterations = 0;
stepConf.toiVelocityIterations = 0;
for (auto i = 0; i < 99; ++i)
{
Real{(
GetY(vel) +
GetY(EarthlyGravity) * time_inc) / 1_mps}));
}
}
struct MyContactListener
{
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 =
GetBody(world, fA);
const auto bB =
GetBody(world, fB);
const auto typeA =
GetType(world, bA);
if (typeA != BodyType::Kinematic)
{
EXPECT_NO_THROW(
SetType(world, bA, typeA));
}
}
{
++end_contacts;
contacting = false;
if (ender)
{
ender(contact);
}
}
{
++pre_solves;
presolver(id, oldManifold);
}
{
++post_solves;
postsolver(id, impulses, solved);
}
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;
};
TEST(
World, NoCorrectionsWithNoVelOrPosIterations)
{
auto presolved = unsigned{0};
auto postsolved = unsigned{0};
MyContactListener listener{
world,
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
unsigned count){
listener.PostSolve(id, impulses, count);
});
ASSERT_EQ(listener.begin_contacts, unsigned(0));
ASSERT_EQ(listener.end_contacts, unsigned(0));
body_def.
type = BodyType::Dynamic;
body_def.bullet = true;
ASSERT_NE(body_a, InvalidBodyID);
EXPECT_EQ(
GetType(world, body_a), BodyType::Dynamic);
ASSERT_NE(fixture1, InvalidFixtureID);
ASSERT_NE(body_b, InvalidBodyID);
ASSERT_NE(fixture2, InvalidFixtureID);
EXPECT_EQ(
GetType(world, body_b), BodyType::Dynamic);
const auto time_inc = .01_s;
conf.regPositionIterations = 0;
conf.regVelocityIterations = 0;
conf.toiPositionIterations = 0;
conf.toiVelocityIterations = 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 baseStepConf = []() {
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);
{
const auto lowerBody = world.
CreateBody(lowerBodyConf);
const auto upperBody = world.
CreateBody(upperBodyConf);
auto numSteps = 0ul;
{
world.
Step(largerStepConf);
upperBodysLowestPoint = std::min(upperBodysLowestPoint,
GetY(
GetLocation(world, upperBody)));
++numSteps;
}
{
case 4: EXPECT_EQ(numSteps, 175ul ); break;
case 8: EXPECT_EQ(numSteps, 176ul); break;
case 16: EXPECT_EQ(numSteps, 175ul); break;
}
EXPECT_NEAR(
static_cast<double>(
Real(upperBodysLowestPoint /
Meter)), 5.9475154876708984, 0.001);
}
{
const auto upperBody = world.
CreateBody(upperBodyConf);
const auto lowerBody = world.
CreateBody(lowerBodyConf);
auto numSteps = 0ul;
{
world.
Step(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);
}
{
const auto lowerBody = world.
CreateBody(lowerBodyConf);
const auto upperBody = world.
CreateBody(upperBodyConf);
auto numSteps = 0ul;
{
world.
Step(smallerStepConf);
upperBodysLowestPoint = std::min(upperBodysLowestPoint,
GetY(
GetLocation(world, upperBody)));
++numSteps;
}
{
case 4: EXPECT_EQ(numSteps, 766ul ); break;
case 8: EXPECT_EQ(numSteps, 767ul ); break;
case 16: EXPECT_EQ(numSteps, 766ul); break;
}
EXPECT_NEAR(
static_cast<double>(
Real(upperBodysLowestPoint /
Meter)), 5.9473052024841309, 0.001);
}
{
const auto upperBody = world.
CreateBody(upperBodyConf);
const auto lowerBody = world.
CreateBody(lowerBodyConf);
auto numSteps = 0ul;
{
world.
Step(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);
}
{
const auto upperBody = world.
CreateBody(upperBodyConf);
const auto lowerBody = world.
CreateBody(lowerBodyConf);
world.
Step(smallerStepConf);
}
}
TEST(
World, PerfectlyOverlappedSameCirclesStayPut)
{
const auto radius = 1_m;
body_def.
type = BodyType::Dynamic;
body_def.bullet = false;
body_def.location =
Length2{0_m, 0_m};
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body1), body_def.location);
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body2), body_def.location);
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.location);
EXPECT_EQ(
GetLocation(world, body2), body_def.location);
}
}
TEST(
World, PerfectlyOverlappedConcentricCirclesStayPut)
{
const auto radius1 = 1_m;
const auto radius2 = 0.6_m;
body_def.
type = BodyType::Dynamic;
body_def.bullet = false;
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body1), body_def.location);
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body2), body_def.location);
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.location);
EXPECT_EQ(
GetLocation(world, body2), body_def.location);
}
}
TEST(
World, ListenerCalledForCircleBodyWithinCircleBody)
{
MyContactListener listener{
world,
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
unsigned count){
listener.PostSolve(id, impulses, count);
});
body_def.
type = BodyType::Dynamic;
for (auto i = 0; i < 2; ++i)
{
ASSERT_NE(body, InvalidBodyID);
}
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)
{
MyContactListener listener{
world,
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
unsigned count){
listener.PostSolve(id, impulses, count);
});
body_def.
type = BodyType::Dynamic;
const auto shape =
Shape{conf};
for (auto i = 0; i < 2; ++i)
{
ASSERT_NE(body, InvalidBodyID);
}
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, PartiallyOverlappedSameCirclesSeparate)
{
const auto radius =
Real(1);
body_def.
type = BodyType::Dynamic;
body_def.bullet = false;
body_def.location = body1pos;
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body1), body_def.location);
body_def.location = body2pos;
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body2), body_def.location);
auto position_diff = body2pos - body1pos;
const auto angle =
GetAngle(position_diff);
ASSERT_EQ(angle, 0_deg);
const auto time_inc = .01_s;
for (auto i = 0; i < 100; ++i)
{
{
break;
}
ASSERT_GE(new_distance, distance);
if (new_distance == distance)
{
break;
}
else
{
{
}
{
}
}
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)
{
body_def.
type = BodyType::Dynamic;
body_def.bullet = false;
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body1), body_def.location);
{
ASSERT_NE(fixture, InvalidFixtureID);
}
ASSERT_EQ(
GetLocation(world, body2), body_def.location);
const auto time_inc = .01_s;
stepConf.maxLinearCorrection =
Real{0.0001f * 40} *
Meter;
for (auto i = 0; i < 100; ++i)
{
}
}
TEST(
World, PartiallyOverlappedSquaresSeparateProperly)
{
body_def.
type = BodyType::Dynamic;
body_def.bullet = false;
const auto half_dim =
Real(64);
body_def.location = body1pos;
{
ASSERT_NE(fixture, InvalidFixtureID);
}
body_def.location = body2pos;
{
ASSERT_NE(fixture, InvalidFixtureID);
}
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);
ASSERT_EQ(world.
GetBodies().size(), World::Bodies::size_type(2));
ASSERT_EQ(world.
GetContacts().size(), World::Contacts::size_type(0));
auto position_diff = body1pos - body2pos;
const auto velocity_iters = 10u;
const auto position_iters = 10u;
const auto time_inc =
Real(.01);
for (auto i = 0; i < 100; ++i)
{
Step(world, 1_s * time_inc, velocity_iters, position_iters);
for (auto&& contact: contacts)
{
++count;
const auto c = contact.second;
const auto body_a =
GetBody(world, fa);
const auto body_b =
GetBody(world, fb);
EXPECT_EQ(body_a, body1);
EXPECT_EQ(body_b, body2);
EXPECT_EQ(manifold.
GetType(), Manifold::e_faceA);
}
ASSERT_EQ(count, decltype(world.
GetContacts().size())(1));
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)
{
{
}
{
}
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;
body_def.
type = BodyType::Dynamic;
MyContactListener listener{
world,
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
unsigned count){
listener.PostSolve(id, impulses, count);
});
ASSERT_NE(body_a, InvalidBodyID);
ASSERT_EQ(
GetType(world, body_a), BodyType::Dynamic);
ASSERT_NE(fixture1, InvalidFixtureID);
ASSERT_NE(body_b, InvalidBodyID);
ASSERT_EQ(
GetType(world, body_b), BodyType::Dynamic);
ASSERT_NE(fixture2, InvalidFixtureID);
const auto time_collision =
Real(1.0099994);
const auto time_inc =
Real(.01);
auto elapsed_time =
Real(0);
for (;;) {
Step(world, 1_s * time_inc);
elapsed_time += time_inc;
if (listener.contacting) {
break;
}
}
{
}
filter.categoryBits = ~filter.categoryBits;
EXPECT_FALSE(
IsSensor(world, fixture1));
EXPECT_FALSE(
IsSensor(world, fixture1));
{
}
const auto time_contacting = elapsed_time;
EXPECT_TRUE(listener.touching);
EXPECT_NEAR(double(time_contacting), double(time_collision), 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 (;;)
{
Step(world, 1_s * time_inc);
elapsed_time += time_inc;
if (!listener.contacting && !listener.touching)
{
break;
}
}
EXPECT_FALSE(listener.touching);
EXPECT_TRUE(
AlmostEqual(elapsed_time, time_contacting + time_inc));
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)
{
constexpr
auto LinearSlop =
Meter / 1000;
constexpr
auto AngularSlop = (
Pi * 2 * 1_rad) / 180;
constexpr auto VertexRadius = LinearSlop * 2;
constexpr auto e_count = 36;
{
const auto a =
Real{0.5f};
const auto N = 200;
const auto M = 10;
for (auto j = 0; j < M; ++j)
{
for (auto i = 0; i < N; ++i)
{
conf.SetAsBox(a *
Meter, a *
Meter, position, 0_deg);
}
}
}
{
const auto a =
Real{0.5f};
conf.UseDensity(5_kgpm2);
const auto shape =
Shape(conf);
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)
{
const auto body = world->
CreateBody(
BodyConf{}.
UseType(BodyType::Dynamic).UseLocation(y).UseLinearAcceleration(EarthlyGravity));
y += deltaY;
}
x += deltaX;
}
}
auto numSteps = 0ul;
auto sumRegPosIters = 0ul;
auto sumRegVelIters = 0ul;
auto sumToiPosIters = 0ul;
auto sumToiVelIters = 0ul;
{
const auto stats = world->
Step(step);
sumRegVelIters += stats.reg.sumVelIters;
sumToiPosIters += stats.toi.sumPosIters;
sumToiVelIters += stats.toi.sumVelIters;
++numSteps;
}
#if defined(__core2__)
{
case 4:
{
#ifndef __FAST_MATH__
EXPECT_EQ(numSteps, 1800ul);
EXPECT_EQ(sumRegPosIters, 36518ul);
EXPECT_EQ(sumRegVelIters, 46965ul);
EXPECT_EQ(sumToiPosIters, 44006ul);
EXPECT_EQ(sumToiVelIters, 113850ul);
#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, 114231ul);
break;
}
case 16:
{
break;
}
default:
{
FAIL(); break;
}
}
#elif defined(__k8__)
{
case 4:
{
EXPECT_EQ(numSteps, 1793ul);
EXPECT_EQ(sumRegPosIters, 36493ul);
EXPECT_EQ(sumRegVelIters, 46884ul);
EXPECT_EQ(sumToiPosIters, 43874ul);
EXPECT_EQ(sumToiVelIters, 113472ul);
break;
}
case 8:
{
EXPECT_EQ(numSteps, 1828ul);
EXPECT_EQ(sumRegPosIters, 36540ul);
EXPECT_EQ(sumRegVelIters, 47173ul);
EXPECT_EQ(sumToiPosIters, 44005ul);
EXPECT_EQ(sumToiVelIters, 114252ul);
break;
}
}
#elif defined(_WIN64) // This is likely wrong as the results are more likely arch dependent
EXPECT_EQ(numSteps, 1794ul);
EXPECT_EQ(sumRegPosIters, 36498ul);
EXPECT_EQ(sumRegVelIters, 46900ul);
EXPECT_EQ(sumToiPosIters, 44074ul);
EXPECT_EQ(sumToiVelIters, 114404ul);
#elif defined(_WIN32)
EXPECT_EQ(numSteps, 1803ul);
EXPECT_EQ(sumRegPosIters, 36528ul);
EXPECT_EQ(sumRegVelIters, 46981ul);
EXPECT_EQ(sumToiPosIters, 43684ul);
EXPECT_EQ(sumToiVelIters, 112778ul);
#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};
MyContactListener listener{
world,
};
listener.BeginContact(id);
});
listener.EndContact(id);
});
listener.PreSolve(id, manifold);
});
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 edge_shape =
Shape(edgeConf);
body_def.
type = BodyType::Static;
const auto left_wall_body = world.
CreateBody(body_def);
ASSERT_NE(left_wall_body, InvalidBodyID);
{
const auto wall_fixture =
CreateFixture(world, left_wall_body, edge_shape);
ASSERT_NE(wall_fixture, InvalidFixtureID);
}
const auto right_wall_body = world.
CreateBody(body_def);
ASSERT_NE(right_wall_body, InvalidBodyID);
{
const auto wall_fixture =
CreateFixture(world, right_wall_body, edge_shape);
ASSERT_NE(wall_fixture, InvalidFixtureID);
}
const auto begin_x =
Real(0);
body_def.
type = BodyType::Dynamic;
const auto ball_body = world.
CreateBody(body_def);
ASSERT_NE(ball_body, InvalidBodyID);
const auto ball_radius = 0.01_m;
const auto ball_fixture =
CreateFixture(world, ball_body, circle_shape);
ASSERT_NE(ball_fixture, InvalidFixtureID);
const auto time_inc = .01_s;
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)
{
listener.begin_contacts = 0;
for (auto travel_r = unsigned{0}; ; ++travel_r)
{
if (travel_r == max_travel)
{
std::cout << "begin_contacts=" << listener.begin_contacts << std::endl;
ASSERT_LT(travel_r, max_travel);
}
const auto last_contact_count = listener.begin_contacts;
{
return;
}
if (listener.begin_contacts % 2 != 0)
{
break;
}
else if (listener.begin_contacts > last_contact_count)
{
++increments;
static_cast<Real>(increments) *
GetX(velocity),
}
else
{
Real{
static_cast<Real>(increments) *
GetX(velocity) / 1_mps}));
}
}
listener.begin_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 last_contact_count = listener.begin_contacts;
{
return;
}
if (listener.begin_contacts % 2 != 0)
{
break;
}
else if (listener.begin_contacts > last_contact_count)
{
++increments;
-
static_cast<Real>(increments) *
GetX(velocity),
}
else
{
Real{-
static_cast<Real>(increments) *
GetX(velocity) / 1_mps}));
}
}
++increments;
static_cast<Real>(increments) *
GetX(velocity),
}
}
TEST(World_Longer, TargetJointWontCauseTunnelling)
{
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;
body_def.
type = BodyType::Static;
edgeConf.
Set(
Length2{0, +half_box_height * 2_m},
Length2{0, -half_box_height * 2_m});
{
const auto left_wall_body = world.
CreateBody(body_def);
ASSERT_NE(left_wall_body, InvalidBodyID);
{
ASSERT_NE(wall_fixture, InvalidFixtureID);
}
}
{
const auto right_wall_body = world.
CreateBody(body_def);
ASSERT_NE(right_wall_body, InvalidBodyID);
{
ASSERT_NE(wall_fixture, InvalidFixtureID);
}
}
edgeConf.Set(
Length2{-half_box_width * 2_m, 0_m},
Length2{+half_box_width * 2_m, 0_m});
{
const auto btm_wall_body = world.
CreateBody(body_def);
ASSERT_NE(btm_wall_body, InvalidBodyID);
{
ASSERT_NE(wall_fixture, InvalidFixtureID);
}
}
{
const auto top_wall_body = world.
CreateBody(body_def);
ASSERT_NE(top_wall_body, InvalidBodyID);
{
ASSERT_NE(wall_fixture, InvalidFixtureID);
}
}
body_def.
type = BodyType::Dynamic;
const auto ball_body = world.
CreateBody(body_def);
ASSERT_NE(ball_body, InvalidBodyID);
const auto ball_radius =
Real(half_box_width / 4) *
Meter;
{
const auto ball_fixture =
CreateFixture(world, ball_body, object_shape);
ASSERT_NE(ball_fixture, InvalidFixtureID);
}
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);
ASSERT_NE(bodies[i], InvalidBodyID);
{
const auto fixture =
CreateFixture(world, bodies[i], object_shape);
ASSERT_NE(fixture, InvalidFixtureID);
}
}
const auto spare_body = [&](){
bodyConf.
UseType(BodyType::Static);
}();
const auto target_joint = [&]() {
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}
};
}();
ASSERT_NE(target_joint, InvalidJointID);
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,
{
const auto oldPointCount = old_manifold.GetPointCount();
switch (oldPointCount)
{
case 0:
ASSERT_EQ(pointStates.state1[0], PointState::NullState);
ASSERT_EQ(pointStates.state1[1], PointState::NullState);
break;
case 1:
ASSERT_NE(pointStates.state1[0], PointState::NullState);
ASSERT_EQ(pointStates.state1[1], PointState::NullState);
break;
case 2:
ASSERT_NE(pointStates.state1[0], PointState::NullState);
ASSERT_NE(pointStates.state1[1], PointState::NullState);
break;
default:
ASSERT_LE(oldPointCount, 2);
break;
}
const auto newPointCount = new_manifold.GetPointCount();
switch (newPointCount)
{
case 0:
ASSERT_EQ(pointStates.state2[0], PointState::NullState);
ASSERT_EQ(pointStates.state2[1], PointState::NullState);
break;
case 1:
ASSERT_NE(pointStates.state2[0], PointState::NullState);
ASSERT_EQ(pointStates.state2[1], PointState::NullState);
break;
case 2:
ASSERT_NE(pointStates.state2[0], PointState::NullState);
ASSERT_NE(pointStates.state2[1], PointState::NullState);
break;
default:
ASSERT_LE(newPointCount, 2);
break;
}
},
{
ASSERT_NE(fA, InvalidFixtureID);
ASSERT_NE(fB, InvalidFixtureID);
const auto body_a =
GetBody(world, fA);
const auto body_b =
GetBody(world, fB);
ASSERT_NE(body_a, InvalidBodyID);
ASSERT_NE(body_b, InvalidBodyID);
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 =
GetBody(world, fA);
const auto body_b =
GetBody(world, fB);
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);
});
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
static void smaller_still_conserves_momentum(
bool bullet,
Real multiplier,
Real time_inc)
{
const auto radius =
Real(1);
const auto start_distance =
Real(10);
for (;;)
{
const auto gravity =
Vec2{};
ASSERT_EQ(
GetX(world.GetGravity()), 0);
ASSERT_EQ(
GetY(world.GetGravity()), 0);
auto maxNormalImpulse =
Real(0);
auto maxTangentImpulse =
Real(0);
auto maxPoints = 0u;
auto numSteps = 0u;
auto failed = false;
MyContactListener listener{
{
const auto bA = fA->GetBody();
const auto bB = fB->GetBody();
preB1 = bA->GetLocation();
preB2 = bB->GetLocation();
},
{
{
const auto count = impulse.GetCount();
maxPoints = std::max(maxPoints, decltype(maxPoints){count});
for (auto i = decltype(count){0}; i < count; ++i)
{
maxNormalImpulse = std::max(maxNormalImpulse, impulse.GetEntryNormal(i));
maxTangentImpulse = std::max(maxTangentImpulse, impulse.GetEntryTanget(i));
}
}
if (maxNormalImpulse == 0 && maxTangentImpulse == 0)
{
failed = true;
#if 0
const auto& manifold = contact.GetManifold();
std::cout << " solved=" << unsigned(solved);
std::cout << " numstp=" << numSteps;
std::cout <<
" type=" << unsigned(manifold.
GetType());
std::cout << " points=" << unsigned(count);
for (auto i = decltype(count){0}; i < count; ++i)
{
}
std::cout << std::endl;
#endif
}
},
}
};
SetContactListener(world, &listener);
ASSERT_EQ(shape->GetRadius(), scale * radius);
fixture_def.friction = 0;
fixture_def.restitution = 1;
body_def.
type = BodyType::Dynamic;
body_def.position =
Vec2{+(scale * start_distance), 0};
ASSERT_EQ(body_1->GetLocation().x, body_def.position.x);
ASSERT_EQ(body_1->GetLocation().y, body_def.position.y);
body_1->CreateFixture(shape, fixture_def);
body_def.position =
Vec2{-(scale * start_distance), 0};
ASSERT_EQ(body_2->GetLocation().x, body_def.position.x);
ASSERT_EQ(body_2->GetLocation().y, body_def.position.y);
body_2->CreateFixture(shape, fixture_def);
for (;;)
{
if (relative_velocity.x >= 0)
{
EXPECT_NEAR(
double(relative_velocity.x),
double(
abs(body_def.
linearVelocity.x) * +2), 0.0001);
break;
}
if (failed)
{
std::cout << " scale=" << scale;
std::cout << " dist0=" << (scale * start_distance * 2);
std::cout << " bcont=" << listener.begin_contacts;
std::cout << " econt=" << listener.end_contacts;
std::cout << " pre-#=" << listener.pre_solves;
std::cout << " post#=" << listener.post_solves;
std::cout << " normi=" << maxNormalImpulse;
std::cout << " tangi=" << maxTangentImpulse;
std::cout << " n-pts=" << maxPoints;
std::cout << std::endl;
std::cout << " pre1.x=" << preB1.x;
std::cout << " pre2.x=" << preB2.x;
std::cout << " pos1.x=" << body_1->GetLocation().x;
std::cout << " pos2.x=" << body_2->GetLocation().x;
std::cout << " preDel=" << (preB1.x - preB2.x);
std::cout << " posDel=" << (body_1->GetLocation().x - body_2->GetLocation().x);
std::cout << " travel=" << (body_1->GetLocation().x - preB1.x);
std::cout << std::endl;
ASSERT_FALSE(failed);
}
++numSteps;
}
scale *= multiplier;
}
}
TEST(
World, SmallerStillConservesMomemtum)
{
smaller_still_conserves_momentum(
false,
Real(0.999),
Real(0.01));
}
TEST(
World, SmallerBulletStillConservesMomemtum)
{
}
#endif
#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();
for (auto i = decltype(numboxes){0}; i < numboxes; ++i)
{
boxes[i] = box;
}
while (loopsTillSleeping < maxLoops)
{
{
break;
}
++loopsTillSleeping;
}
}
protected:
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