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

Fix missing hit events #808

Merged
merged 2 commits into from
Sep 29, 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
7 changes: 7 additions & 0 deletions include/box2d/box2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ B2_API void b2World_Explode( b2WorldId worldId, b2Vec2 position, float radius, f
/// @note Advanced feature
B2_API void b2World_SetContactTuning( b2WorldId worldId, float hertz, float dampingRatio, float pushVelocity );

/// Adjust joint tuning parameters
/// @param worldId The world id
/// @param hertz The contact stiffness (cycles per second)
/// @param dampingRatio The contact bounciness with 1 being critical damping (non-dimensional)
/// @note Advanced feature
B2_API void b2World_SetJointTuning( b2WorldId worldId, float hertz, float dampingRatio );

/// Enable/disable constraint warm starting. Advanced feature for testing. Disabling
/// sleeping greatly reduces stability and provides no performance gain.
B2_API void b2World_EnableWarmStarting( b2WorldId worldId, bool flag );
Expand Down
5 changes: 3 additions & 2 deletions include/box2d/collision.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,10 +488,11 @@ typedef struct b2ManifoldPoint
b2Vec2 point;

/// Location of the contact point relative to bodyA's origin in world space
/// @note When used internally to the Box2D solver, these are relative to the center of mass.
/// @note When used internally to the Box2D solver, this is relative to the center of mass.
b2Vec2 anchorA;

/// Location of the contact point relative to bodyB's origin in world space
/// @note When used internally to the Box2D solver, this is relative to the center of mass.
b2Vec2 anchorB;

/// The separation of the contact point, negative if penetrating
Expand All @@ -504,7 +505,7 @@ typedef struct b2ManifoldPoint
float tangentImpulse;

/// The maximum normal impulse applied during sub-stepping
/// todo not sure this is needed
/// This could be a bool to indicate the point is confirmed (may be a speculative point)
float maxNormalImpulse;

/// Relative normal velocity pre-solve. Used for hit events. If the normal impulse is
Expand Down
3 changes: 3 additions & 0 deletions include/box2d/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,9 @@ typedef struct b2ContactBeginTouchEvent

/// Id of the second shape
b2ShapeId shapeIdB;

/// The initial contact manifold
b2Manifold manifold;
} b2ContactBeginTouchEvent;

/// An end touch event is generated when two shapes stop touching.
Expand Down
132 changes: 62 additions & 70 deletions samples/sample_continuous.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@
#include <GLFW/glfw3.h>
#include <imgui.h>

// This tests continuous collision robustness and also demonstrates the speed limits imposed
// by b2_maxTranslation and b2_maxRotation.
struct HitEvent
{
b2Vec2 point;
float speed;
int stepIndex;
};

class BounceHouse : public Sample
{
Expand All @@ -30,6 +22,13 @@ class BounceHouse : public Sample
e_boxShape
};

struct HitEvent
{
b2Vec2 point;
float speed;
int stepIndex;
};

explicit BounceHouse( Settings& settings )
: Sample( settings )
{
Expand Down Expand Up @@ -389,64 +388,8 @@ class SkinnyBox : public Sample

static int sampleSkinnyBox = RegisterSample( "Continuous", "Skinny Box", SkinnyBox::Create );

class SpeculativeBug : public Sample
{
public:
explicit SpeculativeBug( Settings& settings )
: Sample( settings )
{
if ( settings.restart == false )
{
g_camera.m_center = { 1.0f, 5.0f };
g_camera.m_zoom = 25.0f * 0.25f;
}

{
b2BodyDef bodyDef = b2DefaultBodyDef();
b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef );

b2Segment segment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } };
b2ShapeDef shapeDef = b2DefaultShapeDef();
b2CreateSegmentShape( groundId, &shapeDef, &segment );

shapeDef.friction = 0.0f;
b2Polygon box = b2MakeOffsetBox( 0.05f, 1.0f, { 0.0f, 1.0f }, b2Rot_identity );
b2CreatePolygonShape( groundId, &shapeDef, &box );
}

b2BodyDef bodyDef = b2DefaultBodyDef();
bodyDef.type = b2_dynamicBody;
for (int i = 0; i < 2; ++i)
{
if (i == 0)
{
bodyDef.position = { -0.8f, 0.25f };
bodyDef.isAwake = false;
}
else
{
bodyDef.position = { 0.8f, 2.0f };
bodyDef.isAwake = true;
}

b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef );

