Skip to content

Commit

Permalink
Multiple changes...
Browse files Browse the repository at this point in the history
- Sets the new PreStepStats::proxiesCreated member variable.
- Does world.UpdateContacts(conf) handling for any deltaTime; not just
  when not equal to zero. This means contacts & related manifolds are
  setup and no longer need updating for a zero time delta as well as any
  non-zero delta.
- Related unit test updates including addition of
  World.TouchingBodiesWithZeroDeltaTime test to confirm zero delta time
  behavior for world setup with two touching bodies.
  • Loading branch information
louis-langholtz committed Dec 27, 2023
1 parent df2ee5a commit 9b8e702
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 48 deletions.
28 changes: 15 additions & 13 deletions Library/source/playrho/d2/AabbTreeWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1505,8 +1505,7 @@ RegStepStats AabbTreeWorld::SolveReg(const StepConf& conf)
futures.push_back(std::async(std::launch::async, &AabbTreeWorld::SolveRegIslandViaGS,
this, conf, island));
#else
const auto solverResults = SolveRegIslandViaGS(conf, island);
::playrho::Update(stats, solverResults);
::playrho::Update(stats, SolveRegIslandViaGS(conf, island));
#endif
}
}
Expand Down Expand Up @@ -2123,16 +2122,18 @@ StepStats Step(AabbTreeWorld& world, const StepConf& conf)

// Create proxies herein for access to conf.aabbExtension info!
for (const auto& [bodyID, shapeID]: world.m_fixturesForProxies) {
CreateProxies(world.m_tree, bodyID, shapeID, world.m_shapeBuffer[to_underlying(shapeID)],
GetTransformation(world.m_bodyBuffer[to_underlying(bodyID)]),
conf.aabbExtension,
world.m_bodyProxies[to_underlying(bodyID)], world.m_proxiesForContacts);
stepStats.pre.proxiesCreated +=
CreateProxies(world.m_tree, bodyID, shapeID, world.m_shapeBuffer[to_underlying(shapeID)],
GetTransformation(world.m_bodyBuffer[to_underlying(bodyID)]),
conf.aabbExtension,
world.m_bodyProxies[to_underlying(bodyID)], world.m_proxiesForContacts);
}
world.m_fixturesForProxies = {};

