Skip to content

Commit 0c05e34

Browse files
committed
fix missing hit events
add test for null pair task add manifold to contact begin events
1 parent df7373c commit 0c05e34

File tree

9 files changed

+148
-82
lines changed

9 files changed

+148
-82
lines changed

include/box2d/box2d.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ B2_API void b2World_Explode( b2WorldId worldId, b2Vec2 position, float radius, f
156156
/// @note Advanced feature
157157
B2_API void b2World_SetContactTuning( b2WorldId worldId, float hertz, float dampingRatio, float pushVelocity );
158158

159+
/// Adjust joint tuning parameters
160+
/// @param worldId The world id
161+
/// @param hertz The contact stiffness (cycles per second)
162+
/// @param dampingRatio The contact bounciness with 1 being critical damping (non-dimensional)
163+
/// @note Advanced feature
164+
B2_API void b2World_SetJointTuning( b2WorldId worldId, float hertz, float dampingRatio );
165+
159166
/// Enable/disable constraint warm starting. Advanced feature for testing. Disabling
160167
/// sleeping greatly reduces stability and provides no performance gain.
161168
B2_API void b2World_EnableWarmStarting( b2WorldId worldId, bool flag );

include/box2d/collision.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,10 +488,11 @@ typedef struct b2ManifoldPoint
488488
b2Vec2 point;
489489

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

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

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

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

510511
/// Relative normal velocity pre-solve. Used for hit events. If the normal impulse is

include/box2d/types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,9 @@ typedef struct b2ContactBeginTouchEvent
928928

929929
/// Id of the second shape
930930
b2ShapeId shapeIdB;
931+
932+
/// The initial contact manifold
933+
b2Manifold manifold;
931934
} b2ContactBeginTouchEvent;
932935

933936
/// An end touch event is generated when two shapes stop touching.

samples/sample_continuous.cpp

Lines changed: 62 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@
1111
#include <GLFW/glfw3.h>
1212
#include <imgui.h>
1313

14-
// This tests continuous collision robustness and also demonstrates the speed limits imposed
15-
// by b2_maxTranslation and b2_maxRotation.
16-
struct HitEvent
17-
{
18-
b2Vec2 point;
19-
float speed;
20-
int stepIndex;
21-
};
2214

2315
class BounceHouse : public Sample
2416
{
@@ -30,6 +22,13 @@ class BounceHouse : public Sample
3022
e_boxShape
3123
};
3224

25+
struct HitEvent
26+
{
27+
b2Vec2 point;
28+
float speed;
29+
int stepIndex;
30+
};
31+
3332
explicit BounceHouse( Settings& settings )
3433
: Sample( settings )
3534
{
@@ -389,64 +388,8 @@ class SkinnyBox : public Sample
389388

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

392-
class SpeculativeBug : public Sample
393-
{
394-
public:
395-
explicit SpeculativeBug( Settings& settings )
396-
: Sample( settings )
397-
{
398-
if ( settings.restart == false )
399-
{
400-
g_camera.m_center = { 1.0f, 5.0f };
401-
g_camera.m_zoom = 25.0f * 0.25f;
402-
}
403-
404-
{
405-
b2BodyDef bodyDef = b2DefaultBodyDef();
406-
b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef );
407-
408-
b2Segment segment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } };
409-
b2ShapeDef shapeDef = b2DefaultShapeDef();
410-
b2CreateSegmentShape( groundId, &shapeDef, &segment );
411-
412-
shapeDef.friction = 0.0f;
413-
b2Polygon box = b2MakeOffsetBox( 0.05f, 1.0f, { 0.0f, 1.0f }, b2Rot_identity );
414-
b2CreatePolygonShape( groundId, &shapeDef, &box );
415-
}
416-
417-
b2BodyDef bodyDef = b2DefaultBodyDef();
418-
bodyDef.type = b2_dynamicBody;
419-
for (int i = 0; i < 2; ++i)
420-
{
421-
if (i == 0)
422-
{
423-
bodyDef.position = { -0.8f, 0.25f };
424-
bodyDef.isAwake = false;
425-
}
426-
else
427-
{
428-
bodyDef.position = { 0.8f, 2.0f };
429-
bodyDef.isAwake = true;
430-
}
431-
432-
b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef );
433-
434-
b2ShapeDef shapeDef = b2DefaultShapeDef();
435-
b2Capsule capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, 0.25f };
436-
b2CreateCapsuleShape( bodyId, &shapeDef, &capsule );
437-
}
438-
}
439-
440-
static Sample* Create( Settings& settings )
441-
{
442-
return new SpeculativeBug( settings );
443-
}
444-
};
445-
446-
static int sampleSpeculativeBug = RegisterSample( "Continuous", "Speculative Bug", SpeculativeBug::Create );
447-
448-
// This sample shows ghost collisions
449-
class GhostCollision : public Sample
391+
// This sample shows ghost bumps
392+
class GhostBumps : public Sample
450393
{
451394
public:
452395
enum ShapeType
@@ -456,7 +399,7 @@ class GhostCollision : public Sample
456399
e_boxShape
457400
};
458401

