Skip to content

Commit d0bf516

Browse files
authored
Sensor tree (#860)
Sensors are now handled separately from regular collision. This allows sensors to be on any body type and detect any body type. Sensors are still shapes on bodies, but they don't not stop checking for overlaps when the body is sleeping. Sensors are processed at the end of the step so sensor events have no delay. Sensor events no longer need to be enabled on bodies. Instead the user should setup the appropriate filters to enable or disable sensor events. Note: I did not test what happens if you disable a body with a sensor. Replaced b2Timer with uint64_t Bodies can now have names for debugging Avoid deep clipping into chain shapes by fast bodies
1 parent f377034 commit d0bf516

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+2702
-1749
lines changed

.github/workflows/build.yml

+4-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- uses: actions/checkout@v4
1919

2020
- name: Configure CMake
21-
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF
21+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF -DBOX2D_VALIDATE=ON
2222

2323
- name: Build
2424
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
@@ -35,7 +35,7 @@ jobs:
3535
- uses: actions/checkout@v4
3636

3737
- name: Configure CMake
38-
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_C_COMPILER=clang -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF
38+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_C_COMPILER=clang -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF -DBOX2D_VALIDATE=ON
3939

4040
- name: Build
4141
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
@@ -52,8 +52,7 @@ jobs:
5252
- uses: actions/checkout@v4
5353

5454
- name: Configure CMake
55-
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF
56-
# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF
55+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF -DBOX2D_VALIDATE=ON
5756

5857
- name: Build
5958
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
@@ -75,7 +74,7 @@ jobs:
7574
arch: x64
7675

7776
- name: Configure CMake
78-
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF
77+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF -DBOX2D_VALIDATE=ON
7978
# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF
8079

8180
- name: Build

CMakeLists.txt

+4-6
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,6 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
5757
cmake_dependent_option(BOX2D_AVX2 "Enable AVX2" OFF "BOX2D_ENABLE_SIMD" OFF)
5858
endif()
5959

60-
if(PROJECT_IS_TOP_LEVEL)
61-
# Needed for samples.exe to find box2d.dll
62-
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
63-
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
64-
endif()
65-
6660
# C++17 needed for imgui
6761
set(CMAKE_CXX_STANDARD 17)
6862
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
@@ -82,6 +76,10 @@ if(PROJECT_IS_TOP_LEVEL)
8276
option(BOX2D_VALIDATE "Enable heavy validation" ON)
8377
option(BOX2D_UNIT_TESTS "Build the Box2D unit tests" ON)
8478

79+
# Needed for samples.exe to find box2d.dll
80+
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
81+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
82+
8583
include(GNUInstallDirs)
8684

8785
install(

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ The Box2D library and samples build and run on Windows, Linux, and Mac.
6262

6363
Box2D should be built on recent versions of clang and gcc. You will need the latest Visual Studio version for C11 atomics to compile (17.8.3+).
6464

65-
AVX2 CPU support is assumed on x64. You can turn this off in the CMake options and use SSE2 instead. There are some compatibility issues with very old CPUs.
66-
6765
## Documentation
6866
- [Manual](https://box2d.org/documentation/)
6967
- [Migration Guide](https://github.com/erincatto/box2d/blob/main/docs/migration.md)

benchmark/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ add_executable(benchmark ${BOX2D_BENCHMARK_FILES})
77

88
set_target_properties(benchmark PROPERTIES C_STANDARD 17)
99

10-
if (MSVC)
10+
if (MSVC AND CMAKE_C_COMPILER_ID MATCHES "MSVC")
1111
target_compile_options(benchmark PRIVATE /experimental:c11atomics)
1212
endif()
1313

benchmark/main.c

+119-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#define _CRT_SECURE_NO_WARNINGS
55

66
#include "TaskScheduler_c.h"
7-
87
#include "benchmarks.h"
98

109
#include "box2d/box2d.h"
@@ -28,7 +27,7 @@
2827
#define MAYBE_UNUSED( x ) ( (void)( x ) )
2928

3029
typedef void CreateFcn( b2WorldId worldId );
31-
typedef void StepFcn( b2WorldId worldId, int stepCount );
30+
typedef float StepFcn( b2WorldId worldId, int stepCount );
3231

3332
typedef struct Benchmark
3433
{
@@ -115,11 +114,25 @@ static void FinishTask( void* userTask, void* userContext )
115114
enkiWaitForTaskSet( scheduler, task );
116115
}
117116

118-
// Box2D benchmark application. On Windows I recommend running this in an administrator command prompt. Don't use Windows Terminal.
119-
// Or use affinity. [0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80]
117+
static void MinProfile( b2Profile* p1, const b2Profile* p2 )
118+
{
119+
p1->step = b2MinFloat( p1->step, p2->step );
120+
p1->pairs = b2MinFloat( p1->pairs, p2->pairs );
121+
p1->collide = b2MinFloat( p1->collide, p2->collide );
122+
p1->solveConstraints = b2MinFloat( p1->solveConstraints, p2->solveConstraints );
123+
p1->transforms = b2MinFloat( p1->transforms, p2->transforms );
124+
p1->refit = b2MinFloat( p1->refit, p2->refit );
125+
p1->sleepIslands = b2MinFloat( p1->sleepIslands, p2->sleepIslands );
126+
}
127+
128+
// Box2D benchmark application. On Windows I recommend running this in an administrator command prompt. Don't use Windows
129+
// Terminal. Or use affinity. [0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80]
120130
// Examples:
121131
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=4 -w=4
122132
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=8
133+
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=4 -w=4 -b=3 -r=20 -s
134+
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=4 -w=4 -b=3 -r=1 -nc -s
135+
123136
int main( int argc, char** argv )
124137
{
125138
Benchmark benchmarks[] = {
@@ -128,18 +141,58 @@ int main( int argc, char** argv )
128141
{ "many_pyramids", CreateManyPyramids, NULL, 200 },
129142
{ "rain", CreateRain, StepRain, 1000 },
130143
{ "smash", CreateSmash, NULL, 300 },
131-
{ "spinner", CreateSpinner, NULL, 1400 },
144+
{ "spinner", CreateSpinner, StepSpinner, 1400 },
132145
{ "tumbler", CreateTumbler, NULL, 750 },
133146
};
134147

135148
int benchmarkCount = ARRAY_COUNT( benchmarks );
136149

150+
int maxSteps = benchmarks[0].totalStepCount;
151+
for ( int i = 1; i < benchmarkCount; ++i )
152+
{
153+
maxSteps = b2MaxInt( maxSteps, benchmarks[i].totalStepCount );
154+
}
155+
156+
b2Profile maxProfile = {
157+
.step = FLT_MAX,
158+
.pairs = FLT_MAX,
159+
.collide = FLT_MAX,
160+
.solve = FLT_MAX,
161+
.mergeIslands = FLT_MAX,
162+
.prepareStages = FLT_MAX,
163+
.solveConstraints = FLT_MAX,
164+
.prepareConstraints = FLT_MAX,
165+
.integrateVelocities = FLT_MAX,
166+
.warmStart = FLT_MAX,
167+
.solveImpulses = FLT_MAX,
168+
.integratePositions = FLT_MAX,
169+
.relaxImpulses = FLT_MAX,
170+
.applyRestitution = FLT_MAX,
171+
.storeImpulses = FLT_MAX,
172+
.splitIslands = FLT_MAX,
173+
.transforms = FLT_MAX,
174+
.hitEvents = FLT_MAX,
175+
.refit = FLT_MAX,
176+
.bullets = FLT_MAX,
177+
.sleepIslands = FLT_MAX,
178+
};
179+
180+
b2Profile* profiles = malloc( maxSteps * sizeof( b2Profile ) );
181+
for ( int i = 0; i < maxSteps; ++i )
182+
{
183+
profiles[i] = maxProfile;
184+
}
185+
186+
float* stepResults = malloc( maxSteps * sizeof( float ) );
187+
memset( stepResults, 0, maxSteps * sizeof( float ) );
188+
137189
int maxThreadCount = GetNumberOfCores();
138190
int runCount = 4;
139191
int singleBenchmark = -1;
140192
int singleWorkerCount = -1;
141193
b2Counters counters = { 0 };
142194
bool enableContinuous = true;
195+
bool recordStepTimes = false;
143196

144197
assert( maxThreadCount <= THREAD_LIMIT );
145198

@@ -162,19 +215,25 @@ int main( int argc, char** argv )
162215
}
163216
else if ( strncmp( arg, "-r=", 3 ) == 0 )
164217
{
165-
runCount = b2ClampInt(atoi( arg + 3 ), 1, 16);
218+
runCount = b2ClampInt( atoi( arg + 3 ), 1, 1000 );
166219
}
167220
else if ( strncmp( arg, "-nc", 3 ) == 0 )
168221
{
169222
enableContinuous = false;
223+
printf( "Continuous disabled\n" );
224+
}
225+
else if ( strncmp( arg, "-s", 3 ) == 0 )
226+
{
227+
recordStepTimes = true;
170228
}
171229
else if ( strcmp( arg, "-h" ) == 0 )
172230
{
173231
printf( "Usage\n"
174232
"-t=<integer>: the maximum number of threads to use\n"
175233
"-b=<integer>: run a single benchmark\n"
176234
"-w=<integer>: run a single worker count\n"
177-
"-r=<integer>: number of repeats (default is 4)\n" );
235+
"-r=<integer>: number of repeats (default is 4)\n"
236+
"-s: record step times\n" );
178237
exit( 0 );
179238
}
180239
}
@@ -206,7 +265,7 @@ int main( int argc, char** argv )
206265

207266
printf( "benchmark: %s, steps = %d\n", benchmarks[benchmarkIndex].name, stepCount );
208267

209-
float maxFps[THREAD_LIMIT] = { 0 };
268+
float minTime[THREAD_LIMIT] = { 0 };
210269

211270
for ( int threadCount = 1; threadCount <= maxThreadCount; ++threadCount )
212271
{
@@ -242,30 +301,47 @@ int main( int argc, char** argv )
242301
int subStepCount = 4;
243302

244303
// Initial step can be expensive and skew benchmark
245-
if ( benchmark->stepFcn != NULL)
304+
if ( benchmark->stepFcn != NULL )
246305
{
247-
benchmark->stepFcn( worldId, 0 );
306+
stepResults[0] = benchmark->stepFcn( worldId, 0 );
248307
}
308+
309+
assert( stepCount <= maxSteps );
310+
249311
b2World_Step( worldId, timeStep, subStepCount );
312+
313+
b2Profile profile = b2World_GetProfile( worldId );
314+
MinProfile( profiles + 0, &profile );
315+
250316
taskCount = 0;
251317

252-
b2Timer timer = b2CreateTimer();
318+
uint64_t ticks = b2GetTicks();
253319

254-
for ( int step = 1; step < stepCount; ++step )
320+
for ( int stepIndex = 1; stepIndex < stepCount; ++stepIndex )
255321
{
256-
if ( benchmark->stepFcn != NULL)
322+
if ( benchmark->stepFcn != NULL )
257323
{
258-
benchmark->stepFcn( worldId, step );
324+
stepResults[stepIndex] = benchmark->stepFcn( worldId, stepIndex );
259325
}
326+
260327
b2World_Step( worldId, timeStep, subStepCount );
261328
taskCount = 0;
329+
330+
profile = b2World_GetProfile( worldId );
331+
MinProfile( profiles + stepIndex, &profile );
262332
}
263333

264-
float ms = b2GetMilliseconds( &timer );
265-
float fps = 1000.0f * stepCount / ms;
266-
printf( "run %d : %g (ms), %g (fps)\n", runIndex, ms, fps );
334+
float ms = b2GetMilliseconds( ticks );
335+
printf( "run %d : %g (ms)\n", runIndex, ms );
267336

268-
maxFps[threadCount - 1] = b2MaxFloat( maxFps[threadCount - 1], fps );
337+
if (runIndex == 0)
338+
{
339+
minTime[threadCount - 1] = ms ;
340+
}
341+
else
342+
{
343+
minTime[threadCount - 1] = b2MinFloat( minTime[threadCount - 1], ms );
344+
}
269345

270346
if ( countersAcquired == false )
271347
{
@@ -284,6 +360,26 @@ int main( int argc, char** argv )
284360

285361
enkiDeleteTaskScheduler( scheduler );
286362
scheduler = NULL;
363+
364+
}
365+
366+
if ( recordStepTimes )
367+
{
368+
char fileName[64] = { 0 };
369+
snprintf( fileName, 64, "%s_t%d.dat", benchmarks[benchmarkIndex].name, threadCount );
370+
FILE* file = fopen( fileName, "w" );
371+
if ( file == NULL )
372+
{
373+
continue;
374+
}
375+
376+
for ( int stepIndex = 0; stepIndex < stepCount; ++stepIndex )
377+
{
378+
b2Profile p = profiles[stepIndex];
379+
fprintf( file, "%g %g %g %g %g %g %g\n", p.step, p.pairs, p.collide, p.solveConstraints, p.transforms, p.refit, p.sleepIslands );
380+
}
381+
382+
fclose( file );
287383
}
288384
}
289385

@@ -298,10 +394,10 @@ int main( int argc, char** argv )
298394
continue;
299395
}
300396

301-
fprintf( file, "threads,fps\n" );
397+
fprintf( file, "threads, ms\n" );
302398
for ( int threadIndex = 1; threadIndex <= maxThreadCount; ++threadIndex )
303399
{
304-
fprintf( file, "%d,%g\n", threadIndex, maxFps[threadIndex - 1] );
400+
fprintf( file, "%d,%g\n", threadIndex, minTime[threadIndex - 1] );
305401
}
306402

307403
fclose( file );
@@ -310,5 +406,8 @@ int main( int argc, char** argv )
310406
printf( "======================================\n" );
311407
printf( "All Box2D benchmarks complete!\n" );
312408

409+
free( profiles );
410+
free( stepResults );
411+
313412
return 0;
314413
}