stepStats.pre.proxiesMoved = [&world](const StepConf& cfg){
auto proxiesMoved = PreStepStats::counter_type{0};
for_each(begin(world.m_bodiesForSync), end(world.m_bodiesForSync), [&world,&cfg,&proxiesMoved](const auto& bodyID) {
for_each(begin(world.m_bodiesForSync), end(world.m_bodiesForSync),
[&world,&cfg,&proxiesMoved](const auto& bodyID) {
const auto xfm = GetTransformation(world.m_bodyBuffer[to_underlying(bodyID)]);
// Not always true: assert(GetTransform0(b->GetSweep()) == xfm);
proxiesMoved += world.Synchronize(world.m_bodyProxies[to_underlying(bodyID)], xfm, xfm,
Expand All @@ -2154,14 +2155,16 @@ StepStats Step(AabbTreeWorld& world, const StepConf& conf)
stepStats.pre.added = world.AddContacts(FindContacts(world.m_proxyKeysResource, world.m_tree, std::move(world.m_proxiesForContacts)));
world.m_proxiesForContacts = {};

if (conf.deltaTime != 0_s) {
world.m_inv_dt0 = (conf.deltaTime != 0_s)? Real(1) / conf.deltaTime: 0_Hz;

{
// Could potentially run UpdateContacts multithreaded over split lists...
const auto updateStats = world.UpdateContacts(conf);
stepStats.pre.ignored = updateStats.ignored;
stepStats.pre.updated = updateStats.updated;
stepStats.pre.skipped = updateStats.skipped;
}

if (conf.deltaTime != 0_s) {
world.m_inv_dt0 = Real(1) / conf.deltaTime;

// Integrate velocities, solve velocity constraints, and integrate positions.
if (IsStepComplete(world)) {
Expand Down Expand Up @@ -2395,7 +2398,7 @@ AabbTreeWorld::UpdateContactsStats AabbTreeWorld::UpdateContacts(const StepConf&

ContactCounter
AabbTreeWorld::AddContacts( // NOLINT(readability-function-cognitive-complexity)
std::vector<ProxyKey, pmr::polymorphic_allocator<ProxyKey>>&& keys)
std::vector<ProxyKey, pmr::polymorphic_allocator<ProxyKey>>&& keys)
{
const auto numContactsBefore = size(m_contacts);
for_each(cbegin(keys), cend(keys), [this](const ProxyKey& key) {
Expand Down Expand Up @@ -2491,7 +2494,6 @@ AabbTreeWorld::AddContacts( // NOLINT(readability-function-cognitive-complexity)
contactsA.emplace_back(std::get<0>(key), contactID);
contactsB.emplace_back(std::get<0>(key), contactID);

// Wake up the bodies
if (!IsSensor(contact)) {
if (IsSpeedable(bodyA)) {
bodyA.SetAwakeFlag();
Expand Down Expand Up @@ -2554,7 +2556,7 @@ ContactCounter AabbTreeWorld::Synchronize(const ProxyIDs& bodyProxies,
}

void AabbTreeWorld::Update( // NOLINT(readability-function-cognitive-complexity)
ContactID contactID, const ContactUpdateConf& conf)
ContactID contactID, const ContactUpdateConf& conf)
{
auto& c = m_contactBuffer[to_underlying(contactID)];
auto& manifold = m_manifoldBuffer[to_underlying(contactID)];
Expand Down
96 changes: 61 additions & 35 deletions UnitTests/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,30 +636,30 @@ TEST(World, CreateDestroyContactingBodies)
const auto body1 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(l1));
const auto body2 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation(l2));
EXPECT_EQ(GetBodyCount(world), BodyCounter(2));
EXPECT_EQ(GetBodiesForProxies(world).size(), static_cast<decltype(GetBodiesForProxies(world).size())>(0));
EXPECT_EQ(GetTree(world).GetNodeCount(), static_cast<decltype(GetTree(world).GetNodeCount())>(0));
EXPECT_EQ(GetBodiesForProxies(world).size(), 0u);
EXPECT_EQ(GetTree(world).GetNodeCount(), 0u);

Attach(world, body1, CreateShape(world, DiskShapeConf{1_m}.UseDensity(1_kgpm2)));
Attach(world, body2, CreateShape(world, DiskShapeConf{1_m}.UseDensity(1_kgpm2)));
ASSERT_EQ(size(GetShapes(world, body1)), 1u);
ASSERT_EQ(size(GetShapes(world, body2)), 1u);
EXPECT_EQ(GetBodiesForProxies(world).size(), static_cast<decltype(GetBodiesForProxies(world).size())>(0));
EXPECT_EQ(GetBodiesForProxies(world).size(), 0u);
EXPECT_EQ(GetAssociationCount(world), std::size_t(2));
EXPECT_EQ(GetTree(world).GetNodeCount(), static_cast<decltype(GetTree(world).GetNodeCount())>(0));
EXPECT_EQ(GetTree(world).GetNodeCount(), 0u);

const auto stepConf = StepConf{};

const auto stats0 = Step(world, stepConf);

EXPECT_EQ(GetBodiesForProxies(world).size(), static_cast<decltype(GetBodiesForProxies(world).size())>(0));
EXPECT_EQ(GetTree(world).GetNodeCount(), static_cast<decltype(GetTree(world).GetNodeCount())>(3));
EXPECT_EQ(GetBodiesForProxies(world).size(), 0u);
EXPECT_EQ(GetTree(world).GetNodeCount(), 3u);

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.pre.proxiesCreated, 2u);
EXPECT_EQ(stats0.pre.proxiesMoved, 0u);
EXPECT_EQ(stats0.pre.destroyed, 0u);
EXPECT_EQ(stats0.pre.added, 1u);
EXPECT_EQ(stats0.pre.ignored, 0u);
EXPECT_EQ(stats0.pre.updated, 1u);
EXPECT_EQ(stats0.pre.skipped, 0u);

EXPECT_EQ(stats0.reg.minSeparation, -2.0_m);
EXPECT_EQ(stats0.reg.maxIncImpulse, 0.0_Ns);
Expand Down Expand Up @@ -2706,8 +2706,8 @@ TEST(World, CollidingDynamicBodies)

TEST(World_Longer, TilesComesToRest)
{
constexpr auto LinearSlop = Meter / 1000;
constexpr auto AngularSlop = (Pi * 2 * 1_rad) / 180;
constexpr auto LinearSlop = 1_m / 1000;
constexpr auto AngularSlop = (Pi * 2_rad) / 180;
constexpr auto MinVertexRadius = LinearSlop * 2;
constexpr auto MaxVertexRadius = ::playrho::DefaultMaxVertexRadius;
const auto VertexRadius = Interval<Positive<Length>>{MinVertexRadius, MaxVertexRadius};
Expand All @@ -2718,20 +2718,19 @@ TEST(World_Longer, TilesComesToRest)

{
const auto a = Real{0.5f};
//const auto ground = CreateBody(*world, BodyConf{}.UseLocation(Length2{0, -a * Meter}));
auto ground = Body(BodyConf{}.UseLocation(Length2{0, -a * Meter}));
const auto N = 200;
const auto M = 10;
auto ground = Body(BodyConf{}.UseLocation(Length2{0_m, -a * 1_m}));
constexpr auto N = 200;
constexpr auto M = 10;
Length2 position;
GetY(position) = 0.0_m;
GetY(position) = 0_m;
for (auto j = 0; j < M; ++j) {
GetX(position) = -N * a * Meter;
GetX(position) = -N * a * 1_m;
for (auto i = 0; i < N; ++i) {
conf.SetAsBox(a * Meter, a * Meter, position, 0_deg);
conf.SetAsBox(a * 1_m, a * 1_m, position, 0_deg);
ground.Attach(CreateShape(*world, conf));
GetX(position) += 2.0f * a * Meter;
GetX(position) += 2_m * a;
}
GetY(position) -= 2.0f * a * Meter;
GetY(position) -= 2_m * a;
}
ASSERT_EQ(size(ground.GetShapes()), 2000u);
ASSERT_NO_THROW(CreateBody(*world, ground));
Expand All @@ -2741,7 +2740,7 @@ TEST(World_Longer, TilesComesToRest)
{
const auto a = Real{0.5f};
conf.UseDensity(5_kgpm2);
conf.SetAsBox(a * Meter, a * Meter);
conf.SetAsBox(a * 1_m, a * 1_m);
const auto shapeId = CreateShape(*world, Shape(conf));

Length2 x(-7.0_m, 0.75_m);
Expand Down Expand Up @@ -2775,7 +2774,7 @@ TEST(World_Longer, TilesComesToRest)
step.maxLinearCorrection = LinearSlop * Real(40);
step.maxAngularCorrection = AngularSlop * Real{4};
step.aabbExtension = LinearSlop * Real(20);
step.maxTranslation = Length{Meter * Real(4)};
step.maxTranslation = 4_m;
step.velocityThreshold = (Real{8} / Real{10}) * 1_mps;
step.maxSubSteps = std::uint8_t{48};

Expand All @@ -2790,14 +2789,14 @@ TEST(World_Longer, TilesComesToRest)
auto totalBodiesSlept = 0ul;
auto awakeCount = 0ul;
constexpr auto maxSteps = 3000ul;
while ((awakeCount = GetAwakeCount(*world)) > 0 && numSteps < maxSteps) {
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 (stats.reg.proxiesMoved == 0u && stats.toi.proxiesMoved == 0u) {
if ((stats.reg.proxiesMoved == 0u) && (stats.toi.proxiesMoved == 0u)) {
firstStepWithZeroMoved = numSteps;
}
EXPECT_GT(stats.reg.islandsFound, 0u);
Expand Down Expand Up @@ -2842,11 +2841,7 @@ TEST(World_Longer, TilesComesToRest)
EXPECT_LE(totalBodiesSlept, 670u);
EXPECT_TRUE(firstStepWithZeroMoved);
if (firstStepWithZeroMoved) {
#if defined(PLAYRHO_USE_BOOST_UNITS)
EXPECT_GE(*firstStepWithZeroMoved, 1797u);
#else
EXPECT_GE(*firstStepWithZeroMoved, 1797u);
#endif
EXPECT_LE(*firstStepWithZeroMoved, 1812u);
}
#else // unrecognized arch; just check results are within range of others
Expand Down Expand Up @@ -3062,9 +3057,6 @@ TEST(World_Longer, TilesComesToRest)
EXPECT_EQ(sumToiPosIters, 45022ul);
EXPECT_EQ(sumToiVelIters, 148560ul);
#endif

//std::cout << "Time: " << elapsed_time.count() << "s" << std::endl;
// EXPECT_LT(elapsed_time.count(), 7.0);
}

TEST(World, SpeedingBulletBallWontTunnel)
Expand Down Expand Up @@ -3796,3 +3788,37 @@ TEST(World, GetResourceStatsWhenOn)
EXPECT_EQ(stats->maxAlignment, oldstats->maxAlignment);
#endif
}

TEST(World, TouchingBodiesWithZeroDeltaTime)
{
auto world = World{};
const auto shapeId0 = CreateShape(world, Shape{DiskShapeConf{}.UseRadius(1_m)});
const auto next0 = nextafter(-1_m, 0_m);
ASSERT_GT(next0, -1_m);
const auto bodyId0 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation({-1_m, 0_m}).Use(shapeId0));
const auto bodyId1 = CreateBody(world, BodyConf{}.Use(BodyType::Dynamic).UseLocation({+1_m, 0_m}).Use(shapeId0));
ASSERT_EQ(GetContactRange(world), 0u);
auto stepConf = StepConf{};
stepConf.deltaTime = 0_s;
const auto stats = Step(world, stepConf);
EXPECT_EQ(stats.pre.proxiesCreated, 2u);
EXPECT_EQ(stats.pre.proxiesMoved, 0u);
EXPECT_EQ(stats.pre.destroyed, 0u);
EXPECT_EQ(stats.pre.added, 1u);
EXPECT_EQ(stats.pre.ignored, 0u);
EXPECT_EQ(stats.pre.updated, 1u);
EXPECT_EQ(stats.pre.skipped, 0u);
EXPECT_NE(stats.pre, PreStepStats());
EXPECT_EQ(stats.reg, RegStepStats());
EXPECT_EQ(stats.toi, ToiStepStats());
EXPECT_EQ(GetContactRange(world), 1u);
const auto manifold0 = GetManifold(world, ContactID(0));
EXPECT_EQ(manifold0.GetPointCount(), 1u);
EXPECT_EQ(manifold0.GetType(), Manifold::e_circles);
const auto contact0 = GetContact(world, ContactID(0));
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());
}

0 comments on commit 9b8e702

Please sign in to comment.