459-
explicit GhostCollision( Settings& settings )
402+
explicit GhostBumps( Settings& settings )
460403
: Sample( settings )
461404
{
462405
if ( settings.restart == false )
@@ -671,7 +614,7 @@ class GhostCollision : public Sample
671614
ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
672615
ImGui::SetNextWindowSize( ImVec2( 180.0f, height ) );
673616

674-
ImGui::Begin( "Ghost Collision", nullptr, ImGuiWindowFlags_NoResize );
617+
ImGui::Begin( "Ghost Bumps", nullptr, ImGuiWindowFlags_NoResize );
675618
ImGui::PushItemWidth( 100.0f );
676619

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

721664
static Sample* Create( Settings& settings )
722665
{
723-
return new GhostCollision( settings );
666+
return new GhostBumps( settings );
724667
}
725668

726669
b2BodyId m_groundId;
@@ -733,7 +676,7 @@ class GhostCollision : public Sample
733676
bool m_useChain;
734677
};
735678

736-
static int sampleGhostCollision = RegisterSample( "Continuous", "Ghost Collision", GhostCollision::Create );
679+
static int sampleGhostCollision = RegisterSample( "Continuous", "Ghost Collision", GhostBumps::Create );
737680

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

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

732+
// This shows that while Box2D uses speculative collision, it does not lead to speculative ghost collisions at small distances
733+
class SpeculativeGhost : public Sample
734+
{
735+
public:
736+
explicit SpeculativeGhost( Settings& settings )
737+
: Sample( settings )
738+
{
739+
if ( settings.restart == false )
740+
{
741+
g_camera.m_center = { 0.0f, 1.75f };
742+
g_camera.m_zoom = 2.0f;
743+
}
744+
745+
{
746+
b2BodyDef bodyDef = b2DefaultBodyDef();
747+
b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef );
748+
749+
b2ShapeDef shapeDef = b2DefaultShapeDef();
750+
b2Segment segment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } };
751+
b2CreateSegmentShape( groundId, &shapeDef, &segment );
752+
753+
b2Polygon box = b2MakeOffsetBox( 1.0f, 0.1f, { 0.0f, 0.9f }, b2Rot_identity );
754+
b2CreatePolygonShape( groundId, &shapeDef, &box );
755+
}
756+
757+
{
758+
b2BodyDef bodyDef = b2DefaultBodyDef();
759+
bodyDef.type = b2_dynamicBody;
760+
761+
// The speculative distance is 0.02 meters, so this avoid it
762+
bodyDef.position = { 0.015f, 2.515f };
763+
bodyDef.linearVelocity = { 0.1f * 1.25f * settings.hertz, -0.1f * 1.25f * settings.hertz };
764+
bodyDef.gravityScale = 0.0f;
765+
b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef );
766+
767+
b2ShapeDef shapeDef = b2DefaultShapeDef();
768+
b2Polygon box = b2MakeSquare( 0.25f );
769+
b2CreatePolygonShape( bodyId, &shapeDef, &box );
770+
}
771+
}
772+
773+
static Sample* Create( Settings& settings )
774+
{
775+
return new SpeculativeGhost( settings );
776+
}
777+
};
778+
779+
static int sampleSpeculativeGhost = RegisterSample( "Continuous", "Speculative Ghost", SpeculativeGhost::Create );
780+
789781
// This shows a fast moving body that uses continuous collision versus static and dynamic bodies.
790782
// This is achieved by setting the ball body as a *bullet*.
791783
class Pinball : public Sample

samples/sample_stacking.cpp

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,16 @@ class VerticalStack : public Sample
383383

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

