Skip to content

Commit

Permalink
Sensor tree (#860)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
erincatto authored Jan 20, 2025
1 parent f377034 commit d0bf516
Show file tree
Hide file tree
Showing 75 changed files with 2,702 additions and 1,749 deletions.
9 changes: 4 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v4

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

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

- name: Configure CMake
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
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

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

- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF
# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF
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

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

- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF
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
# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF

- name: Build
Expand Down
10 changes: 4 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
cmake_dependent_option(BOX2D_AVX2 "Enable AVX2" OFF "BOX2D_ENABLE_SIMD" OFF)
endif()

if(PROJECT_IS_TOP_LEVEL)
# Needed for samples.exe to find box2d.dll
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
endif()

# C++17 needed for imgui
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
Expand All @@ -82,6 +76,10 @@ if(PROJECT_IS_TOP_LEVEL)
option(BOX2D_VALIDATE "Enable heavy validation" ON)
option(BOX2D_UNIT_TESTS "Build the Box2D unit tests" ON)

# Needed for samples.exe to find box2d.dll
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")

include(GNUInstallDirs)

install(
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ The Box2D library and samples build and run on Windows, Linux, and Mac.

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+).

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.

## Documentation
- [Manual](https://box2d.org/documentation/)
- [Migration Guide](https://github.com/erincatto/box2d/blob/main/docs/migration.md)
Expand Down
2 changes: 1 addition & 1 deletion benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ add_executable(benchmark ${BOX2D_BENCHMARK_FILES})

set_target_properties(benchmark PROPERTIES C_STANDARD 17)

if (MSVC)
if (MSVC AND CMAKE_C_COMPILER_ID MATCHES "MSVC")
target_compile_options(benchmark PRIVATE /experimental:c11atomics)
endif()

Expand Down
139 changes: 119 additions & 20 deletions benchmark/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#define _CRT_SECURE_NO_WARNINGS

#include "TaskScheduler_c.h"

#include "benchmarks.h"

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

typedef void CreateFcn( b2WorldId worldId );
typedef void StepFcn( b2WorldId worldId, int stepCount );
typedef float StepFcn( b2WorldId worldId, int stepCount );

typedef struct Benchmark
{
Expand Down Expand Up @@ -115,11 +114,25 @@ static void FinishTask( void* userTask, void* userContext )
enkiWaitForTaskSet( scheduler, task );
}

// Box2D benchmark application. On Windows I recommend running this in an administrator command prompt. Don't use Windows Terminal.
// Or use affinity. [0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80]
static void MinProfile( b2Profile* p1, const b2Profile* p2 )
{
p1->step = b2MinFloat( p1->step, p2->step );
p1->pairs = b2MinFloat( p1->pairs, p2->pairs );
p1->collide = b2MinFloat( p1->collide, p2->collide );
p1->solveConstraints = b2MinFloat( p1->solveConstraints, p2->solveConstraints );
p1->transforms = b2MinFloat( p1->transforms, p2->transforms );
p1->refit = b2MinFloat( p1->refit, p2->refit );
p1->sleepIslands = b2MinFloat( p1->sleepIslands, p2->sleepIslands );
}

// Box2D benchmark application. On Windows I recommend running this in an administrator command prompt. Don't use Windows
// Terminal. Or use affinity. [0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80]
// Examples:
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=4 -w=4
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=8
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=4 -w=4 -b=3 -r=20 -s
// start /affinity 0x5555 .\build\bin\Release\benchmark.exe -t=4 -w=4 -b=3 -r=1 -nc -s

int main( int argc, char** argv )
{
Benchmark benchmarks[] = {
Expand All @@ -128,18 +141,58 @@ int main( int argc, char** argv )
{ "many_pyramids", CreateManyPyramids, NULL, 200 },
{ "rain", CreateRain, StepRain, 1000 },
{ "smash", CreateSmash, NULL, 300 },
{ "spinner", CreateSpinner, NULL, 1400 },
{ "spinner", CreateSpinner, StepSpinner, 1400 },
{ "tumbler", CreateTumbler, NULL, 750 },
};

int benchmarkCount = ARRAY_COUNT( benchmarks );

int maxSteps = benchmarks[0].totalStepCount;
for ( int i = 1; i < benchmarkCount; ++i )
{
maxSteps = b2MaxInt( maxSteps, benchmarks[i].totalStepCount );
}

b2Profile maxProfile = {
.step = FLT_MAX,
.pairs = FLT_MAX,
.collide = FLT_MAX,
.solve = FLT_MAX,
.mergeIslands = FLT_MAX,
.prepareStages = FLT_MAX,
.solveConstraints = FLT_MAX,
.prepareConstraints = FLT_MAX,
.integrateVelocities = FLT_MAX,
.warmStart = FLT_MAX,
.solveImpulses = FLT_MAX,
.integratePositions = FLT_MAX,
.relaxImpulses = FLT_MAX,
.applyRestitution = FLT_MAX,
.storeImpulses = FLT_MAX,
.splitIslands = FLT_MAX,
.transforms = FLT_MAX,
.hitEvents = FLT_MAX,
.refit = FLT_MAX,
.bullets = FLT_MAX,
.sleepIslands = FLT_MAX,
};

b2Profile* profiles = malloc( maxSteps * sizeof( b2Profile ) );
for ( int i = 0; i < maxSteps; ++i )
{
profiles[i] = maxProfile;
}

float* stepResults = malloc( maxSteps * sizeof( float ) );
memset( stepResults, 0, maxSteps * sizeof( float ) );

int maxThreadCount = GetNumberOfCores();
int runCount = 4;
int singleBenchmark = -1;
int singleWorkerCount = -1;
b2Counters counters = { 0 };
bool enableContinuous = true;
bool recordStepTimes = false;

assert( maxThreadCount <= THREAD_LIMIT );

Expand All @@ -162,19 +215,25 @@ int main( int argc, char** argv )
}
else if ( strncmp( arg, "-r=", 3 ) == 0 )
{
runCount = b2ClampInt(atoi( arg + 3 ), 1, 16);
runCount = b2ClampInt( atoi( arg + 3 ), 1, 1000 );
}
else if ( strncmp( arg, "-nc", 3 ) == 0 )
{
enableContinuous = false;
printf( "Continuous disabled\n" );
}
else if ( strncmp( arg, "-s", 3 ) == 0 )
{
recordStepTimes = true;
}
else if ( strcmp( arg, "-h" ) == 0 )
{
printf( "Usage\n"
"-t=<integer>: the maximum number of threads to use\n"
"-b=<integer>: run a single benchmark\n"
"-w=<integer>: run a single worker count\n"
"-r=<integer>: number of repeats (default is 4)\n" );
"-r=<integer>: number of repeats (default is 4)\n"
"-s: record step times\n" );
exit( 0 );
}
}
Expand Down Expand Up @@ -206,7 +265,7 @@ int main( int argc, char** argv )

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

float maxFps[THREAD_LIMIT] = { 0 };
float minTime[THREAD_LIMIT] = { 0 };

for ( int threadCount = 1; threadCount <= maxThreadCount; ++threadCount )
{
Expand Down Expand Up @@ -242,30 +301,47 @@ int main( int argc, char** argv )
int subStepCount = 4;

// Initial step can be expensive and skew benchmark
if ( benchmark->stepFcn != NULL)
if ( benchmark->stepFcn != NULL )
{
benchmark->stepFcn( worldId, 0 );
stepResults[0] = benchmark->stepFcn( worldId, 0 );
}

assert( stepCount <= maxSteps );

b2World_Step( worldId, timeStep, subStepCount );

b2Profile profile = b2World_GetProfile( worldId );
MinProfile( profiles + 0, &profile );

taskCount = 0;

b2Timer timer = b2CreateTimer();
uint64_t ticks = b2GetTicks();

for ( int step = 1; step < stepCount; ++step )
for ( int stepIndex = 1; stepIndex < stepCount; ++stepIndex )
{
if ( benchmark->stepFcn != NULL)
if ( benchmark->stepFcn != NULL )
{
benchmark->stepFcn( worldId, step );
stepResults[stepIndex] = benchmark->stepFcn( worldId, stepIndex );
}

b2World_Step( worldId, timeStep, subStepCount );
taskCount = 0;

profile = b2World_GetProfile( worldId );
MinProfile( profiles + stepIndex, &profile );
}

float ms = b2GetMilliseconds( &timer );
float fps = 1000.0f * stepCount / ms;
printf( "run %d : %g (ms), %g (fps)\n", runIndex, ms, fps );
float ms = b2GetMilliseconds( ticks );
printf( "run %d : %g (ms)\n", runIndex, ms );

maxFps[threadCount - 1] = b2MaxFloat( maxFps[threadCount - 1], fps );
if (runIndex == 0)
{
minTime[threadCount - 1] = ms ;
}
else
{
minTime[threadCount - 1] = b2MinFloat( minTime[threadCount - 1], ms );
}

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

enkiDeleteTaskScheduler( scheduler );
scheduler = NULL;

}

if ( recordStepTimes )
{
char fileName[64] = { 0 };
snprintf( fileName, 64, "%s_t%d.dat", benchmarks[benchmarkIndex].name, threadCount );
FILE* file = fopen( fileName, "w" );
if ( file == NULL )
{
continue;
}

for ( int stepIndex = 0; stepIndex < stepCount; ++stepIndex )
{
b2Profile p = profiles[stepIndex];
fprintf( file, "%g %g %g %g %g %g %g\n", p.step, p.pairs, p.collide, p.solveConstraints, p.transforms, p.refit, p.sleepIslands );
}

fclose( file );
}
}

