Skip to content

Commit c7ce16a

Browse files
authored
Sensor unit test (erincatto#870)
added sensor unit test added null checks added WASM link updated sensor documentation added b2Body_GetLocalPointVelocity and b2Body_GetWorldPointVelocity added b2Body_GetMass erincatto#848 erincatto#864 erincatto#870
1 parent f3202f2 commit c7ce16a

8 files changed

Lines changed: 213 additions & 21 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,6 @@ Support development of Box2D through [Github Sponsors](https://github.com/sponso
8484
Please consider starring this repository and subscribing to my [YouTube channel](https://www.youtube.com/@erin_catto).
8585

8686
## Ports, wrappers, and bindings
87-
- https://github.com/EnokViking/Box2DBeef
88-
- https://github.com/HolyBlackCat/box2cpp
87+
- Beef bindings - https://github.com/EnokViking/Box2DBeef
88+
- C++ bindings - https://github.com/HolyBlackCat/box2cpp
89+
- WASM - https://github.com/Birch-san/box2d3-wasm

docs/simulation.md

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -877,18 +877,50 @@ is a shape that detects overlap but does not produce a response.
877877

878878
You can flag any shape as being a sensor. Sensors may be static,
879879
kinematic, or dynamic. Remember that you may have multiple shapes per
880-
body and you can have any mix of sensors and solid shapes. Also,
881-
sensors only form contacts when at least one body is dynamic, so you
882-
will not get sensors overlap detection for kinematic versus kinematic,
883-
kinematic versus static, or static versus static. Finally sensors do not
880+
body and you can have any mix of sensors and solid shapes. Sensors do not
884881
detect other sensors.
885882

883+
Sensors are processed at the end of the world step and generate begin and end
884+
events without delay. User operations may cause overlaps to begin or end. These
885+
are processed the next time step. Such operations include:
886+
- destroying a body or shape
887+
- changing a shape filter
888+
- disabling or enabling a body
889+
- setting a body transform
890+
886891
Sensors do not detect objects that pass through the sensor shape within
887892
one time step. If you have fast moving object and/or small sensors then you
888893
should use a ray or shape cast to detect these events.
889894

890-
Sensor overlap detection is achieved using events, which are described
891-
below.
895+
You can access the current sensor overlaps. Be careful because some shape ids may
896+
be invalid due to a shape being destroyed. Use `b2Shape_IsValid` to ensure an
897+
overlapping shape is still valid.
898+
899+
```cpp
900+
// First determine the required array capacity to hold all the overlapping shape ids.
901+
int capacity = b2Shape_GetSensorCapacity( sensorShapeId );
902+
std::vector<b2ShapeId> overlaps;
903+
overlaps.resize( capacity );
904+
905+
// Now get all overlaps and record the actual count
906+
int count = b2Shape_GetSensorOverlaps( sensorShapeId, overlaps.data(), capacity );
907+
overlaps.resize( count );
908+
909+
for ( int i = 0; i < count; ++i )
910+
{
911+
b2ShapeId visitorId = overlaps[i];
912+
913+
// Ensure the visitorId is valid
914+
if ( b2Shape_IsValid( visitorId ) == false )
915+
{
916+
continue;
917+
}
918+
919+
// process overlap using game logic
920+
}
921+
```
922+
923+
Sensor overlap can also be achieved using events, which are described below.
892924

893925
## Contacts
894926
Contacts are internal objects created by Box2D to manage collision between pairs of
@@ -1028,18 +1060,6 @@ for (int i = 0; i < sensorEvents.endCount; ++i)
10281060
Sensor events should be processed after the world step and before other game logic. This should
10291061
help you avoid processing stale data.
10301062

1031-
There are several user operations that can cause sensors to stop touching. Such operations
1032-
include:
1033-
- destroying a body or shape
1034-
- changing the filter on a shape
1035-
- disabling a body
1036-
- setting the body transform
1037-
These may generate end-touch events and these events are included with simulation events available
1038-
after the next call to `b2World_Step`.
1039-
1040-
Sensor events are only enabled for a non-sensor shape if `b2ShapeDef::enableSensorEvents`
1041-
is true.
1042-
10431063
### Contact Events
10441064
Contact events are available after each world step. Like sensor events these should be
10451065
retrieved and processed before performing other game logic. Otherwise

include/box2d/box2d.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ B2_API void b2Body_SetLinearVelocity( b2BodyId bodyId, b2Vec2 linearVelocity );
288288
/// Set the angular velocity of a body in radians per second
289289
B2_API void b2Body_SetAngularVelocity( b2BodyId bodyId, float angularVelocity );
290290

291+
/// Get the linear velocity of a local point attached to a body. Usually in meters per second.
292+
B2_API b2Vec2 b2Body_GetLocalPointVelocity( b2BodyId bodyId, b2Vec2 localPoint );
293+
294+
/// Get the linear velocity of a world point attached to a body. Usually in meters per second.
295+
B2_API b2Vec2 b2Body_GetWorldPointVelocity( b2BodyId bodyId, b2Vec2 worldPoint );
296+
291297
/// Apply a force at a world point. If the force is not applied at the center of mass,
292298
/// it will generate a torque and affect the angular velocity. This optionally wakes up the body.
293299
/// The force is ignored if the body is not awake.
@@ -649,6 +655,9 @@ B2_API int b2Shape_GetSensorOverlaps( b2ShapeId shapeId, b2ShapeId* overlaps, in
649655
/// Get the current world AABB
650656
B2_API b2AABB b2Shape_GetAABB( b2ShapeId shapeId );
651657

658+
/// Get the mass data for a shape
659+
B2_API b2MassData b2Shape_GetMassData( b2ShapeId shapeId );
660+
652661
/// Get the closest point on a shape to a target point. Target and result are in world space.
653662
/// todo need sample
654663
B2_API b2Vec2 b2Shape_GetClosestPoint( b2ShapeId shapeId, b2Vec2 target );

samples/sample_bodies.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,17 @@ class Weeble : public Sample
553553
Sample::Step( settings );
554554

555555
g_draw.DrawCircle( m_explosionPosition, m_explosionRadius, b2_colorAzure );
556+
557+
// This shows how to get the velocity of a point on a body
558+
b2Vec2 localPoint = { 0.0f, 2.0f };
559+
b2Vec2 worldPoint = b2Body_GetWorldPoint( m_weebleId, localPoint );
560+
561+
b2Vec2 v1 = b2Body_GetLocalPointVelocity( m_weebleId, localPoint );
562+
b2Vec2 v2 = b2Body_GetWorldPointVelocity( m_weebleId, worldPoint );
563+
564+
b2Vec2 offset = { 0.05f, 0.0f };
565+
g_draw.DrawSegment( worldPoint, worldPoint + v1, b2_colorRed );
566+
g_draw.DrawSegment( worldPoint + offset, worldPoint + v2 + offset, b2_colorGreen );
556567
}
557568

558569
static Sample* Create( Settings& settings )

samples/sample_collision.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2368,7 +2368,7 @@ class Manifold : public Sample
23682368
}
23692369
else
23702370
{
2371-
g_draw.DrawPoint( p1, 5.0f, b2_colorBlue );
2371+
g_draw.DrawPoint( p1, 10.0f, b2_colorBlue );
23722372
}
23732373

23742374
if ( m_showIds )
@@ -2551,6 +2551,24 @@ class Manifold : public Sample
25512551

25522552
offset = { -10.0f, 0.0f };
25532553

2554+
// square-square
2555+
{
2556+
b2Polygon box1 = b2MakeSquare( 0.5f );
2557+
b2Polygon box = b2MakeSquare( 0.5f );
2558+
2559+
b2Transform transform1 = { offset, b2Rot_identity };
2560+
b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };
2561+
2562+
b2Manifold m = b2CollidePolygons( &box1, transform1, &box, transform2 );
2563+
2564+
g_draw.DrawSolidPolygon( transform1, box1.vertices, box1.count, box1.radius, color1 );
2565+
g_draw.DrawSolidPolygon( transform2, box.vertices, box.count, box.radius, color2 );
2566+
2567+
DrawManifold( &m, transform1.p, transform2.p );
2568+
2569+
offset = b2Add( offset, increment );
2570+
}
2571+
25542572
// box-box
25552573
{
25562574
b2Polygon box1 = b2MakeBox( 2.0f, 0.1f );

src/body.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,42 @@ void b2Body_SetAngularVelocity( b2BodyId bodyId, float angularVelocity )
814814
state->angularVelocity = angularVelocity;
815815
}
816816

817+
b2Vec2 b2Body_GetLocalPointVelocity(b2BodyId bodyId, b2Vec2 localPoint)
818+
{
819+
b2World* world = b2GetWorld( bodyId.world0 );
820+
b2Body* body = b2GetBodyFullId( world, bodyId );
821+
b2BodyState* state = b2GetBodyState( world, body );
822+
if ( state == NULL )
823+
{
824+
return b2Vec2_zero;
825+
}
826+
827+
b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, body->setIndex );
828+
b2BodySim* bodySim = b2BodySimArray_Get( &set->bodySims, body->localIndex );
829+
830+
b2Vec2 r = b2RotateVector( bodySim->transform.q, b2Sub(localPoint, bodySim->localCenter) );
831+
b2Vec2 v = b2Add(state->linearVelocity, b2CrossSV( state->angularVelocity, r ));
832+
return v;
833+
}
834+
835+
b2Vec2 b2Body_GetWorldPointVelocity(b2BodyId bodyId, b2Vec2 worldPoint)
836+
{
837+
b2World* world = b2GetWorld( bodyId.world0 );
838+
b2Body* body = b2GetBodyFullId( world, bodyId );
839+
b2BodyState* state = b2GetBodyState( world, body );
840+
if ( state == NULL )
841+
{
842+
return b2Vec2_zero;
843+
}
844+
845+
b2SolverSet* set = b2SolverSetArray_Get( &world->solverSets, body->setIndex );
846+
b2BodySim* bodySim = b2BodySimArray_Get( &set->bodySims, body->localIndex );
847+
848+
b2Vec2 r = b2Sub( worldPoint, bodySim->center );
849+
b2Vec2 v = b2Add( state->linearVelocity, b2CrossSV( state->angularVelocity, r ) );
850+
return v;
851+
}
852+
817853
void b2Body_ApplyForce( b2BodyId bodyId, b2Vec2 force, b2Vec2 point, bool wake )
818854
{
819855
b2World* world = b2GetWorld( bodyId.world0 );

src/shape.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ static void b2DestroyShapeInternal( b2World* world, b2Shape* shape, b2Body* body
316316
void b2DestroyShape( b2ShapeId shapeId, bool updateBodyMass )
317317
{
318318
b2World* world = b2GetWorldLocked( shapeId.world0 );
319+
if ( world == NULL )
320+
{
321+
return;
322+
}
319323

320324
b2Shape* shape = b2GetShape( world, shapeId );
321325

@@ -449,6 +453,10 @@ b2ChainId b2CreateChain( b2BodyId bodyId, const b2ChainDef* def )
449453
void b2DestroyChain( b2ChainId chainId )
450454
{
451455
b2World* world = b2GetWorldLocked( chainId.world0 );
456+
if ( world == NULL )
457+
{
458+
return;
459+
}
452460

453461
b2ChainShape* chain = b2GetChainShape( world, chainId );
454462
bool wakeBodies = true;
@@ -503,13 +511,23 @@ b2WorldId b2Chain_GetWorld( b2ChainId chainId )
503511
int b2Chain_GetSegmentCount( b2ChainId chainId )
504512
{
505513
b2World* world = b2GetWorldLocked( chainId.world0 );
514+
if ( world == NULL )
515+
{
516+
return 0;
517+
}
518+
506519
b2ChainShape* chain = b2GetChainShape( world, chainId );
507520
return chain->count;
508521
}
509522

510523
int b2Chain_GetSegments( b2ChainId chainId, b2ShapeId* segmentArray, int capacity )
511524
{
512525
b2World* world = b2GetWorldLocked( chainId.world0 );
526+
if ( world == NULL )
527+
{
528+
return 0;
529+
}
530+
513531
b2ChainShape* chain = b2GetChainShape( world, chainId );
514532

515533
int count = b2MinInt( chain->count, capacity );
@@ -1505,6 +1523,18 @@ b2AABB b2Shape_GetAABB( b2ShapeId shapeId )
15051523
return shape->aabb;
15061524
}
15071525

1526+
b2MassData b2Shape_GetMassData(b2ShapeId shapeId)
1527+
{
1528+
b2World* world = b2GetWorld( shapeId.world0 );
1529+
if ( world == NULL )
1530+
{
1531+
return ( b2MassData ){ 0 };
1532+
}
1533+
1534+
b2Shape* shape = b2GetShape( world, shapeId );
1535+
return b2ComputeShapeMass( shape );
1536+
}
1537+
15081538
b2Vec2 b2Shape_GetClosestPoint( b2ShapeId shapeId, b2Vec2 target )
15091539
{
15101540
b2World* world = b2GetWorld( shapeId.world0 );

test/test_world.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,72 @@ int TestWorldCoverage( void )
326326
return 0;
327327
}
328328

329+
static int TestSensor( void )
330+
{
331+
b2WorldDef worldDef = b2DefaultWorldDef();
332+
b2WorldId worldId = b2CreateWorld( &worldDef );
333+
334+
// Wall from x = 1 to x = 2
335+
b2BodyDef bodyDef = b2DefaultBodyDef();
336+
bodyDef.type = b2_staticBody;
337+
bodyDef.position.x = 1.5f;
338+
bodyDef.position.y = 11.0f;
339+
b2BodyId wallId = b2CreateBody( worldId, &bodyDef );
340+
b2Polygon box = b2MakeBox( 0.5f, 10.0f );
341+
b2ShapeDef shapeDef = b2DefaultShapeDef();
342+
b2CreatePolygonShape( wallId, &shapeDef, &box );
343+
344+
// Bullet fired towards the wall
345+
bodyDef = b2DefaultBodyDef();
346+
bodyDef.type = b2_dynamicBody;
347+
bodyDef.isBullet = true;
348+
bodyDef.gravityScale = 0.0f;
349+
bodyDef.position = (b2Vec2){ 7.39814, 4.0 };
350+
bodyDef.linearVelocity = (b2Vec2){ -20.0f, 0.0f };
351+
b2BodyId bulletId = b2CreateBody( worldId, &bodyDef );
352+
shapeDef = b2DefaultShapeDef();
353+
shapeDef.isSensor = true;
354+
b2Circle circle = { { 0.0f, 0.0f }, 0.1f };
355+
b2CreateCircleShape( bulletId, &shapeDef, &circle );
356+
357+
int beginCount = 0;
358+
int endCount = 0;
359+
360+
while ( true )
361+
{
362+
float timeStep = 1.0f / 60.0f;
363+
int subStepCount = 4;
364+
b2World_Step( worldId, timeStep, subStepCount );
365+
366+
b2Vec2 bulletPos = b2Body_GetPosition( bulletId );
367+
//printf( "Bullet pos: %g %g\n", bulletPos.x, bulletPos.y );
368+
369+
b2SensorEvents events = b2World_GetSensorEvents( worldId );
370+
371+
if ( events.beginCount > 0 )
372+
{
373+
beginCount += 1;
374+
}
375+
376+
if ( events.endCount > 0 )
377+
{
378+
endCount += 1;
379+
}
380+
381+
if ( bulletPos.x < -1.0f )
382+
{
383+
break;
384+
}
385+
}
386+
387+
b2DestroyWorld( worldId );
388+
389+
ENSURE( beginCount == 1 );
390+
ENSURE( endCount == 1 );
391+
392+
return 0;
393+
}
394+
329395
int WorldTest( void )
330396
{
331397
RUN_SUBTEST( HelloWorld );
@@ -334,6 +400,7 @@ int WorldTest( void )
334400
RUN_SUBTEST( TestIsValid );
335401
RUN_SUBTEST( TestWorldRecycle );
336402
RUN_SUBTEST( TestWorldCoverage );
403+
RUN_SUBTEST( TestSensor );
337404

338405
return 0;
339406
}

0 commit comments

Comments
 (0)