386-
// This shows how to handle high gravity and small shapes using a small time step
386+
// A simple circle stack that also shows how to collect hit events
387387
class CircleStack : public Sample
388388
{
389389
public:
390+
391+
struct Event
392+
{
393+
int indexA, indexB;
394+
};
395+
390396
explicit CircleStack( Settings& settings )
391397
: Sample( settings )
392398
{
@@ -396,11 +402,16 @@ class CircleStack : public Sample
396402
g_camera.m_zoom = 6.0f;
397403
}
398404

405+
int shapeIndex = 0;
406+
399407
{
400408
b2BodyDef bodyDef = b2DefaultBodyDef();
401409
b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef );
402410

403411
b2ShapeDef shapeDef = b2DefaultShapeDef();
412+
shapeDef.userData = reinterpret_cast<void*>( intptr_t( shapeIndex ) );
413+
shapeIndex += 1;
414+
404415
b2Segment segment = { { -10.0f, 0.0f }, { 10.0f, 0.0f } };
405416
b2CreateSegmentShape( groundId, &shapeDef, &segment );
406417
}
@@ -409,9 +420,11 @@ class CircleStack : public Sample
409420
b2World_SetContactTuning( m_worldId, 0.25f * 360.0f, 10.0f, 3.0f );
410421

411422
b2Circle circle = {};
412-
circle.radius = 0.5f;
423+
circle.radius = 0.25f;
413424

414425
b2ShapeDef shapeDef = b2DefaultShapeDef();
426+
shapeDef.enableHitEvents = true;
427+
415428
b2BodyDef bodyDef = b2DefaultBodyDef();
416429
bodyDef.type = b2_dynamicBody;
417430

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

424437
b2BodyId bodyId = b2CreateBody( m_worldId, &bodyDef );
438+
439+
shapeDef.userData = reinterpret_cast<void*>( intptr_t( shapeIndex ) );
440+
shapeIndex += 1;
425441
b2CreateCircleShape( bodyId, &shapeDef, &circle );
426442

427-
y += 1.0f;
443+
y += 2.0f;
444+
}
445+
}
446+
447+
void Step( Settings& settings ) override
448+
{
449+
Sample::Step( settings );
450+
451+
b2ContactEvents events = b2World_GetContactEvents( m_worldId );
452+
for ( int i = 0; i < events.hitCount; ++i )
453+
{
454+
b2ContactHitEvent* event = events.hitEvents + i;
455+
456+
void* userDataA = b2Shape_GetUserData( event->shapeIdA );
457+
void* userDataB = b2Shape_GetUserData( event->shapeIdB );
458+
int indexA = static_cast<int>( reinterpret_cast<intptr_t>( userDataA ) );
459+
int indexB = static_cast<int>( reinterpret_cast<intptr_t>( userDataB ) );
460+
461+
g_draw.DrawPoint( event->point, 10.0f, b2_colorWhite );
462+
463+
m_events.push_back( { indexA, indexB } );
464+
}
465+
466+
int eventCount = m_events.size();
467+
for (int i = 0; i < eventCount; ++i)
468+
{
469+
g_draw.DrawString( 5, m_textLine, "%d, %d", m_events[i].indexA, m_events[i].indexB );
470+
m_textLine += m_textIncrement;
428471
}
429472
}
430473

431474
static Sample* Create( Settings& settings )
432475
{
433476
return new CircleStack( settings );
434477
}
478+
479+
std::vector<Event> m_events;
435480
};
436481

437482
static int sampleCircleStack = RegisterSample( "Stacking", "Circle Stack", CircleStack::Create );

src/broad_phase.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,11 @@ void b2UpdateBroadPhasePairs( b2World* world )
360360

361361
int minRange = 64;
362362
void* userPairTask = world->enqueueTaskFcn( &b2FindPairsTask, moveCount, minRange, world, world->userTaskContext );
363-
world->finishTaskFcn( userPairTask, world->userTaskContext );
364-
world->taskCount += 1;
363+
if (userPairTask != NULL)
364+
{
365+
world->finishTaskFcn( userPairTask, world->userTaskContext );
366+
world->taskCount += 1;
367+
}
365368

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

src/core.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ extern float b2_lengthUnitsPerMeter;
139139
#define b2_maxWorkers 64
140140

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

145145
// A small length used as a collision and constraint tolerance. Usually it is

src/solver.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1721,7 +1721,9 @@ void b2Solve( b2World* world, b2StepContext* stepContext )
17211721
{
17221722
b2ManifoldPoint* mp = contactSim->manifold.points + k;
17231723
float approachSpeed = -mp->normalVelocity;
1724-
if ( approachSpeed > event.approachSpeed && mp->normalImpulse > 0.0f )
1724+
1725+
// Need to check max impulse because the point may be speculative and not colliding
1726+
if ( approachSpeed > event.approachSpeed && mp->maxNormalImpulse > 0.0f )
17251727
{
17261728
event.approachSpeed = approachSpeed;
17271729
event.point = mp->point;

0 commit comments

Comments
 (0)