docs/FAQ.md

+2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ Box2D is also deterministic under multithreading. A simulation using two threads
108108

109109
However, people often want more stringent determinism. People often want to know if Box2D can produce identical results on different binaries and on different platforms. Currently this is not provided, but the situation may improve in a future update.
110110

111+
todo update here on cross-platform determinism
112+
111113
### But I really want determinism
112114
This naturally leads to the question of fixed-point math. Box2D does not support fixed-point math. In the past Box2D was ported to the NDS in fixed-point and apparently it worked okay. Fixed-point math is slower and more tedious to develop, so I have chosen not to use fixed-point for the development of Box2D.
113115

docs/overview.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ Shapes are created in a similar way. For example, here is how a box shape is cre
240240
```c
241241
b2ShapeDef shapeDef = b2DefaultShapeDef();
242242
shapeDef.friction = 0.42f;
243-
b2Polygon box = b2MakeBody(0.5f, 0.25f);
244-
b2ShapeId myShapeId = b2CreateCircleShape(myBodyId, &shapeDef, &box);
243+
b2Polygon box = b2MakeBox(0.5f, 0.25f);
244+
b2ShapeId myShapeId = b2CreatePolygonShape(myBodyId, &shapeDef, &box);
245245
```
246246

247247
And the shape may be destroyed as follows:

include/box2d/base.h

+11-20
Original file line numberDiff line numberDiff line change
@@ -110,29 +110,20 @@ B2_API b2Version b2GetVersion( void );
110110
/**@}*/
111111

112112
//! @cond
113-
// Timer for profiling. This has platform specific code and may not work on every platform.
114-
typedef struct b2Timer
115-
{
116-
#if defined( _WIN32 )
117-
int64_t start;
118-
#elif defined( __linux__ ) || defined( __EMSCRIPTEN__ )
119-
int64_t tv_sec;
120-
int64_t tv_nsec;
121-
#elif defined( __APPLE__ )
122-
uint64_t start;
123-
#else
124-
int32_t dummy;
125-
#endif
126-
} b2Timer;
127113

128-
B2_API b2Timer b2CreateTimer( void );
129-
B2_API int64_t b2GetTicks( b2Timer* timer );
130-
B2_API float b2GetMilliseconds( const b2Timer* timer );
131-
B2_API float b2GetMillisecondsAndReset( b2Timer* timer );
132-
B2_API void b2SleepMilliseconds( int milliseconds );
114+
/// Get the absolute number of system ticks. The value is platform specific.
115+
B2_API uint64_t b2GetTicks( void );
116+
117+
/// Get the milliseconds passed from an initial tick value.
118+
B2_API float b2GetMilliseconds( uint64_t ticks );
119+
120+
/// Get the milliseconds passed from an initial tick value.
121+
B2_API float b2GetMillisecondsAndReset( uint64_t* ticks );
122+
123+
/// Yield to be used in a busy loop.
133124
B2_API void b2Yield( void );
134125

135-
// Simple djb2 hash function for determinism testing
126+
/// Simple djb2 hash function for determinism testing
136127
#define B2_HASH_INIT 5381
137128
B2_API uint32_t b2Hash( uint32_t hash, const uint8_t* data, int count );
138129

0 commit comments

Comments
 (0)