Skip to content

Commit

Permalink
fix missing hit events
Browse files Browse the repository at this point in the history
add test for null pair task
add manifold to contact begin events
  • Loading branch information
erincatto committed Sep 29, 2024
1 parent df7373c commit 0c05e34
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 82 deletions.
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 Collision", 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

0 comments on commit 0c05e34

Please sign in to comment.