Expand All @@ -298,10 +394,10 @@ int main( int argc, char** argv )
continue;
}

fprintf( file, "threads,fps\n" );
fprintf( file, "threads, ms\n" );
for ( int threadIndex = 1; threadIndex <= maxThreadCount; ++threadIndex )
{
fprintf( file, "%d,%g\n", threadIndex, maxFps[threadIndex - 1] );
fprintf( file, "%d,%g\n", threadIndex, minTime[threadIndex - 1] );
}

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

free( profiles );
free( stepResults );

return 0;
}
2 changes: 2 additions & 0 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ Box2D is also deterministic under multithreading. A simulation using two threads

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.

todo update here on cross-platform determinism

### But I really want determinism
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.

Expand Down
4 changes: 2 additions & 2 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ Shapes are created in a similar way. For example, here is how a box shape is cre
```c
b2ShapeDef shapeDef = b2DefaultShapeDef();
shapeDef.friction = 0.42f;
b2Polygon box = b2MakeBody(0.5f, 0.25f);
b2ShapeId myShapeId = b2CreateCircleShape(myBodyId, &shapeDef, &box);
b2Polygon box = b2MakeBox(0.5f, 0.25f);
b2ShapeId myShapeId = b2CreatePolygonShape(myBodyId, &shapeDef, &box);
```