b2ShapeDef shapeDef = b2DefaultShapeDef();
b2Capsule capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, 0.25f };
b2CreateCapsuleShape( bodyId, &shapeDef, &capsule );
}
}

static Sample* Create( Settings& settings )
{
return new SpeculativeBug( settings );
}
};

static int sampleSpeculativeBug = RegisterSample( "Continuous", "Speculative Bug", SpeculativeBug::Create );

// This sample shows ghost collisions
class GhostCollision : public Sample
// This sample shows ghost bumps
class GhostBumps : public Sample
{
public:
enum ShapeType
Expand All @@ -456,7 +399,7 @@ class GhostCollision : public Sample
e_boxShape
};

explicit GhostCollision( Settings& settings )
explicit GhostBumps( Settings& settings )
: Sample( settings )
{
if ( settings.restart == false )
Expand Down Expand Up @@ -671,7 +614,7 @@ class GhostCollision : public Sample
ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
ImGui::SetNextWindowSize( ImVec2( 180.0f, height ) );

ImGui::Begin( "Ghost Collision", nullptr, ImGuiWindowFlags_NoResize );
ImGui::Begin( "Ghost Bumps", nullptr, ImGuiWindowFlags_NoResize );
ImGui::PushItemWidth( 100.0f );

if ( ImGui::Checkbox( "Chain", &m_useChain ) )
Expand Down Expand Up @@ -720,7 +663,7 @@ class GhostCollision : public Sample

static Sample* Create( Settings& settings )
{
return new GhostCollision( settings );
return new GhostBumps( settings );
}

b2BodyId m_groundId;
Expand All @@ -733,7 +676,7 @@ class GhostCollision : public Sample
bool m_useChain;
};

static int sampleGhostCollision = RegisterSample( "Continuous", "Ghost Collision", GhostCollision::Create );
static int sampleGhostCollision = RegisterSample( "Continuous", "Ghost Bumps", GhostBumps::Create );

// Speculative collision failure case suggested by Dirk Gregorius. This uses
// a simple fallback scheme to prevent tunneling.
Expand Down Expand Up @@ -786,6 +729,55 @@ class SpeculativeFallback : public Sample

static int sampleSpeculativeFallback = RegisterSample( "Continuous", "Speculative Fallback", SpeculativeFallback::Create );

// This shows that while Box2D uses speculative collision, it does not lead to speculative ghost collisions at small distances
class SpeculativeGhost : public Sample
{
public:
explicit SpeculativeGhost( Settings& settings )
: Sample( settings )
{
if ( settings.restart == false )
{
g_camera.m_center = { 0.0f, 1.75f };
g_camera.m_zoom = 2.0f;
}

{
b2BodyDef bodyDef = b2DefaultBodyDef();
b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef );

b2ShapeDef shapeDef = b2DefaultShapeDef();
b2Segment segment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } };
b2CreateSegmentShape( groundId, &shapeDef, &segment );

b2Polygon box = b2MakeOffsetBox( 1.0f, 0.1f, { 0.0f, 0.9f }, b2Rot_identity );
b2CreatePolygonShape( groundId, &shapeDef, &box );
}

{
b2BodyDef bodyDef = b2DefaultBodyDef();
bodyDef.type = b2_dynamicBody;

// The speculative distance is 0.02 meters, so this avoid it
bodyDef.position = { 0.015f, 2.515f };
bodyDef.linearVelocity = { 0.1f * 1.25f * settings.hertz, -0.1f * 1.25f * settings.hertz };
bodyDef.gravityScale = 0.0f;
b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef );

b2ShapeDef shapeDef = b2DefaultShapeDef();
b2Polygon box = b2MakeSquare( 0.25f );
b2CreatePolygonShape( bodyId, &shapeDef, &box );
}
}

static Sample* Create( Settings& settings )
{
return new SpeculativeGhost( settings );
}
};

static int sampleSpeculativeGhost = RegisterSample( "Continuous", "Speculative Ghost", SpeculativeGhost::Create );

// This shows a fast moving body that uses continuous collision versus static and dynamic bodies.
// This is achieved by setting the ball body as a *bullet*.
class Pinball : public Sample
Expand Down
51 changes: 48 additions & 3 deletions samples/sample_stacking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,16 @@ class VerticalStack : public Sample

static int sampleVerticalStack = RegisterSample( "Stacking", "Vertical Stack", VerticalStack::Create );

