From 456a176cbeab5134e5227f012327184824c4b669 Mon Sep 17 00:00:00 2001 From: Erin Catto Date: Fri, 24 Nov 2023 20:40:48 -0800 Subject: [PATCH] - fixture purge - sorted ray cast sample --- include/box2d/box2d.h | 5 +- samples/collection/sample_ray_cast.cpp | 173 +++++++++++++++---- samples/collection/sample_vertical_stack.cpp | 7 - samples/sample.h | 14 -- src/body.c | 106 +----------- src/broad_phase.c | 4 +- src/contact.c | 4 +- src/contact.h | 2 +- src/world.c | 32 ---- test/test_world.c | 2 +- 10 files changed, 152 insertions(+), 197 deletions(-) diff --git a/include/box2d/box2d.h b/include/box2d/box2d.h index daa6effc..3401a7ee 100644 --- a/include/box2d/box2d.h +++ b/include/box2d/box2d.h @@ -137,14 +137,13 @@ BOX2D_API float b2RevoluteJoint_GetMotorTorque(b2JointId jointId, float inverseT BOX2D_API void b2RevoluteJoint_SetMaxMotorTorque(b2JointId jointId, float torque); BOX2D_API b2Vec2 b2RevoluteJoint_GetConstraintForce(b2JointId jointId); -/// Query the world for all fixtures that potentially overlap the -/// provided AABB. +/// Query the world for all shapes that potentially overlap the provided AABB. /// @param callback a user implemented callback class. /// @param aabb the query box. BOX2D_API void b2World_QueryAABB(b2WorldId worldId, b2QueryResultFcn* fcn, b2AABB aabb, b2QueryFilter filter, void* context); -/// Ray-cast the world for all fixtures in the path of the ray. Your callback +/// Ray-cast the world for all shapes in the path of the ray. Your callback /// controls whether you get the closest point, any point, or n-points. /// The ray-cast ignores shapes that contain the starting point. /// @param callback a user implemented callback class. diff --git a/samples/collection/sample_ray_cast.cpp b/samples/collection/sample_ray_cast.cpp index f262f9c5..93fa3dd8 100644 --- a/samples/collection/sample_ray_cast.cpp +++ b/samples/collection/sample_ray_cast.cpp @@ -367,6 +367,7 @@ struct RayCastContext { b2Vec2 points[3]; b2Vec2 normals[3]; + float fractions[3]; int count; }; @@ -378,8 +379,8 @@ static float RayCastClosestCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 norm ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData(shapeId); if (userData != nullptr && userData->ignore) { - // By returning -1, we instruct the calling code to ignore this fixture and - // continue the ray-cast to the next fixture. + // By returning -1, we instruct the calling code to ignore this shape and + // continue the ray-cast to the next shape. return -1.0f; } @@ -388,8 +389,8 @@ static float RayCastClosestCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 norm rayContext->count = 1; // By returning the current fraction, we instruct the calling code to clip the ray and - // continue the ray-cast to the next fixture. WARNING: do not assume that fixtures - // are reported in order. However, by clipping, we can always get the closest fixture. + // continue the ray-cast to the next shape. WARNING: do not assume that shapes + // are reported in order. However, by clipping, we can always get the closest shape. return fraction; } @@ -403,8 +404,8 @@ static float RayCastAnyCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData(shapeId); if (userData != nullptr && userData->ignore) { - // By returning -1, we instruct the calling code to ignore this fixture and - // continue the ray-cast to the next fixture. + // By returning -1, we instruct the calling code to ignore this shape and + // continue the ray-cast to the next shape. return -1.0f; } @@ -417,9 +418,9 @@ static float RayCastAnyCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, return 0.0f; } -// This ray cast collects multiple hits along the ray. Polygon 0 is filtered. -// The fixtures are not necessary reported in order, so we might not capture -// the closest fixture. +// This ray cast collects multiple hits along the ray. +// The shapes are not necessary reported in order, so we might not capture +// the closest shape. // NOTE: shape hits are not ordered, so this may return hits in any order. This means that // if you limit the number of results, you may discard the closest hit. You can see this // behavior in the sample. @@ -430,8 +431,8 @@ static float RayCastMultipleCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 nor ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData(shapeId); if (userData != nullptr && userData->ignore) { - // By returning -1, we instruct the calling code to ignore this fixture and - // continue the ray-cast to the next fixture. + // By returning -1, we instruct the calling code to ignore this shape and + // continue the ray-cast to the next shape. return -1.0f; } @@ -453,6 +454,63 @@ static float RayCastMultipleCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 nor return 1.0f; } +// This ray cast collects multiple hits along the ray and sorts them. +static float RayCastSortedCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context) +{ + RayCastContext* rayContext = (RayCastContext*)context; + + ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData(shapeId); + if (userData != nullptr && userData->ignore) + { + // By returning -1, we instruct the calling code to ignore this shape and + // continue the ray-cast to the next shape. + return -1.0f; + } + + int count = rayContext->count; + assert(count <= 3); + + int index = 3; + while (fraction < rayContext->fractions[index-1]) + { + index -= 1; + + if (index == 0) + { + break; + } + } + + if (index == 3) + { + // not closer, continue but tell the caller not to consider fractions further than the largest fraction acquired + // this only happens once the buffer is full + assert(rayContext->count == 3); + assert(rayContext->fractions[2] <= 1.0f); + return rayContext->fractions[2]; + } + + for (int j = 2; j > index; --j) + { + rayContext->points[j] = rayContext->points[j - 1]; + rayContext->normals[j] = rayContext->normals[j - 1]; + rayContext->fractions[j] = rayContext->fractions[j - 1]; + } + + rayContext->points[index] = point; + rayContext->normals[index] = normal; + rayContext->fractions[index] = fraction; + rayContext->count = count < 3 ? count + 1 : 3; + + if (rayContext->count == 3) + { + return rayContext->fractions[2]; + } + + // By returning 1, we instruct the caller to continue without clipping the ray. + return 1.0f; +} + class RayCastWorld : public Sample { public: @@ -460,7 +518,8 @@ class RayCastWorld : public Sample { e_any = 0, e_closest = 1, - e_multiple = 2 + e_multiple = 2, + e_sorted = 3 }; enum @@ -617,36 +676,50 @@ class RayCastWorld : public Sample void UpdateUI() override { ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); - ImGui::SetNextWindowSize(ImVec2(210.0f, 300.0f)); + ImGui::SetNextWindowSize(ImVec2(210.0f, 310.0f)); ImGui::Begin("Options", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); - if (ImGui::Button("Polygon 1")) Create(0); + if (ImGui::Button("Polygon 1")) + Create(0); ImGui::SameLine(); - if (ImGui::Button("10x##Poly1")) CreateN(0, 10); + if (ImGui::Button("10x##Poly1")) + CreateN(0, 10); - if (ImGui::Button("Polygon 2")) Create(1); + if (ImGui::Button("Polygon 2")) + Create(1); ImGui::SameLine(); - if (ImGui::Button("10x##Poly2")) CreateN(1, 10); + if (ImGui::Button("10x##Poly2")) + CreateN(1, 10); - if (ImGui::Button("Polygon 3")) Create(2); + if (ImGui::Button("Polygon 3")) + Create(2); ImGui::SameLine(); - if (ImGui::Button("10x##Poly3")) CreateN(2, 10); + if (ImGui::Button("10x##Poly3")) + CreateN(2, 10); - if (ImGui::Button("Box")) Create(3); + if (ImGui::Button("Box")) + Create(3); ImGui::SameLine(); - if (ImGui::Button("10x##Box")) CreateN(3, 10); + if (ImGui::Button("10x##Box")) + CreateN(3, 10); - if (ImGui::Button("Circle")) Create(4); + if (ImGui::Button("Circle")) + Create(4); ImGui::SameLine(); - if (ImGui::Button("10x##Circle")) CreateN(4, 10); + if (ImGui::Button("10x##Circle")) + CreateN(4, 10); - if (ImGui::Button("Capsule")) Create(5); + if (ImGui::Button("Capsule")) + Create(5); ImGui::SameLine(); - if (ImGui::Button("10x##Capsule")) CreateN(5, 10); + if (ImGui::Button("10x##Capsule")) + CreateN(5, 10); - if (ImGui::Button("Segment")) Create(6); + if (ImGui::Button("Segment")) + Create(6); ImGui::SameLine(); - if (ImGui::Button("10x##Segment")) CreateN(6, 10); + if (ImGui::Button("10x##Segment")) + CreateN(6, 10); if (ImGui::Button("Destroy Shape")) { @@ -656,6 +729,7 @@ class RayCastWorld : public Sample ImGui::RadioButton("Any", &m_mode, e_any); ImGui::RadioButton("Closest", &m_mode, e_closest); ImGui::RadioButton("Multiple", &m_mode, e_multiple); + ImGui::RadioButton("Sorted", &m_mode, e_sorted); ImGui::End(); } @@ -671,7 +745,7 @@ class RayCastWorld : public Sample switch (m_mode) { case e_closest: - g_draw.DrawString(5, m_textLine, "Ray-cast mode: closest - find closest fixture along the ray"); + g_draw.DrawString(5, m_textLine, "Ray-cast mode: closest - find closest shape along the ray"); break; case e_any: @@ -679,7 +753,11 @@ class RayCastWorld : public Sample break; case e_multiple: - g_draw.DrawString(5, m_textLine, "Ray-cast mode: multiple - gather multiple fixtures - unsorted"); + g_draw.DrawString(5, m_textLine, "Ray-cast mode: multiple - gather multiple shapes - unsorted"); + break; + + case e_sorted: + g_draw.DrawString(5, m_textLine, "Ray-cast mode: sorted - gather multiple shapes sorted by closeness"); break; } @@ -710,7 +788,7 @@ class RayCastWorld : public Sample else if (m_mode == e_any) { RayCastContext context = {0}; - b2World_RayCast(m_worldId, RayCastAnyCallback, m_rayStart, m_rayEnd, b2_defaultQueryFilter, & context); + b2World_RayCast(m_worldId, RayCastAnyCallback, m_rayStart, m_rayEnd, b2_defaultQueryFilter, &context); if (context.count > 0) { @@ -727,8 +805,8 @@ class RayCastWorld : public Sample else if (m_mode == e_multiple) { RayCastContext context = {0}; - b2World_RayCast(m_worldId, RayCastMultipleCallback, m_rayStart, m_rayEnd, b2_defaultQueryFilter, & context); - + b2World_RayCast(m_worldId, RayCastMultipleCallback, m_rayStart, m_rayEnd, b2_defaultQueryFilter, &context); + if (context.count > 0) { for (int i = 0; i < context.count; ++i) @@ -746,6 +824,37 @@ class RayCastWorld : public Sample g_draw.DrawSegment(m_rayStart, m_rayEnd, color2); } } + else if (m_mode == e_sorted) + { + RayCastContext context = {0}; + + // Must initialize fractions for sorting + context.fractions[0] = FLT_MAX; + context.fractions[1] = FLT_MAX; + context.fractions[2] = FLT_MAX; + + b2World_RayCast(m_worldId, RayCastSortedCallback, m_rayStart, m_rayEnd, b2_defaultQueryFilter, &context); + + if (context.count > 0) + { + assert(context.count <= 3); + b2Color colors[3] = {b2MakeColor(b2_colorRed, 1.0f), b2MakeColor(b2_colorGreen, 1.0f), + b2MakeColor(b2_colorBlue, 1.0f)}; + for (int i = 0; i < context.count; ++i) + { + b2Vec2 p = context.points[i]; + b2Vec2 n = context.normals[i]; + g_draw.DrawPoint(p, 5.0f, colors[i]); + g_draw.DrawSegment(m_rayStart, p, color2); + b2Vec2 head = b2MulAdd(p, 0.5f, n); + g_draw.DrawSegment(p, head, color3); + } + } + else + { + g_draw.DrawSegment(m_rayStart, m_rayEnd, color2); + } + } g_draw.DrawPoint(m_rayStart, 5.0f, green); diff --git a/samples/collection/sample_vertical_stack.cpp b/samples/collection/sample_vertical_stack.cpp index 04a045a0..b2e21972 100644 --- a/samples/collection/sample_vertical_stack.cpp +++ b/samples/collection/sample_vertical_stack.cpp @@ -36,13 +36,6 @@ class VerticalStack : public Sample b2Polygon box = b2MakeBox(1000.0f, 1.0f); b2ShapeDef sd = b2DefaultShapeDef(); b2Body_CreatePolygon(groundId, &sd, &box); - - //b2EdgeShape shape; - //shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); - //ground->CreateFixture(&shape, 0.0f); - - //shape.SetTwoSided(b2Vec2(20.0f, 0.0f), b2Vec2(20.0f, 20.0f)); - //ground->CreateFixture(&shape, 0.0f); } for (int32_t i = 0; i < e_maxRows * e_maxColumns; ++i) diff --git a/samples/sample.h b/samples/sample.h index efa5a789..a59d5a48 100644 --- a/samples/sample.h +++ b/samples/sample.h @@ -43,20 +43,6 @@ inline float RandomFloat(float lo, float hi) return r; } -#if 0 -// This is called when a joint in the world is implicitly destroyed -// because an attached body is destroyed. This gives us a chance to -// nullify the mouse joint. -class DestructionListener : public b2DestructionListener -{ -public: - void SayGoodbye(b2Fixture* fixture) override { B2_MAYBE_UNUSED(fixture); } - void SayGoodbye(b2Joint* joint) override; - - Test* test; -}; -#endif - constexpr int32_t k_maxContactPoints = 12 * 2048; struct ContactPoint diff --git a/src/body.c b/src/body.c index 41e43002..2f7736f0 100644 --- a/src/body.c +++ b/src/body.c @@ -940,107 +940,7 @@ bool b2ShouldBodiesCollide(b2World* world, b2Body* bodyA, b2Body* bodyB) } #if 0 -void b2Body::SetType(b2BodyType type) -{ - b2Assert(m_world->IsLocked() == false); - if (m_world->IsLocked() == true) - { - return; - } - - if (m_type == type) - { - return; - } - - m_type = type; - - ResetMassData(); - - if (m_type == b2_staticBody) - { - m_linearVelocity.SetZero(); - m_angularVelocity = 0.0f; - m_sweep.a0 = m_sweep.a; - m_sweep.c0 = m_sweep.c; - m_flags &= ~e_awakeFlag; - SynchronizeFixtures(); - } - - SetAwake(true); - - m_force.SetZero(); - m_torque = 0.0f; - - // Delete the attached contacts. - b2ContactEdge* ce = m_contactList; - while (ce) - { - b2ContactEdge* ce0 = ce; - ce = ce->next; - m_world->m_contactManager.Destroy(ce0->contact); - } - m_contactList = nullptr; - - // Touch the proxies so that new contacts will be created (when appropriate) - b2BroadPhase* broadPhase = &m_world->m_contactManager.m_broadPhase; - for (b2Fixture* f = m_fixtureList; f; f = f->m_next) - { - int32 proxyCount = f->m_proxyCount; - for (int32 i = 0; i < proxyCount; ++i) - { - broadPhase->TouchProxy(f->m_proxies[i].proxyId); - } - } -} - -void b2Body::SetEnabled(bool flag) -{ - b2Assert(m_world->IsLocked() == false); - - if (flag == IsEnabled()) - { - return; - } - - if (flag) - { - m_flags |= e_enabledFlag; - - // Create all proxies. - b2BroadPhase* broadPhase = &m_world->m_contactManager.m_broadPhase; - for (b2Fixture* f = m_fixtureList; f; f = f->m_next) - { - f->CreateProxies(broadPhase, m_xf); - } - - // Contacts are created at the beginning of the next - m_world->m_newContacts = true; - } - else - { - m_flags &= ~e_enabledFlag; - - // Destroy all proxies. - b2BroadPhase* broadPhase = &m_world->m_contactManager.m_broadPhase; - for (b2Fixture* f = m_fixtureList; f; f = f->m_next) - { - f->DestroyProxies(broadPhase); - } - - // Destroy the attached contacts. - b2ContactEdge* ce = m_contactList; - while (ce) - { - b2ContactEdge* ce0 = ce; - ce = ce->next; - m_world->m_contactManager.Destroy(ce0->contact); - } - m_contactList = nullptr; - } -} - -void b2Body::SetFixedRotation(bool flag) +void b2Body_SetFixedRotation(bool flag) { bool status = (m_flags & e_fixedRotationFlag) == e_fixedRotationFlag; if (status == flag) @@ -1086,10 +986,10 @@ void b2Body_Dump(b2Body* b) b2Dump(" bd.gravityScale = %.9g;\n", m_gravityScale); b2Dump(" bodies[%d] = m_world->CreateBody(&bd);\n", m_islandIndex); b2Dump("\n"); - for (b2Fixture* f = m_fixtureList; f; f = f->m_next) + for (b2Shape* shape = m_shapeList; shape; shape = shape->m_next) { b2Dump(" {\n"); - f->Dump(bodyIndex); + shape->Dump(bodyIndex); b2Dump(" }\n"); } b2Dump("}\n"); diff --git a/src/broad_phase.c b/src/broad_phase.c index 5ef1c58b..5506d936 100644 --- a/src/broad_phase.c +++ b/src/broad_phase.c @@ -212,7 +212,7 @@ static bool b2PairQueryCallback(int32_t proxyId, int32_t shapeIndex, void* conte b2Shape* shapeA = world->shapes + shapeIndexA; b2Shape* shapeB = world->shapes + shapeIndexB; - // Are the fixtures on the same body? + // Are the shapes on the same body? if (shapeA->bodyIndex == shapeB->bodyIndex) { return true; @@ -365,7 +365,7 @@ void b2UpdateBroadPhasePairs(b2World* world) while (pair != NULL) { // TODO_ERIN Check user filtering. - // if (m_contactFilter && m_contactFilter->ShouldCollide(fixtureA, fixtureB) == false) + // if (m_contactFilter && m_contactFilter->ShouldCollide(shapeA, shapeB) == false) //{ // return; //} diff --git a/src/contact.c b/src/contact.c index e1cee643..7b8f31aa 100644 --- a/src/contact.c +++ b/src/contact.c @@ -38,7 +38,7 @@ // - As long as contacts are created in deterministic order, island link order is deterministic. // - This keeps the order of contacts in islands deterministic -// Friction mixing law. The idea is to allow either fixture to drive the friction to zero. +// Friction mixing law. The idea is to allow either shape to drive the friction to zero. // For example, anything slides on ice. static inline float b2MixFriction(float friction1, float friction2) { @@ -396,7 +396,7 @@ static bool b2TestShapeOverlap(const b2Shape* shapeA, b2Transform xfA, const b2S } // Update the contact manifold and touching status. -// Note: do not assume the fixture AABBs are overlapping or are valid. +// Note: do not assume the shape AABBs are overlapping or are valid. void b2UpdateContact(b2World* world, b2Contact* contact, b2Shape* shapeA, b2Body* bodyA, b2Shape* shapeB, b2Body* bodyB) { b2Manifold oldManifold = contact->manifold; diff --git a/src/contact.h b/src/contact.h index 26dcd498..4df090f5 100644 --- a/src/contact.h +++ b/src/contact.h @@ -35,7 +35,7 @@ enum b2ContactFlags // This contact can be disabled (by user) b2_contactEnabledFlag = 0x00000004, - // This contact needs filtering because a fixture filter was changed. + // This contact needs filtering because a shape filter was changed. // TODO_ERIN don't defer this anymore b2_contactFilterFlag = 0x00000008, diff --git a/src/world.c b/src/world.c index fa79ceb4..913029ce 100644 --- a/src/world.c +++ b/src/world.c @@ -658,22 +658,6 @@ void b2World_Draw(b2WorldId worldId, b2DebugDraw* draw) } } - // if (debugDraw->drawPi & b2Draw::e_pairBit) - //{ - // b2Color color(0.3f, 0.9f, 0.9f); - // for (b2Contact* c = m_contactManager.m_contactList; c; c = c->GetNext()) - // { - // b2Shape* fixtureA = c->GetFixtureA(); - // b2Shape* fixtureB = c->GetFixtureB(); - // int32 indexA = c->GetChildIndexA(); - // int32 indexB = c->GetChildIndexB(); - // b2Vec2 cA = fixtureA->GetAABB(indexA).GetCenter(); - // b2Vec2 cB = fixtureB->GetAABB(indexB).GetCenter(); - - // m_debugDraw->DrawSegment(cA, cB, color); - // } - //} - if (draw->drawAABBs) { b2Color color = {0.9f, 0.3f, 0.9f, 1.0f}; @@ -707,22 +691,6 @@ void b2World_Draw(b2WorldId worldId, b2DebugDraw* draw) shapeIndex = shape->nextShapeIndex; } } - - // for (b2Shape* f = b->GetFixtureList(); f; f = f->GetNext()) - //{ - // for (int32 i = 0; i < f->m_proxyCount; ++i) - // { - // b2FixtureProxy* proxy = f->m_proxies + i; - // b2AABB aabb = bp->GetFatAABB(proxy->proxyId); - // b2Vec2 vs[4]; - // vs[0].Set(aabb.lowerBound.x, aabb.lowerBound.y); - // vs[1].Set(aabb.upperBound.x, aabb.lowerBound.y); - // vs[2].Set(aabb.upperBound.x, aabb.upperBound.y); - // vs[3].Set(aabb.lowerBound.x, aabb.upperBound.y); - - // m_debugDraw->DrawPolygon(vs, 4, color); - // } - //} } if (draw->drawMass) diff --git a/test/test_world.c b/test/test_world.c index 71e74778..bfbd3493 100644 --- a/test/test_world.c +++ b/test/test_world.c @@ -49,7 +49,7 @@ int HelloWorld() // Define another box shape for our dynamic body. b2Polygon dynamicBox = b2MakeBox(1.0f, 1.0f); - // Define the dynamic body fixture. + // Define the dynamic body shape b2ShapeDef shapeDef = b2DefaultShapeDef(); // Set the box density to be non-zero, so it will be dynamic.