Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event bookend #837

Merged
merged 4 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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("$<$<CONFIG:Debug>:/ZI>")
add_link_options("$<$<CONFIG:Debug>:/INCREMENTAL>")
endif()
endif()
endif()

Expand Down
1 change: 1 addition & 0 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
33 changes: 26 additions & 7 deletions docs/simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
17 changes: 13 additions & 4 deletions include/box2d/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
168 changes: 161 additions & 7 deletions samples/sample_events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#include <imgui.h>
#include <vector>

class SensorEvent : public Sample
class SensorFunnel : public Sample
{
public:
enum
Expand All @@ -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 )
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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];
Expand All @@ -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
{
Expand Down Expand Up @@ -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" );
Expand Down
Loading