// This shows how to handle high gravity and small shapes using a small time step
// A simple circle stack that also shows how to collect hit events
class CircleStack : public Sample
{
public:

struct Event
{
int indexA, indexB;
};

explicit CircleStack( Settings& settings )
: Sample( settings )
{
Expand All @@ -396,11 +402,16 @@ class CircleStack : public Sample
g_camera.m_zoom = 6.0f;
}

int shapeIndex = 0;

{
b2BodyDef bodyDef = b2DefaultBodyDef();
b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef );

b2ShapeDef shapeDef = b2DefaultShapeDef();
shapeDef.userData = reinterpret_cast<void*>( intptr_t( shapeIndex ) );
shapeIndex += 1;

b2Segment segment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } };
b2CreateSegmentShape( groundId, &shapeDef, &segment );
}
Expand All @@ -409,9 +420,11 @@ class CircleStack : public Sample
b2World_SetContactTuning( m_worldId, 0.25f * 360.0f, 10.0f, 3.0f );

b2Circle circle = {};
circle.radius = 0.5f;
circle.radius = 0.25f;

b2ShapeDef shapeDef = b2DefaultShapeDef();
shapeDef.enableHitEvents = true;

b2BodyDef bodyDef = b2DefaultBodyDef();
bodyDef.type = b2_dynamicBody;

Expand All @@ -422,16 +435,48 @@ class CircleStack : public Sample
bodyDef.position.y = y;

b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef );

shapeDef.userData = reinterpret_cast<void*>( intptr_t( shapeIndex ) );
shapeIndex += 1;
b2CreateCircleShape( bodyId, &shapeDef, &circle );

y += 1.0f;
y += 2.0f;
}
}

void Step( Settings& settings ) override
{
Sample::Step( settings );

b2ContactEvents events = b2World_GetContactEvents( m_worldId );
for ( int i = 0; i < events.hitCount; ++i )
{
b2ContactHitEvent* event = events.hitEvents + i;

void* userDataA = b2Shape_GetUserData( event->shapeIdA );
void* userDataB = b2Shape_GetUserData( event->shapeIdB );
int indexA = static_cast<int>( reinterpret_cast<intptr_t>( userDataA ) );
int indexB = static_cast<int>( reinterpret_cast<intptr_t>( userDataB ) );

g_draw.DrawPoint( event->point, 10.0f, b2_colorWhite );

m_events.push_back( { indexA, indexB } );
}

int eventCount = m_events.size();
for (int i = 0; i < eventCount; ++i)
{
g_draw.DrawString( 5, m_textLine, "%d, %d", m_events[i].indexA, m_events[i].indexB );
m_textLine += m_textIncrement;
}
}

static Sample* Create( Settings& settings )
{
return new CircleStack( settings );
}

std::vector<Event> m_events;
};

static int sampleCircleStack = RegisterSample( "Stacking", "Circle Stack", CircleStack::Create );
Expand Down
7 changes: 5 additions & 2 deletions src/broad_phase.c
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,11 @@ void b2UpdateBroadPhasePairs( b2World* world )

int minRange = 64;
void* userPairTask = world->enqueueTaskFcn( &b2FindPairsTask, moveCount, minRange, world, world->userTaskContext );
world->finishTaskFcn( userPairTask, world->userTaskContext );
world->taskCount += 1;
if (userPairTask != NULL)
{
world->finishTaskFcn( userPairTask, world->userTaskContext );
world->taskCount += 1;
}

b2TracyCZoneNC( create_contacts, "Create Contacts", b2_colorGold, true );

Expand Down
2 changes: 1 addition & 1 deletion src/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ extern float b2_lengthUnitsPerMeter;
#define b2_maxWorkers 64

// Maximum number of colors in the constraint graph. Constraints that cannot
// find a color are added to the overflow set which are solved single-threaded.
// find a color are added to the overflow set which are solved single-threaded.
#define b2_graphColorCount 12

// A small length used as a collision and constraint tolerance. Usually it is
Expand Down
4 changes: 3 additions & 1 deletion src/solver.c
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,9 @@ void b2Solve( b2World* world, b2StepContext* stepContext )
{
b2ManifoldPoint* mp = contactSim->manifold.points + k;
float approachSpeed = -mp->normalVelocity;
if ( approachSpeed > event.approachSpeed && mp->normalImpulse > 0.0f )

// Need to check max impulse because the point may be speculative and not colliding
if ( approachSpeed > event.approachSpeed && mp->maxNormalImpulse > 0.0f )
{
event.approachSpeed = approachSpeed;
event.point = mp->point;
Expand Down
Loading