diff --git a/CMakeLists.txt b/CMakeLists.txt index e8a9f91ce..68506845d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,12 @@ if (MSVC OR APPLE) add_compile_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined) add_link_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined) endif() + else() + if(MSVC) + # enable hot reloading + add_compile_options("$<$:/ZI>") + add_link_options("$<$:/INCREMENTAL>") + endif() endif() endif() diff --git a/docs/overview.md b/docs/overview.md index e2231f8ea..17ef4c66f 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -123,6 +123,7 @@ of the time step: - body movement events - contact begin and end events +- sensor begin and end events - contact hit events These events allow your application to react to changes in the simulation. diff --git a/docs/simulation.md b/docs/simulation.md index 4f88069fd..c826968cf 100644 --- a/docs/simulation.md +++ b/docs/simulation.md @@ -1009,21 +1009,34 @@ for (int i = 0; i < sensorEvents.beginCount; ++i) } ``` -And there are events when a shape stops overlapping with a sensor. +And there are events when a shape stops overlapping with a sensor. Be careful with end +touch events because they may be generated when shapes are destroyed. Test the shape +ids with `b2Shape_IsValid`. ```c for (int i = 0; i < sensorEvents.endCount; ++i) { b2SensorEndTouchEvent* endTouch = sensorEvents.endEvents + i; - void* myUserData = b2Shape_GetUserData(endTouch->visitorShapeId); - // process end event + if (b2Shape_IsValid(endTouch->visitorShapeId)) + { + void* myUserData = b2Shape_GetUserData(endTouch->visitorShapeId); + // process end event + } } ``` -You will not get end events if a shape is destroyed. Sensor events should -be processed after the world step and before other game logic. This should +Sensor events should be processed after the world step and before other game logic. This should help you avoid processing stale data. +There are several user operations that can cause sensors to stop touching. Such operations +include: +- destroying a body or shape +- changing the filter on a shape +- disabling a body +- setting the body transform +These may generate end-touch events and these events are included with simulation events available +after the next call to `b2World_Step`. + Sensor events are only enabled for a non-sensor shape if `b2ShapeDef::enableSensorEvents` is true. @@ -1065,11 +1078,17 @@ contain the two shape ids. for (int i = 0; i < contactEvents.endCount; ++i) { b2ContactEndTouchEvent* endEvent = contactEvents.endEvents + i; - ShapesStopTouching(endEvent->shapeIdA, endEvent->shapeIdB); + + // Use b2Shape_IsValid because a shape may have been destroyed + if (b2Shape_IsValid(endEvent->shapeIdA) && b2Shape_IsValid(endEvent->shapeIdB)) + { + ShapesStopTouching(endEvent->shapeIdA, endEvent->shapeIdB); + } } ``` -The end touch events are not generated when you destroy a shape or the body that owns it. +Similar to `b2SensorEndTouchEvent`, `b2ContactEndTouchEvent` may be generated due to a user operation, +such as destroying a body or shape. These events are included with simulation events after the next `b2World_Step`. Shapes only generate begin and end touch events if `b2ShapeDef::enableContactEvents` is true. diff --git a/include/box2d/types.h b/include/box2d/types.h index 7bd8ce41c..fa0c14300 100644 --- a/include/box2d/types.h +++ b/include/box2d/types.h @@ -969,16 +969,21 @@ typedef struct b2SensorBeginTouchEvent } b2SensorBeginTouchEvent; /// An end touch event is generated when a shape stops overlapping a sensor shape. -/// You will not get an end event if you do anything that destroys contacts outside -/// of the world step. These include things like setting the transform, destroying a body +/// You will get an end event if you do anything that destroys contacts previous to the last +/// world step. These include things like setting the transform, destroying a body /// or shape, or changing a filter or body type. typedef struct b2SensorEndTouchEvent { /// The id of the sensor shape + /// @warning this shape may have been destroyed + /// @see b2Shape_IsValid b2ShapeId sensorShapeId; /// The id of the dynamic shape that stopped touching the sensor shape + /// @warning this shape may have been destroyed + /// @see b2Shape_IsValid b2ShapeId visitorShapeId; + } b2SensorEndTouchEvent; /// Sensor events are buffered in the Box2D world and are available @@ -1013,15 +1018,19 @@ typedef struct b2ContactBeginTouchEvent } b2ContactBeginTouchEvent; /// An end touch event is generated when two shapes stop touching. -/// You will not get an end event if you do anything that destroys contacts outside -/// of the world step. These include things like setting the transform, destroying a body +/// You will get an end event if you do anything that destroys contacts previous to the last +/// world step. These include things like setting the transform, destroying a body /// or shape, or changing a filter or body type. typedef struct b2ContactEndTouchEvent { /// Id of the first shape + /// @warning this shape may have been destroyed + /// @see b2Shape_IsValid b2ShapeId shapeIdA; /// Id of the second shape + /// @warning this shape may have been destroyed + /// @see b2Shape_IsValid b2ShapeId shapeIdB; } b2ContactEndTouchEvent; diff --git a/samples/sample_events.cpp b/samples/sample_events.cpp index 3dbbc6eb3..75c80455e 100644 --- a/samples/sample_events.cpp +++ b/samples/sample_events.cpp @@ -14,7 +14,7 @@ #include #include -class SensorEvent : public Sample +class SensorFunnel : public Sample { public: enum @@ -24,7 +24,7 @@ class SensorEvent : public Sample e_count = 32 }; - explicit SensorEvent( Settings& settings ) + explicit SensorFunnel( Settings& settings ) : Sample( settings ) { if ( settings.restart == false ) @@ -56,7 +56,7 @@ class SensorEvent : public Sample { -8.26825142, 11.906374 }, { -16.8672504, 17.1978741 }, }; - int count = sizeof( points ) / sizeof( points[0] ); + int count = std::size( points ); // float scale = 0.25f; // b2Vec2 lower = {FLT_MAX, FLT_MAX}; @@ -101,7 +101,6 @@ class SensorEvent : public Sample float y = 14.0f; for ( int i = 0; i < 3; ++i ) { - b2BodyDef bodyDef = b2DefaultBodyDef(); bodyDef.position = { 0.0f, y }; bodyDef.type = b2_dynamicBody; @@ -317,7 +316,7 @@ class SensorEvent : public Sample static Sample* Create( Settings& settings ) { - return new SensorEvent( settings ); + return new SensorFunnel( settings ); } Human m_humans[e_count]; @@ -328,7 +327,161 @@ class SensorEvent : public Sample float m_side; }; -static int sampleSensorEvent = RegisterSample( "Events", "Sensor", SensorEvent::Create ); +static int sampleSensorBeginEvent = RegisterSample( "Events", "Sensor Funnel", SensorFunnel::Create ); + +class SensorBookend : public Sample +{ +public: + explicit SensorBookend( Settings& settings ) + : Sample( settings ) + { + if ( settings.restart == false ) + { + g_camera.m_center = { 0.0f, 6.0f }; + g_camera.m_zoom = 7.5f; + } + + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef ); + b2ShapeDef shapeDef = b2DefaultShapeDef(); + + b2Segment groundSegment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } }; + b2CreateSegmentShape( groundId, &shapeDef, &groundSegment ); + + groundSegment = { { -10.0f, 0.0f }, { -10.0f, 10.0f } }; + b2CreateSegmentShape( groundId, &shapeDef, &groundSegment ); + + groundSegment = { { 10.0f, 0.0f }, { 10.0f, 10.0f } }; + b2CreateSegmentShape( groundId, &shapeDef, &groundSegment ); + + m_isVisiting = false; + } + + CreateSensor(); + + CreateVisitor(); + } + + void CreateSensor() + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + + bodyDef.position = { 0.0f, 1.0f }; + m_sensorBodyId = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.isSensor = true; + b2Polygon box = b2MakeSquare( 1.0f ); + m_sensorShapeId = b2CreatePolygonShape( m_sensorBodyId, &shapeDef, &box ); + } + + void CreateVisitor() + { + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.position = { -4.0f, 1.0f }; + bodyDef.type = b2_dynamicBody; + + m_visitorBodyId = b2CreateBody( m_worldId, &bodyDef ); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.enableSensorEvents = true; + + b2Circle circle = { { 0.0f, 0.0f }, 0.5f }; + m_visitorShapeId = b2CreateCircleShape( m_visitorBodyId, &shapeDef, &circle ); + } + + void UpdateUI() override + { + float height = 90.0f; + ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once ); + ImGui::SetNextWindowSize( ImVec2( 140.0f, height ) ); + + ImGui::Begin( "Sensor Bookend", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize ); + + if ( B2_IS_NULL( m_visitorBodyId ) ) + { + if ( ImGui::Button( "create visitor" ) ) + { + CreateVisitor(); + } + } + else + { + if ( ImGui::Button( "destroy visitor" ) ) + { + b2DestroyBody( m_visitorBodyId ); + m_visitorBodyId = b2_nullBodyId; + m_visitorShapeId = b2_nullShapeId; + } + } + + if ( B2_IS_NULL( m_sensorBodyId ) ) + { + if ( ImGui::Button( "create sensor" ) ) + { + CreateSensor(); + } + } + else + { + if ( ImGui::Button( "destroy sensor" ) ) + { + b2DestroyBody( m_sensorBodyId ); + m_sensorBodyId = b2_nullBodyId; + m_sensorShapeId = b2_nullShapeId; + } + } + + ImGui::End(); + } + + void Step( Settings& settings ) override + { + Sample::Step( settings ); + + b2SensorEvents sensorEvents = b2World_GetSensorEvents( m_worldId ); + for ( int i = 0; i < sensorEvents.beginCount; ++i ) + { + b2SensorBeginTouchEvent event = sensorEvents.beginEvents[i]; + + if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId ) ) + { + assert( m_isVisiting == false ); + m_isVisiting = true; + } + } + + for ( int i = 0; i < sensorEvents.endCount; ++i ) + { + b2SensorEndTouchEvent event = sensorEvents.endEvents[i]; + + bool wasVisitorDestroyed = b2Shape_IsValid( event.visitorShapeId ) == false; + if ( B2_ID_EQUALS( event.visitorShapeId, m_visitorShapeId ) || wasVisitorDestroyed ) + { + assert( m_isVisiting == true ); + m_isVisiting = false; + } + } + + g_draw.DrawString( 5, m_textLine, "visiting == %s", m_isVisiting ? "true" : "false" ); + m_textLine += m_textIncrement; + } + + static Sample* Create( Settings& settings ) + { + return new SensorBookend( settings ); + } + + b2BodyId m_sensorBodyId; + b2ShapeId m_sensorShapeId; + + b2BodyId m_visitorBodyId; + b2ShapeId m_visitorShapeId; + bool m_isVisiting; +}; + +static int sampleSensorBookendEvent = RegisterSample( "Events", "Sensor Bookend", SensorBookend::Create ); struct BodyUserData { @@ -927,7 +1080,8 @@ class Platformer : public Sample b2ContactData contactData = {}; int contactCount = b2Body_GetContactData( m_platformId, &contactData, 1 ); - g_draw.DrawString( 5, m_textLine, "Platform contact count = %d, point count = %d", contactCount, contactData.manifold.pointCount ); + g_draw.DrawString( 5, m_textLine, "Platform contact count = %d, point count = %d", contactCount, + contactData.manifold.pointCount ); m_textLine += m_textIncrement; g_draw.DrawString( 5, m_textLine, "Movement: A/D/Space" ); diff --git a/src/contact.c b/src/contact.c index 06641a1aa..b61b0117c 100644 --- a/src/contact.c +++ b/src/contact.c @@ -42,7 +42,7 @@ B2_ARRAY_SOURCE( b2ContactSim, b2ContactSim ); static inline float b2MixFloats( float value1, float value2, b2MixingRule mixingRule ) { - switch (mixingRule) + switch ( mixingRule ) { case b2_mixAverage: return 0.5f * ( value1 + value2 ); @@ -52,10 +52,10 @@ static inline float b2MixFloats( float value1, float value2, b2MixingRule mixing case b2_mixMultiply: return value1 * value2; - + case b2_mixMinimum: return value1 < value2 ? value1 : value2; - + case b2_mixMaximum: return value1 > value2 ? value1 : value2; @@ -248,7 +248,7 @@ void b2CreateContact( b2World* world, b2Shape* shapeA, b2Shape* shapeB ) int shapeIdA = shapeA->id; int shapeIdB = shapeB->id; - b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId ); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId ); contact->contactId = contactId; contact->setIndex = setIndex; contact->colorIndex = B2_NULL_INDEX; @@ -367,10 +367,31 @@ void b2DestroyContact( b2World* world, b2Contact* contact, bool wakeBodies ) b2Body* bodyA = b2BodyArray_Get( &world->bodies, bodyIdA ); b2Body* bodyB = b2BodyArray_Get( &world->bodies, bodyIdB ); - // if (contactListener && contact->IsTouching()) - //{ - // contactListener->EndContact(contact); - // } + uint32_t flags = contact->flags; + if ( ( flags & ( b2_contactTouchingFlag | b2_contactSensorTouchingFlag ) ) != 0 && + ( flags & ( b2_contactEnableContactEvents | b2_contactEnableSensorEvents ) ) != 0 ) + { + int16_t worldId = world->worldId; + const b2Shape* shapeA = b2ShapeArray_Get( &world->shapes, contact->shapeIdA ); + const b2Shape* shapeB = b2ShapeArray_Get( &world->shapes, contact->shapeIdB ); + b2ShapeId shapeIdA = { shapeA->id + 1, worldId, shapeA->revision }; + b2ShapeId shapeIdB = { shapeB->id + 1, worldId, shapeB->revision }; + + // Was touching? + if ( ( flags & b2_contactTouchingFlag ) != 0 && ( flags & b2_contactEnableContactEvents ) != 0 ) + { + B2_ASSERT( ( flags & b2_contactSensorFlag ) == 0 ); + b2ContactEndTouchEvent event = { shapeIdA, shapeIdB }; + b2ContactEndTouchEventArray_Push( world->contactEndEvents + world->endEventArrayIndex, event ); + } + + if ( ( flags & b2_contactSensorTouchingFlag ) != 0 && ( flags & b2_contactEnableSensorEvents ) != 0 ) + { + B2_ASSERT( ( flags & b2_contactSensorFlag ) != 0 ); + b2SensorEndTouchEvent event = { shapeIdA, shapeIdB }; + b2SensorEndTouchEventArray_Push( world->sensorEndEvents + world->endEventArrayIndex, event ); + } + } // Remove from body A if ( edgeA->prevKey != B2_NULL_INDEX ) diff --git a/src/solver.c b/src/solver.c index cd575598f..83e9d159f 100644 --- a/src/solver.c +++ b/src/solver.c @@ -1035,36 +1035,11 @@ static void b2SolveContinuous( b2World* world, int bodySimIndex ) if ( b2AABB_Contains( shape->fatAABB, aabb ) == false ) { -#if 0 - b2AABB fatAABB = aabb; - if ( deltax > 0.0f ) - { - fatAABB.lowerBound.x -= aabbMargin; - fatAABB.upperBound.x += deltax + aabbMargin; - } - else - { - fatAABB.lowerBound.x += deltax - aabbMargin; - fatAABB.upperBound.x += aabbMargin; - } - - if ( deltay > 0.0f ) - { - fatAABB.lowerBound.y -= aabbMargin; - fatAABB.upperBound.y += deltay + aabbMargin; - } - else - { - fatAABB.lowerBound.y += deltay - aabbMargin; - fatAABB.upperBound.y += aabbMargin; - } -#else b2AABB fatAABB; fatAABB.lowerBound.x = aabb.lowerBound.x - aabbMargin; fatAABB.lowerBound.y = aabb.lowerBound.y - aabbMargin; fatAABB.upperBound.x = aabb.upperBound.x + aabbMargin; fatAABB.upperBound.y = aabb.upperBound.y + aabbMargin; -#endif shape->fatAABB = fatAABB; shape->enlargedAABB = true; @@ -1092,36 +1067,11 @@ static void b2SolveContinuous( b2World* world, int bodySimIndex ) if ( b2AABB_Contains( shape->fatAABB, shape->aabb ) == false ) { -#if 0 - b2AABB fatAABB = shape->aabb; - if ( deltax > 0.0f ) - { - fatAABB.lowerBound.x -= aabbMargin; - fatAABB.upperBound.x += deltax + aabbMargin; - } - else - { - fatAABB.lowerBound.x += deltax - aabbMargin; - fatAABB.upperBound.x += aabbMargin; - } - - if ( deltay > 0.0f ) - { - fatAABB.lowerBound.y -= aabbMargin; - fatAABB.upperBound.y += deltay + aabbMargin; - } - else - { - fatAABB.lowerBound.y += deltay - aabbMargin; - fatAABB.upperBound.y += aabbMargin; - } -#else b2AABB fatAABB; fatAABB.lowerBound.x = shape->aabb.lowerBound.x - aabbMargin; fatAABB.lowerBound.y = shape->aabb.lowerBound.y - aabbMargin; fatAABB.upperBound.x = shape->aabb.upperBound.x + aabbMargin; fatAABB.upperBound.y = shape->aabb.upperBound.y + aabbMargin; -#endif shape->fatAABB = fatAABB; shape->enlargedAABB = true; diff --git a/src/world.c b/src/world.c index b751d5f0e..15c67e701 100644 --- a/src/world.c +++ b/src/world.c @@ -161,10 +161,13 @@ b2WorldId b2CreateWorld( const b2WorldDef* def ) world->bodyMoveEvents = b2BodyMoveEventArray_Create( 4 ); world->sensorBeginEvents = b2SensorBeginTouchEventArray_Create( 4 ); - world->sensorEndEvents = b2SensorEndTouchEventArray_Create( 4 ); + world->sensorEndEvents[0] = b2SensorEndTouchEventArray_Create( 4 ); + world->sensorEndEvents[1] = b2SensorEndTouchEventArray_Create( 4 ); world->contactBeginEvents = b2ContactBeginTouchEventArray_Create( 4 ); - world->contactEndEvents = b2ContactEndTouchEventArray_Create( 4 ); + world->contactEndEvents[0] = b2ContactEndTouchEventArray_Create( 4 ); + world->contactEndEvents[1] = b2ContactEndTouchEventArray_Create( 4 ); world->contactHitEvents = b2ContactHitEventArray_Create( 4 ); + world->endEventArrayIndex = 0; world->stepIndex = 0; world->splitIslandId = B2_NULL_INDEX; @@ -240,9 +243,11 @@ void b2DestroyWorld( b2WorldId worldId ) b2BodyMoveEventArray_Destroy( &world->bodyMoveEvents ); b2SensorBeginTouchEventArray_Destroy( &world->sensorBeginEvents ); - b2SensorEndTouchEventArray_Destroy( &world->sensorEndEvents ); + b2SensorEndTouchEventArray_Destroy( world->sensorEndEvents + 0 ); + b2SensorEndTouchEventArray_Destroy( world->sensorEndEvents + 1 ); b2ContactBeginTouchEventArray_Destroy( &world->contactBeginEvents ); - b2ContactEndTouchEventArray_Destroy( &world->contactEndEvents ); + b2ContactEndTouchEventArray_Destroy( world->contactEndEvents + 0); + b2ContactEndTouchEventArray_Destroy( world->contactEndEvents + 1); b2ContactHitEventArray_Destroy( &world->contactHitEvents ); int chainCapacity = world->chainShapes.count; @@ -408,7 +413,7 @@ static void b2RemoveNonTouchingContact( b2World* world, int setIndex, int localI if ( movedIndex != B2_NULL_INDEX ) { b2ContactSim* movedContactSim = set->contactSims.data + localIndex; - b2Contact* movedContact = b2ContactArray_Get(&world->contacts, movedContactSim->contactId); + b2Contact* movedContact = b2ContactArray_Get( &world->contacts, movedContactSim->contactId ); B2_ASSERT( movedContact->setIndex == setIndex ); B2_ASSERT( movedContact->localIndex == movedIndex ); B2_ASSERT( movedContact->colorIndex == B2_NULL_INDEX ); @@ -508,6 +513,8 @@ static void b2Collide( b2StepContext* context ) b2SolverSet* awakeSet = b2SolverSetArray_Get( &world->solverSets, b2_awakeSet ); + int endEventArrayIndex = world->endEventArrayIndex; + const b2Shape* shapes = world->shapes.data; int16_t worldId = world->worldId; @@ -520,7 +527,7 @@ static void b2Collide( b2StepContext* context ) uint32_t ctz = b2CTZ64( bits ); int contactId = (int)( 64 * k + ctz ); - b2Contact* contact = b2ContactArray_Get(&world->contacts, contactId); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId ); B2_ASSERT( contact->setIndex == b2_awakeSet ); int colorIndex = contact->colorIndex; @@ -548,21 +555,7 @@ static void b2Collide( b2StepContext* context ) if ( simFlags & b2_simDisjoint ) { - // Was touching? - if ( ( flags & b2_contactTouchingFlag ) != 0 && ( flags & b2_contactEnableContactEvents ) != 0 ) - { - b2ContactEndTouchEvent event = { shapeIdA, shapeIdB }; - b2ContactEndTouchEventArray_Push( &world->contactEndEvents, event ); - } - - if ( ( flags & b2_contactSensorTouchingFlag ) != 0 && ( flags & b2_contactEnableSensorEvents ) != 0 ) - { - b2SensorEndTouchEvent event = { shapeIdA, shapeIdB }; - b2SensorEndTouchEventArray_Push( &world->sensorEndEvents, event ); - } - // Bounding boxes no longer overlap - contact->flags &= ~b2_contactTouchingFlag; b2DestroyContact( world, contact, false ); contact = NULL; contactSim = NULL; @@ -637,13 +630,13 @@ static void b2Collide( b2StepContext* context ) if ( shapeA->isSensor ) { b2SensorEndTouchEvent event = { shapeIdA, shapeIdB }; - b2SensorEndTouchEventArray_Push( &world->sensorEndEvents, event ); + b2SensorEndTouchEventArray_Push( world->sensorEndEvents + endEventArrayIndex, event ); } if ( shapeB->isSensor ) { b2SensorEndTouchEvent event = { shapeIdB, shapeIdA }; - b2SensorEndTouchEventArray_Push( &world->sensorEndEvents, event ); + b2SensorEndTouchEventArray_Push( world->sensorEndEvents + endEventArrayIndex, event ); } } } @@ -655,7 +648,7 @@ static void b2Collide( b2StepContext* context ) if ( contact->flags & b2_contactEnableContactEvents ) { b2ContactEndTouchEvent event = { shapeIdA, shapeIdB }; - b2ContactEndTouchEventArray_Push( &world->contactEndEvents, event ); + b2ContactEndTouchEventArray_Push( world->contactEndEvents + endEventArrayIndex, event ); } B2_ASSERT( contactSim->manifold.pointCount == 0 ); @@ -692,19 +685,26 @@ void b2World_Step( b2WorldId worldId, float timeStep, int subStepCount ) return; } + // 1. destroy shape + // 2. step + // 3. get events + // Prepare to capture events // Ensure user does not access stale data if there is an early return b2BodyMoveEventArray_Clear( &world->bodyMoveEvents ); b2SensorBeginTouchEventArray_Clear( &world->sensorBeginEvents ); - b2SensorEndTouchEventArray_Clear( &world->sensorEndEvents ); b2ContactBeginTouchEventArray_Clear( &world->contactBeginEvents ); - b2ContactEndTouchEventArray_Clear( &world->contactEndEvents ); b2ContactHitEventArray_Clear( &world->contactHitEvents ); world->profile = ( b2Profile ){ 0 }; if ( timeStep == 0.0f ) { + // Swap end event array buffers + world->endEventArrayIndex = 1 - world->endEventArrayIndex; + b2SensorEndTouchEventArray_Clear( world->sensorEndEvents + world->endEventArrayIndex ); + b2ContactEndTouchEventArray_Clear( world->contactEndEvents + world->endEventArrayIndex ); + // todo would be useful to still process collision while paused return; } @@ -784,6 +784,11 @@ void b2World_Step( b2WorldId worldId, float timeStep, int subStepCount ) B2_ASSERT( world->activeTaskCount == 0 ); b2TracyCZoneEnd( world_step ); + + // Swap end event array buffers + world->endEventArrayIndex = 1 - world->endEventArrayIndex; + b2SensorEndTouchEventArray_Clear( world->sensorEndEvents + world->endEventArrayIndex ); + b2ContactEndTouchEventArray_Clear( world->contactEndEvents + world->endEventArrayIndex ); } static void b2DrawShape( b2DebugDraw* draw, b2Shape* shape, b2Transform xf, b2HexColor color ) @@ -1025,7 +1030,7 @@ static void b2DrawWithBounds( b2World* world, b2DebugDraw* draw ) { int contactId = contactKey >> 1; int edgeIndex = contactKey & 1; - b2Contact* contact = b2ContactArray_Get(&world->contacts, contactId); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId ); contactKey = contact->edges[edgeIndex].nextKey; if ( contact->setIndex != b2_awakeSet || contact->colorIndex == B2_NULL_INDEX ) @@ -1391,10 +1396,18 @@ b2SensorEvents b2World_GetSensorEvents( b2WorldId worldId ) return ( b2SensorEvents ){ 0 }; } + // Careful to use previous buffer + int endEventArrayIndex = 1 - world->endEventArrayIndex; + int beginCount = world->sensorBeginEvents.count; - int endCount = world->sensorEndEvents.count; + int endCount = world->sensorEndEvents[endEventArrayIndex].count; - b2SensorEvents events = { world->sensorBeginEvents.data, world->sensorEndEvents.data, beginCount, endCount }; + b2SensorEvents events = { + .beginEvents = world->sensorBeginEvents.data, + .endEvents = world->sensorEndEvents[endEventArrayIndex].data, + .beginCount = beginCount, + .endCount = endCount, + }; return events; } @@ -1407,12 +1420,20 @@ b2ContactEvents b2World_GetContactEvents( b2WorldId worldId ) return ( b2ContactEvents ){ 0 }; } + // Careful to use previous buffer + int endEventArrayIndex = 1 - world->endEventArrayIndex; + int beginCount = world->contactBeginEvents.count; - int endCount = world->contactEndEvents.count; + int endCount = world->contactEndEvents[endEventArrayIndex].count; int hitCount = world->contactHitEvents.count; b2ContactEvents events = { - world->contactBeginEvents.data, world->contactEndEvents.data, world->contactHitEvents.data, beginCount, endCount, hitCount, + .beginEvents = world->contactBeginEvents.data, + .endEvents = world->contactEndEvents[endEventArrayIndex].data, + .hitEvents = world->contactHitEvents.data, + .beginCount = beginCount, + .endCount = endCount, + .hitCount = hitCount, }; return events; @@ -1693,7 +1714,7 @@ void b2World_SetContactTuning( b2WorldId worldId, float hertz, float dampingRati world->contactPushoutVelocity = b2ClampFloat( pushVelocity, 0.0f, FLT_MAX ); } -void b2World_SetJointTuning(b2WorldId worldId, float hertz, float dampingRatio) +void b2World_SetJointTuning( b2WorldId worldId, float hertz, float dampingRatio ) { b2World* world = b2GetWorldFromId( worldId ); B2_ASSERT( world->locked == false ); @@ -1706,7 +1727,7 @@ void b2World_SetJointTuning(b2WorldId worldId, float hertz, float dampingRatio) world->jointDampingRatio = b2ClampFloat( dampingRatio, 0.0f, FLT_MAX ); } -void b2World_SetMaximumLinearVelocity(b2WorldId worldId, float maximumLinearVelocity) +void b2World_SetMaximumLinearVelocity( b2WorldId worldId, float maximumLinearVelocity ) { B2_ASSERT( b2IsValid( maximumLinearVelocity ) && maximumLinearVelocity > 0.0f ); @@ -1720,7 +1741,7 @@ void b2World_SetMaximumLinearVelocity(b2WorldId worldId, float maximumLinearVelo world->maxLinearVelocity = maximumLinearVelocity; } -float b2World_GetMaximumLinearVelocity(b2WorldId worldId) +float b2World_GetMaximumLinearVelocity( b2WorldId worldId ) { b2World* world = b2GetWorldFromId( worldId ); return world->maxLinearVelocity; @@ -1760,13 +1781,13 @@ b2Counters b2World_GetCounters( b2WorldId worldId ) return s; } -void b2World_SetUserData(b2WorldId worldId, void* userData) +void b2World_SetUserData( b2WorldId worldId, void* userData ) { b2World* world = b2GetWorldFromId( worldId ); world->userData = userData; } -void* b2World_GetUserData(b2WorldId worldId) +void* b2World_GetUserData( b2WorldId worldId ) { b2World* world = b2GetWorldFromId( worldId ); return world->userData; @@ -1903,8 +1924,7 @@ static bool TreeQueryCallback( int proxyId, int shapeId, void* context ) return result; } -b2TreeStats b2World_OverlapAABB( b2WorldId worldId, b2AABB aabb, b2QueryFilter filter, b2OverlapResultFcn* fcn, - void* context ) +b2TreeStats b2World_OverlapAABB( b2WorldId worldId, b2AABB aabb, b2QueryFilter filter, b2OverlapResultFcn* fcn, void* context ) { b2TreeStats treeStats = { 0 }; @@ -1989,7 +2009,7 @@ b2TreeStats b2World_OverlapPoint( b2WorldId worldId, b2Vec2 point, b2Transform t } b2TreeStats b2World_OverlapCircle( b2WorldId worldId, const b2Circle* circle, b2Transform transform, b2QueryFilter filter, - b2OverlapResultFcn* fcn, void* context ) + b2OverlapResultFcn* fcn, void* context ) { b2TreeStats treeStats = { 0 }; @@ -2021,7 +2041,7 @@ b2TreeStats b2World_OverlapCircle( b2WorldId worldId, const b2Circle* circle, b2 } b2TreeStats b2World_OverlapCapsule( b2WorldId worldId, const b2Capsule* capsule, b2Transform transform, b2QueryFilter filter, - b2OverlapResultFcn* fcn, void* context ) + b2OverlapResultFcn* fcn, void* context ) { b2TreeStats treeStats = { 0 }; @@ -2053,7 +2073,7 @@ b2TreeStats b2World_OverlapCapsule( b2WorldId worldId, const b2Capsule* capsule, } b2TreeStats b2World_OverlapPolygon( b2WorldId worldId, const b2Polygon* polygon, b2Transform transform, b2QueryFilter filter, - b2OverlapResultFcn* fcn, void* context ) + b2OverlapResultFcn* fcn, void* context ) { b2TreeStats treeStats = { 0 }; @@ -2125,7 +2145,7 @@ static float RayCastCallback( const b2RayCastInput* input, int proxyId, int shap } b2TreeStats b2World_CastRay( b2WorldId worldId, b2Vec2 origin, b2Vec2 translation, b2QueryFilter filter, b2CastResultFcn* fcn, - void* context ) + void* context ) { b2TreeStats treeStats = { 0 }; @@ -2145,7 +2165,8 @@ b2TreeStats b2World_CastRay( b2WorldId worldId, b2Vec2 origin, b2Vec2 translatio for ( int i = 0; i < b2_bodyTypeCount; ++i ) { - b2TreeStats treeResult = b2DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, RayCastCallback, &worldContext ); + b2TreeStats treeResult = + b2DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, RayCastCallback, &worldContext ); treeStats.nodeVisits += treeResult.nodeVisits; treeStats.leafVisits += treeResult.leafVisits; @@ -2191,7 +2212,8 @@ b2RayResult b2World_CastRayClosest( b2WorldId worldId, b2Vec2 origin, b2Vec2 tra for ( int i = 0; i < b2_bodyTypeCount; ++i ) { - b2TreeStats treeResult = b2DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, RayCastCallback, &worldContext ); + b2TreeStats treeResult = + b2DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, RayCastCallback, &worldContext ); result.nodeVisits += treeResult.nodeVisits; result.leafVisits += treeResult.leafVisits; @@ -2239,7 +2261,7 @@ static float ShapeCastCallback( const b2ShapeCastInput* input, int proxyId, int } b2TreeStats b2World_CastCircle( b2WorldId worldId, const b2Circle* circle, b2Transform originTransform, b2Vec2 translation, - b2QueryFilter filter, b2CastResultFcn* fcn, void* context ) + b2QueryFilter filter, b2CastResultFcn* fcn, void* context ) { b2TreeStats treeStats = { 0 }; @@ -2281,9 +2303,8 @@ b2TreeStats b2World_CastCircle( b2WorldId worldId, const b2Circle* circle, b2Tra return treeStats; } -b2TreeStats b2World_CastCapsule( b2WorldId worldId, const b2Capsule* capsule, b2Transform originTransform, - b2Vec2 translation, - b2QueryFilter filter, b2CastResultFcn* fcn, void* context ) +b2TreeStats b2World_CastCapsule( b2WorldId worldId, const b2Capsule* capsule, b2Transform originTransform, b2Vec2 translation, + b2QueryFilter filter, b2CastResultFcn* fcn, void* context ) { b2TreeStats treeStats = { 0 }; @@ -2326,9 +2347,8 @@ b2TreeStats b2World_CastCapsule( b2WorldId worldId, const b2Capsule* capsule, b2 return treeStats; } -b2TreeStats b2World_CastPolygon( b2WorldId worldId, const b2Polygon* polygon, b2Transform originTransform, - b2Vec2 translation, - b2QueryFilter filter, b2CastResultFcn* fcn, void* context ) +b2TreeStats b2World_CastPolygon( b2WorldId worldId, const b2Polygon* polygon, b2Transform originTransform, b2Vec2 translation, + b2QueryFilter filter, b2CastResultFcn* fcn, void* context ) { b2TreeStats treeStats = { 0 }; @@ -2544,7 +2564,7 @@ static bool ExplosionCallback( int proxyId, int shapeId, void* context ) } b2Vec2 direction = b2Sub( closestPoint, explosionContext->position ); - if (b2LengthSquared(direction) > 100.0f * FLT_EPSILON * FLT_EPSILON) + if ( b2LengthSquared( direction ) > 100.0f * FLT_EPSILON * FLT_EPSILON ) { direction = b2Normalize( direction ); } @@ -2553,12 +2573,12 @@ static bool ExplosionCallback( int proxyId, int shapeId, void* context ) direction = ( b2Vec2 ){ 1.0f, 0.0f }; } - b2Vec2 localLine = b2InvRotateVector( transform.q, b2LeftPerp(direction) ); + b2Vec2 localLine = b2InvRotateVector( transform.q, b2LeftPerp( direction ) ); float perimeter = b2GetShapeProjectedPerimeter( shape, localLine ); float scale = 1.0f; if ( output.distance > radius && falloff > 0.0f ) { - scale = b2ClampFloat((radius + falloff - output.distance) / falloff, 0.0f, 1.0f); + scale = b2ClampFloat( ( radius + falloff - output.distance ) / falloff, 0.0f, 1.0f ); } float magnitude = explosionContext->impulsePerLength * perimeter * scale; @@ -2594,20 +2614,18 @@ void b2World_Explode( b2WorldId worldId, const b2ExplosionDef* explosionDef ) return; } - struct ExplosionContext explosionContext = { world, position, radius, falloff, - impulsePerLength }; + struct ExplosionContext explosionContext = { world, position, radius, falloff, impulsePerLength }; b2AABB aabb; - aabb.lowerBound.x = position.x - (radius + falloff); - aabb.lowerBound.y = position.y - (radius + falloff); - aabb.upperBound.x = position.x + (radius + falloff); - aabb.upperBound.y = position.y + (radius + falloff); + aabb.lowerBound.x = position.x - ( radius + falloff ); + aabb.lowerBound.y = position.y - ( radius + falloff ); + aabb.upperBound.x = position.x + ( radius + falloff ); + aabb.upperBound.y = position.y + ( radius + falloff ); - b2DynamicTree_Query( world->broadPhase.trees + b2_dynamicBody, aabb, maskBits, ExplosionCallback, - &explosionContext ); + b2DynamicTree_Query( world->broadPhase.trees + b2_dynamicBody, aabb, maskBits, ExplosionCallback, &explosionContext ); } -void b2World_RebuildStaticTree(b2WorldId worldId) +void b2World_RebuildStaticTree( b2WorldId worldId ) { b2World* world = b2GetWorldFromId( worldId ); B2_ASSERT( world->locked == false ); @@ -2620,7 +2638,6 @@ void b2World_RebuildStaticTree(b2WorldId worldId) b2DynamicTree_Rebuild( staticTree, true ); } - #if B2_VALIDATE // When validating islands ids I have to compare the root island // ids because islands are not merged until the next time step. @@ -2672,7 +2689,7 @@ void b2ValidateConnectivity( b2World* world ) int contactId = contactKey >> 1; int edgeIndex = contactKey & 1; - b2Contact* contact = b2ContactArray_Get(&world->contacts, contactId); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId ); bool touching = ( contact->flags & b2_contactTouchingFlag ) != 0; if ( touching && ( contact->flags & b2_contactSensorFlag ) == 0 ) @@ -2825,7 +2842,7 @@ void b2ValidateSolverSets( b2World* world ) int contactId = contactKey >> 1; int edgeIndex = contactKey & 1; - b2Contact* contact = b2ContactArray_Get(&world->contacts, contactId); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactId ); B2_ASSERT( contact->setIndex != b2_staticSet ); B2_ASSERT( contact->edges[0].bodyId == bodyId || contact->edges[1].bodyId == bodyId ); contactKey = contact->edges[edgeIndex].nextKey; @@ -2878,7 +2895,7 @@ void b2ValidateSolverSets( b2World* world ) for ( int i = 0; i < set->contactSims.count; ++i ) { b2ContactSim* contactSim = set->contactSims.data + i; - b2Contact* contact = b2ContactArray_Get(&world->contacts, contactSim->contactId); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactSim->contactId ); if ( setIndex == b2_awakeSet ) { // contact should be non-touching if awake @@ -2948,7 +2965,7 @@ void b2ValidateSolverSets( b2World* world ) for ( int i = 0; i < color->contactSims.count; ++i ) { b2ContactSim* contactSim = color->contactSims.data + i; - b2Contact* contact = b2ContactArray_Get(&world->contacts, contactSim->contactId); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactSim->contactId ); // contact should be touching in the constraint graph or awaiting transfer to non-touching B2_ASSERT( contactSim->manifold.pointCount > 0 || ( contactSim->simFlags & ( b2_simStoppedTouching | b2_simDisjoint ) ) != 0 ); @@ -3055,7 +3072,7 @@ void b2ValidateContacts( b2World* world ) for ( int contactIndex = 0; contactIndex < contactCount; ++contactIndex ) { - b2Contact* contact = b2ContactArray_Get(&world->contacts, contactIndex); + b2Contact* contact = b2ContactArray_Get( &world->contacts, contactIndex ); if ( contact->contactId == B2_NULL_INDEX ) { continue; diff --git a/src/world.h b/src/world.h index c15db7048..db3523dba 100644 --- a/src/world.h +++ b/src/world.h @@ -100,11 +100,16 @@ typedef struct b2World b2BodyMoveEventArray bodyMoveEvents; b2SensorBeginTouchEventArray sensorBeginEvents; - b2SensorEndTouchEventArray sensorEndEvents; b2ContactBeginTouchEventArray contactBeginEvents; - b2ContactEndTouchEventArray contactEndEvents; + + // End events are double buffered so that the user doesn't need to flush events + b2SensorEndTouchEventArray sensorEndEvents[2]; + b2ContactEndTouchEventArray contactEndEvents[2]; + int endEventArrayIndex; + b2ContactHitEventArray contactHitEvents; + // Used to track debug draw b2BitSet debugBodySet; b2BitSet debugJointSet;