And the shape may be destroyed as follows:
Expand Down
31 changes: 11 additions & 20 deletions include/box2d/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,29 +110,20 @@ B2_API b2Version b2GetVersion( void );
/**@}*/

//! @cond
// Timer for profiling. This has platform specific code and may not work on every platform.
typedef struct b2Timer
{
#if defined( _WIN32 )
int64_t start;
#elif defined( __linux__ ) || defined( __EMSCRIPTEN__ )
int64_t tv_sec;
int64_t tv_nsec;
#elif defined( __APPLE__ )
uint64_t start;
#else
int32_t dummy;
#endif
} b2Timer;

B2_API b2Timer b2CreateTimer( void );
B2_API int64_t b2GetTicks( b2Timer* timer );
B2_API float b2GetMilliseconds( const b2Timer* timer );
B2_API float b2GetMillisecondsAndReset( b2Timer* timer );
B2_API void b2SleepMilliseconds( int milliseconds );
/// Get the absolute number of system ticks. The value is platform specific.
B2_API uint64_t b2GetTicks( void );

/// Get the milliseconds passed from an initial tick value.
B2_API float b2GetMilliseconds( uint64_t ticks );

/// Get the milliseconds passed from an initial tick value.
B2_API float b2GetMillisecondsAndReset( uint64_t* ticks );

/// Yield to be used in a busy loop.
B2_API void b2Yield( void );

// Simple djb2 hash function for determinism testing
/// Simple djb2 hash function for determinism testing
#define B2_HASH_INIT 5381
B2_API uint32_t b2Hash( uint32_t hash, const uint8_t* data, int count );

Expand Down
Loading

0 comments on commit d0bf516

Please sign in to comment.