├── .clang-format
├── .gitattributes
├── .github
├── FUNDING.yml
├── issue_template.md
├── pull_request_template.md
└── workflows
│ └── build.yml
├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
├── benchmark
├── CMakeLists.txt
├── amd7950x_avx2
│ ├── joint_grid.csv
│ ├── large_pyramid.csv
│ ├── many_pyramids.csv
│ ├── rain.csv
│ ├── smash.csv
│ ├── spinner.csv
│ └── tumbler.csv
├── amd7950x_float
│ ├── joint_grid.csv
│ ├── large_pyramid.csv
│ ├── many_pyramids.csv
│ ├── rain.csv
│ ├── smash.csv
│ ├── spinner.csv
│ └── tumbler.csv
├── amd7950x_sse2
│ ├── joint_grid.csv
│ ├── large_pyramid.csv
│ ├── many_pyramids.csv
│ ├── rain.csv
│ ├── smash.csv
│ ├── spinner.csv
│ └── tumbler.csv
├── m2air_float
│ ├── joint_grid.csv
│ ├── large_pyramid.csv
│ ├── many_pyramids.csv
│ ├── rain.csv
│ ├── smash.csv
│ ├── spinner.csv
│ └── tumbler.csv
├── m2air_neon
│ ├── joint_grid.csv
│ ├── large_pyramid.csv
│ ├── many_pyramids.csv
│ ├── rain.csv
│ ├── smash.csv
│ ├── spinner.csv
│ └── tumbler.csv
├── main.c
└── n100_sse2
│ ├── joint_grid.csv
│ ├── large_pyramid.csv
│ ├── many_pyramids.csv
│ ├── rain.csv
│ ├── smash.csv
│ ├── spinner.csv
│ └── tumbler.csv
├── build.sh
├── build_emscripten.sh
├── create_sln.bat
├── deploy_docs.sh
├── docs
├── CMakeLists.txt
├── FAQ.md
├── character.md
├── collision.md
├── extra.css
├── foundation.md
├── hello.md
├── images
│ ├── capsule.svg
│ ├── captured_toi.svg
│ ├── center_of_mass.svg
│ ├── chain_loop_inwards.svg
│ ├── chain_loop_outwards.svg
│ ├── chain_shape.svg
│ ├── circle.svg
│ ├── convex_concave.svg
│ ├── distance.svg
│ ├── distance_joint.svg
│ ├── ghost_collision.svg
│ ├── ghost_vertices.svg
│ ├── logo.svg
│ ├── manifolds.svg
│ ├── missed_toi.svg
│ ├── mover.png
│ ├── overlap_test.svg
│ ├── prismatic_joint.svg
│ ├── raycast.svg
│ ├── revolute_joint.svg
│ ├── samples.png
│ ├── self_intersect.svg
│ ├── simulation_loop.svg
│ ├── skin_collision.svg
│ ├── tunneling1.svg
│ ├── tunneling2.svg
│ ├── wheel_joint.svg
│ └── winding.svg
├── layout.xml
├── loose_ends.md
├── migration.md
├── overview.md
├── reading.md
├── release_notes_v310.md
├── samples.md
└── simulation.md
├── extern
├── glad
│ ├── include
│ │ ├── KHR
│ │ │ └── khrplatform.h
│ │ └── glad
│ │ │ └── glad.h
│ └── src
│ │ └── glad.c
└── jsmn
│ └── jsmn.h
├── include
└── box2d
│ ├── base.h
│ ├── box2d.h
│ ├── collision.h
│ ├── id.h
│ ├── math_functions.h
│ └── types.h
├── samples
├── CMakeLists.txt
├── car.cpp
├── car.h
├── data
│ ├── background.fs
│ ├── background.vs
│ ├── circle.fs
│ ├── circle.vs
│ ├── droid_sans.ttf
│ ├── map01.svg
│ ├── map02.svg
│ ├── map03.svg
│ ├── ramp.svg
│ ├── solid_capsule.fs
│ ├── solid_capsule.vs
│ ├── solid_circle.fs
│ ├── solid_circle.vs
│ ├── solid_polygon.fs
│ └── solid_polygon.vs
├── donut.cpp
├── donut.h
├── doohickey.cpp
├── doohickey.h
├── draw.cpp
├── draw.h
├── main.cpp
├── sample.cpp
├── sample.h
├── sample_benchmark.cpp
├── sample_bodies.cpp
├── sample_character.cpp
├── sample_collision.cpp
├── sample_continuous.cpp
├── sample_determinism.cpp
├── sample_events.cpp
├── sample_geometry.cpp
├── sample_joints.cpp
├── sample_robustness.cpp
├── sample_shapes.cpp
├── sample_stacking.cpp
├── sample_world.cpp
├── shader.cpp
└── shader.h
├── shared
├── CMakeLists.txt
├── benchmarks.c
├── benchmarks.h
├── determinism.c
├── determinism.h
├── human.c
├── human.h
├── random.c
└── random.h
├── src
├── CMakeLists.txt
├── aabb.c
├── aabb.h
├── arena_allocator.c
├── arena_allocator.h
├── array.c
├── array.h
├── atomic.h
├── bitset.c
├── bitset.h
├── body.c
├── body.h
├── box2d.natvis
├── broad_phase.c
├── broad_phase.h
├── constants.h
├── constraint_graph.c
├── constraint_graph.h
├── contact.c
├── contact.h
├── contact_solver.c
├── contact_solver.h
├── core.c
├── core.h
├── ctz.h
├── distance.c
├── distance_joint.c
├── dynamic_tree.c
├── geometry.c
├── hull.c
├── id_pool.c
├── id_pool.h
├── island.c
├── island.h
├── joint.c
├── joint.h
├── manifold.c
├── math_functions.c
├── motor_joint.c
├── mouse_joint.c
├── mover.c
├── prismatic_joint.c
├── revolute_joint.c
├── sensor.c
├── sensor.h
├── shape.c
├── shape.h
├── solver.c
├── solver.h
├── solver_set.c
├── solver_set.h
├── table.c
├── table.h
├── timer.c
├── types.c
├── weld_joint.c
├── wheel_joint.c
├── world.c
└── world.h
└── test
├── CMakeLists.txt
├── main.c
├── test_bitset.c
├── test_collision.c
├── test_determinism.c
├── test_distance.c
├── test_id.c
├── test_macros.h
├── test_math.c
├── test_shape.c
├── test_table.c
└── test_world.c
/.clang-format:
--------------------------------------------------------------------------------
1 | # Reference: https://clang.llvm.org/docs/ClangFormatStyleOptions.html
2 | # /c/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/Llvm/bin
3 | # https://clang-format-configurator.site/
4 | # default values:
5 | # https://github.com/llvm/llvm-project/blob/main/clang/lib/Format/Format.cpp
6 | ---
7 | Language: Cpp
8 | BasedOnStyle: Microsoft
9 |
10 | BreakBeforeBraces: Custom
11 | BraceWrapping:
12 | AfterCaseLabel: true
13 | AfterUnion: true
14 | BeforeWhile: true
15 |
16 | ColumnLimit: 130
17 | PointerAlignment: Left
18 | UseTab: Always
19 | BreakConstructorInitializers: BeforeComma
20 |
21 | InsertNewlineAtEOF: true
22 | IncludeBlocks: Regroup
23 |
24 | IncludeCategories:
25 | - Regex: '^"(box2d\/)'
26 | Priority: 2
27 | - Regex: '^<.*'
28 | Priority: 3
29 | - Regex: '^".*'
30 | Priority: 1
31 |
32 | IndentExternBlock: NoIndent
33 | IndentCaseLabels: true
34 | IndentAccessModifiers: false
35 | AccessModifierOffset: -4
36 |
37 | SpacesInParens: Custom
38 | SpacesInParensOptions:
39 | Other: true
40 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | *.c text
4 | *.cpp text
5 | *.h text
6 | *.md text
7 | *.txt text
8 | *.sh text eol=lf
9 |
10 | *.ttf binary
11 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [erincatto]
4 | patreon: Box2D
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | Make sure these boxes are checked before submitting your issue - thank you!
2 |
3 | - [ ] Ask for help on [discord](https://discord.gg/NKYgCBP)
4 | - [ ] Consider providing a dump file using b2World::Dump
5 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Pull requests for core Box2D code are generally not accepted. Please consider filing an issue instead.
2 |
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | imgui.ini
3 | settings.ini
4 | .vscode/
5 | .vs/
6 | .cap
7 | .DS_Store
8 | CMakeUserPresets.json
9 | .cache/
10 | .idea/
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Erin Catto
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Build Status
4 | [](https://github.com/erincatto/box2d/actions)
5 |
6 | # Box2D
7 | Box2D is a 2D physics engine for games.
8 |
9 | [](https://www.youtube.com/watch?v=dAoM-xjOWtA)
10 |
11 | ## Features
12 |
13 | ### Collision
14 | - Continuous collision detection
15 | - Contact events
16 | - Convex polygons, capsules, circles, rounded polygons, segments, and chains
17 | - Multiple shapes per body
18 | - Collision filtering
19 | - Ray casts, shape casts, and overlap queries
20 | - Sensor system
21 |
22 | ### Physics
23 | - Robust _Soft Step_ rigid body solver
24 | - Continuous physics for fast translations and rotations
25 | - Island based sleep
26 | - Revolute, prismatic, distance, mouse joint, weld, and wheel joints
27 | - Joint limits, motors, springs, and friction
28 | - Joint and contact forces
29 | - Body movement events and sleep notification
30 |
31 | ### System
32 | - Data-oriented design
33 | - Written in portable C17
34 | - Extensive multithreading and SIMD
35 | - Optimized for large piles of bodies
36 |
37 | ### Samples
38 | - OpenGL with GLFW and enkiTS
39 | - Graphical user interface with imgui
40 | - Many samples to demonstrate features and performance
41 |
42 | ## Building for Visual Studio
43 | - Install [CMake](https://cmake.org/)
44 | - Ensure CMake is in the user `PATH`
45 | - Run `create_sln.bat`
46 | - Open and build `build/box2d.sln`
47 |
48 | ## Building for Linux
49 | - Run `build.sh` from a bash shell
50 | - Results are in the build sub-folder
51 |
52 | ## Building for Xcode
53 | - Install [CMake](https://cmake.org)
54 | - Add Cmake to the path in .zprofile (the default Terminal shell is zsh)
55 | - export PATH="/Applications/CMake.app/Contents/bin:$PATH"
56 | - mkdir build
57 | - cd build
58 | - cmake -G Xcode ..
59 | - Open `box2d.xcodeproj`
60 | - Select the samples scheme
61 | - Build and run the samples
62 |
63 | ## Building and installing
64 | - mkdir build
65 | - cd build
66 | - cmake ..
67 | - cmake --build . --config Release
68 | - cmake --install . (might need sudo)
69 |
70 | ## Compatibility
71 | The Box2D library and samples build and run on Windows, Linux, and Mac.
72 |
73 | You will need a compiler that supports C17 to build the Box2D library.
74 |
75 | You will need a compiler that supports C++20 to build the samples.
76 |
77 | Box2D uses SSE2 and Neon SIMD math to improve performance. This can be disabled by defining `BOX2D_DISABLE_SIMD`.
78 |
79 | ## Documentation
80 | - [Manual](https://box2d.org/documentation/)
81 | - [Migration Guide](https://github.com/erincatto/box2d/blob/main/docs/migration.md)
82 |
83 | ## Community
84 | - [Discord](https://discord.gg/NKYgCBP)
85 |
86 | ## Contributing
87 | Please do not submit pull requests. Instead, please file an issue for bugs or feature requests. For support, please visit the Discord server.
88 |
89 | # Giving Feedback
90 | Please file an issue or start a chat on discord. You can also use [GitHub Discussions](https://github.com/erincatto/box2d/discussions).
91 |
92 | ## License
93 | Box2D is developed by Erin Catto and uses the [MIT license](https://en.wikipedia.org/wiki/MIT_License).
94 |
95 | ## Sponsorship
96 | Support development of Box2D through [Github Sponsors](https://github.com/sponsors/erincatto).
97 |
98 | Please consider starring this repository and subscribing to my [YouTube channel](https://www.youtube.com/@erin_catto).
99 |
100 | ## External ports, wrappers, and bindings (unsupported)
101 | - Beef bindings - https://github.com/EnokViking/Box2DBeef
102 | - C++ bindings - https://github.com/HolyBlackCat/box2cpp
103 | - WASM - https://github.com/Birch-san/box2d3-wasm
104 |
--------------------------------------------------------------------------------
/benchmark/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Box2D benchmark app
2 |
3 | set(BOX2D_BENCHMARK_FILES
4 | main.c
5 | )
6 | add_executable(benchmark ${BOX2D_BENCHMARK_FILES})
7 |
8 | set_target_properties(benchmark PROPERTIES
9 | C_STANDARD 17
10 | C_STANDARD_REQUIRED YES
11 | C_EXTENSIONS NO
12 | )
13 |
14 | if (BOX2D_COMPILE_WARNING_AS_ERROR)
15 | set_target_properties(benchmark PROPERTIES COMPILE_WARNING_AS_ERROR ON)
16 | endif()
17 |
18 | target_link_libraries(benchmark PRIVATE box2d shared enkiTS)
19 |
20 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${BOX2D_BENCHMARK_FILES})
--------------------------------------------------------------------------------
/benchmark/amd7950x_avx2/joint_grid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,3174.52
3 | 2,1795.2
4 | 3,1217.45
5 | 4,996.27
6 | 5,857.203
7 | 6,763.975
8 | 7,696.29
9 | 8,648.49
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_avx2/large_pyramid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,1951.88
3 | 2,1026.26
4 | 3,721.553
5 | 4,563.934
6 | 5,473.542
7 | 6,415.048
8 | 7,367.646
9 | 8,362.217
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_avx2/many_pyramids.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,3312.37
3 | 2,1674.87
4 | 3,1116.48
5 | 4,859.268
6 | 5,691.412
7 | 6,594.236
8 | 7,513.412
9 | 8,462.289
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_avx2/rain.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,8415.32
3 | 2,4830.51
4 | 3,3684.98
5 | 4,3008.05
6 | 5,2568.19
7 | 6,2275.1
8 | 7,2054.01
9 | 8,1896.27
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_avx2/smash.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,1946.15
3 | 2,1212.81
4 | 3,937.648
5 | 4,786.533
6 | 5,699.338
7 | 6,636.796
8 | 7,597.153
9 | 8,564.691
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_avx2/spinner.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,5145.33
3 | 2,3090.7
4 | 3,2362.34
5 | 4,1890.21
6 | 5,1643.82
7 | 6,1473.02
8 | 7,1339.32
9 | 8,1256.21
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_avx2/tumbler.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2035.14
3 | 2,1273.93
4 | 3,961.824
5 | 4,790.221
6 | 5,679.686
7 | 6,609.556
8 | 7,561.948
9 | 8,531.353
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_float/joint_grid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,3070.3
3 | 2,1743.41
4 | 3,1190.54
5 | 4,973.725
6 | 5,839.892
7 | 6,749.347
8 | 7,684.411
9 | 8,639.372
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_float/large_pyramid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,4324.94
3 | 2,2218.25
4 | 3,1489.84
5 | 4,1132.51
6 | 5,935.469
7 | 6,818.171
8 | 7,722.364
9 | 8,658.744
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_float/many_pyramids.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,6803.92
3 | 2,3421.51
4 | 3,2310.75
5 | 4,1766.54
6 | 5,1424.53
7 | 6,1213.28
8 | 7,1043.65
9 | 8,915.268
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_float/rain.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,10023.3
3 | 2,5685.88
4 | 3,4189.81
5 | 4,3390.77
6 | 5,2873.75
7 | 6,2522.69
8 | 7,2275.75
9 | 8,2098.4
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_float/smash.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2660.24
3 | 2,1604.04
4 | 3,1208.81
5 | 4,997.755
6 | 5,871.282
7 | 6,791.138
8 | 7,731.148
9 | 8,687.379
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_float/spinner.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,8296.22
3 | 2,4821.7
4 | 3,3555.21
5 | 4,2816.7
6 | 5,2427.56
7 | 6,2162.31
8 | 7,2007.07
9 | 8,1896.91
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_float/tumbler.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,3327.12
3 | 2,1933.37
4 | 3,1436.84
5 | 4,1169.7
6 | 5,1014.31
7 | 6,907.688
8 | 7,840.671
9 | 8,786.672
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_sse2/joint_grid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,3038.45
3 | 2,1745.8
4 | 3,1193.08
5 | 4,973.296
6 | 5,836.918
7 | 6,748.394
8 | 7,684.64
9 | 8,638.312
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_sse2/large_pyramid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2301.71
3 | 2,1203.32
4 | 3,823.862
5 | 4,643.807
6 | 5,536.494
7 | 6,474.743
8 | 7,420.65
9 | 8,389.896
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_sse2/many_pyramids.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,3683.42
3 | 2,1875.2
4 | 3,1253.11
5 | 4,969.308
6 | 5,786.832
7 | 6,674.359
8 | 7,580.961
9 | 8,514.787
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_sse2/rain.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,8594.2
3 | 2,4899.89
4 | 3,3750.87
5 | 4,3054.75
6 | 5,2617.95
7 | 6,2315.89
8 | 7,2091.46
9 | 8,1930.53
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_sse2/smash.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2054.9
3 | 2,1285.32
4 | 3,993.549
5 | 4,828.812
6 | 5,735.313
7 | 6,671.355
8 | 7,626.385
9 | 8,592.127
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_sse2/spinner.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,5670.76
3 | 2,3374.29
4 | 3,2551.01
5 | 4,2019.3
6 | 5,1754.54
7 | 6,1572.94
8 | 7,1451.41
9 | 8,1381.05
10 |
--------------------------------------------------------------------------------
/benchmark/amd7950x_sse2/tumbler.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2308.28
3 | 2,1407.11
4 | 3,1067.58
5 | 4,872.976
6 | 5,758.85
7 | 6,681.924
8 | 7,633.338
9 | 8,596.088
10 |
--------------------------------------------------------------------------------
/benchmark/m2air_float/joint_grid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2367.43
3 | 2,1449.57
4 | 3,999.678
5 | 4,838.636
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_float/large_pyramid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2317.53
3 | 2,1252.4
4 | 3,891.763
5 | 4,694.968
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_float/many_pyramids.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,3559.49
3 | 2,1888.42
4 | 3,1357.7
5 | 4,1085.2
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_float/rain.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,7119.67
3 | 2,4144.16
4 | 3,3192.93
5 | 4,2623.99
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_float/smash.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,1757.78
3 | 2,1079.8
4 | 3,849.502
5 | 4,709.022
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_float/spinner.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,5434.23
3 | 2,3244.67
4 | 3,2462.03
5 | 4,1998.78
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_float/tumbler.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2094.15
3 | 2,1270.1
4 | 3,1020.63
5 | 4,835.34
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_neon/joint_grid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2377.18
3 | 2,1444.78
4 | 3,998.22
5 | 4,837.361
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_neon/large_pyramid.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,1600.72
3 | 2,880.846
4 | 3,632.579
5 | 4,502.146
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_neon/many_pyramids.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,2455.91
3 | 2,1329.33
4 | 3,984.499
5 | 4,820.902
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_neon/rain.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,6834.98
3 | 2,4020.86
4 | 3,3123.64
5 | 4,2574.69
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_neon/smash.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,1595.13
3 | 2,1005.41
4 | 3,798.89
5 | 4,670.542
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_neon/spinner.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,4715.34
3 | 2,2939.07
4 | 3,2264.93
5 | 4,1855.98
6 |
--------------------------------------------------------------------------------
/benchmark/m2air_neon/tumbler.csv:
--------------------------------------------------------------------------------
1 | threads,ms
2 | 1,1804.16
3 | 2,1150.51
4 | 3,945.004
5 | 4,777.227
6 |
--------------------------------------------------------------------------------
/benchmark/n100_sse2/joint_grid.csv:
--------------------------------------------------------------------------------
1 | threads,fps
2 | 1,75.5947
3 | 2,123.228
4 | 3,160.379
5 | 4,181.545
6 |
--------------------------------------------------------------------------------
/benchmark/n100_sse2/large_pyramid.csv:
--------------------------------------------------------------------------------
1 | threads,fps
2 | 1,127.236
3 | 2,226.291
4 | 3,297.628
5 | 4,345.526
6 |
--------------------------------------------------------------------------------
/benchmark/n100_sse2/many_pyramids.csv:
--------------------------------------------------------------------------------
1 | threads,fps
2 | 1,30.8828
3 | 2,55.0462
4 | 3,69.5406
5 | 4,77.7339
6 |
--------------------------------------------------------------------------------
/benchmark/n100_sse2/rain.csv:
--------------------------------------------------------------------------------
1 | threads,fps
2 | 1,72.2901
3 | 2,118.753
4 | 3,142.61
5 | 4,162.35
6 |
--------------------------------------------------------------------------------
/benchmark/n100_sse2/smash.csv:
--------------------------------------------------------------------------------
1 | threads,fps
2 | 1,86.2381
3 | 2,132.306
4 | 3,160.725
5 | 4,181.842
6 |
--------------------------------------------------------------------------------
/benchmark/n100_sse2/spinner.csv:
--------------------------------------------------------------------------------
1 | threads,fps
2 | 1,156.855
3 | 2,258.638
4 | 3,303.717
5 | 4,358.492
6 |
--------------------------------------------------------------------------------
/benchmark/n100_sse2/tumbler.csv:
--------------------------------------------------------------------------------
1 | threads,fps
2 | 1,199.492
3 | 2,313.012
4 | 3,381.983
5 | 4,441.825
6 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Use this to build box2d on any system with a bash shell
4 | rm -rf build
5 | mkdir build
6 | cd build
7 |
8 | # I haven't been able to get Wayland working on WSL but X11 works.
9 | # https://www.glfw.org/docs/latest/compile.html
10 | cmake -DBOX2D_BUILD_DOCS=OFF -DGLFW_BUILD_WAYLAND=OFF ..
11 | cmake --build .
12 |
--------------------------------------------------------------------------------
/build_emscripten.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # source emsdk_env.sh
4 |
5 | # Use this to build box2d on any system with a bash shell
6 | rm -rf build
7 | mkdir build
8 | cd build
9 | emcmake cmake -DBOX2D_VALIDATE=OFF -DBOX2D_UNIT_TESTS=ON -DBOX2D_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Debug ..
10 | cmake --build .
11 |
--------------------------------------------------------------------------------
/create_sln.bat:
--------------------------------------------------------------------------------
1 | rem Use this batch file to build box2d for Visual Studio
2 | rmdir /s /q build
3 | mkdir build
4 | cd build
5 | cmake ..
6 |
--------------------------------------------------------------------------------
/deploy_docs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Copies documentation to blog
4 | cp -R build/docs/html/. ../box2d_blog/public/documentation/
5 |
--------------------------------------------------------------------------------
/docs/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | find_package(Doxygen REQUIRED dot)
2 |
3 | set(DOXYGEN_PROJECT_NAME "Box2D")
4 | set(DOXYGEN_GENERATE_HTML YES)
5 | set(DOXYGEN_USE_MATHJAX YES)
6 | set(DOXYGEN_MATHJAX_VERSION MathJax_3)
7 | set(DOXYGEN_MATHJAX_FORMAT SVG)
8 | set(DOXYGEN_EXTRACT_ALL NO)
9 | set(DOXYGEN_FILE_PATTERNS *.h)
10 | set(DOXYGEN_ENABLE_PREPROCESSING YES)
11 | set(DOXYGEN_MACRO_EXPANSION YES)
12 | set(DOXYGEN_EXPAND_ONLY_PREDEF YES)
13 | set(DOXYGEN_PREDEFINED B2_API= B2_INLINE=)
14 | set(DOXYGEN_WARN_IF_UNDOCUMENTED YES)
15 |
16 | # In multiline comments, this takes the first line/sentence as a brief description to use in the table of functions.
17 | # So I don't need to use @brief tags to separate the short description from the full description.
18 | set(DOXYGEN_JAVADOC_AUTOBRIEF YES)
19 |
20 | set(DOXYGEN_IMAGE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/images")
21 | set(DOXYGEN_HTML_EXTRA_STYLESHEET "${CMAKE_CURRENT_SOURCE_DIR}/extra.css")
22 | set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_CURRENT_SOURCE_DIR}/overview.md")
23 | set(DOXYGEN_PROJECT_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/images/logo.svg")
24 | set(DOXYGEN_LAYOUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/layout.xml")
25 |
26 | set(DOXYGEN_INLINE_SIMPLE_STRUCTS YES)
27 | set(DOXYGEN_TYPEDEF_HIDES_STRUCT YES)
28 | # set(DOXYGEN_DISABLE_INDEX YES)
29 | set(DOXYGEN_GENERATE_TREEVIEW YES)
30 | set(DOXYGEN_FULL_SIDEBAR NO)
31 |
32 | # force dark mode to work with extra.css
33 | set(DOXYGEN_HTML_COLORSTYLE DARK)
34 |
35 | # this tells doxygen to label structs as structs instead of classes
36 | set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES)
37 | set(DOXYGEN_WARN_IF_INCOMPLETE_DOC NO)
38 |
39 | doxygen_add_docs(doc
40 | "${CMAKE_SOURCE_DIR}/include/box2d"
41 | "overview.md"
42 | "hello.md"
43 | "samples.md"
44 | "foundation.md"
45 | "collision.md"
46 | "simulation.md"
47 | "loose_ends.md"
48 | "character.md"
49 | "reading.md"
50 | "faq.md"
51 | "migration.md"
52 | "release_notes_v310.md"
53 | ALL
54 | COMMENT "Generate HTML documentation")
55 |
--------------------------------------------------------------------------------
/docs/character.md:
--------------------------------------------------------------------------------
1 | # Character mover
2 |
3 | > **Caution**:
4 | > The character mover feature is new to version 3.1 and should be considered experimental.
5 |
6 | Box2D provides a few structures and functions you can use to build a character mover.
7 |
8 | These features support a `geometric` character mover. This is like
9 | a `kinematic` mover except the character mover is not a rigid body and does
10 | not exist in the simulation world. This is done to achieve features that
11 | would be difficult with a kinematic body.
12 |
13 | This type of mover may not be suitable for your game. It is less physical than a rigid body, but
14 | it gives you more control over the movement. It is the type of mover you might find
15 | in a first-person shooter or a game with platforming elements.
16 |
17 | The mover is assumed to be a capsule. Using a capsule helps keep movement smooth. The capsule should
18 | have a significant radius. It does not have to be a vertical capsule, but that is likely
19 | to be the easiest setup. There is no explicit handling of rotation. But slow rotation can
20 | work with this system.
21 |
22 | Let's review the features. First there are a couple world query functions.
23 |
24 | `b2World_CastMover()` is a custom shape cast that tries to avoid getting stuck when shapes start out touching.
25 | The feature is called _encroachment_. Since the capsule has a significant radius it can move closer
26 | to a surface it is touching without the inner line segment generating an overlap, which would cause
27 | the shape cast to fail. Due to the internal use of GJK, encroachment has little cost. The idea with encroachment is that
28 | the mover is trying to slide along a surface and we don't want to stop that even if there is some small movement into the surface.
29 |
30 | `b2World_CollideMover()` complements the cast function. This function generates collision planes for touching and/or overlapped surfaces. The character mover is assumed to have a fixed rotation, so it doesn't need contact manifolds or contact points. It just needs collision planes. Each plane is returned with the `b2Plane` and a `b2ShapeId` for each shape the mover is touching.
31 |
32 | Once you have some collision planes from `b2World_CollideMover()`, you can process and filter them to generate an array of `b2CollisionPlane`. These collision planes can then be sent to `b2SolvePlanes()` to generate a new position for the mover
33 | that attempts to find the optimal new position given the current position.
34 |
35 | These collision planes support *soft collision* using a `pushLimit`. This push limit is a distance value. A rigid surface will have a push limit of `FLT_MAX`. However, you may want some surfaces to have a limited effect on the character. For example, you may want the mover to push through other players or enemies yet still resolve the collision so they are not overlapped. Another example is a door or elevator that could otherwise push the mover through the floor.
36 |
37 | Finally after calling `b2SolverPlanes()` you can call `b2ClipVector()` to clip your velocity vector so the mover will not keep trying to push into a wall, which could lead to a huge velocity accumulation otherwise.
38 |
39 | The `Mover` sample shows all these functions being used together. It also includes other common character features such as acceleration and friction, jumping, and a pogo stick.
40 |
41 | 
42 |
--------------------------------------------------------------------------------
/docs/images/captured_toi.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
125 |
--------------------------------------------------------------------------------
/docs/images/convex_concave.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/docs/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
127 |
--------------------------------------------------------------------------------
/docs/images/mover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erincatto/box2d/4b5e72bd44bd139fab67ad890b70e1690ff405a8/docs/images/mover.png
--------------------------------------------------------------------------------
/docs/images/samples.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erincatto/box2d/4b5e72bd44bd139fab67ad890b70e1690ff405a8/docs/images/samples.png
--------------------------------------------------------------------------------
/docs/reading.md:
--------------------------------------------------------------------------------
1 | # Further Reading
2 | - [Erin Catto's Publications](https://box2d.org/publications/)
3 | - [Erin Catto's Blog Posts](https://box2d.org/posts/)
4 | - Collision Detection in Interactive 3D Environments, Gino van den Bergen, 2004
5 | - Real-Time Collision Detection, Christer Ericson, 2005
6 |
--------------------------------------------------------------------------------
/docs/release_notes_v310.md:
--------------------------------------------------------------------------------
1 | # v3.1 Release Notes
2 |
3 | ## API Changes
4 | - 64-bit filter categories and masks
5 | - 64-bit dynamic tree user data
6 | - Renamed `b2SmoothSegment` to `b2ChainSegment`
7 | - Cast and overlap functions modified for argument consistency
8 | - Contact begin events now provide the manifold
9 | - More consistent functions to make polygons
10 | - Contact events are now disabled by default
11 | - Replaced `b2Timer` with `uint64_t`
12 | - Shape material properties now use `b2SurfaceMaterial`
13 |
14 | ## New Features
15 | - New character mover features and sample
16 | - Revised sensor system is now independent of body type and sleep
17 | - Rolling resistance and tangent speed
18 | - Friction and restitution mixing callbacks
19 | - World explosions
20 | - World access to the maximum linear speed
21 | - More control over body mass updates
22 | - Filter joint to disable collision between specific bodies
23 | - Bodies can now have names for debugging
24 | - Added `b2Body_SetTargetTransform` for kinematic bodies
25 |
26 | ## Improvements
27 | - Cross-platform determinism
28 | - Custom SSE2 and Neon for significantly improved performance
29 | - SSE2 is the default instead of AVX2
30 | - Removed SIMDE library dependency
31 | - Faster ray and shape casts
32 | - Faster continuous collision
33 | - Each segment of a chain shape may have a different surface material
34 | - Reduced overhead of restitution when not used
35 | - Implemented atomic platform wrappers eliminating the `experimental:c11atomics` flag
36 |
37 | ## Bugs Fixes
38 | - Many bug fixes based on user testing
39 | - Fixed missing hit events
40 | - Capsule and polygon manifold fixes
41 | - Fixed missing contact end events
42 | - PreSolve is now called in continuous collision
43 | - Reduced clipping into chain shapes by fast bodies
44 | - Friction and restitution are now remixed in the contact solver every time step
45 | - Body move events are now correctly adjusted for time of impact
46 |
47 | ## Infrastructure
48 | - Unit test for API coverage
49 | - macOS and Windows samples built in GitHub actions
50 | - CMake install
51 | - imgui and glfw versions are now pinned in FetchContent
52 | - Initial Emscripten support
53 |
--------------------------------------------------------------------------------
/docs/samples.md:
--------------------------------------------------------------------------------
1 | # Samples {#samples}
2 | Once you have conquered the HelloWorld example, you should start looking
3 | at Box2D's samples application. The samples application is a testing framework and demo
4 | environment. Here are some of the features:
5 | - Camera with pan and zoom
6 | - Mouse dragging of dynamic bodies
7 | - Many samples in a tree view
8 | - GUI for selecting samples, parameter tuning, and debug drawing options
9 | - Pause and single step simulation
10 | - Multithreading and performance data
11 |
12 | 
13 |
14 | The samples application has many examples of Box2D usage in the test cases and the
15 | framework itself. I encourage you to explore and tinker with the samples
16 | as you learn Box2D.
17 |
18 | Note: the sample application is written using [GLFW](https://www.glfw.org),
19 | [imgui](https://github.com/ocornut/imgui), and [enkiTS](https://github.com/dougbinks/enkiTS).
20 | The samples app is not part of the Box2D library. The Box2D library is agnostic about rendering.
21 | As shown by the HelloWorld example, you don't need a renderer to use Box2D.
22 |
--------------------------------------------------------------------------------
/include/box2d/base.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include
7 |
8 | // clang-format off
9 | //
10 | // Shared library macros
11 | #if defined( _MSC_VER ) && defined( box2d_EXPORTS )
12 | // build the Windows DLL
13 | #define BOX2D_EXPORT __declspec( dllexport )
14 | #elif defined( _MSC_VER ) && defined( BOX2D_DLL )
15 | // using the Windows DLL
16 | #define BOX2D_EXPORT __declspec( dllimport )
17 | #elif defined( box2d_EXPORTS )
18 | // building or using the shared library
19 | #define BOX2D_EXPORT __attribute__( ( visibility( "default" ) ) )
20 | #else
21 | // static library
22 | #define BOX2D_EXPORT
23 | #endif
24 |
25 | // C++ macros
26 | #ifdef __cplusplus
27 | #define B2_API extern "C" BOX2D_EXPORT
28 | #define B2_INLINE inline
29 | #define B2_LITERAL(T) T
30 | #define B2_ZERO_INIT {}
31 | #else
32 | #define B2_API BOX2D_EXPORT
33 | #define B2_INLINE static inline
34 | /// Used for C literals like (b2Vec2){1.0f, 2.0f} where C++ requires b2Vec2{1.0f, 2.0f}
35 | #define B2_LITERAL(T) (T)
36 | #define B2_ZERO_INIT {0}
37 | #endif
38 | // clang-format on
39 |
40 | /**
41 | * @defgroup base Base
42 | * Base functionality
43 | * @{
44 | */
45 |
46 | /// Prototype for user allocation function
47 | /// @param size the allocation size in bytes
48 | /// @param alignment the required alignment, guaranteed to be a power of 2
49 | typedef void* b2AllocFcn( unsigned int size, int alignment );
50 |
51 | /// Prototype for user free function
52 | /// @param mem the memory previously allocated through `b2AllocFcn`
53 | typedef void b2FreeFcn( void* mem );
54 |
55 | /// Prototype for the user assert callback. Return 0 to skip the debugger break.
56 | typedef int b2AssertFcn( const char* condition, const char* fileName, int lineNumber );
57 |
58 | /// This allows the user to override the allocation functions. These should be
59 | /// set during application startup.
60 | B2_API void b2SetAllocator( b2AllocFcn* allocFcn, b2FreeFcn* freeFcn );
61 |
62 | /// @return the total bytes allocated by Box2D
63 | B2_API int b2GetByteCount( void );
64 |
65 | /// Override the default assert callback
66 | /// @param assertFcn a non-null assert callback
67 | B2_API void b2SetAssertFcn( b2AssertFcn* assertFcn );
68 |
69 | /// Version numbering scheme.
70 | /// See https://semver.org/
71 | typedef struct b2Version
72 | {
73 | /// Significant changes
74 | int major;
75 |
76 | /// Incremental changes
77 | int minor;
78 |
79 | /// Bug fixes
80 | int revision;
81 | } b2Version;
82 |
83 | /// Get the current version of Box2D
84 | B2_API b2Version b2GetVersion( void );
85 |
86 | /**@}*/
87 |
88 | //! @cond
89 |
90 | // see https://github.com/scottt/debugbreak
91 | #if defined( _MSC_VER )
92 | #define B2_BREAKPOINT __debugbreak()
93 | #elif defined( __GNUC__ ) || defined( __clang__ )
94 | #define B2_BREAKPOINT __builtin_trap()
95 | #else
96 | // Unknown compiler
97 | #include
98 | #define B2_BREAKPOINT assert( 0 )
99 | #endif
100 |
101 | #if !defined( NDEBUG ) || defined( B2_ENABLE_ASSERT )
102 | B2_API int b2InternalAssertFcn( const char* condition, const char* fileName, int lineNumber );
103 | #define B2_ASSERT( condition ) \
104 | do \
105 | { \
106 | if ( !( condition ) && b2InternalAssertFcn( #condition, __FILE__, (int)__LINE__ ) ) \
107 | B2_BREAKPOINT; \
108 | } \
109 | while ( 0 )
110 | #else
111 | #define B2_ASSERT( ... ) ( (void)0 )
112 | #endif
113 |
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. Resets the passed in
121 | /// value to the current tick value.
122 | B2_API float b2GetMillisecondsAndReset( uint64_t* ticks );
123 |
124 | /// Yield to be used in a busy loop.
125 | B2_API void b2Yield( void );
126 |
127 | /// Simple djb2 hash function for determinism testing
128 | #define B2_HASH_INIT 5381
129 | B2_API uint32_t b2Hash( uint32_t hash, const uint8_t* data, int count );
130 |
131 | //! @endcond
132 |
--------------------------------------------------------------------------------
/include/box2d/id.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "base.h"
7 |
8 | #include
9 |
10 | /**
11 | * @defgroup id Ids
12 | * These ids serve as handles to internal Box2D objects.
13 | * These should be considered opaque data and passed by value.
14 | * Include this header if you need the id types and not the whole Box2D API.
15 | * All ids are considered null if initialized to zero.
16 | *
17 | * For example in C++:
18 | *
19 | * @code{.cxx}
20 | * b2WorldId worldId = {};
21 | * @endcode
22 | *
23 | * Or in C:
24 | *
25 | * @code{.c}
26 | * b2WorldId worldId = {0};
27 | * @endcode
28 | *
29 | * These are both considered null.
30 | *
31 | * @warning Do not use the internals of these ids. They are subject to change. Ids should be treated as opaque objects.
32 | * @warning You should use ids to access objects in Box2D. Do not access files within the src folder. Such usage is unsupported.
33 | * @{
34 | */
35 |
36 | /// World id references a world instance. This should be treated as an opaque handle.
37 | typedef struct b2WorldId
38 | {
39 | uint16_t index1;
40 | uint16_t generation;
41 | } b2WorldId;
42 |
43 | /// Body id references a body instance. This should be treated as an opaque handle.
44 | typedef struct b2BodyId
45 | {
46 | int32_t index1;
47 | uint16_t world0;
48 | uint16_t generation;
49 | } b2BodyId;
50 |
51 | /// Shape id references a shape instance. This should be treated as an opaque handle.
52 | typedef struct b2ShapeId
53 | {
54 | int32_t index1;
55 | uint16_t world0;
56 | uint16_t generation;
57 | } b2ShapeId;
58 |
59 | /// Chain id references a chain instances. This should be treated as an opaque handle.
60 | typedef struct b2ChainId
61 | {
62 | int32_t index1;
63 | uint16_t world0;
64 | uint16_t generation;
65 | } b2ChainId;
66 |
67 | /// Joint id references a joint instance. This should be treated as an opaque handle.
68 | typedef struct b2JointId
69 | {
70 | int32_t index1;
71 | uint16_t world0;
72 | uint16_t generation;
73 | } b2JointId;
74 |
75 | /// Use these to make your identifiers null.
76 | /// You may also use zero initialization to get null.
77 | static const b2WorldId b2_nullWorldId = B2_ZERO_INIT;
78 | static const b2BodyId b2_nullBodyId = B2_ZERO_INIT;
79 | static const b2ShapeId b2_nullShapeId = B2_ZERO_INIT;
80 | static const b2ChainId b2_nullChainId = B2_ZERO_INIT;
81 | static const b2JointId b2_nullJointId = B2_ZERO_INIT;
82 |
83 | /// Macro to determine if any id is null.
84 | #define B2_IS_NULL( id ) ( id.index1 == 0 )
85 |
86 | /// Macro to determine if any id is non-null.
87 | #define B2_IS_NON_NULL( id ) ( id.index1 != 0 )
88 |
89 | /// Compare two ids for equality. Doesn't work for b2WorldId.
90 | #define B2_ID_EQUALS( id1, id2 ) ( id1.index1 == id2.index1 && id1.world0 == id2.world0 && id1.generation == id2.generation )
91 |
92 | /// Store a body id into a uint64_t.
93 | B2_INLINE uint64_t b2StoreBodyId( b2BodyId id )
94 | {
95 | return ( (uint64_t)id.index1 << 32 ) | ( (uint64_t)id.world0 ) << 16 | (uint64_t)id.generation;
96 | }
97 |
98 | /// Load a uint64_t into a body id.
99 | B2_INLINE b2BodyId b2LoadBodyId( uint64_t x )
100 | {
101 | b2BodyId id = { (int32_t)( x >> 32 ), (uint16_t)( x >> 16 ), (uint16_t)( x ) };
102 | return id;
103 | }
104 |
105 | /// Store a shape id into a uint64_t.
106 | B2_INLINE uint64_t b2StoreShapeId( b2ShapeId id )
107 | {
108 | return ( (uint64_t)id.index1 << 32 ) | ( (uint64_t)id.world0 ) << 16 | (uint64_t)id.generation;
109 | }
110 |
111 | /// Load a uint64_t into a shape id.
112 | B2_INLINE b2ShapeId b2LoadShapeId( uint64_t x )
113 | {
114 | b2ShapeId id = { (int32_t)( x >> 32 ), (uint16_t)( x >> 16 ), (uint16_t)( x ) };
115 | return id;
116 | }
117 |
118 | /// Store a chain id into a uint64_t.
119 | B2_INLINE uint64_t b2StoreChainId( b2ChainId id )
120 | {
121 | return ( (uint64_t)id.index1 << 32 ) | ( (uint64_t)id.world0 ) << 16 | (uint64_t)id.generation;
122 | }
123 |
124 | /// Load a uint64_t into a chain id.
125 | B2_INLINE b2ChainId b2LoadChainId( uint64_t x )
126 | {
127 | b2ChainId id = { (int32_t)( x >> 32 ), (uint16_t)( x >> 16 ), (uint16_t)( x ) };
128 | return id;
129 | }
130 |
131 | /// Store a joint id into a uint64_t.
132 | B2_INLINE uint64_t b2StoreJointId( b2JointId id )
133 | {
134 | return ( (uint64_t)id.index1 << 32 ) | ( (uint64_t)id.world0 ) << 16 | (uint64_t)id.generation;
135 | }
136 |
137 | /// Load a uint64_t into a joint id.
138 | B2_INLINE b2JointId b2LoadJointId( uint64_t x )
139 | {
140 | b2JointId id = { (int32_t)( x >> 32 ), (uint16_t)( x >> 16 ), (uint16_t)( x ) };
141 | return id;
142 | }
143 |
144 | /**@}*/
145 |
--------------------------------------------------------------------------------
/samples/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Box2D samples app
2 |
3 | # glad for OpenGL API
4 | set(GLAD_DIR ${CMAKE_SOURCE_DIR}/extern/glad)
5 |
6 | add_library(
7 | glad STATIC
8 | ${GLAD_DIR}/src/glad.c
9 | ${GLAD_DIR}/include/glad/glad.h
10 | ${GLAD_DIR}/include/KHR/khrplatform.h
11 | )
12 | target_include_directories(glad PUBLIC ${GLAD_DIR}/include)
13 |
14 | # glfw for windowing and input
15 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "GLFW Docs")
16 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "GLFW Examples")
17 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "GLFW Tests")
18 | set(GLFW_INSTALL OFF CACHE BOOL "GLFW Install")
19 |
20 | FetchContent_Declare(
21 | glfw
22 | GIT_REPOSITORY https://github.com/glfw/glfw.git
23 | GIT_TAG 3.4
24 | GIT_SHALLOW TRUE
25 | GIT_PROGRESS TRUE
26 | )
27 | FetchContent_MakeAvailable(glfw)
28 |
29 | # imgui and glfw backend for GUI
30 | # https://gist.github.com/jeffamstutz/992723dfabac4e3ffff265eb71a24cd9
31 | # Modified to pin to a specific imgui release
32 | FetchContent_Populate(imgui
33 | URL https://github.com/ocornut/imgui/archive/refs/tags/v1.91.3.zip
34 | SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/imgui
35 | )
36 |
37 | set(IMGUI_DIR ${CMAKE_SOURCE_DIR}/build/imgui)
38 |
39 | add_library(imgui STATIC
40 | ${IMGUI_DIR}/imconfig.h
41 | ${IMGUI_DIR}/imgui.h
42 |
43 | ${IMGUI_DIR}/imgui.cpp
44 | ${IMGUI_DIR}/imgui_draw.cpp
45 | ${IMGUI_DIR}/imgui_demo.cpp
46 | ${IMGUI_DIR}/imgui_tables.cpp
47 | ${IMGUI_DIR}/imgui_widgets.cpp
48 |
49 | ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
50 | ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
51 | )
52 |
53 | target_link_libraries(imgui PUBLIC glfw glad)
54 | target_include_directories(imgui PUBLIC ${IMGUI_DIR} ${IMGUI_DIR}/backends)
55 | target_compile_definitions(imgui PUBLIC IMGUI_DISABLE_OBSOLETE_FUNCTIONS)
56 | # The sample app also uses stb_truetype and this keeps the symbols separate
57 | target_compile_definitions(imgui PRIVATE IMGUI_STB_NAMESPACE=imgui_stb)
58 | set_target_properties(imgui PROPERTIES
59 | CXX_STANDARD 20
60 | CXX_STANDARD_REQUIRED YES
61 | CXX_EXTENSIONS NO
62 | )
63 |
64 | # jsmn for json
65 | set(JSMN_DIR ${CMAKE_SOURCE_DIR}/extern/jsmn)
66 |
67 | add_library(jsmn INTERFACE ${JSMN_DIR}/jsmn.h)
68 | target_include_directories(jsmn INTERFACE ${JSMN_DIR})
69 |
70 | set(BOX2D_SAMPLE_FILES
71 | car.cpp
72 | car.h
73 | donut.cpp
74 | donut.h
75 | doohickey.cpp
76 | doohickey.h
77 | draw.cpp
78 | draw.h
79 | main.cpp
80 | sample.cpp
81 | sample.h
82 | sample_benchmark.cpp
83 | sample_bodies.cpp
84 | sample_character.cpp
85 | sample_collision.cpp
86 | sample_continuous.cpp
87 | sample_determinism.cpp
88 | sample_events.cpp
89 | sample_geometry.cpp
90 | sample_joints.cpp
91 | sample_robustness.cpp
92 | sample_shapes.cpp
93 | sample_stacking.cpp
94 | sample_world.cpp
95 | shader.cpp
96 | shader.h)
97 |
98 | add_executable(samples ${BOX2D_SAMPLE_FILES})
99 |
100 | set_target_properties(samples PROPERTIES
101 | CXX_STANDARD 20
102 | CXX_STANDARD_REQUIRED YES
103 | CXX_EXTENSIONS NO
104 | )
105 |
106 | if (BOX2D_COMPILE_WARNING_AS_ERROR)
107 | set_target_properties(samples PROPERTIES COMPILE_WARNING_AS_ERROR ON)
108 | endif()
109 |
110 | target_include_directories(samples PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${JSMN_DIR})
111 | target_link_libraries(samples PUBLIC box2d shared imgui glfw glad enkiTS)
112 |
113 | # target_compile_definitions(samples PRIVATE "$<$:SAMPLES_DEBUG>")
114 | # message(STATUS "runtime = ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
115 | # message(STATUS "binary = ${CMAKE_CURRENT_BINARY_DIR}")
116 |
117 | # Copy font files, etc
118 | add_custom_command(
119 | TARGET samples POST_BUILD
120 | COMMAND ${CMAKE_COMMAND} -E copy_directory
121 | ${CMAKE_CURRENT_SOURCE_DIR}/data/
122 | ${CMAKE_CURRENT_BINARY_DIR}/data/)
123 |
124 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${BOX2D_SAMPLE_FILES})
125 |
--------------------------------------------------------------------------------
/samples/car.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/types.h"
7 |
8 | class Car
9 | {
10 | public:
11 | Car();
12 |
13 | void Spawn( b2WorldId worldId, b2Vec2 position, float scale, float hertz, float dampingRatio, float torque, void* userData );
14 | void Despawn();
15 |
16 | void SetSpeed( float speed );
17 | void SetTorque( float torque );
18 | void SetHertz( float hertz );
19 | void SetDampingRadio( float dampingRatio );
20 |
21 | b2BodyId m_chassisId;
22 | b2BodyId m_rearWheelId;
23 | b2BodyId m_frontWheelId;
24 | b2JointId m_rearAxleId;
25 | b2JointId m_frontAxleId;
26 | bool m_isSpawned;
27 | };
28 |
29 | class Truck
30 | {
31 | public:
32 | Truck();
33 |
34 | void Spawn( b2WorldId worldId, b2Vec2 position, float scale, float hertz, float dampingRatio, float torque, float density,
35 | void* userData );
36 | void Despawn();
37 |
38 | void SetSpeed( float speed );
39 | void SetTorque( float torque );
40 | void SetHertz( float hertz );
41 | void SetDampingRadio( float dampingRatio );
42 |
43 | b2BodyId m_chassisId;
44 | b2BodyId m_rearWheelId;
45 | b2BodyId m_frontWheelId;
46 | b2JointId m_rearAxleId;
47 | b2JointId m_frontAxleId;
48 | bool m_isSpawned;
49 | };
50 |
--------------------------------------------------------------------------------
/samples/data/background.fs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | out vec4 FragColor;
7 |
8 | uniform float time;
9 | uniform vec2 resolution;
10 | uniform vec3 baseColor;
11 |
12 | // A simple pseudo-random function
13 | float random(vec2 st)
14 | {
15 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
16 | }
17 |
18 | void main()
19 | {
20 | vec2 uv = gl_FragCoord.xy / resolution.xy;
21 |
22 | // Create some noise
23 | float noise = random(uv + time * 0.1);
24 |
25 | // Adjust these values to control the intensity and color of the grain
26 | float grainIntensity = 0.01;
27 |
28 | // Mix the base color with the noise
29 | vec3 color = baseColor + vec3(noise * grainIntensity);
30 |
31 | FragColor = vec4(color, 1.0);
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/samples/data/background.vs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 | #version 330
4 |
5 | layout(location = 0) in vec2 v_position;
6 |
7 | void main(void)
8 | {
9 | gl_Position = vec4(v_position, 0.0f, 1.0f);
10 | }
11 |
--------------------------------------------------------------------------------
/samples/data/circle.fs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | in vec2 f_position;
7 | in vec4 f_color;
8 | in float f_thickness;
9 |
10 | out vec4 fragColor;
11 |
12 | void main()
13 | {
14 | // radius in unit quad
15 | float radius = 1.0;
16 |
17 | // distance to circle
18 | vec2 w = f_position;
19 | float dw = length(w);
20 | float d = abs(dw - radius);
21 |
22 | fragColor = vec4(f_color.rgb, smoothstep(f_thickness, 0.0, d));
23 | }
24 |
--------------------------------------------------------------------------------
/samples/data/circle.vs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | uniform mat4 projectionMatrix;
7 | uniform float pixelScale;
8 |
9 | layout(location = 0) in vec2 v_localPosition;
10 | layout(location = 1) in vec2 v_instancePosition;
11 | layout(location = 2) in float v_instanceRadius;
12 | layout(location = 3) in vec4 v_instanceColor;
13 |
14 | out vec2 f_position;
15 | out vec4 f_color;
16 | out float f_thickness;
17 |
18 | void main()
19 | {
20 | f_position = v_localPosition;
21 | f_color = v_instanceColor;
22 | float radius = v_instanceRadius;
23 |
24 | // resolution.y = pixelScale * radius
25 | f_thickness = 3.0f / (pixelScale * radius);
26 |
27 | vec2 p = vec2(radius * v_localPosition.x, radius * v_localPosition.y) + v_instancePosition;
28 | gl_Position = projectionMatrix * vec4(p, 0.0f, 1.0f);
29 | }
30 |
--------------------------------------------------------------------------------
/samples/data/droid_sans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erincatto/box2d/4b5e72bd44bd139fab67ad890b70e1690ff405a8/samples/data/droid_sans.ttf
--------------------------------------------------------------------------------
/samples/data/map01.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/samples/data/map02.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
66 |
--------------------------------------------------------------------------------
/samples/data/map03.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
66 |
--------------------------------------------------------------------------------
/samples/data/ramp.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
67 |
--------------------------------------------------------------------------------
/samples/data/solid_capsule.fs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | in vec2 f_position;
7 | in vec4 f_color;
8 | in float f_length;
9 | in float f_thickness;
10 |
11 | out vec4 color;
12 |
13 | // Thanks to baz and kolyan3040 for help on this shader
14 | // todo this can be optimized a bit, keeping some terms for clarity
15 |
16 | // https://en.wikipedia.org/wiki/Alpha_compositing
17 | vec4 blend_colors(vec4 front,vec4 back)
18 | {
19 | vec3 cSrc = front.rgb;
20 | float alphaSrc = front.a;
21 | vec3 cDst = back.rgb;
22 | float alphaDst = back.a;
23 |
24 | vec3 cOut = cSrc * alphaSrc + cDst * alphaDst * (1.0 - alphaSrc);
25 | float alphaOut = alphaSrc + alphaDst * (1.0 - alphaSrc);
26 |
27 | // remove alpha from rgb
28 | cOut = cOut / alphaOut;
29 |
30 | return vec4(cOut, alphaOut);
31 | }
32 |
33 | void main()
34 | {
35 | // radius in unit quad
36 | float radius = 0.5 * (2.0 - f_length);
37 |
38 | vec4 borderColor = f_color;
39 | vec4 fillColor = 0.6f * borderColor;
40 |
41 | vec2 v1 = vec2(-0.5 * f_length, 0);
42 | vec2 v2 = vec2(0.5 * f_length, 0);
43 |
44 | // distance to line segment
45 | vec2 e = v2 - v1;
46 | vec2 w = f_position - v1;
47 | float we = dot(w, e);
48 | vec2 b = w - e * clamp(we / dot(e, e), 0.0, 1.0);
49 | float dw = length(b);
50 |
51 | // SDF union of capsule and line segment
52 | float d = min(dw, abs(dw - radius));
53 |
54 | // roll the fill alpha down at the border
55 | vec4 back = vec4(fillColor.rgb, fillColor.a * smoothstep(radius + f_thickness, radius, dw));
56 |
57 | // roll the border alpha down from 1 to 0 across the border thickness
58 | vec4 front = vec4(borderColor.rgb, smoothstep(f_thickness, 0.0f, d));
59 |
60 | color = blend_colors(front, back);
61 | }
62 |
--------------------------------------------------------------------------------
/samples/data/solid_capsule.vs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | uniform mat4 projectionMatrix;
7 | uniform float pixelScale;
8 |
9 | layout(location=0) in vec2 v_localPosition;
10 | layout(location=1) in vec4 v_instanceTransform;
11 | layout(location=2) in float v_instanceRadius;
12 | layout(location=3) in float v_instanceLength;
13 | layout(location=4) in vec4 v_instanceColor;
14 |
15 | out vec2 f_position;
16 | out vec4 f_color;
17 | out float f_length;
18 | out float f_thickness;
19 |
20 | void main()
21 | {
22 | f_position = v_localPosition;
23 | f_color = v_instanceColor;
24 |
25 | float radius = v_instanceRadius;
26 | float length = v_instanceLength;
27 |
28 | // scale quad large enough to hold capsule
29 | float scale = radius + 0.5 * length;
30 |
31 | // quad range of [-1, 1] implies normalize radius and length
32 | f_length = length / scale;
33 |
34 | // resolution.y = pixelScale * scale
35 | f_thickness = 3.0f / (pixelScale * scale);
36 |
37 | float x = v_instanceTransform.x;
38 | float y = v_instanceTransform.y;
39 | float c = v_instanceTransform.z;
40 | float s = v_instanceTransform.w;
41 | vec2 p = vec2(scale * v_localPosition.x, scale * v_localPosition.y);
42 | p = vec2((c * p.x - s * p.y) + x, (s * p.x + c * p.y) + y);
43 | gl_Position = projectionMatrix * vec4(p, 0.0, 1.0);
44 | }
45 |
--------------------------------------------------------------------------------
/samples/data/solid_circle.fs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | in vec2 f_position;
7 | in vec4 f_color;
8 | in float f_thickness;
9 |
10 | out vec4 fragColor;
11 |
12 | // https://en.wikipedia.org/wiki/Alpha_compositing
13 | vec4 blend_colors(vec4 front, vec4 back)
14 | {
15 | vec3 cSrc = front.rgb;
16 | float alphaSrc = front.a;
17 | vec3 cDst = back.rgb;
18 | float alphaDst = back.a;
19 |
20 | vec3 cOut = cSrc * alphaSrc + cDst * alphaDst * (1.0 - alphaSrc);
21 | float alphaOut = alphaSrc + alphaDst * (1.0 - alphaSrc);
22 | cOut = cOut / alphaOut;
23 |
24 | return vec4(cOut, alphaOut);
25 | }
26 |
27 | void main()
28 | {
29 | // radius in unit quad
30 | float radius = 1.0;
31 |
32 | // distance to axis line segment
33 | vec2 e = vec2(radius, 0);
34 | vec2 w = f_position;
35 | float we = dot(w, e);
36 | vec2 b = w - e * clamp(we / dot(e, e), 0.0, 1.0);
37 | float da = length(b);
38 |
39 | // distance to circle
40 | float dw = length(w);
41 | float dc = abs(dw - radius);
42 |
43 | // union of circle and axis
44 | float d = min(da, dc);
45 |
46 | vec4 borderColor = f_color;
47 | vec4 fillColor = 0.6f * borderColor;
48 |
49 | // roll the fill alpha down at the border
50 | vec4 back = vec4(fillColor.rgb, fillColor.a * smoothstep(radius + f_thickness, radius, dw));
51 |
52 | // roll the border alpha down from 1 to 0 across the border thickness
53 | vec4 front = vec4(borderColor.rgb, smoothstep(f_thickness, 0.0f, d));
54 |
55 | fragColor = blend_colors(front, back);
56 | }
57 |
--------------------------------------------------------------------------------
/samples/data/solid_circle.vs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | uniform mat4 projectionMatrix;
7 | uniform float pixelScale;
8 |
9 | layout(location = 0) in vec2 v_localPosition;
10 | layout(location = 1) in vec4 v_instanceTransform;
11 | layout(location = 2) in float v_instanceRadius;
12 | layout(location = 3) in vec4 v_instanceColor;
13 |
14 | out vec2 f_position;
15 | out vec4 f_color;
16 | out float f_thickness;
17 |
18 | void main()
19 | {
20 | f_position = v_localPosition;
21 | f_color = v_instanceColor;
22 | float radius = v_instanceRadius;
23 |
24 | // resolution.y = pixelScale * radius
25 | f_thickness = 3.0f / (pixelScale * radius);
26 |
27 | float x = v_instanceTransform.x;
28 | float y = v_instanceTransform.y;
29 | float c = v_instanceTransform.z;
30 | float s = v_instanceTransform.w;
31 | vec2 p = vec2(radius * v_localPosition.x, radius * v_localPosition.y);
32 | p = vec2((c * p.x - s * p.y) + x, (s * p.x + c * p.y) + y);
33 | gl_Position = projectionMatrix * vec4(p, 0.0f, 1.0f);
34 | }
35 |
--------------------------------------------------------------------------------
/samples/data/solid_polygon.fs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | in vec2 f_position;
7 | in vec2 f_points[8];
8 | flat in int f_count;
9 | in float f_radius;
10 | in vec4 f_color;
11 | in float f_thickness;
12 |
13 | out vec4 fragColor;
14 |
15 | // https://en.wikipedia.org/wiki/Alpha_compositing
16 | vec4 blend_colors(vec4 front, vec4 back)
17 | {
18 | vec3 cSrc = front.rgb;
19 | float alphaSrc = front.a;
20 | vec3 cDst = back.rgb;
21 | float alphaDst = back.a;
22 |
23 | vec3 cOut = cSrc * alphaSrc + cDst * alphaDst * (1.0 - alphaSrc);
24 | float alphaOut = alphaSrc + alphaDst * (1.0 - alphaSrc);
25 |
26 | // remove alpha from rgb
27 | cOut = cOut / alphaOut;
28 |
29 | return vec4(cOut, alphaOut);
30 | }
31 |
32 | float cross2d(in vec2 v1, in vec2 v2)
33 | {
34 | return v1.x * v2.y - v1.y * v2.x;
35 | }
36 |
37 | // Signed distance function for convex polygon
38 | float sdConvexPolygon(in vec2 p, in vec2[8] v, in int count)
39 | {
40 | // Initial squared distance
41 | float d = dot(p - v[0], p - v[0]);
42 |
43 | // Consider query point inside to start
44 | float side = -1.0;
45 | int j = count - 1;
46 | for (int i = 0; i < count; ++i)
47 | {
48 | // Distance to a polygon edge
49 | vec2 e = v[i] - v[j];
50 | vec2 w = p - v[j];
51 | float we = dot(w, e);
52 | vec2 b = w - e * clamp(we / dot(e, e), 0.0, 1.0);
53 | float bb = dot(b, b);
54 |
55 | // Track smallest distance
56 | if (bb < d)
57 | {
58 | d = bb;
59 | }
60 |
61 | // If the query point is outside any edge then it is outside the entire polygon.
62 | // This depends on the CCW winding order of points.
63 | float s = cross2d(w, e);
64 | if (s >= 0.0)
65 | {
66 | side = 1.0;
67 | }
68 |
69 | j = i;
70 | }
71 |
72 | return side * sqrt(d);
73 | }
74 |
75 | void main()
76 | {
77 | vec4 borderColor = f_color;
78 | vec4 fillColor = 0.6f * borderColor;
79 |
80 | float dw = sdConvexPolygon(f_position, f_points, f_count);
81 | float d = abs(dw - f_radius);
82 |
83 | // roll the fill alpha down at the border
84 | vec4 back = vec4(fillColor.rgb, fillColor.a * smoothstep(f_radius + f_thickness, f_radius, dw));
85 |
86 | // roll the border alpha down from 1 to 0 across the border thickness
87 | vec4 front = vec4(borderColor.rgb, smoothstep(f_thickness, 0.0f, d));
88 |
89 | fragColor = blend_colors(front, back);
90 |
91 | // todo debugging
92 | // float resy = 3.0f / f_thickness;
93 |
94 | // if (resy < 539.9f)
95 | // {
96 | // fragColor = vec4(1, 0, 0, 1);
97 | // }
98 | // else if (resy > 540.1f)
99 | // {
100 | // fragColor = vec4(0, 1, 0, 1);
101 | // }
102 | // else
103 | // {
104 | // fragColor = vec4(0, 0, 1, 1);
105 | // }
106 | }
107 |
--------------------------------------------------------------------------------
/samples/data/solid_polygon.vs:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #version 330
5 |
6 | uniform mat4 projectionMatrix;
7 | uniform float pixelScale;
8 |
9 | layout(location = 0) in vec2 v_localPosition;
10 | layout(location = 1) in vec4 v_instanceTransform;
11 | layout(location = 2) in vec4 v_instancePoints12;
12 | layout(location = 3) in vec4 v_instancePoints34;
13 | layout(location = 4) in vec4 v_instancePoints56;
14 | layout(location = 5) in vec4 v_instancePoints78;
15 | layout(location = 6) in int v_instanceCount;
16 | layout(location = 7) in float v_instanceRadius;
17 | layout(location = 8) in vec4 v_instanceColor;
18 |
19 | out vec2 f_position;
20 | out vec4 f_color;
21 | out vec2 f_points[8];
22 | flat out int f_count;
23 | out float f_radius;
24 | out float f_thickness;
25 |
26 | void main()
27 | {
28 | f_position = v_localPosition;
29 | f_color = v_instanceColor;
30 |
31 | f_radius = v_instanceRadius;
32 | f_count = v_instanceCount;
33 |
34 | f_points[0] = v_instancePoints12.xy;
35 | f_points[1] = v_instancePoints12.zw;
36 | f_points[2] = v_instancePoints34.xy;
37 | f_points[3] = v_instancePoints34.zw;
38 | f_points[4] = v_instancePoints56.xy;
39 | f_points[5] = v_instancePoints56.zw;
40 | f_points[6] = v_instancePoints78.xy;
41 | f_points[7] = v_instancePoints78.zw;
42 |
43 | // Compute polygon AABB
44 | vec2 lower = f_points[0];
45 | vec2 upper = f_points[0];
46 | for (int i = 1; i < v_instanceCount; ++i)
47 | {
48 | lower = min(lower, f_points[i]);
49 | upper = max(upper, f_points[i]);
50 | }
51 |
52 | vec2 center = 0.5 * (lower + upper);
53 | vec2 width = upper - lower;
54 | float maxWidth = max(width.x, width.y);
55 |
56 | float scale = f_radius + 0.5 * maxWidth;
57 | float invScale = 1.0 / scale;
58 |
59 | // Shift and scale polygon points so they fit in 2x2 quad
60 | for (int i = 0; i < f_count; ++i)
61 | {
62 | f_points[i] = invScale * (f_points[i] - center);
63 | }
64 |
65 | // Scale radius as well
66 | f_radius = invScale * f_radius;
67 |
68 | // resolution.y = pixelScale * scale
69 | f_thickness = 3.0f / (pixelScale * scale);
70 |
71 | // scale up and transform quad to fit polygon
72 | float x = v_instanceTransform.x;
73 | float y = v_instanceTransform.y;
74 | float c = v_instanceTransform.z;
75 | float s = v_instanceTransform.w;
76 | vec2 p = vec2(scale * v_localPosition.x, scale * v_localPosition.y) + center;
77 | p = vec2((c * p.x - s * p.y) + x, (s * p.x + c * p.y) + y);
78 | gl_Position = projectionMatrix * vec4(p, 0.0f, 1.0f);
79 | }
80 |
--------------------------------------------------------------------------------
/samples/donut.cpp:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "donut.h"
5 |
6 | #include "box2d/box2d.h"
7 | #include "box2d/math_functions.h"
8 |
9 | #include
10 |
11 | Donut::Donut()
12 | {
13 | for ( int i = 0; i < m_sides; ++i )
14 | {
15 | m_bodyIds[i] = b2_nullBodyId;
16 | m_jointIds[i] = b2_nullJointId;
17 | }
18 |
19 | m_isSpawned = false;
20 | }
21 |
22 | void Donut::Create( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, bool enableSensorEvents, void* userData )
23 | {
24 | assert( m_isSpawned == false );
25 |
26 | for ( int i = 0; i < m_sides; ++i )
27 | {
28 | assert( B2_IS_NULL( m_bodyIds[i] ) );
29 | assert( B2_IS_NULL( m_jointIds[i] ) );
30 | }
31 |
32 | float radius = 1.0f * scale;
33 | float deltaAngle = 2.0f * B2_PI / m_sides;
34 | float length = 2.0f * B2_PI * radius / m_sides;
35 |
36 | b2Capsule capsule = { { 0.0f, -0.5f * length }, { 0.0f, 0.5f * length }, 0.25f * scale };
37 |
38 | b2Vec2 center = position;
39 |
40 | b2BodyDef bodyDef = b2DefaultBodyDef();
41 | bodyDef.type = b2_dynamicBody;
42 | bodyDef.userData = userData;
43 |
44 | b2ShapeDef shapeDef = b2DefaultShapeDef();
45 | shapeDef.enableSensorEvents = enableSensorEvents;
46 | shapeDef.filter.groupIndex = -groupIndex;
47 | shapeDef.material.friction = 0.3f;
48 |
49 | // Create bodies
50 | float angle = 0.0f;
51 | for ( int i = 0; i < m_sides; ++i )
52 | {
53 | bodyDef.position = { radius * cosf( angle ) + center.x, radius * sinf( angle ) + center.y };
54 | bodyDef.rotation = b2MakeRot( angle );
55 |
56 | m_bodyIds[i] = b2CreateBody( worldId, &bodyDef );
57 | b2CreateCapsuleShape( m_bodyIds[i], &shapeDef, &capsule );
58 |
59 | angle += deltaAngle;
60 | }
61 |
62 | // Create joints
63 | b2WeldJointDef weldDef = b2DefaultWeldJointDef();
64 | weldDef.angularHertz = 5.0f;
65 | weldDef.angularDampingRatio = 0.0f;
66 | weldDef.localAnchorA = { 0.0f, 0.5f * length };
67 | weldDef.localAnchorB = { 0.0f, -0.5f * length };
68 |
69 | b2BodyId prevBodyId = m_bodyIds[m_sides - 1];
70 | for ( int i = 0; i < m_sides; ++i )
71 | {
72 | weldDef.bodyIdA = prevBodyId;
73 | weldDef.bodyIdB = m_bodyIds[i];
74 | b2Rot rotA = b2Body_GetRotation( prevBodyId );
75 | b2Rot rotB = b2Body_GetRotation( m_bodyIds[i] );
76 | weldDef.referenceAngle = b2RelativeAngle( rotB, rotA );
77 | m_jointIds[i] = b2CreateWeldJoint( worldId, &weldDef );
78 | prevBodyId = weldDef.bodyIdB;
79 | }
80 |
81 | m_isSpawned = true;
82 | }
83 |
84 | void Donut::Destroy()
85 | {
86 | assert( m_isSpawned == true );
87 |
88 | for ( int i = 0; i < m_sides; ++i )
89 | {
90 | b2DestroyBody( m_bodyIds[i] );
91 | m_bodyIds[i] = b2_nullBodyId;
92 | m_jointIds[i] = b2_nullJointId;
93 | }
94 |
95 | m_isSpawned = false;
96 | }
97 |
--------------------------------------------------------------------------------
/samples/donut.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/types.h"
7 |
8 | class Donut
9 | {
10 | public:
11 | Donut();
12 |
13 | void Create( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, bool enableSensorEvents, void* userData );
14 | void Destroy();
15 |
16 | static constexpr int m_sides = 7;
17 | b2BodyId m_bodyIds[m_sides];
18 | b2JointId m_jointIds[m_sides];
19 | bool m_isSpawned;
20 | };
21 |
--------------------------------------------------------------------------------
/samples/doohickey.cpp:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2022 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "doohickey.h"
5 |
6 | #include "box2d/box2d.h"
7 | #include "box2d/math_functions.h"
8 |
9 | #include
10 |
11 | Doohickey::Doohickey()
12 | {
13 | m_wheelId1 = {};
14 | m_wheelId2 = {};
15 | m_barId1 = {};
16 | m_barId2 = {};
17 |
18 | m_axleId1 = {};
19 | m_axleId2 = {};
20 | m_sliderId = {};
21 |
22 | m_isSpawned = false;
23 | }
24 |
25 | void Doohickey::Spawn( b2WorldId worldId, b2Vec2 position, float scale )
26 | {
27 | assert( m_isSpawned == false );
28 |
29 | b2BodyDef bodyDef = b2DefaultBodyDef();
30 | bodyDef.type = b2_dynamicBody;
31 |
32 | b2ShapeDef shapeDef = b2DefaultShapeDef();
33 | b2Circle circle = { { 0.0f, 0.0f }, 1.0f * scale };
34 | b2Capsule capsule = { { -3.5f * scale, 0.0f }, { 3.5f * scale, 0.0f }, 0.15f * scale };
35 |
36 | bodyDef.position = b2MulAdd( position, scale, { -5.0f, 3.0f } );
37 | m_wheelId1 = b2CreateBody( worldId, &bodyDef );
38 | b2CreateCircleShape( m_wheelId1, &shapeDef, &circle );
39 |
40 | bodyDef.position = b2MulAdd( position, scale, { 5.0f, 3.0f } );
41 | m_wheelId2 = b2CreateBody( worldId, &bodyDef );
42 | b2CreateCircleShape( m_wheelId2, &shapeDef, &circle );
43 |
44 | bodyDef.position = b2MulAdd( position, scale, { -1.5f, 3.0f } );
45 | m_barId1 = b2CreateBody( worldId, &bodyDef );
46 | b2CreateCapsuleShape( m_barId1, &shapeDef, &capsule );
47 |
48 | bodyDef.position = b2MulAdd( position, scale, { 1.5f, 3.0f } );
49 | m_barId2 = b2CreateBody( worldId, &bodyDef );
50 | b2CreateCapsuleShape( m_barId2, &shapeDef, &capsule );
51 |
52 | b2RevoluteJointDef revoluteDef = b2DefaultRevoluteJointDef();
53 |
54 | revoluteDef.bodyIdA = m_wheelId1;
55 | revoluteDef.bodyIdB = m_barId1;
56 | revoluteDef.localAnchorA = { 0.0f, 0.0f };
57 | revoluteDef.localAnchorB = { -3.5f * scale, 0.0f };
58 | revoluteDef.enableMotor = true;
59 | revoluteDef.maxMotorTorque = 2.0f * scale;
60 | b2CreateRevoluteJoint( worldId, &revoluteDef );
61 |
62 | revoluteDef.bodyIdA = m_wheelId2;
63 | revoluteDef.bodyIdB = m_barId2;
64 | revoluteDef.localAnchorA = { 0.0f, 0.0f };
65 | revoluteDef.localAnchorB = { 3.5f * scale, 0.0f };
66 | revoluteDef.enableMotor = true;
67 | revoluteDef.maxMotorTorque = 2.0f * scale;
68 | b2CreateRevoluteJoint( worldId, &revoluteDef );
69 |
70 | b2PrismaticJointDef prismaticDef = b2DefaultPrismaticJointDef();
71 | prismaticDef.bodyIdA = m_barId1;
72 | prismaticDef.bodyIdB = m_barId2;
73 | prismaticDef.localAxisA = { 1.0f, 0.0f };
74 | prismaticDef.localAnchorA = { 2.0f * scale, 0.0f };
75 | prismaticDef.localAnchorB = { -2.0f * scale, 0.0f };
76 | prismaticDef.lowerTranslation = -2.0f * scale;
77 | prismaticDef.upperTranslation = 2.0f * scale;
78 | prismaticDef.enableLimit = true;
79 | prismaticDef.enableMotor = true;
80 | prismaticDef.maxMotorForce = 2.0f * scale;
81 | prismaticDef.enableSpring = true;
82 | prismaticDef.hertz = 1.0f;
83 | prismaticDef.dampingRatio = 0.5;
84 | b2CreatePrismaticJoint( worldId, &prismaticDef );
85 | }
86 |
87 | void Doohickey::Despawn()
88 | {
89 | assert( m_isSpawned == true );
90 |
91 | b2DestroyJoint( m_axleId1 );
92 | b2DestroyJoint( m_axleId2 );
93 | b2DestroyJoint( m_sliderId );
94 |
95 | b2DestroyBody( m_wheelId1 );
96 | b2DestroyBody( m_wheelId2 );
97 | b2DestroyBody( m_barId1 );
98 | b2DestroyBody( m_barId2 );
99 |
100 | m_isSpawned = false;
101 | }
102 |
--------------------------------------------------------------------------------
/samples/doohickey.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/types.h"
7 |
8 | class Doohickey
9 | {
10 | public:
11 | Doohickey();
12 |
13 | void Spawn( b2WorldId worldId, b2Vec2 position, float scale );
14 | void Despawn();
15 |
16 | b2BodyId m_wheelId1;
17 | b2BodyId m_wheelId2;
18 | b2BodyId m_barId1;
19 | b2BodyId m_barId2;
20 |
21 | b2JointId m_axleId1;
22 | b2JointId m_axleId2;
23 | b2JointId m_sliderId;
24 |
25 | bool m_isSpawned;
26 | };
27 |
--------------------------------------------------------------------------------
/samples/draw.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/types.h"
7 |
8 | struct ImFont;
9 |
10 | struct Camera
11 | {
12 | Camera();
13 |
14 | void ResetView();
15 | b2Vec2 ConvertScreenToWorld( b2Vec2 screenPoint );
16 | b2Vec2 ConvertWorldToScreen( b2Vec2 worldPoint );
17 | void BuildProjectionMatrix( float* m, float zBias );
18 | b2AABB GetViewBounds();
19 |
20 | b2Vec2 m_center;
21 | float m_zoom;
22 | int m_width;
23 | int m_height;
24 | };
25 |
26 | // This class implements Box2D debug drawing callbacks
27 | class Draw
28 | {
29 | public:
30 | Draw();
31 | ~Draw();
32 |
33 | void Create( Camera* camera );
34 | void Destroy();
35 |
36 | void DrawPolygon( const b2Vec2* vertices, int32_t vertexCount, b2HexColor color );
37 | void DrawSolidPolygon( b2Transform transform, const b2Vec2* vertices, int32_t vertexCount, float radius, b2HexColor color );
38 |
39 | void DrawCircle( b2Vec2 center, float radius, b2HexColor color );
40 | void DrawSolidCircle( b2Transform transform, b2Vec2 center, float radius, b2HexColor color );
41 |
42 | void DrawSolidCapsule( b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color );
43 |
44 | void DrawSegment( b2Vec2 p1, b2Vec2 p2, b2HexColor color );
45 |
46 | void DrawTransform( b2Transform transform );
47 |
48 | void DrawPoint( b2Vec2 p, float size, b2HexColor color );
49 |
50 | void DrawString( int x, int y, const char* string, ... );
51 |
52 | void DrawString( b2Vec2 p, const char* string, ... );
53 |
54 | void DrawAABB( b2AABB aabb, b2HexColor color );
55 |
56 | void Flush();
57 | void DrawBackground();
58 |
59 | Camera* m_camera;
60 | bool m_showUI;
61 | struct GLBackground* m_background;
62 | struct GLPoints* m_points;
63 | struct GLLines* m_lines;
64 | struct GLCircles* m_circles;
65 | struct GLSolidCircles* m_solidCircles;
66 | struct GLSolidCapsules* m_solidCapsules;
67 | struct GLSolidPolygons* m_solidPolygons;
68 | b2DebugDraw m_debugDraw;
69 |
70 | ImFont* m_smallFont;
71 | ImFont* m_regularFont;
72 | ImFont* m_mediumFont;
73 | ImFont* m_largeFont;
74 | };
75 |
--------------------------------------------------------------------------------
/samples/sample.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/id.h"
7 | #include "box2d/types.h"
8 | #include "draw.h"
9 |
10 | #define ARRAY_COUNT( A ) (int)( sizeof( A ) / sizeof( A[0] ) )
11 |
12 | namespace enki
13 | {
14 | class TaskScheduler;
15 | };
16 |
17 | struct SampleContext
18 | {
19 | void Save();
20 | void Load();
21 |
22 | struct GLFWwindow* window = nullptr;
23 | Camera camera;
24 | Draw draw;
25 | float hertz = 60.0f;
26 | int subStepCount = 4;
27 | int workerCount = 1;
28 | bool restart = false;
29 | bool pause = false;
30 | bool singleStep = false;
31 | bool useCameraBounds = false;
32 | bool drawJointExtras = false;
33 | bool drawBounds = false;
34 | bool drawMass = false;
35 | bool drawBodyNames = false;
36 | bool drawContactPoints = false;
37 | bool drawContactNormals = false;
38 | bool drawContactImpulses = false;
39 | bool drawContactFeatures = false;
40 | bool drawFrictionImpulses = false;
41 | bool drawIslands = false;
42 | bool drawGraphColors = false;
43 | bool drawCounters = false;
44 | bool drawProfile = false;
45 | bool enableWarmStarting = true;
46 | bool enableContinuous = true;
47 | bool enableSleep = true;
48 |
49 | // These are persisted
50 | int sampleIndex = 0;
51 | bool drawShapes = true;
52 | bool drawJoints = true;
53 | };
54 |
55 | class Sample
56 | {
57 | public:
58 | explicit Sample( SampleContext* context );
59 | virtual ~Sample();
60 |
61 | void CreateWorld( );
62 |
63 | void DrawTitle( const char* string );
64 | virtual void Step( );
65 | virtual void UpdateGui()
66 | {
67 | }
68 | virtual void Keyboard( int )
69 | {
70 | }
71 | virtual void MouseDown( b2Vec2 p, int button, int mod );
72 | virtual void MouseUp( b2Vec2 p, int button );
73 | virtual void MouseMove( b2Vec2 p );
74 |
75 | void DrawTextLine( const char* text, ... );
76 | void ResetProfile();
77 | void ShiftOrigin( b2Vec2 newOrigin );
78 |
79 | static int ParsePath( const char* svgPath, b2Vec2 offset, b2Vec2* points, int capacity, float scale, bool reverseOrder );
80 |
81 | friend class DestructionListener;
82 | friend class BoundaryListener;
83 | friend class ContactListener;
84 |
85 | static constexpr int m_maxTasks = 64;
86 | static constexpr int m_maxThreads = 64;
87 |
88 | #ifdef NDEBUG
89 | static constexpr bool m_isDebug = false;
90 | #else
91 | static constexpr bool m_isDebug = true;
92 | #endif
93 |
94 | SampleContext* m_context;
95 | Camera* m_camera;
96 | Draw* m_draw;
97 |
98 | enki::TaskScheduler* m_scheduler;
99 | class SampleTask* m_tasks;
100 | int m_taskCount;
101 | int m_threadCount;
102 |
103 | b2BodyId m_groundBodyId;
104 |
105 | b2WorldId m_worldId;
106 | b2JointId m_mouseJointId;
107 | int m_stepCount;
108 | b2Profile m_maxProfile;
109 | b2Profile m_totalProfile;
110 |
111 | private:
112 | int m_textLine;
113 | int m_textIncrement;
114 | };
115 |
116 | typedef Sample* SampleCreateFcn( SampleContext* context );
117 |
118 | int RegisterSample( const char* category, const char* name, SampleCreateFcn* fcn );
119 |
120 | struct SampleEntry
121 | {
122 | const char* category;
123 | const char* name;
124 | SampleCreateFcn* createFcn;
125 | };
126 |
127 | #define MAX_SAMPLES 256
128 | extern SampleEntry g_sampleEntries[MAX_SAMPLES];
129 | extern int g_sampleCount;
130 |
--------------------------------------------------------------------------------
/samples/sample_determinism.cpp:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2022 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "determinism.h"
5 | #include "draw.h"
6 | #include "sample.h"
7 |
8 | #include "box2d/box2d.h"
9 | #include "box2d/math_functions.h"
10 |
11 | #include
12 | #include
13 | #include
14 |
15 | // This sample provides a visual representation of the cross platform determinism unit test.
16 | // The scenario is designed to produce a chaotic result engaging:
17 | // - continuous collision
18 | // - joint limits (approximate atan2)
19 | // - b2MakeRot (approximate sin/cos)
20 | // Once all the bodies go to sleep the step counter and transform hash is emitted which
21 | // can then be transferred to the unit test and tested in GitHub build actions.
22 | // See CrossPlatformTest in the unit tests.
23 | class FallingHinges : public Sample
24 | {
25 | public:
26 |
27 | explicit FallingHinges( SampleContext* context )
28 | : Sample( context )
29 | {
30 | if ( m_context->restart == false )
31 | {
32 | m_context->camera.m_center = { 0.0f, 7.5f };
33 | m_context->camera.m_zoom = 10.0f;
34 | }
35 |
36 | m_data = CreateFallingHinges( m_worldId );
37 | m_done = false;
38 | }
39 |
40 | ~FallingHinges() override
41 | {
42 | DestroyFallingHinges( &m_data );
43 | }
44 |
45 | void Step() override
46 | {
47 | Sample::Step();
48 |
49 | if (m_done == false)
50 | {
51 | m_done = UpdateFallingHinges( m_worldId, &m_data );
52 | }
53 | else
54 | {
55 | DrawTextLine( "sleep step = %d, hash = 0x%08x", m_data.sleepStep, m_data.hash );
56 | }
57 | }
58 |
59 | static Sample* Create( SampleContext* context )
60 | {
61 | return new FallingHinges( context );
62 | }
63 |
64 | FallingHingeData m_data;
65 | bool m_done;
66 | };
67 |
68 | static int sampleFallingHinges = RegisterSample( "Determinism", "Falling Hinges", FallingHinges::Create );
69 |
--------------------------------------------------------------------------------
/samples/sample_geometry.cpp:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "draw.h"
5 | #include "random.h"
6 | #include "sample.h"
7 |
8 | #include "box2d/math_functions.h"
9 |
10 | #include
11 |
12 | class ConvexHull : public Sample
13 | {
14 | public:
15 | enum
16 | {
17 | e_count = B2_MAX_POLYGON_VERTICES
18 | };
19 |
20 | explicit ConvexHull( SampleContext* context )
21 | : Sample( context )
22 | {
23 | if ( m_context->restart == false )
24 | {
25 | m_context->camera.m_center = { 0.5f, 0.0f };
26 | m_context->camera.m_zoom = 25.0f * 0.3f;
27 | }
28 |
29 | m_generation = 0;
30 | m_auto = false;
31 | m_bulk = false;
32 | Generate();
33 | }
34 |
35 | void Generate()
36 | {
37 | #if 0
38 | m_points[0] = { 5.65314484f, 0.204832315f };
39 | m_points[1] = {-5.65314484f, -0.204832315f };
40 | m_points[2] = {2.34463644f, 1.15731204f };
41 | m_points[3] = {0.0508846045f, 3.23230696f };
42 | m_points[4] = {-5.65314484f, -0.204832315f };
43 | m_points[5] = {-5.65314484f, -0.204832315f };
44 | m_points[6] = {3.73758054f, -1.11098099f };
45 | m_points[7] = {1.33504069f, -4.43795443f };
46 |
47 | m_count = e_count;
48 | #elif 0
49 | m_points[0] = { -0.328125, 0.179688 };
50 | m_points[1] = { -0.203125, 0.304688 };
51 | m_points[2] = { 0.171875, 0.304688 };
52 | m_points[3] = { 0.359375, 0.117188 };
53 | m_points[4] = { 0.359375, -0.195313 };
54 | m_points[5] = { 0.234375, -0.320313 };
55 | m_points[6] = { -0.265625, -0.257813 };
56 | m_points[7] = { -0.328125, -0.132813 };
57 |
58 | b2Hull hull = b2ComputeHull( m_points, 8 );
59 | bool valid = b2ValidateHull( &hull );
60 | if ( valid == false )
61 | {
62 | assert( valid );
63 | }
64 |
65 | m_count = e_count;
66 | #else
67 |
68 | float angle = B2_PI * RandomFloat();
69 | b2Rot r = b2MakeRot( angle );
70 |
71 | b2Vec2 lowerBound = { -4.0f, -4.0f };
72 | b2Vec2 upperBound = { 4.0f, 4.0f };
73 |
74 | for ( int i = 0; i < e_count; ++i )
75 | {
76 | float x = 10.0f * RandomFloat();
77 | float y = 10.0f * RandomFloat();
78 |
79 | // Clamp onto a square to help create collinearities.
80 | // This will stress the convex hull algorithm.
81 | b2Vec2 v = b2Clamp( { x, y }, lowerBound, upperBound );
82 | m_points[i] = b2RotateVector( r, v );
83 | }
84 |
85 | m_count = e_count;
86 | #endif
87 |
88 | m_generation += 1;
89 | }
90 |
91 | void Keyboard( int key ) override
92 | {
93 | switch ( key )
94 | {
95 | case GLFW_KEY_A:
96 | m_auto = !m_auto;
97 | break;
98 |
99 | case GLFW_KEY_B:
100 | m_bulk = !m_bulk;
101 | break;
102 |
103 | case GLFW_KEY_G:
104 | Generate();
105 | break;
106 |
107 | default:
108 | break;
109 | }
110 | }
111 |
112 | void Step() override
113 | {
114 | Sample::Step();
115 |
116 | DrawTextLine( "Options: generate(g), auto(a), bulk(b)" );
117 |
118 | b2Hull hull;
119 | bool valid = false;
120 | float milliseconds = 0.0f;
121 |
122 | if ( m_bulk )
123 | {
124 | #if 1
125 | // defect hunting
126 | for ( int i = 0; i < 10000; ++i )
127 | {
128 | Generate();
129 | hull = b2ComputeHull( m_points, m_count );
130 | if ( hull.count == 0 )
131 | {
132 | // m_bulk = false;
133 | // break;
134 | continue;
135 | }
136 |
137 | valid = b2ValidateHull( &hull );
138 | if ( valid == false || m_bulk == false )
139 | {
140 | m_bulk = false;
141 | break;
142 | }
143 | }
144 | #else
145 | // performance
146 | Generate();
147 | b2Timer timer;
148 | for ( int i = 0; i < 1000000; ++i )
149 | {
150 | hull = b2ComputeHull( m_points, m_count );
151 | }
152 | valid = hull.count > 0;
153 | milliseconds = timer.GetMilliseconds();
154 | #endif
155 | }
156 | else
157 | {
158 | if ( m_auto )
159 | {
160 | Generate();
161 | }
162 |
163 | hull = b2ComputeHull( m_points, m_count );
164 | if ( hull.count > 0 )
165 | {
166 | valid = b2ValidateHull( &hull );
167 | if ( valid == false )
168 | {
169 | m_auto = false;
170 | }
171 | }
172 | }
173 |
174 | if ( valid == false )
175 | {
176 | DrawTextLine( "generation = %d, FAILED", m_generation );
177 | }
178 | else
179 | {
180 | DrawTextLine( "generation = %d, count = %d", m_generation, hull.count );
181 | }
182 |
183 | if ( milliseconds > 0.0f )
184 | {
185 | DrawTextLine( "milliseconds = %g", milliseconds );
186 | }
187 |
188 | m_context->draw.DrawPolygon( hull.points, hull.count, b2_colorGray );
189 |
190 | for ( int32_t i = 0; i < m_count; ++i )
191 | {
192 | m_context->draw.DrawPoint( m_points[i], 5.0f, b2_colorBlue );
193 | m_context->draw.DrawString( b2Add( m_points[i], { 0.1f, 0.1f } ), "%d", i );
194 | }
195 |
196 | for ( int32_t i = 0; i < hull.count; ++i )
197 | {
198 | m_context->draw.DrawPoint( hull.points[i], 6.0f, b2_colorGreen );
199 | }
200 | }
201 |
202 | static Sample* Create( SampleContext* context )
203 | {
204 | return new ConvexHull( context );
205 | }
206 |
207 | b2Vec2 m_points[B2_MAX_POLYGON_VERTICES];
208 | int32_t m_count;
209 | int32_t m_generation;
210 | bool m_auto;
211 | bool m_bulk;
212 | };
213 |
214 | static int sampleIndex = RegisterSample( "Geometry", "Convex Hull", ConvexHull::Create );
215 |
--------------------------------------------------------------------------------
/samples/shader.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2024 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include
7 |
8 | uint32_t CreateProgramFromFiles( const char* vertexPath, const char* fragmentPath );
9 | uint32_t CreateProgramFromStrings( const char* vertexString, const char* fragmentString );
10 |
11 | void CheckOpenGL();
12 | void DumpInfoGL();
13 | void PrintLogGL( int object );
14 |
--------------------------------------------------------------------------------
/shared/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Box2D code shared by samples, benchmarks, and unit tests
2 |
3 | set(BOX2D_SHARED_FILES
4 | benchmarks.c
5 | benchmarks.h
6 | determinism.c
7 | determinism.h
8 | human.c
9 | human.h
10 | random.c
11 | random.h
12 | )
13 |
14 | add_library(shared STATIC ${BOX2D_SHARED_FILES})
15 |
16 | set_target_properties(shared PROPERTIES
17 | C_STANDARD 17
18 | )
19 |
20 | if (BOX2D_COMPILE_WARNING_AS_ERROR)
21 | set_target_properties(shared PROPERTIES COMPILE_WARNING_AS_ERROR ON)
22 | endif()
23 |
24 | target_include_directories(shared PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
25 | target_link_libraries(shared PRIVATE box2d)
26 |
27 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${BOX2D_SHARED_FILES})
--------------------------------------------------------------------------------
/shared/benchmarks.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2022 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 | #pragma once
4 |
5 | #include "box2d/id.h"
6 |
7 | // This allows benchmarks to be tested on the benchmark app and also visualized in the samples app
8 |
9 | #ifdef __cplusplus
10 | extern "C"
11 | {
12 | #endif
13 |
14 | void CreateJointGrid( b2WorldId worldId );
15 | void CreateLargePyramid( b2WorldId worldId );
16 | void CreateManyPyramids( b2WorldId worldId );
17 | void CreateRain( b2WorldId worldId );
18 | float StepRain( b2WorldId worldId, int stepCount );
19 | void CreateSpinner( b2WorldId worldId );
20 | float StepSpinner( b2WorldId worldId, int stepCount );
21 | void CreateSmash( b2WorldId worldId );
22 | void CreateTumbler( b2WorldId worldId );
23 |
24 | #ifdef __cplusplus
25 | }
26 | #endif
27 |
--------------------------------------------------------------------------------
/shared/determinism.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2022 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "determinism.h"
5 |
6 | #include "box2d/box2d.h"
7 |
8 | #include
9 | #include
10 |
11 | FallingHingeData CreateFallingHinges( b2WorldId worldId )
12 | {
13 | {
14 | b2BodyDef bodyDef = b2DefaultBodyDef();
15 | bodyDef.position = (b2Vec2){ 0.0f, -1.0f };
16 | b2BodyId groundId = b2CreateBody( worldId, &bodyDef );
17 |
18 | b2Polygon box = b2MakeBox( 20.0f, 1.0f );
19 | b2ShapeDef shapeDef = b2DefaultShapeDef();
20 | b2CreatePolygonShape( groundId, &shapeDef, &box );
21 | }
22 |
23 | int columnCount = 4;
24 | int rowCount = 30;
25 | int bodyCount = rowCount * columnCount;
26 |
27 | b2BodyId* bodyIds = calloc( bodyCount, sizeof( b2BodyId ) );
28 |
29 | float h = 0.25f;
30 | float r = 0.1f * h;
31 | b2Polygon box = b2MakeRoundedBox( h - r, h - r, r );
32 |
33 | b2ShapeDef shapeDef = b2DefaultShapeDef();
34 | shapeDef.material.friction = 0.3f;
35 |
36 | float offset = 0.4f * h;
37 | float dx = 10.0f * h;
38 | float xroot = -0.5f * dx * ( columnCount - 1.0f );
39 |
40 | b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef();
41 | jointDef.enableLimit = true;
42 | jointDef.lowerAngle = -0.1f * B2_PI;
43 | jointDef.upperAngle = 0.2f * B2_PI;
44 | jointDef.enableSpring = true;
45 | jointDef.hertz = 0.5f;
46 | jointDef.dampingRatio = 0.5f;
47 | jointDef.localAnchorA = (b2Vec2){ h, h };
48 | jointDef.localAnchorB = (b2Vec2){ offset, -h };
49 | jointDef.drawSize = 0.1f;
50 |
51 | int bodyIndex = 0;
52 |
53 | for ( int j = 0; j < columnCount; ++j )
54 | {
55 | float x = xroot + j * dx;
56 |
57 | b2BodyId prevBodyId = b2_nullBodyId;
58 |
59 | for ( int i = 0; i < rowCount; ++i )
60 | {
61 | b2BodyDef bodyDef = b2DefaultBodyDef();
62 | bodyDef.type = b2_dynamicBody;
63 |
64 | bodyDef.position.x = x + offset * i;
65 | bodyDef.position.y = h + 2.0f * h * i;
66 |
67 | // this tests the deterministic cosine and sine functions
68 | bodyDef.rotation = b2MakeRot( 0.1f * i - 1.0f );
69 |
70 | b2BodyId bodyId = b2CreateBody( worldId, &bodyDef );
71 |
72 | if ( ( i & 1 ) == 0 )
73 | {
74 | prevBodyId = bodyId;
75 | }
76 | else
77 | {
78 | jointDef.bodyIdA = prevBodyId;
79 | jointDef.bodyIdB = bodyId;
80 | b2CreateRevoluteJoint( worldId, &jointDef );
81 | prevBodyId = b2_nullBodyId;
82 | }
83 |
84 | b2CreatePolygonShape( bodyId, &shapeDef, &box );
85 |
86 | assert( bodyIndex < bodyCount );
87 | bodyIds[bodyIndex] = bodyId;
88 |
89 | bodyIndex += 1;
90 | }
91 | }
92 |
93 | assert( bodyIndex == bodyCount );
94 |
95 | FallingHingeData data = {
96 | .bodyIds = bodyIds,
97 | .bodyCount = bodyCount,
98 | .stepCount = 0,
99 | .sleepStep = -1,
100 | .hash = 0,
101 | };
102 | return data;
103 | }
104 |
105 | bool UpdateFallingHinges( b2WorldId worldId, FallingHingeData* data )
106 | {
107 | if ( data->hash == 0 )
108 | {
109 | b2BodyEvents bodyEvents = b2World_GetBodyEvents( worldId );
110 |
111 | if ( bodyEvents.moveCount == 0 )
112 | {
113 | int awakeCount = b2World_GetAwakeBodyCount( worldId );
114 | assert( awakeCount == 0 );
115 |
116 | data->hash = B2_HASH_INIT;
117 | for ( int i = 0; i < data->bodyCount; ++i )
118 | {
119 | b2Transform xf = b2Body_GetTransform( data->bodyIds[i] );
120 | data->hash = b2Hash( data->hash, (uint8_t*)( &xf ), sizeof( b2Transform ) );
121 | }
122 |
123 | data->sleepStep = data->stepCount;
124 | }
125 | }
126 |
127 | data->stepCount += 1;
128 |
129 | return data->hash != 0;
130 | }
131 |
132 | void DestroyFallingHinges( FallingHingeData* data )
133 | {
134 | free( data->bodyIds );
135 | data->bodyIds = NULL;
136 | }
--------------------------------------------------------------------------------
/shared/determinism.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2025 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 | #pragma once
4 |
5 | #include "box2d/id.h"
6 |
7 | #include
8 |
9 | #ifdef __cplusplus
10 | extern "C"
11 | {
12 | #endif
13 |
14 | typedef struct FallingHingeData
15 | {
16 | b2BodyId* bodyIds;
17 | int bodyCount;
18 | int stepCount;
19 | int sleepStep;
20 | uint32_t hash;
21 | } FallingHingeData;
22 |
23 | FallingHingeData CreateFallingHinges( b2WorldId worldId );
24 | bool UpdateFallingHinges( b2WorldId worldId, FallingHingeData* data );
25 | void DestroyFallingHinges( FallingHingeData* data );
26 |
27 | #ifdef __cplusplus
28 | }
29 | #endif
30 |
--------------------------------------------------------------------------------
/shared/human.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/types.h"
7 |
8 | typedef enum BoneId
9 | {
10 | bone_hip = 0,
11 | bone_torso = 1,
12 | bone_head = 2,
13 | bone_upperLeftLeg = 3,
14 | bone_lowerLeftLeg = 4,
15 | bone_upperRightLeg = 5,
16 | bone_lowerRightLeg = 6,
17 | bone_upperLeftArm = 7,
18 | bone_lowerLeftArm = 8,
19 | bone_upperRightArm = 9,
20 | bone_lowerRightArm = 10,
21 | bone_count = 11,
22 | } BoneId;
23 |
24 | typedef struct Bone
25 | {
26 | b2BodyId bodyId;
27 | b2JointId jointId;
28 | float frictionScale;
29 | float maxTorque;
30 | int parentIndex;
31 | } Bone;
32 |
33 | typedef struct Human
34 | {
35 | Bone bones[bone_count];
36 | float frictionTorque;
37 | float originalScale;
38 | float scale;
39 | bool isSpawned;
40 | } Human;
41 |
42 | #ifdef __cplusplus
43 | extern "C"
44 | {
45 | #endif
46 |
47 | void CreateHuman( Human* human, b2WorldId worldId, b2Vec2 position, float scale, float frictionTorque, float hertz,
48 | float dampingRatio, int groupIndex, void* userData, bool colorize );
49 |
50 | void DestroyHuman( Human* human );
51 |
52 | void Human_SetVelocity( Human* human, b2Vec2 velocity );
53 | void Human_ApplyRandomAngularImpulse( Human* human, float magnitude );
54 | void Human_SetJointFrictionTorque( Human* human, float torque );
55 | void Human_SetJointSpringHertz( Human* human, float hertz );
56 | void Human_SetJointDampingRatio( Human* human, float dampingRatio );
57 | void Human_EnableSensorEvents( Human* human, bool enable );
58 | void Human_SetScale( Human* human, float scale );
59 |
60 | #ifdef __cplusplus
61 | }
62 | #endif
63 |
--------------------------------------------------------------------------------
/shared/random.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "random.h"
5 |
6 | uint32_t g_randomSeed = RAND_SEED;
7 |
8 | b2Polygon RandomPolygon( float extent )
9 | {
10 | b2Vec2 points[B2_MAX_POLYGON_VERTICES];
11 | int count = 3 + RandomInt() % 6;
12 | for ( int i = 0; i < count; ++i )
13 | {
14 | points[i] = RandomVec2( -extent, extent );
15 | }
16 |
17 | b2Hull hull = b2ComputeHull( points, count );
18 | if ( hull.count > 0 )
19 | {
20 | return b2MakePolygon( &hull, 0.0f );
21 | }
22 |
23 | return b2MakeSquare( extent );
24 | }
25 |
--------------------------------------------------------------------------------
/shared/random.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/collision.h"
7 | #include "box2d/math_functions.h"
8 |
9 | #define RAND_LIMIT 32767
10 | #define RAND_SEED 12345
11 |
12 | // Global seed for simple random number generator.
13 |
14 | #ifdef __cplusplus
15 | extern "C"
16 | {
17 | #endif
18 |
19 | extern uint32_t g_randomSeed;
20 | b2Polygon RandomPolygon( float extent );
21 |
22 | #ifdef __cplusplus
23 | }
24 | #endif
25 |
26 | // Simple random number generator. Using this instead of rand() for cross-platform determinism.
27 | B2_INLINE int RandomInt()
28 | {
29 | // XorShift32 algorithm
30 | uint32_t x = g_randomSeed;
31 | x ^= x << 13;
32 | x ^= x >> 17;
33 | x ^= x << 5;
34 | g_randomSeed = x;
35 |
36 | // Map the 32-bit value to the range 0 to RAND_LIMIT
37 | return (int)( x % ( RAND_LIMIT + 1 ) );
38 | }
39 |
40 | // Random integer in range [lo, hi]
41 | B2_INLINE int RandomIntRange( int lo, int hi )
42 | {
43 | return lo + RandomInt() % ( hi - lo + 1 );
44 | }
45 |
46 | // Random number in range [-1,1]
47 | B2_INLINE float RandomFloat()
48 | {
49 | float r = (float)( RandomInt() & ( RAND_LIMIT ) );
50 | r /= RAND_LIMIT;
51 | r = 2.0f * r - 1.0f;
52 | return r;
53 | }
54 |
55 | // Random floating point number in range [lo, hi]
56 | B2_INLINE float RandomFloatRange( float lo, float hi )
57 | {
58 | float r = (float)( RandomInt() & ( RAND_LIMIT ) );
59 | r /= RAND_LIMIT;
60 | r = ( hi - lo ) * r + lo;
61 | return r;
62 | }
63 |
64 | // Random vector with coordinates in range [lo, hi]
65 | B2_INLINE b2Vec2 RandomVec2( float lo, float hi )
66 | {
67 | b2Vec2 v;
68 | v.x = RandomFloatRange( lo, hi );
69 | v.y = RandomFloatRange( lo, hi );
70 | return v;
71 | }
72 |
73 | // Random rotation with angle in range [-pi, pi]
74 | B2_INLINE b2Rot RandomRot( void )
75 | {
76 | float angle = RandomFloatRange( -B2_PI, B2_PI );
77 | return b2MakeRot( angle );
78 | }
79 |
--------------------------------------------------------------------------------
/src/aabb.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "aabb.h"
5 |
6 | #include "box2d/math_functions.h"
7 |
8 | #include
9 |
10 | bool b2IsValidAABB( b2AABB a )
11 | {
12 | b2Vec2 d = b2Sub( a.upperBound, a.lowerBound );
13 | bool valid = d.x >= 0.0f && d.y >= 0.0f;
14 | valid = valid && b2IsValidVec2( a.lowerBound ) && b2IsValidVec2( a.upperBound );
15 | return valid;
16 | }
17 |
18 | // From Real-time Collision Detection, p179.
19 | b2CastOutput b2AABB_RayCast( b2AABB a, b2Vec2 p1, b2Vec2 p2 )
20 | {
21 | // Radius not handled
22 | b2CastOutput output = { 0 };
23 |
24 | float tmin = -FLT_MAX;
25 | float tmax = FLT_MAX;
26 |
27 | b2Vec2 p = p1;
28 | b2Vec2 d = b2Sub( p2, p1 );
29 | b2Vec2 absD = b2Abs( d );
30 |
31 | b2Vec2 normal = b2Vec2_zero;
32 |
33 | // x-coordinate
34 | if ( absD.x < FLT_EPSILON )
35 | {
36 | // parallel
37 | if ( p.x < a.lowerBound.x || a.upperBound.x < p.x )
38 | {
39 | return output;
40 | }
41 | }
42 | else
43 | {
44 | float inv_d = 1.0f / d.x;
45 | float t1 = ( a.lowerBound.x - p.x ) * inv_d;
46 | float t2 = ( a.upperBound.x - p.x ) * inv_d;
47 |
48 | // Sign of the normal vector.
49 | float s = -1.0f;
50 |
51 | if ( t1 > t2 )
52 | {
53 | float tmp = t1;
54 | t1 = t2;
55 | t2 = tmp;
56 | s = 1.0f;
57 | }
58 |
59 | // Push the min up
60 | if ( t1 > tmin )
61 | {
62 | normal.y = 0.0f;
63 | normal.x = s;
64 | tmin = t1;
65 | }
66 |
67 | // Pull the max down
68 | tmax = b2MinFloat( tmax, t2 );
69 |
70 | if ( tmin > tmax )
71 | {
72 | return output;
73 | }
74 | }
75 |
76 | // y-coordinate
77 | if ( absD.y < FLT_EPSILON )
78 | {
79 | // parallel
80 | if ( p.y < a.lowerBound.y || a.upperBound.y < p.y )
81 | {
82 | return output;
83 | }
84 | }
85 | else
86 | {
87 | float inv_d = 1.0f / d.y;
88 | float t1 = ( a.lowerBound.y - p.y ) * inv_d;
89 | float t2 = ( a.upperBound.y - p.y ) * inv_d;
90 |
91 | // Sign of the normal vector.
92 | float s = -1.0f;
93 |
94 | if ( t1 > t2 )
95 | {
96 | float tmp = t1;
97 | t1 = t2;
98 | t2 = tmp;
99 | s = 1.0f;
100 | }
101 |
102 | // Push the min up
103 | if ( t1 > tmin )
104 | {
105 | normal.x = 0.0f;
106 | normal.y = s;
107 | tmin = t1;
108 | }
109 |
110 | // Pull the max down
111 | tmax = b2MinFloat( tmax, t2 );
112 |
113 | if ( tmin > tmax )
114 | {
115 | return output;
116 | }
117 | }
118 |
119 | // Does the ray start inside the box?
120 | // Does the ray intersect beyond the max fraction?
121 | if ( tmin < 0.0f || 1.0f < tmin )
122 | {
123 | return output;
124 | }
125 |
126 | // Intersection.
127 | output.fraction = tmin;
128 | output.normal = normal;
129 | output.point = b2Lerp( p1, p2, tmin );
130 | output.hit = true;
131 | return output;
132 | }
133 |
--------------------------------------------------------------------------------
/src/aabb.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/types.h"
7 |
8 | // Ray cast an AABB
9 | b2CastOutput b2AABB_RayCast( b2AABB a, b2Vec2 p1, b2Vec2 p2 );
10 |
11 | // Get surface area of an AABB (the perimeter length)
12 | static inline float b2Perimeter( b2AABB a )
13 | {
14 | float wx = a.upperBound.x - a.lowerBound.x;
15 | float wy = a.upperBound.y - a.lowerBound.y;
16 | return 2.0f * ( wx + wy );
17 | }
18 |
19 | /// Enlarge a to contain b
20 | /// @return true if the AABB grew
21 | static inline bool b2EnlargeAABB( b2AABB* a, b2AABB b )
22 | {
23 | bool changed = false;
24 | if ( b.lowerBound.x < a->lowerBound.x )
25 | {
26 | a->lowerBound.x = b.lowerBound.x;
27 | changed = true;
28 | }
29 |
30 | if ( b.lowerBound.y < a->lowerBound.y )
31 | {
32 | a->lowerBound.y = b.lowerBound.y;
33 | changed = true;
34 | }
35 |
36 | if ( a->upperBound.x < b.upperBound.x )
37 | {
38 | a->upperBound.x = b.upperBound.x;
39 | changed = true;
40 | }
41 |
42 | if ( a->upperBound.y < b.upperBound.y )
43 | {
44 | a->upperBound.y = b.upperBound.y;
45 | changed = true;
46 | }
47 |
48 | return changed;
49 | }
50 |
--------------------------------------------------------------------------------
/src/arena_allocator.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "arena_allocator.h"
5 |
6 | #include "array.h"
7 | #include "core.h"
8 |
9 | #include
10 | #include
11 |
12 | B2_ARRAY_SOURCE( b2ArenaEntry, b2ArenaEntry )
13 |
14 | b2ArenaAllocator b2CreateArenaAllocator( int capacity )
15 | {
16 | B2_ASSERT( capacity >= 0 );
17 | b2ArenaAllocator allocator = { 0 };
18 | allocator.capacity = capacity;
19 | allocator.data = b2Alloc( capacity );
20 | allocator.allocation = 0;
21 | allocator.maxAllocation = 0;
22 | allocator.index = 0;
23 | allocator.entries = b2ArenaEntryArray_Create( 32 );
24 | return allocator;
25 | }
26 |
27 | void b2DestroyArenaAllocator( b2ArenaAllocator* allocator )
28 | {
29 | b2ArenaEntryArray_Destroy( &allocator->entries );
30 | b2Free( allocator->data, allocator->capacity );
31 | }
32 |
33 | void* b2AllocateArenaItem( b2ArenaAllocator* alloc, int size, const char* name )
34 | {
35 | // ensure allocation is 32 byte aligned to support 256-bit SIMD
36 | int size32 = ( ( size - 1 ) | 0x1F ) + 1;
37 |
38 | b2ArenaEntry entry;
39 | entry.size = size32;
40 | entry.name = name;
41 | if ( alloc->index + size32 > alloc->capacity )
42 | {
43 | // fall back to the heap (undesirable)
44 | entry.data = b2Alloc( size32 );
45 | entry.usedMalloc = true;
46 |
47 | B2_ASSERT( ( (uintptr_t)entry.data & 0x1F ) == 0 );
48 | }
49 | else
50 | {
51 | entry.data = alloc->data + alloc->index;
52 | entry.usedMalloc = false;
53 | alloc->index += size32;
54 |
55 | B2_ASSERT( ( (uintptr_t)entry.data & 0x1F ) == 0 );
56 | }
57 |
58 | alloc->allocation += size32;
59 | if ( alloc->allocation > alloc->maxAllocation )
60 | {
61 | alloc->maxAllocation = alloc->allocation;
62 | }
63 |
64 | b2ArenaEntryArray_Push( &alloc->entries, entry );
65 | return entry.data;
66 | }
67 |
68 | void b2FreeArenaItem( b2ArenaAllocator* alloc, void* mem )
69 | {
70 | int entryCount = alloc->entries.count;
71 | B2_ASSERT( entryCount > 0 );
72 | b2ArenaEntry* entry = alloc->entries.data + ( entryCount - 1 );
73 | B2_ASSERT( mem == entry->data );
74 | if ( entry->usedMalloc )
75 | {
76 | b2Free( mem, entry->size );
77 | }
78 | else
79 | {
80 | alloc->index -= entry->size;
81 | }
82 | alloc->allocation -= entry->size;
83 | b2ArenaEntryArray_Pop( &alloc->entries );
84 | }
85 |
86 | void b2GrowArena( b2ArenaAllocator* alloc )
87 | {
88 | // Stack must not be in use
89 | B2_ASSERT( alloc->allocation == 0 );
90 |
91 | if ( alloc->maxAllocation > alloc->capacity )
92 | {
93 | b2Free( alloc->data, alloc->capacity );
94 | alloc->capacity = alloc->maxAllocation + alloc->maxAllocation / 2;
95 | alloc->data = b2Alloc( alloc->capacity );
96 | }
97 | }
98 |
99 | int b2GetArenaCapacity( b2ArenaAllocator* alloc )
100 | {
101 | return alloc->capacity;
102 | }
103 |
104 | int b2GetArenaAllocation( b2ArenaAllocator* alloc )
105 | {
106 | return alloc->allocation;
107 | }
108 |
109 | int b2GetMaxArenaAllocation( b2ArenaAllocator* alloc )
110 | {
111 | return alloc->maxAllocation;
112 | }
113 |
--------------------------------------------------------------------------------
/src/arena_allocator.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 |
8 | B2_ARRAY_DECLARE( b2ArenaEntry, b2ArenaEntry );
9 |
10 | typedef struct b2ArenaEntry
11 | {
12 | char* data;
13 | const char* name;
14 | int size;
15 | bool usedMalloc;
16 | } b2ArenaEntry;
17 |
18 | // This is a stack-like arena allocator used for fast per step allocations.
19 | // You must nest allocate/free pairs. The code will B2_ASSERT
20 | // if you try to interleave multiple allocate/free pairs.
21 | // This allocator uses the heap if space is insufficient.
22 | // I could remove the need to free entries individually.
23 | typedef struct b2ArenaAllocator
24 | {
25 | char* data;
26 | int capacity;
27 | int index;
28 |
29 | int allocation;
30 | int maxAllocation;
31 |
32 | b2ArenaEntryArray entries;
33 | } b2ArenaAllocator;
34 |
35 | b2ArenaAllocator b2CreateArenaAllocator( int capacity );
36 | void b2DestroyArenaAllocator( b2ArenaAllocator* allocator );
37 |
38 | void* b2AllocateArenaItem( b2ArenaAllocator* alloc, int size, const char* name );
39 | void b2FreeArenaItem( b2ArenaAllocator* alloc, void* mem );
40 |
41 | // Grow the arena based on usage
42 | void b2GrowArena( b2ArenaAllocator* alloc );
43 |
44 | int b2GetArenaCapacity( b2ArenaAllocator* alloc );
45 | int b2GetArenaAllocation( b2ArenaAllocator* alloc );
46 | int b2GetMaxArenaAllocation( b2ArenaAllocator* alloc );
47 |
48 | B2_ARRAY_INLINE( b2ArenaEntry, b2ArenaEntry )
49 |
--------------------------------------------------------------------------------
/src/array.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "array.h"
5 |
6 | #include
7 |
8 | B2_ARRAY_SOURCE( int, b2Int )
9 |
--------------------------------------------------------------------------------
/src/atomic.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "core.h"
7 |
8 | #include
9 |
10 | #if defined( _MSC_VER )
11 | #include
12 | #endif
13 |
14 | static inline void b2AtomicStoreInt( b2AtomicInt* a, int value )
15 | {
16 | #if defined( _MSC_VER )
17 | (void)_InterlockedExchange( (long*)&a->value, value );
18 | #elif defined( __GNUC__ ) || defined( __clang__ )
19 | __atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST );
20 | #else
21 | #error "Unsupported platform"
22 | #endif
23 | }
24 |
25 | static inline int b2AtomicLoadInt( b2AtomicInt* a )
26 | {
27 | #if defined( _MSC_VER )
28 | return _InterlockedOr( (long*)&a->value, 0 );
29 | #elif defined( __GNUC__ ) || defined( __clang__ )
30 | return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST );
31 | #else
32 | #error "Unsupported platform"
33 | #endif
34 | }
35 |
36 | static inline int b2AtomicFetchAddInt( b2AtomicInt* a, int increment )
37 | {
38 | #if defined( _MSC_VER )
39 | return _InterlockedExchangeAdd( (long*)&a->value, (long)increment );
40 | #elif defined( __GNUC__ ) || defined( __clang__ )
41 | return __atomic_fetch_add( &a->value, increment, __ATOMIC_SEQ_CST );
42 | #else
43 | #error "Unsupported platform"
44 | #endif
45 | }
46 |
47 | static inline bool b2AtomicCompareExchangeInt( b2AtomicInt* a, int expected, int desired )
48 | {
49 | #if defined( _MSC_VER )
50 | return _InterlockedCompareExchange( (long*)&a->value, (long)desired, (long)expected ) == expected;
51 | #elif defined( __GNUC__ ) || defined( __clang__ )
52 | // The value written to expected is ignored
53 | return __atomic_compare_exchange_n( &a->value, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST );
54 | #else
55 | #error "Unsupported platform"
56 | #endif
57 | }
58 |
59 | static inline void b2AtomicStoreU32( b2AtomicU32* a, uint32_t value )
60 | {
61 | #if defined( _MSC_VER )
62 | (void)_InterlockedExchange( (long*)&a->value, value );
63 | #elif defined( __GNUC__ ) || defined( __clang__ )
64 | __atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST );
65 | #else
66 | #error "Unsupported platform"
67 | #endif
68 | }
69 |
70 | static inline uint32_t b2AtomicLoadU32( b2AtomicU32* a )
71 | {
72 | #if defined( _MSC_VER )
73 | return (uint32_t)_InterlockedOr( (long*)&a->value, 0 );
74 | #elif defined( __GNUC__ ) || defined( __clang__ )
75 | return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST );
76 | #else
77 | #error "Unsupported platform"
78 | #endif
79 | }
80 |
--------------------------------------------------------------------------------
/src/bitset.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "bitset.h"
5 |
6 | #include
7 |
8 | b2BitSet b2CreateBitSet( uint32_t bitCapacity )
9 | {
10 | b2BitSet bitSet = { 0 };
11 |
12 | bitSet.blockCapacity = ( bitCapacity + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 );
13 | bitSet.blockCount = 0;
14 | bitSet.bits = b2Alloc( bitSet.blockCapacity * sizeof( uint64_t ) );
15 | memset( bitSet.bits, 0, bitSet.blockCapacity * sizeof( uint64_t ) );
16 | return bitSet;
17 | }
18 |
19 | void b2DestroyBitSet( b2BitSet* bitSet )
20 | {
21 | b2Free( bitSet->bits, bitSet->blockCapacity * sizeof( uint64_t ) );
22 | bitSet->blockCapacity = 0;
23 | bitSet->blockCount = 0;
24 | bitSet->bits = NULL;
25 | }
26 |
27 | void b2SetBitCountAndClear( b2BitSet* bitSet, uint32_t bitCount )
28 | {
29 | uint32_t blockCount = ( bitCount + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 );
30 | if ( bitSet->blockCapacity < blockCount )
31 | {
32 | b2DestroyBitSet( bitSet );
33 | uint32_t newBitCapacity = bitCount + ( bitCount >> 1 );
34 | *bitSet = b2CreateBitSet( newBitCapacity );
35 | }
36 |
37 | bitSet->blockCount = blockCount;
38 | memset( bitSet->bits, 0, bitSet->blockCount * sizeof( uint64_t ) );
39 | }
40 |
41 | void b2GrowBitSet( b2BitSet* bitSet, uint32_t blockCount )
42 | {
43 | B2_ASSERT( blockCount > bitSet->blockCount );
44 | if ( blockCount > bitSet->blockCapacity )
45 | {
46 | uint32_t oldCapacity = bitSet->blockCapacity;
47 | bitSet->blockCapacity = blockCount + blockCount / 2;
48 | uint64_t* newBits = b2Alloc( bitSet->blockCapacity * sizeof( uint64_t ) );
49 | memset( newBits, 0, bitSet->blockCapacity * sizeof( uint64_t ) );
50 | B2_ASSERT( bitSet->bits != NULL );
51 | memcpy( newBits, bitSet->bits, oldCapacity * sizeof( uint64_t ) );
52 | b2Free( bitSet->bits, oldCapacity * sizeof( uint64_t ) );
53 | bitSet->bits = newBits;
54 | }
55 |
56 | bitSet->blockCount = blockCount;
57 | }
58 |
59 | void b2InPlaceUnion( b2BitSet* B2_RESTRICT setA, const b2BitSet* B2_RESTRICT setB )
60 | {
61 | B2_ASSERT( setA->blockCount == setB->blockCount );
62 | uint32_t blockCount = setA->blockCount;
63 | for ( uint32_t i = 0; i < blockCount; ++i )
64 | {
65 | setA->bits[i] |= setB->bits[i];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/bitset.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "core.h"
7 |
8 | #include
9 | #include
10 |
11 | // Bit set provides fast operations on large arrays of bits.
12 | typedef struct b2BitSet
13 | {
14 | uint64_t* bits;
15 | uint32_t blockCapacity;
16 | uint32_t blockCount;
17 | } b2BitSet;
18 |
19 | b2BitSet b2CreateBitSet( uint32_t bitCapacity );
20 | void b2DestroyBitSet( b2BitSet* bitSet );
21 | void b2SetBitCountAndClear( b2BitSet* bitSet, uint32_t bitCount );
22 | void b2InPlaceUnion( b2BitSet* setA, const b2BitSet* setB );
23 | void b2GrowBitSet( b2BitSet* bitSet, uint32_t blockCount );
24 |
25 | static inline void b2SetBit( b2BitSet* bitSet, uint32_t bitIndex )
26 | {
27 | uint32_t blockIndex = bitIndex / 64;
28 | B2_ASSERT( blockIndex < bitSet->blockCount );
29 | bitSet->bits[blockIndex] |= ( (uint64_t)1 << bitIndex % 64 );
30 | }
31 |
32 | static inline void b2SetBitGrow( b2BitSet* bitSet, uint32_t bitIndex )
33 | {
34 | uint32_t blockIndex = bitIndex / 64;
35 | if ( blockIndex >= bitSet->blockCount )
36 | {
37 | b2GrowBitSet( bitSet, blockIndex + 1 );
38 | }
39 | bitSet->bits[blockIndex] |= ( (uint64_t)1 << bitIndex % 64 );
40 | }
41 |
42 | static inline void b2ClearBit( b2BitSet* bitSet, uint32_t bitIndex )
43 | {
44 | uint32_t blockIndex = bitIndex / 64;
45 | if ( blockIndex >= bitSet->blockCount )
46 | {
47 | return;
48 | }
49 | bitSet->bits[blockIndex] &= ~( (uint64_t)1 << bitIndex % 64 );
50 | }
51 |
52 | static inline bool b2GetBit( const b2BitSet* bitSet, uint32_t bitIndex )
53 | {
54 | uint32_t blockIndex = bitIndex / 64;
55 | if ( blockIndex >= bitSet->blockCount )
56 | {
57 | return false;
58 | }
59 | return ( bitSet->bits[blockIndex] & ( (uint64_t)1 << bitIndex % 64 ) ) != 0;
60 | }
61 |
62 | static inline int b2GetBitSetBytes( b2BitSet* bitSet )
63 | {
64 | return bitSet->blockCapacity * sizeof( uint64_t );
65 | }
66 |
--------------------------------------------------------------------------------
/src/box2d.natvis:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | [{m128_f32[0]}, {m128_f32[1]}, {m128_f32[2]}, {m128_f32[3]}]
5 |
6 | - m128_f32[0]
7 | - m128_f32[1]
8 | - m128_f32[2]
9 | - m128_f32[3]
10 | - (void*)this
11 |
12 |
13 |
14 | [{m256_f32[0]}, {m256_f32[1]}, {m256_f32[2]}, {m256_f32[3]}, {m256_f32[4]}, {m256_f32[5]}, {m256_f32[6]}, {m256_f32[7]}]
15 |
16 | - m256_f32[0]
17 | - m256_f32[1]
18 | - m256_f32[2]
19 | - m256_f32[3]
20 | - m256_f32[4]
21 | - m256_f32[5]
22 | - m256_f32[6]
23 | - m256_f32[7]
24 | - (void*)this
25 |
26 |
27 |
28 |
29 | index: {index1}
30 |
31 | b2_worlds[ index1 - 1 ]
32 |
33 |
34 |
35 |
36 |
37 | {b2_worlds[world0].bodies.data[index1-1].name,na},
38 | {b2_worlds[world0].bodies.data[index1-1].type}
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/broad_phase.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 | #include "table.h"
8 |
9 | #include "box2d/collision.h"
10 | #include "box2d/types.h"
11 |
12 | typedef struct b2Shape b2Shape;
13 | typedef struct b2MovePair b2MovePair;
14 | typedef struct b2MoveResult b2MoveResult;
15 | typedef struct b2ArenaAllocator b2ArenaAllocator;
16 | typedef struct b2World b2World;
17 |
18 | // Store the proxy type in the lower 2 bits of the proxy key. This leaves 30 bits for the id.
19 | #define B2_PROXY_TYPE( KEY ) ( (b2BodyType)( ( KEY ) & 3 ) )
20 | #define B2_PROXY_ID( KEY ) ( ( KEY ) >> 2 )
21 | #define B2_PROXY_KEY( ID, TYPE ) ( ( ( ID ) << 2 ) | ( TYPE ) )
22 |
23 | /// The broad-phase is used for computing pairs and performing volume queries and ray casts.
24 | /// This broad-phase does not persist pairs. Instead, this reports potentially new pairs.
25 | /// It is up to the client to consume the new pairs and to track subsequent overlap.
26 | typedef struct b2BroadPhase
27 | {
28 | b2DynamicTree trees[b2_bodyTypeCount];
29 |
30 | // The move set and array are used to track shapes that have moved significantly
31 | // and need a pair query for new contacts. The array has a deterministic order.
32 | // todo perhaps just a move set?
33 | // todo implement a 32bit hash set for faster lookup
34 | // todo moveSet can grow quite large on the first time step and remain large
35 | b2HashSet moveSet;
36 | b2IntArray moveArray;
37 |
38 | // These are the results from the pair query and are used to create new contacts
39 | // in deterministic order.
40 | // todo these could be in the step context
41 | b2MoveResult* moveResults;
42 | b2MovePair* movePairs;
43 | int movePairCapacity;
44 | b2AtomicInt movePairIndex;
45 |
46 | // Tracks shape pairs that have a b2Contact
47 | // todo pairSet can grow quite large on the first time step and remain large
48 | b2HashSet pairSet;
49 |
50 | } b2BroadPhase;
51 |
52 | void b2CreateBroadPhase( b2BroadPhase* bp );
53 | void b2DestroyBroadPhase( b2BroadPhase* bp );
54 |
55 | int b2BroadPhase_CreateProxy( b2BroadPhase* bp, b2BodyType proxyType, b2AABB aabb, uint64_t categoryBits, int shapeIndex,
56 | bool forcePairCreation );
57 | void b2BroadPhase_DestroyProxy( b2BroadPhase* bp, int proxyKey );
58 |
59 | void b2BroadPhase_MoveProxy( b2BroadPhase* bp, int proxyKey, b2AABB aabb );
60 | void b2BroadPhase_EnlargeProxy( b2BroadPhase* bp, int proxyKey, b2AABB aabb );
61 |
62 | void b2BroadPhase_RebuildTrees( b2BroadPhase* bp );
63 |
64 | int b2BroadPhase_GetShapeIndex( b2BroadPhase* bp, int proxyKey );
65 |
66 | void b2UpdateBroadPhasePairs( b2World* world );
67 | bool b2BroadPhase_TestOverlap( const b2BroadPhase* bp, int proxyKeyA, int proxyKeyB );
68 |
69 | void b2ValidateBroadphase( const b2BroadPhase* bp );
70 | void b2ValidateNoEnlarged( const b2BroadPhase* bp );
71 |
72 | // This is what triggers new contact pairs to be created
73 | // Warning: this must be called in deterministic order
74 | static inline void b2BufferMove( b2BroadPhase* bp, int queryProxy )
75 | {
76 | // Adding 1 because 0 is the sentinel
77 | bool alreadyAdded = b2AddKey( &bp->moveSet, queryProxy + 1 );
78 | if ( alreadyAdded == false )
79 | {
80 | b2IntArray_Push( &bp->moveArray, queryProxy );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/constants.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | extern float b2_lengthUnitsPerMeter;
7 |
8 | // Used to detect bad values. Positions greater than about 16km will have precision
9 | // problems, so 100km as a limit should be fine in all cases.
10 | #define B2_HUGE ( 100000.0f * b2_lengthUnitsPerMeter )
11 |
12 | // Maximum parallel workers. Used to size some static arrays.
13 | #define B2_MAX_WORKERS 64
14 |
15 | // Maximum number of colors in the constraint graph. Constraints that cannot
16 | // find a color are added to the overflow set which are solved single-threaded.
17 | #define B2_GRAPH_COLOR_COUNT 12
18 |
19 | // A small length used as a collision and constraint tolerance. Usually it is
20 | // chosen to be numerically significant, but visually insignificant. In meters.
21 | // Normally this is 0.5cm.
22 | // @warning modifying this can have a significant impact on stability
23 | #define B2_LINEAR_SLOP ( 0.005f * b2_lengthUnitsPerMeter )
24 |
25 | // Maximum number of simultaneous worlds that can be allocated
26 | #ifndef B2_MAX_WORLDS
27 | #define B2_MAX_WORLDS 128
28 | #endif
29 |
30 | // The maximum rotation of a body per time step. This limit is very large and is used
31 | // to prevent numerical problems. You shouldn't need to adjust this.
32 | // @warning increasing this to 0.5f * b2_pi or greater will break continuous collision.
33 | #define B2_MAX_ROTATION ( 0.25f * B2_PI )
34 |
35 | // Box2D uses limited speculative collision. This reduces jitter.
36 | // Normally this is 2cm.
37 | // @warning modifying this can have a significant impact on performance and stability
38 | #define B2_SPECULATIVE_DISTANCE ( 4.0f * B2_LINEAR_SLOP )
39 |
40 | // This is used to fatten AABBs in the dynamic tree. This allows proxies
41 | // to move by a small amount without triggering a tree adjustment. This is in meters.
42 | // Normally this is 5cm.
43 | // @warning modifying this can have a significant impact on performance
44 | #define B2_AABB_MARGIN ( 0.05f * b2_lengthUnitsPerMeter )
45 |
46 | // The time that a body must be still before it will go to sleep. In seconds.
47 | #define B2_TIME_TO_SLEEP 0.5f
48 |
49 | enum b2TreeNodeFlags
50 | {
51 | b2_allocatedNode = 0x0001,
52 | b2_enlargedNode = 0x0002,
53 | b2_leafNode = 0x0004,
54 | };
55 |
--------------------------------------------------------------------------------
/src/constraint_graph.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 | #include "bitset.h"
8 | #include "constants.h"
9 |
10 | typedef struct b2Body b2Body;
11 | typedef struct b2ContactSim b2ContactSim;
12 | typedef struct b2Contact b2Contact;
13 | typedef struct b2ContactConstraint b2ContactConstraint;
14 | typedef struct b2ContactConstraintSIMD b2ContactConstraintSIMD;
15 | typedef struct b2JointSim b2JointSim;
16 | typedef struct b2Joint b2Joint;
17 | typedef struct b2StepContext b2StepContext;
18 | typedef struct b2World b2World;
19 |
20 | // This holds constraints that cannot fit the graph color limit. This happens when a single dynamic body
21 | // is touching many other bodies.
22 | #define B2_OVERFLOW_INDEX (B2_GRAPH_COLOR_COUNT - 1)
23 |
24 | typedef struct b2GraphColor
25 | {
26 | // This bitset is indexed by bodyId so this is over-sized to encompass static bodies
27 | // however I never traverse these bits or use the bit count for anything
28 | // This bitset is unused on the overflow color.
29 | // todo consider having a uint_16 per body that tracks the graph color membership
30 | b2BitSet bodySet;
31 |
32 | // cache friendly arrays
33 | b2ContactSimArray contactSims;
34 | b2JointSimArray jointSims;
35 |
36 | // transient
37 | union
38 | {
39 | b2ContactConstraintSIMD* simdConstraints;
40 | b2ContactConstraint* overflowConstraints;
41 | };
42 | } b2GraphColor;
43 |
44 | typedef struct b2ConstraintGraph
45 | {
46 | // including overflow at the end
47 | b2GraphColor colors[B2_GRAPH_COLOR_COUNT];
48 | } b2ConstraintGraph;
49 |
50 | void b2CreateGraph( b2ConstraintGraph* graph, int bodyCapacity );
51 | void b2DestroyGraph( b2ConstraintGraph* graph );
52 |
53 | void b2AddContactToGraph( b2World* world, b2ContactSim* contactSim, b2Contact* contact );
54 | void b2RemoveContactFromGraph( b2World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex );
55 |
56 | b2JointSim* b2CreateJointInGraph( b2World* world, b2Joint* joint );
57 | void b2AddJointToGraph( b2World* world, b2JointSim* jointSim, b2Joint* joint );
58 | void b2RemoveJointFromGraph( b2World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex );
59 |
--------------------------------------------------------------------------------
/src/contact.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 | #include "core.h"
8 |
9 | #include "box2d/collision.h"
10 | #include "box2d/types.h"
11 |
12 | typedef struct b2Shape b2Shape;
13 | typedef struct b2World b2World;
14 |
15 | enum b2ContactFlags
16 | {
17 | // Set when the solid shapes are touching.
18 | b2_contactTouchingFlag = 0x00000001,
19 |
20 | // Contact has a hit event
21 | b2_contactHitEventFlag = 0x00000002,
22 |
23 | // This contact wants contact events
24 | b2_contactEnableContactEvents = 0x00000004,
25 | };
26 |
27 | // A contact edge is used to connect bodies and contacts together
28 | // in a contact graph where each body is a node and each contact
29 | // is an edge. A contact edge belongs to a doubly linked list
30 | // maintained in each attached body. Each contact has two contact
31 | // edges, one for each attached body.
32 | typedef struct b2ContactEdge
33 | {
34 | int bodyId;
35 | int prevKey;
36 | int nextKey;
37 | } b2ContactEdge;
38 |
39 | // Cold contact data. Used as a persistent handle and for persistent island
40 | // connectivity.
41 | typedef struct b2Contact
42 | {
43 | // index of simulation set stored in b2World
44 | // B2_NULL_INDEX when slot is free
45 | int setIndex;
46 |
47 | // index into the constraint graph color array
48 | // B2_NULL_INDEX for non-touching or sleeping contacts
49 | // B2_NULL_INDEX when slot is free
50 | int colorIndex;
51 |
52 | // contact index within set or graph color
53 | // B2_NULL_INDEX when slot is free
54 | int localIndex;
55 |
56 | b2ContactEdge edges[2];
57 | int shapeIdA;
58 | int shapeIdB;
59 |
60 | // A contact only belongs to an island if touching, otherwise B2_NULL_INDEX.
61 | int islandPrev;
62 | int islandNext;
63 | int islandId;
64 |
65 | int contactId;
66 |
67 | // b2ContactFlags
68 | uint32_t flags;
69 |
70 | bool isMarked;
71 | } b2Contact;
72 |
73 | // Shifted to be distinct from b2ContactFlags
74 | enum b2ContactSimFlags
75 | {
76 | // Set when the shapes are touching
77 | b2_simTouchingFlag = 0x00010000,
78 |
79 | // This contact no longer has overlapping AABBs
80 | b2_simDisjoint = 0x00020000,
81 |
82 | // This contact started touching
83 | b2_simStartedTouching = 0x00040000,
84 |
85 | // This contact stopped touching
86 | b2_simStoppedTouching = 0x00080000,
87 |
88 | // This contact has a hit event
89 | b2_simEnableHitEvent = 0x00100000,
90 |
91 | // This contact wants pre-solve events
92 | b2_simEnablePreSolveEvents = 0x00200000,
93 | };
94 |
95 | /// The class manages contact between two shapes. A contact exists for each overlapping
96 | /// AABB in the broad-phase (except if filtered). Therefore a contact object may exist
97 | /// that has no contact points.
98 | typedef struct b2ContactSim
99 | {
100 | int contactId;
101 |
102 | #if B2_VALIDATE
103 | int bodyIdA;
104 | int bodyIdB;
105 | #endif
106 |
107 | int bodySimIndexA;
108 | int bodySimIndexB;
109 |
110 | int shapeIdA;
111 | int shapeIdB;
112 |
113 | float invMassA;
114 | float invIA;
115 |
116 | float invMassB;
117 | float invIB;
118 |
119 | b2Manifold manifold;
120 |
121 | // Mixed friction and restitution
122 | float friction;
123 | float restitution;
124 | float rollingResistance;
125 | float tangentSpeed;
126 |
127 | // b2ContactSimFlags
128 | uint32_t simFlags;
129 |
130 | b2SimplexCache cache;
131 | } b2ContactSim;
132 |
133 | void b2InitializeContactRegisters( void );
134 |
135 | void b2CreateContact( b2World* world, b2Shape* shapeA, b2Shape* shapeB );
136 | void b2DestroyContact( b2World* world, b2Contact* contact, bool wakeBodies );
137 |
138 | b2ContactSim* b2GetContactSim( b2World* world, b2Contact* contact );
139 |
140 |
141 | bool b2UpdateContact( b2World* world, b2ContactSim* contactSim, b2Shape* shapeA, b2Transform transformA, b2Vec2 centerOffsetA,
142 | b2Shape* shapeB, b2Transform transformB, b2Vec2 centerOffsetB );
143 |
144 | b2Manifold b2ComputeManifold( b2Shape* shapeA, b2Transform transformA, b2Shape* shapeB, b2Transform transformB );
145 |
146 | B2_ARRAY_INLINE( b2Contact, b2Contact )
147 | B2_ARRAY_INLINE( b2ContactSim, b2ContactSim )
148 |
--------------------------------------------------------------------------------
/src/contact_solver.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "solver.h"
7 |
8 | typedef struct b2ContactSim b2ContactSim;
9 |
10 | typedef struct b2ContactConstraintPoint
11 | {
12 | b2Vec2 anchorA, anchorB;
13 | float baseSeparation;
14 | float relativeVelocity;
15 | float normalImpulse;
16 | float tangentImpulse;
17 | float totalNormalImpulse;
18 | float normalMass;
19 | float tangentMass;
20 | } b2ContactConstraintPoint;
21 |
22 | typedef struct b2ContactConstraint
23 | {
24 | int indexA;
25 | int indexB;
26 | b2ContactConstraintPoint points[2];
27 | b2Vec2 normal;
28 | float invMassA, invMassB;
29 | float invIA, invIB;
30 | float friction;
31 | float restitution;
32 | float tangentSpeed;
33 | float rollingResistance;
34 | float rollingMass;
35 | float rollingImpulse;
36 | b2Softness softness;
37 | int pointCount;
38 | } b2ContactConstraint;
39 |
40 | int b2GetContactConstraintSIMDByteCount( void );
41 |
42 | // Overflow contacts don't fit into the constraint graph coloring
43 | void b2PrepareOverflowContacts( b2StepContext* context );
44 | void b2WarmStartOverflowContacts( b2StepContext* context );
45 | void b2SolveOverflowContacts( b2StepContext* context, bool useBias );
46 | void b2ApplyOverflowRestitution( b2StepContext* context );
47 | void b2StoreOverflowImpulses( b2StepContext* context );
48 |
49 | // Contacts that live within the constraint graph coloring
50 | void b2PrepareContactsTask( int startIndex, int endIndex, b2StepContext* context );
51 | void b2WarmStartContactsTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex );
52 | void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex, bool useBias );
53 | void b2ApplyRestitutionTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex );
54 | void b2StoreImpulsesTask( int startIndex, int endIndex, b2StepContext* context );
55 |
--------------------------------------------------------------------------------
/src/core.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "core.h"
5 |
6 | #if defined( B2_COMPILER_MSVC )
7 | #define _CRTDBG_MAP_ALLOC
8 | #include
9 | #include
10 | #else
11 | #include
12 | #endif
13 |
14 | #include
15 | #include
16 |
17 | #ifdef BOX2D_PROFILE
18 |
19 | #include
20 | #define b2TracyCAlloc( ptr, size ) TracyCAlloc( ptr, size )
21 | #define b2TracyCFree( ptr ) TracyCFree( ptr )
22 |
23 | #else
24 |
25 | #define b2TracyCAlloc( ptr, size )
26 | #define b2TracyCFree( ptr )
27 |
28 | #endif
29 |
30 | #include "atomic.h"
31 |
32 | // This allows the user to change the length units at runtime
33 | float b2_lengthUnitsPerMeter = 1.0f;
34 |
35 | void b2SetLengthUnitsPerMeter( float lengthUnits )
36 | {
37 | B2_ASSERT( b2IsValidFloat( lengthUnits ) && lengthUnits > 0.0f );
38 | b2_lengthUnitsPerMeter = lengthUnits;
39 | }
40 |
41 | float b2GetLengthUnitsPerMeter( void )
42 | {
43 | return b2_lengthUnitsPerMeter;
44 | }
45 |
46 | static int b2DefaultAssertFcn( const char* condition, const char* fileName, int lineNumber )
47 | {
48 | printf( "BOX2D ASSERTION: %s, %s, line %d\n", condition, fileName, lineNumber );
49 |
50 | // return non-zero to break to debugger
51 | return 1;
52 | }
53 |
54 | b2AssertFcn* b2AssertHandler = b2DefaultAssertFcn;
55 |
56 | void b2SetAssertFcn( b2AssertFcn* assertFcn )
57 | {
58 | B2_ASSERT( assertFcn != NULL );
59 | b2AssertHandler = assertFcn;
60 | }
61 |
62 | #if !defined( NDEBUG ) || defined( B2_ENABLE_ASSERT )
63 | int b2InternalAssertFcn( const char* condition, const char* fileName, int lineNumber )
64 | {
65 | return b2AssertHandler( condition, fileName, lineNumber );
66 | }
67 | #endif
68 |
69 | b2Version b2GetVersion( void )
70 | {
71 | return (b2Version){
72 | .major = 3,
73 | .minor = 1,
74 | .revision = 1,
75 | };
76 | }
77 |
78 | static b2AllocFcn* b2_allocFcn = NULL;
79 | static b2FreeFcn* b2_freeFcn = NULL;
80 |
81 | b2AtomicInt b2_byteCount;
82 |
83 | void b2SetAllocator( b2AllocFcn* allocFcn, b2FreeFcn* freeFcn )
84 | {
85 | b2_allocFcn = allocFcn;
86 | b2_freeFcn = freeFcn;
87 | }
88 |
89 | // Use 32 byte alignment for everything. Works with 256bit SIMD.
90 | #define B2_ALIGNMENT 32
91 |
92 | void* b2Alloc( int size )
93 | {
94 | if ( size == 0 )
95 | {
96 | return NULL;
97 | }
98 |
99 | // This could cause some sharing issues, however Box2D rarely calls b2Alloc.
100 | b2AtomicFetchAddInt( &b2_byteCount, size );
101 |
102 | // Allocation must be a multiple of 32 or risk a seg fault
103 | // https://en.cppreference.com/w/c/memory/aligned_alloc
104 | int size32 = ( ( size - 1 ) | 0x1F ) + 1;
105 |
106 | if ( b2_allocFcn != NULL )
107 | {
108 | void* ptr = b2_allocFcn( size32, B2_ALIGNMENT );
109 | b2TracyCAlloc( ptr, size );
110 |
111 | B2_ASSERT( ptr != NULL );
112 | B2_ASSERT( ( (uintptr_t)ptr & 0x1F ) == 0 );
113 |
114 | return ptr;
115 | }
116 |
117 | #ifdef B2_PLATFORM_WINDOWS
118 | void* ptr = _aligned_malloc( size32, B2_ALIGNMENT );
119 | #elif defined( B2_PLATFORM_ANDROID )
120 | void* ptr = NULL;
121 | if ( posix_memalign( &ptr, B2_ALIGNMENT, size32 ) != 0 )
122 | {
123 | // allocation failed, exit the application
124 | exit( EXIT_FAILURE );
125 | }
126 | #else
127 | void* ptr = aligned_alloc( B2_ALIGNMENT, size32 );
128 | #endif
129 |
130 | b2TracyCAlloc( ptr, size );
131 |
132 | B2_ASSERT( ptr != NULL );
133 | B2_ASSERT( ( (uintptr_t)ptr & 0x1F ) == 0 );
134 |
135 | return ptr;
136 | }
137 |
138 | void b2Free( void* mem, int size )
139 | {
140 | if ( mem == NULL )
141 | {
142 | return;
143 | }
144 |
145 | b2TracyCFree( mem );
146 |
147 | if ( b2_freeFcn != NULL )
148 | {
149 | b2_freeFcn( mem );
150 | }
151 | else
152 | {
153 | #ifdef B2_PLATFORM_WINDOWS
154 | _aligned_free( mem );
155 | #else
156 | free( mem );
157 | #endif
158 | }
159 |
160 | b2AtomicFetchAddInt( &b2_byteCount, -size );
161 | }
162 |
163 | void* b2GrowAlloc( void* oldMem, int oldSize, int newSize )
164 | {
165 | B2_ASSERT( newSize > oldSize );
166 | void* newMem = b2Alloc( newSize );
167 | if ( oldSize > 0 )
168 | {
169 | memcpy( newMem, oldMem, oldSize );
170 | b2Free( oldMem, oldSize );
171 | }
172 | return newMem;
173 | }
174 |
175 | int b2GetByteCount( void )
176 | {
177 | return b2AtomicLoadInt( &b2_byteCount );
178 | }
179 |
--------------------------------------------------------------------------------
/src/core.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "box2d/math_functions.h"
7 |
8 | // clang-format off
9 |
10 | #define B2_NULL_INDEX ( -1 )
11 |
12 | // for performance comparisons
13 | #define B2_RESTRICT restrict
14 |
15 | #ifdef NDEBUG
16 | #define B2_DEBUG 0
17 | #else
18 | #define B2_DEBUG 1
19 | #endif
20 |
21 | #if defined( BOX2D_VALIDATE ) && !defined( NDEBUG )
22 | #define B2_VALIDATE 1
23 | #else
24 | #define B2_VALIDATE 0
25 | #endif
26 |
27 | // Define platform
28 | #if defined(_WIN32) || defined(_WIN64)
29 | #define B2_PLATFORM_WINDOWS
30 | #elif defined( __ANDROID__ )
31 | #define B2_PLATFORM_ANDROID
32 | #elif defined( __linux__ )
33 | #define B2_PLATFORM_LINUX
34 | #elif defined( __APPLE__ )
35 | #include
36 | #if defined( TARGET_OS_IPHONE ) && !TARGET_OS_IPHONE
37 | #define B2_PLATFORM_MACOS
38 | #else
39 | #define B2_PLATFORM_IOS
40 | #endif
41 | #elif defined( __EMSCRIPTEN__ )
42 | #define B2_PLATFORM_WASM
43 | #else
44 | #define B2_PLATFORM_UNKNOWN
45 | #endif
46 |
47 | // Define CPU
48 | #if defined( __x86_64__ ) || defined( _M_X64 ) || defined( __i386__ ) || defined( _M_IX86 )
49 | #define B2_CPU_X86_X64
50 | #elif defined( __aarch64__ ) || defined( _M_ARM64 ) || defined( __arm__ ) || defined( _M_ARM )
51 | #define B2_CPU_ARM
52 | #elif defined( __EMSCRIPTEN__ )
53 | #define B2_CPU_WASM
54 | #else
55 | #define B2_CPU_UNKNOWN
56 | #endif
57 |
58 | // Define SIMD
59 | #if defined( BOX2D_DISABLE_SIMD )
60 | #define B2_SIMD_NONE
61 | // note: I tried width of 1 and got no performance change
62 | #define B2_SIMD_WIDTH 4
63 | #else
64 | #if defined( B2_CPU_X86_X64 )
65 | #if defined( BOX2D_AVX2 )
66 | #define B2_SIMD_AVX2
67 | #define B2_SIMD_WIDTH 8
68 | #else
69 | #define B2_SIMD_SSE2
70 | #define B2_SIMD_WIDTH 4
71 | #endif
72 | #elif defined( B2_CPU_ARM )
73 | #define B2_SIMD_NEON
74 | #define B2_SIMD_WIDTH 4
75 | #elif defined( B2_CPU_WASM )
76 | #define B2_CPU_WASM
77 | #define B2_SIMD_SSE2
78 | #define B2_SIMD_WIDTH 4
79 | #else
80 | #define B2_SIMD_NONE
81 | #define B2_SIMD_WIDTH 4
82 | #endif
83 | #endif
84 |
85 | // Define compiler
86 | #if defined( __clang__ )
87 | #define B2_COMPILER_CLANG
88 | #elif defined( __GNUC__ )
89 | #define B2_COMPILER_GCC
90 | #elif defined( _MSC_VER )
91 | #define B2_COMPILER_MSVC
92 | #endif
93 |
94 | /// Tracy profiler instrumentation
95 | /// https://github.com/wolfpld/tracy
96 | #ifdef BOX2D_PROFILE
97 | #include
98 | #define b2TracyCZoneC( ctx, color, active ) TracyCZoneC( ctx, color, active )
99 | #define b2TracyCZoneNC( ctx, name, color, active ) TracyCZoneNC( ctx, name, color, active )
100 | #define b2TracyCZoneEnd( ctx ) TracyCZoneEnd( ctx )
101 | #else
102 | #define b2TracyCZoneC( ctx, color, active )
103 | #define b2TracyCZoneNC( ctx, name, color, active )
104 | #define b2TracyCZoneEnd( ctx )
105 | #endif
106 |
107 | // clang-format on
108 |
109 | // Returns the number of elements of an array
110 | #define B2_ARRAY_COUNT( A ) (int)( sizeof( A ) / sizeof( A[0] ) )
111 |
112 | // Used to prevent the compiler from warning about unused variables
113 | #define B2_UNUSED( ... ) (void)sizeof( ( __VA_ARGS__, 0 ) )
114 |
115 | // Use to validate definitions. Do not take my cookie.
116 | #define B2_SECRET_COOKIE 1152023
117 |
118 | // Snoop counters. These should be disabled in optimized builds because they are expensive.
119 | #if defined( box2d_EXPORTS )
120 | #define B2_SNOOP_TABLE_COUNTERS B2_DEBUG
121 | #define B2_SNOOP_PAIR_COUNTERS B2_DEBUG
122 | #define B2_SNOOP_TOI_COUNTERS B2_DEBUG
123 | #else
124 | #define B2_SNOOP_TABLE_COUNTERS 0
125 | #define B2_SNOOP_PAIR_COUNTERS 0
126 | #define B2_SNOOP_TOI_COUNTERS 0
127 | #endif
128 |
129 | #define B2_CHECK_DEF( DEF ) B2_ASSERT( DEF->internalValue == B2_SECRET_COOKIE )
130 |
131 | typedef struct b2AtomicInt
132 | {
133 | int value;
134 | } b2AtomicInt;
135 |
136 | typedef struct b2AtomicU32
137 | {
138 | uint32_t value;
139 | } b2AtomicU32;
140 |
141 | void* b2Alloc( int size );
142 | #define B2_ALLOC_STRUCT( type ) b2Alloc(sizeof(type))
143 | #define B2_ALLOC_ARRAY( count, type ) b2Alloc(count * sizeof(type))
144 |
145 | void b2Free( void* mem, int size );
146 | #define B2_FREE_STRUCT( mem, type ) b2Free( mem, sizeof(type));
147 | #define B2_FREE_ARRAY( mem, count, type ) b2Free(mem, count * sizeof(type))
148 |
149 | void* b2GrowAlloc( void* oldMem, int oldSize, int newSize );
150 |
--------------------------------------------------------------------------------
/src/ctz.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include
7 | #include
8 |
9 | #if defined( _MSC_VER ) && !defined( __clang__ )
10 | #include
11 |
12 | // https://en.wikipedia.org/wiki/Find_first_set
13 |
14 | static inline uint32_t b2CTZ32( uint32_t block )
15 | {
16 | unsigned long index;
17 | _BitScanForward( &index, block );
18 | return index;
19 | }
20 |
21 | // This function doesn't need to be fast, so using the Ivy Bridge fallback.
22 | static inline uint32_t b2CLZ32( uint32_t value )
23 | {
24 | #if 1
25 |
26 | // Use BSR (Bit Scan Reverse) which is available on Ivy Bridge
27 | unsigned long index;
28 | if ( _BitScanReverse( &index, value ) )
29 | {
30 | // BSR gives the index of the most significant 1-bit
31 | // We need to invert this to get the number of leading zeros
32 | return 31 - index;
33 | }
34 | else
35 | {
36 | // If x is 0, BSR sets the zero flag and doesn't modify index
37 | // LZCNT should return 32 for an input of 0
38 | return 32;
39 | }
40 |
41 | #else
42 |
43 | return __lzcnt( value );
44 |
45 | #endif
46 | }
47 |
48 | static inline uint32_t b2CTZ64( uint64_t block )
49 | {
50 | unsigned long index;
51 |
52 | #ifdef _WIN64
53 | _BitScanForward64( &index, block );
54 | #else
55 | // 32-bit fall back
56 | if ( (uint32_t)block != 0 )
57 | {
58 | _BitScanForward( &index, (uint32_t)block );
59 | }
60 | else
61 | {
62 | _BitScanForward( &index, (uint32_t)( block >> 32 ) );
63 | index += 32;
64 | }
65 | #endif
66 |
67 | return index;
68 | }
69 |
70 | #else
71 |
72 | static inline uint32_t b2CTZ32( uint32_t block )
73 | {
74 | return __builtin_ctz( block );
75 | }
76 |
77 | static inline uint32_t b2CLZ32( uint32_t value )
78 | {
79 | return __builtin_clz( value );
80 | }
81 |
82 | static inline uint32_t b2CTZ64( uint64_t block )
83 | {
84 | return __builtin_ctzll( block );
85 | }
86 |
87 | #endif
88 |
89 | static inline bool b2IsPowerOf2( int x )
90 | {
91 | return ( x & ( x - 1 ) ) == 0;
92 | }
93 |
94 | static inline int b2BoundingPowerOf2( int x )
95 | {
96 | if ( x <= 1 )
97 | {
98 | return 1;
99 | }
100 |
101 | return 32 - (int)b2CLZ32( (uint32_t)x - 1 );
102 | }
103 |
104 | static inline int b2RoundUpPowerOf2( int x )
105 | {
106 | if ( x <= 1 )
107 | {
108 | return 1;
109 | }
110 |
111 | return 1 << ( 32 - (int)b2CLZ32( (uint32_t)x - 1 ) );
112 | }
113 |
--------------------------------------------------------------------------------
/src/id_pool.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "id_pool.h"
5 |
6 | b2IdPool b2CreateIdPool( void )
7 | {
8 | b2IdPool pool = { 0 };
9 | pool.freeArray = b2IntArray_Create( 32 );
10 | return pool;
11 | }
12 |
13 | void b2DestroyIdPool( b2IdPool* pool )
14 | {
15 | b2IntArray_Destroy( &pool->freeArray );
16 | *pool = ( b2IdPool ){ 0 };
17 | }
18 |
19 | int b2AllocId( b2IdPool* pool )
20 | {
21 | int count = pool->freeArray.count;
22 | if ( count > 0 )
23 | {
24 | int id = b2IntArray_Pop( &pool->freeArray );
25 | return id;
26 | }
27 |
28 | int id = pool->nextIndex;
29 | pool->nextIndex += 1;
30 | return id;
31 | }
32 |
33 | void b2FreeId( b2IdPool* pool, int id )
34 | {
35 | B2_ASSERT( pool->nextIndex > 0 );
36 | B2_ASSERT( 0 <= id && id < pool->nextIndex );
37 | b2IntArray_Push( &pool->freeArray, id );
38 | }
39 |
40 | #if B2_VALIDATE
41 |
42 | void b2ValidateFreeId( b2IdPool* pool, int id )
43 | {
44 | int freeCount = pool->freeArray.count;
45 | for ( int i = 0; i < freeCount; ++i )
46 | {
47 | if ( pool->freeArray.data[i] == id )
48 | {
49 | return;
50 | }
51 | }
52 |
53 | B2_ASSERT( 0 );
54 | }
55 |
56 | void b2ValidateUsedId( b2IdPool* pool, int id )
57 | {
58 | int freeCount = pool->freeArray.count;
59 | for ( int i = 0; i < freeCount; ++i )
60 | {
61 | if ( pool->freeArray.data[i] == id )
62 | {
63 | B2_ASSERT( 0 );
64 | }
65 | }
66 | }
67 |
68 | #else
69 |
70 | void b2ValidateFreeId( b2IdPool* pool, int id )
71 | {
72 | B2_UNUSED( pool, id );
73 | }
74 |
75 | void b2ValidateUsedId( b2IdPool* pool, int id )
76 | {
77 | B2_UNUSED( pool, id );
78 | }
79 | #endif
80 |
--------------------------------------------------------------------------------
/src/id_pool.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 |
8 | typedef struct b2IdPool
9 | {
10 | b2IntArray freeArray;
11 | int nextIndex;
12 | } b2IdPool;
13 |
14 | b2IdPool b2CreateIdPool( void );
15 | void b2DestroyIdPool( b2IdPool* pool );
16 |
17 | int b2AllocId( b2IdPool* pool );
18 | void b2FreeId( b2IdPool* pool, int id );
19 | void b2ValidateFreeId( b2IdPool* pool, int id );
20 | void b2ValidateUsedId( b2IdPool* pool, int id );
21 |
22 | static inline int b2GetIdCount( b2IdPool* pool )
23 | {
24 | return pool->nextIndex - pool->freeArray.count;
25 | }
26 |
27 | static inline int b2GetIdCapacity( b2IdPool* pool )
28 | {
29 | return pool->nextIndex;
30 | }
31 |
32 | static inline int b2GetIdBytes( b2IdPool* pool )
33 | {
34 | return b2IntArray_ByteCount(&pool->freeArray);
35 | }
36 |
--------------------------------------------------------------------------------
/src/island.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 |
8 | #include
9 | #include
10 |
11 | typedef struct b2Contact b2Contact;
12 | typedef struct b2Joint b2Joint;
13 | typedef struct b2World b2World;
14 |
15 | // Deterministic solver
16 | //
17 | // Collide all awake contacts
18 | // Use bit array to emit start/stop touching events in defined order, per thread. Try using contact index, assuming contacts are
19 | // created in a deterministic order. bit-wise OR together bit arrays and issue changes:
20 | // - start touching: merge islands - temporary linked list - mark root island dirty - wake all - largest island is root
21 | // - stop touching: increment constraintRemoveCount
22 |
23 | // Persistent island for awake bodies, joints, and contacts
24 | // https://en.wikipedia.org/wiki/Component_(graph_theory)
25 | // https://en.wikipedia.org/wiki/Dynamic_connectivity
26 | // map from int to solver set and index
27 | typedef struct b2Island
28 | {
29 | // index of solver set stored in b2World
30 | // may be B2_NULL_INDEX
31 | int setIndex;
32 |
33 | // island index within set
34 | // may be B2_NULL_INDEX
35 | int localIndex;
36 |
37 | int islandId;
38 |
39 | int headBody;
40 | int tailBody;
41 | int bodyCount;
42 |
43 | int headContact;
44 | int tailContact;
45 | int contactCount;
46 |
47 | int headJoint;
48 | int tailJoint;
49 | int jointCount;
50 |
51 | // Union find
52 | // todo this could go away if islands are merged immediately with b2LinkJoint and b2LinkContact
53 | int parentIsland;
54 |
55 | // Keeps track of how many contacts have been removed from this island.
56 | // This is used to determine if an island is a candidate for splitting.
57 | int constraintRemoveCount;
58 | } b2Island;
59 |
60 | // This is used to move islands across solver sets
61 | typedef struct b2IslandSim
62 | {
63 | int islandId;
64 | } b2IslandSim;
65 |
66 | b2Island* b2CreateIsland( b2World* world, int setIndex );
67 | void b2DestroyIsland( b2World* world, int islandId );
68 |
69 | // Link contacts into the island graph when it starts having contact points
70 | void b2LinkContact( b2World* world, b2Contact* contact );
71 |
72 | // Unlink contact from the island graph when it stops having contact points
73 | void b2UnlinkContact( b2World* world, b2Contact* contact );
74 |
75 | // Link a joint into the island graph when it is created
76 | void b2LinkJoint( b2World* world, b2Joint* joint, bool mergeIslands );
77 |
78 | // Unlink a joint from the island graph when it is destroyed
79 | void b2UnlinkJoint( b2World* world, b2Joint* joint );
80 |
81 | void b2MergeAwakeIslands( b2World* world );
82 |
83 | void b2SplitIsland( b2World* world, int baseId );
84 | void b2SplitIslandTask( int startIndex, int endIndex, uint32_t threadIndex, void* context );
85 |
86 | void b2ValidateIsland( b2World* world, int islandId );
87 |
88 | B2_ARRAY_INLINE( b2Island, b2Island )
89 | B2_ARRAY_INLINE( b2IslandSim, b2IslandSim )
90 |
--------------------------------------------------------------------------------
/src/math_functions.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "box2d/math_functions.h"
5 |
6 | #include
7 |
8 | _Static_assert( sizeof( int32_t ) == sizeof( int ), "Box2D expects int32_t and int to be the same" );
9 |
10 | bool b2IsValidFloat( float a )
11 | {
12 | if ( isnan( a ) )
13 | {
14 | return false;
15 | }
16 |
17 | if ( isinf( a ) )
18 | {
19 | return false;
20 | }
21 |
22 | return true;
23 | }
24 |
25 | bool b2IsValidVec2( b2Vec2 v )
26 | {
27 | if ( isnan( v.x ) || isnan( v.y ) )
28 | {
29 | return false;
30 | }
31 |
32 | if ( isinf( v.x ) || isinf( v.y ) )
33 | {
34 | return false;
35 | }
36 |
37 | return true;
38 | }
39 |
40 | bool b2IsValidRotation( b2Rot q )
41 | {
42 | if ( isnan( q.s ) || isnan( q.c ) )
43 | {
44 | return false;
45 | }
46 |
47 | if ( isinf( q.s ) || isinf( q.c ) )
48 | {
49 | return false;
50 | }
51 |
52 | return b2IsNormalizedRot( q );
53 | }
54 |
55 | bool b2IsValidPlane( b2Plane a )
56 | {
57 | return b2IsValidVec2( a.normal ) && b2IsNormalized( a.normal ) && b2IsValidFloat( a.offset );
58 | }
59 |
60 | // https://stackoverflow.com/questions/46210708/atan2-approximation-with-11bits-in-mantissa-on-x86with-sse2-and-armwith-vfpv4
61 | float b2Atan2( float y, float x )
62 | {
63 | // Added check for (0,0) to match atan2f and avoid NaN
64 | if (x == 0.0f && y == 0.0f)
65 | {
66 | return 0.0f;
67 | }
68 |
69 | float ax = b2AbsFloat( x );
70 | float ay = b2AbsFloat( y );
71 | float mx = b2MaxFloat( ay, ax );
72 | float mn = b2MinFloat( ay, ax );
73 | float a = mn / mx;
74 |
75 | // Minimax polynomial approximation to atan(a) on [0,1]
76 | float s = a * a;
77 | float c = s * a;
78 | float q = s * s;
79 | float r = 0.024840285f * q + 0.18681418f;
80 | float t = -0.094097948f * q - 0.33213072f;
81 | r = r * s + t;
82 | r = r * c + a;
83 |
84 | // Map to full circle
85 | if ( ay > ax )
86 | {
87 | r = 1.57079637f - r;
88 | }
89 |
90 | if ( x < 0 )
91 | {
92 | r = 3.14159274f - r;
93 | }
94 |
95 | if ( y < 0 )
96 | {
97 | r = -r;
98 | }
99 |
100 | return r;
101 | }
102 |
103 | // Approximate cosine and sine for determinism. In my testing cosf and sinf produced
104 | // the same results on x64 and ARM using MSVC, GCC, and Clang. However, I don't trust
105 | // this result.
106 | // https://en.wikipedia.org/wiki/Bh%C4%81skara_I%27s_sine_approximation_formula
107 | b2CosSin b2ComputeCosSin( float radians )
108 | {
109 | float x = b2UnwindAngle( radians );
110 | float pi2 = B2_PI * B2_PI;
111 |
112 | // cosine needs angle in [-pi/2, pi/2]
113 | float c;
114 | if ( x < -0.5f * B2_PI )
115 | {
116 | float y = x + B2_PI;
117 | float y2 = y * y;
118 | c = -( pi2 - 4.0f * y2 ) / ( pi2 + y2 );
119 | }
120 | else if ( x > 0.5f * B2_PI )
121 | {
122 | float y = x - B2_PI;
123 | float y2 = y * y;
124 | c = -( pi2 - 4.0f * y2 ) / ( pi2 + y2 );
125 | }
126 | else
127 | {
128 | float y2 = x * x;
129 | c = ( pi2 - 4.0f * y2 ) / ( pi2 + y2 );
130 | }
131 |
132 | // sine needs angle in [0, pi]
133 | float s;
134 | if ( x < 0.0f )
135 | {
136 | float y = x + B2_PI;
137 | s = -16.0f * y * ( B2_PI - y ) / ( 5.0f * pi2 - 4.0f * y * ( B2_PI - y ) );
138 | }
139 | else
140 | {
141 | s = 16.0f * x * ( B2_PI - x ) / ( 5.0f * pi2 - 4.0f * x * ( B2_PI - x ) );
142 | }
143 |
144 | float mag = sqrtf( s * s + c * c );
145 | float invMag = mag > 0.0 ? 1.0f / mag : 0.0f;
146 | b2CosSin cs = { c * invMag, s * invMag };
147 | return cs;
148 | }
149 |
150 | b2Rot b2ComputeRotationBetweenUnitVectors(b2Vec2 v1, b2Vec2 v2)
151 | {
152 | B2_ASSERT( b2AbsFloat( 1.0f - b2Length( v1 ) ) < 100.0f * FLT_EPSILON );
153 | B2_ASSERT( b2AbsFloat( 1.0f - b2Length( v2 ) ) < 100.0f * FLT_EPSILON );
154 |
155 | b2Rot rot;
156 | rot.c = b2Dot( v1, v2 );
157 | rot.s = b2Cross( v1, v2 );
158 | return b2NormalizeRot( rot );
159 | }
160 |
--------------------------------------------------------------------------------
/src/mover.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2025 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "constants.h"
5 |
6 | #include "box2d/collision.h"
7 |
8 | b2PlaneSolverResult b2SolvePlanes( b2Vec2 position, b2CollisionPlane* planes, int count )
9 | {
10 | for ( int i = 0; i < count; ++i )
11 | {
12 | planes[i].push = 0.0f;
13 | }
14 |
15 | b2Vec2 delta = b2Vec2_zero;
16 | float tolerance = B2_LINEAR_SLOP;
17 |
18 | int iteration;
19 | for ( iteration = 0; iteration < 20; ++iteration )
20 | {
21 | float totalPush = 0.0f;
22 | for ( int planeIndex = 0; planeIndex < count; ++planeIndex )
23 | {
24 | b2CollisionPlane* plane = planes + planeIndex;
25 |
26 | // Add slop to prevent jitter
27 | float separation = b2PlaneSeparation( plane->plane, delta ) + B2_LINEAR_SLOP;
28 | // if (separation > 0.0f)
29 | //{
30 | // continue;
31 | // }
32 |
33 | float push = -separation;
34 |
35 | // Clamp accumulated push
36 | float accumulatedPush = plane->push;
37 | plane->push = b2ClampFloat( plane->push + push, 0.0f, plane->pushLimit );
38 | push = plane->push - accumulatedPush;
39 | delta = b2MulAdd( delta, push, plane->plane.normal );
40 |
41 | // Track maximum push for convergence
42 | totalPush += b2AbsFloat( push );
43 | }
44 |
45 | if ( totalPush < tolerance )
46 | {
47 | break;
48 | }
49 | }
50 |
51 | return (b2PlaneSolverResult){
52 | .position = b2Add( delta, position ),
53 | .iterationCount = iteration,
54 | };
55 | }
56 |
57 | b2Vec2 b2ClipVector( b2Vec2 vector, const b2CollisionPlane* planes, int count )
58 | {
59 | b2Vec2 v = vector;
60 |
61 | for ( int planeIndex = 0; planeIndex < count; ++planeIndex )
62 | {
63 | const b2CollisionPlane* plane = planes + planeIndex;
64 | if ( plane->push == 0.0f || plane->clipVelocity == false )
65 | {
66 | continue;
67 | }
68 |
69 | v = b2MulSub( v, b2MinFloat( 0.0f, b2Dot( v, plane->plane.normal ) ), plane->plane.normal );
70 | }
71 |
72 | return v;
73 | }
74 |
--------------------------------------------------------------------------------
/src/sensor.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 | #include "bitset.h"
8 |
9 | typedef struct b2Shape b2Shape;
10 | typedef struct b2World b2World;
11 |
12 | typedef struct b2ShapeRef
13 | {
14 | int shapeId;
15 | uint16_t generation;
16 | } b2ShapeRef;
17 |
18 | typedef struct b2Sensor
19 | {
20 | b2ShapeRefArray overlaps1;
21 | b2ShapeRefArray overlaps2;
22 | int shapeId;
23 | } b2Sensor;
24 |
25 | typedef struct b2SensorTaskContext
26 | {
27 | b2BitSet eventBits;
28 | } b2SensorTaskContext;
29 |
30 | void b2OverlapSensors( b2World* world );
31 |
32 | void b2DestroySensor( b2World* world, b2Shape* sensorShape );
33 |
34 | B2_ARRAY_INLINE( b2ShapeRef, b2ShapeRef )
35 | B2_ARRAY_INLINE( b2Sensor, b2Sensor )
36 | B2_ARRAY_INLINE( b2SensorTaskContext, b2SensorTaskContext )
37 |
--------------------------------------------------------------------------------
/src/shape.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 |
8 | #include "box2d/types.h"
9 |
10 | typedef struct b2BroadPhase b2BroadPhase;
11 | typedef struct b2World b2World;
12 |
13 | typedef struct b2Shape
14 | {
15 | int id;
16 | int bodyId;
17 | int prevShapeId;
18 | int nextShapeId;
19 | int sensorIndex;
20 | b2ShapeType type;
21 | float density;
22 | float friction;
23 | float restitution;
24 | float rollingResistance;
25 | float tangentSpeed;
26 | int userMaterialId;
27 |
28 | b2AABB aabb;
29 | b2AABB fatAABB;
30 | b2Vec2 localCentroid;
31 | int proxyKey;
32 |
33 | b2Filter filter;
34 | void* userData;
35 | uint32_t customColor;
36 |
37 | union
38 | {
39 | b2Capsule capsule;
40 | b2Circle circle;
41 | b2Polygon polygon;
42 | b2Segment segment;
43 | b2ChainSegment chainSegment;
44 | };
45 |
46 | uint16_t generation;
47 | bool enableSensorEvents;
48 | bool enableContactEvents;
49 | bool enableHitEvents;
50 | bool enablePreSolveEvents;
51 | bool enlargedAABB;
52 | } b2Shape;
53 |
54 | typedef struct b2ChainShape
55 | {
56 | int id;
57 | int bodyId;
58 | int nextChainId;
59 | int count;
60 | int materialCount;
61 | int* shapeIndices;
62 | b2SurfaceMaterial* materials;
63 | uint16_t generation;
64 | } b2ChainShape;
65 |
66 | typedef struct b2ShapeExtent
67 | {
68 | float minExtent;
69 | float maxExtent;
70 | } b2ShapeExtent;
71 |
72 | // Sensors are shapes that live in the broad-phase but never have contacts.
73 | // At the end of the time step all sensors are queried for overlap with any other shapes.
74 | // Sensors ignore body type and sleeping.
75 | // Sensors generate events when there is a new overlap or and overlap disappears.
76 | // The sensor overlaps don't get cleared until the next time step regardless of the overlapped
77 | // shapes being destroyed.
78 | // When a sensor is destroyed.
79 | typedef struct
80 | {
81 | b2IntArray overlaps;
82 | } b2SensorOverlaps;
83 |
84 | void b2CreateShapeProxy( b2Shape* shape, b2BroadPhase* bp, b2BodyType type, b2Transform transform, bool forcePairCreation );
85 | void b2DestroyShapeProxy( b2Shape* shape, b2BroadPhase* bp );
86 |
87 | void b2FreeChainData( b2ChainShape* chain );
88 |
89 | b2MassData b2ComputeShapeMass( const b2Shape* shape );
90 | b2ShapeExtent b2ComputeShapeExtent( const b2Shape* shape, b2Vec2 localCenter );
91 | b2AABB b2ComputeShapeAABB( const b2Shape* shape, b2Transform transform );
92 | b2Vec2 b2GetShapeCentroid( const b2Shape* shape );
93 | float b2GetShapePerimeter( const b2Shape* shape );
94 | float b2GetShapeProjectedPerimeter( const b2Shape* shape, b2Vec2 line );
95 |
96 | b2ShapeProxy b2MakeShapeDistanceProxy( const b2Shape* shape );
97 |
98 | b2CastOutput b2RayCastShape( const b2RayCastInput* input, const b2Shape* shape, b2Transform transform );
99 | b2CastOutput b2ShapeCastShape( const b2ShapeCastInput* input, const b2Shape* shape, b2Transform transform );
100 |
101 | b2PlaneResult b2CollideMoverAndCircle( const b2Circle* shape, const b2Capsule* mover );
102 | b2PlaneResult b2CollideMoverAndCapsule( const b2Capsule* shape, const b2Capsule* mover );
103 | b2PlaneResult b2CollideMoverAndPolygon( const b2Polygon* shape, const b2Capsule* mover );
104 | b2PlaneResult b2CollideMoverAndSegment( const b2Segment* shape, const b2Capsule* mover );
105 | b2PlaneResult b2CollideMover( const b2Shape* shape, b2Transform transform, const b2Capsule* mover );
106 |
107 | static inline float b2GetShapeRadius( const b2Shape* shape )
108 | {
109 | switch ( shape->type )
110 | {
111 | case b2_capsuleShape:
112 | return shape->capsule.radius;
113 | case b2_circleShape:
114 | return shape->circle.radius;
115 | case b2_polygonShape:
116 | return shape->polygon.radius;
117 | default:
118 | return 0.0f;
119 | }
120 | }
121 |
122 | static inline bool b2ShouldShapesCollide( b2Filter filterA, b2Filter filterB )
123 | {
124 | if ( filterA.groupIndex == filterB.groupIndex && filterA.groupIndex != 0 )
125 | {
126 | return filterA.groupIndex > 0;
127 | }
128 |
129 | return ( filterA.maskBits & filterB.categoryBits ) != 0 && ( filterA.categoryBits & filterB.maskBits ) != 0;
130 | }
131 |
132 | static inline bool b2ShouldQueryCollide( b2Filter shapeFilter, b2QueryFilter queryFilter )
133 | {
134 | return ( shapeFilter.categoryBits & queryFilter.maskBits ) != 0 && ( shapeFilter.maskBits & queryFilter.categoryBits ) != 0;
135 | }
136 |
137 | B2_ARRAY_INLINE( b2ChainShape, b2ChainShape )
138 | B2_ARRAY_INLINE( b2Shape, b2Shape )
139 |
--------------------------------------------------------------------------------
/src/solver.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "core.h"
7 |
8 | #include "box2d/math_functions.h"
9 |
10 | #include
11 | #include
12 |
13 | typedef struct b2BodySim b2BodySim;
14 | typedef struct b2BodyState b2BodyState;
15 | typedef struct b2ContactSim b2ContactSim;
16 | typedef struct b2JointSim b2JointSim;
17 | typedef struct b2World b2World;
18 |
19 | typedef struct b2Softness
20 | {
21 | float biasRate;
22 | float massScale;
23 | float impulseScale;
24 | } b2Softness;
25 |
26 | typedef enum b2SolverStageType
27 | {
28 | b2_stagePrepareJoints,
29 | b2_stagePrepareContacts,
30 | b2_stageIntegrateVelocities,
31 | b2_stageWarmStart,
32 | b2_stageSolve,
33 | b2_stageIntegratePositions,
34 | b2_stageRelax,
35 | b2_stageRestitution,
36 | b2_stageStoreImpulses
37 | } b2SolverStageType;
38 |
39 | typedef enum b2SolverBlockType
40 | {
41 | b2_bodyBlock,
42 | b2_jointBlock,
43 | b2_contactBlock,
44 | b2_graphJointBlock,
45 | b2_graphContactBlock
46 | } b2SolverBlockType;
47 |
48 | // Each block of work has a sync index that gets incremented when a worker claims the block. This ensures only a single worker
49 | // claims a block, yet lets work be distributed dynamically across multiple workers (work stealing). This also reduces contention
50 | // on a single block index atomic. For non-iterative stages the sync index is simply set to one. For iterative stages (solver
51 | // iteration) the same block of work is executed once per iteration and the atomic sync index is shared across iterations, so it
52 | // increases monotonically.
53 | typedef struct b2SolverBlock
54 | {
55 | int startIndex;
56 | int16_t count;
57 | int16_t blockType; // b2SolverBlockType
58 | // todo consider false sharing of this atomic
59 | b2AtomicInt syncIndex;
60 | } b2SolverBlock;
61 |
62 | // Each stage must be completed before going to the next stage.
63 | // Non-iterative stages use a stage instance once while iterative stages re-use the same instance each iteration.
64 | typedef struct b2SolverStage
65 | {
66 | b2SolverStageType type;
67 | b2SolverBlock* blocks;
68 | int blockCount;
69 | int colorIndex;
70 | // todo consider false sharing of this atomic
71 | b2AtomicInt completionCount;
72 | } b2SolverStage;
73 |
74 | // Context for a time step. Recreated each time step.
75 | typedef struct b2StepContext
76 | {
77 | // time step
78 | float dt;
79 |
80 | // inverse time step (0 if dt == 0).
81 | float inv_dt;
82 |
83 | // sub-step
84 | float h;
85 | float inv_h;
86 |
87 | int subStepCount;
88 |
89 | b2Softness jointSoftness;
90 | b2Softness contactSoftness;
91 | b2Softness staticSoftness;
92 |
93 | float restitutionThreshold;
94 | float maxLinearVelocity;
95 |
96 | struct b2World* world;
97 | struct b2ConstraintGraph* graph;
98 |
99 | // shortcut to body states from awake set
100 | b2BodyState* states;
101 |
102 | // shortcut to body sims from awake set
103 | b2BodySim* sims;
104 |
105 | // array of all shape ids for shapes that have enlarged AABBs
106 | int* enlargedShapes;
107 | int enlargedShapeCount;
108 |
109 | // Array of bullet bodies that need continuous collision handling
110 | int* bulletBodies;
111 | b2AtomicInt bulletBodyCount;
112 |
113 | // joint pointers for simplified parallel-for access.
114 | b2JointSim** joints;
115 |
116 | // contact pointers for simplified parallel-for access.
117 | // - parallel-for collide with no gaps
118 | // - parallel-for prepare and store contacts with NULL gaps for SIMD remainders
119 | // despite being an array of pointers, these are contiguous sub-arrays corresponding
120 | // to constraint graph colors
121 | b2ContactSim** contacts;
122 |
123 | struct b2ContactConstraintSIMD* simdContactConstraints;
124 | int activeColorCount;
125 | int workerCount;
126 |
127 | b2SolverStage* stages;
128 | int stageCount;
129 | bool enableWarmStarting;
130 |
131 | // todo padding to prevent false sharing
132 | char dummy1[64];
133 |
134 | // sync index (16-bits) | stage type (16-bits)
135 | b2AtomicU32 atomicSyncBits;
136 |
137 | char dummy2[64];
138 |
139 | } b2StepContext;
140 |
141 | static inline b2Softness b2MakeSoft( float hertz, float zeta, float h )
142 | {
143 | if ( hertz == 0.0f )
144 | {
145 | return (b2Softness){
146 | .biasRate = 0.0f,
147 | .massScale = 0.0f,
148 | .impulseScale = 0.0f,
149 | };
150 | }
151 |
152 | float omega = 2.0f * B2_PI * hertz;
153 | float a1 = 2.0f * zeta + h * omega;
154 | float a2 = h * omega * a1;
155 | float a3 = 1.0f / ( 1.0f + a2 );
156 |
157 | return (b2Softness){
158 | .biasRate = omega / a1,
159 | .massScale = a2 * a3,
160 | .impulseScale = a3,
161 | };
162 | }
163 |
164 | void b2Solve( b2World* world, b2StepContext* stepContext );
165 |
--------------------------------------------------------------------------------
/src/solver_set.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include "array.h"
7 |
8 | typedef struct b2Body b2Body;
9 | typedef struct b2Joint b2Joint;
10 | typedef struct b2World b2World;
11 |
12 | // This holds solver set data. The following sets are used:
13 | // - static set for all static bodies (no contacts or joints)
14 | // - active set for all active bodies with body states (no contacts or joints)
15 | // - disabled set for disabled bodies and their joints
16 | // - all further sets are sleeping island sets along with their contacts and joints
17 | // The purpose of solver sets is to achieve high memory locality.
18 | // https://www.youtube.com/watch?v=nZNd5FjSquk
19 | typedef struct b2SolverSet
20 | {
21 | // Body array. Empty for unused set.
22 | b2BodySimArray bodySims;
23 |
24 | // Body state only exists for active set
25 | b2BodyStateArray bodyStates;
26 |
27 | // This holds sleeping/disabled joints. Empty for static/active set.
28 | b2JointSimArray jointSims;
29 |
30 | // This holds all contacts for sleeping sets.
31 | // This holds non-touching contacts for the awake set.
32 | b2ContactSimArray contactSims;
33 |
34 | // The awake set has an array of islands. Sleeping sets normally have a single islands. However, joints
35 | // created between sleeping sets causes the sets to merge, leaving them with multiple islands. These sleeping
36 | // islands will be naturally merged with the set is woken.
37 | // The static and disabled sets have no islands.
38 | // Islands live in the solver sets to limit the number of islands that need to be considered for sleeping.
39 | b2IslandSimArray islandSims;
40 |
41 | // Aligns with b2World::solverSetIdPool. Used to create a stable id for body/contact/joint/islands.
42 | int setIndex;
43 | } b2SolverSet;
44 |
45 | void b2DestroySolverSet( b2World* world, int setIndex );
46 |
47 | void b2WakeSolverSet( b2World* world, int setIndex );
48 | void b2TrySleepIsland( b2World* world, int islandId );
49 |
50 | // Merge set 2 into set 1 then destroy set 2.
51 | // Warning: any pointers into these sets will be orphaned.
52 | void b2MergeSolverSets( b2World* world, int setIndex1, int setIndex2 );
53 |
54 | void b2TransferBody( b2World* world, b2SolverSet* targetSet, b2SolverSet* sourceSet, b2Body* body );
55 | void b2TransferJoint( b2World* world, b2SolverSet* targetSet, b2SolverSet* sourceSet, b2Joint* joint );
56 |
57 | B2_ARRAY_INLINE( b2SolverSet, b2SolverSet )
58 |
--------------------------------------------------------------------------------
/src/table.h:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include
7 | #include
8 |
9 | #define B2_SHAPE_PAIR_KEY( K1, K2 ) K1 < K2 ? (uint64_t)K1 << 32 | (uint64_t)K2 : (uint64_t)K2 << 32 | (uint64_t)K1
10 |
11 | typedef struct b2SetItem
12 | {
13 | uint64_t key;
14 | uint32_t hash;
15 | } b2SetItem;
16 |
17 | typedef struct b2HashSet
18 | {
19 | b2SetItem* items;
20 | uint32_t capacity;
21 | uint32_t count;
22 | } b2HashSet;
23 |
24 | b2HashSet b2CreateSet( int capacity );
25 | void b2DestroySet( b2HashSet* set );
26 |
27 | void b2ClearSet( b2HashSet* set );
28 |
29 | // Returns true if key was already in set
30 | bool b2AddKey( b2HashSet* set, uint64_t key );
31 |
32 | // Returns true if the key was found
33 | bool b2RemoveKey( b2HashSet* set, uint64_t key );
34 |
35 | bool b2ContainsKey( const b2HashSet* set, uint64_t key );
36 |
37 | int b2GetHashSetBytes( b2HashSet* set );
38 |
--------------------------------------------------------------------------------
/src/timer.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "box2d/base.h"
5 |
6 | #include
7 |
8 | #if defined( _MSC_VER )
9 |
10 | #ifndef WIN32_LEAN_AND_MEAN
11 | #define WIN32_LEAN_AND_MEAN 1
12 | #endif
13 |
14 | #include
15 |
16 | static double s_invFrequency = 0.0;
17 |
18 | uint64_t b2GetTicks( void )
19 | {
20 | LARGE_INTEGER counter;
21 | QueryPerformanceCounter( &counter );
22 | return (uint64_t)counter.QuadPart;
23 | }
24 |
25 | float b2GetMilliseconds( uint64_t ticks )
26 | {
27 | if ( s_invFrequency == 0.0 )
28 | {
29 | LARGE_INTEGER frequency;
30 | QueryPerformanceFrequency( &frequency );
31 |
32 | s_invFrequency = (double)frequency.QuadPart;
33 | if ( s_invFrequency > 0.0 )
34 | {
35 | s_invFrequency = 1000.0 / s_invFrequency;
36 | }
37 | }
38 |
39 | uint64_t ticksNow = b2GetTicks();
40 | return (float)( s_invFrequency * ( ticksNow - ticks ) );
41 | }
42 |
43 | float b2GetMillisecondsAndReset( uint64_t* ticks )
44 | {
45 | if ( s_invFrequency == 0.0 )
46 | {
47 | LARGE_INTEGER frequency;
48 | QueryPerformanceFrequency( &frequency );
49 |
50 | s_invFrequency = (double)frequency.QuadPart;
51 | if ( s_invFrequency > 0.0 )
52 | {
53 | s_invFrequency = 1000.0 / s_invFrequency;
54 | }
55 | }
56 |
57 | uint64_t ticksNow = b2GetTicks();
58 | float ms = (float)( s_invFrequency * ( ticksNow - *ticks ) );
59 | *ticks = ticksNow;
60 | return ms;
61 | }
62 |
63 | void b2Yield( void )
64 | {
65 | SwitchToThread();
66 | }
67 |
68 | #elif defined( __linux__ ) || defined( __EMSCRIPTEN__ )
69 |
70 | #include
71 | #include
72 |
73 | uint64_t b2GetTicks( void )
74 | {
75 | struct timespec ts;
76 | clock_gettime( CLOCK_MONOTONIC, &ts );
77 | return ts.tv_sec * 1000000000LL + ts.tv_nsec;
78 | }
79 |
80 | float b2GetMilliseconds( uint64_t ticks )
81 | {
82 | uint64_t ticksNow = b2GetTicks();
83 | return (float)( (ticksNow - ticks) / 1000000.0 );
84 | }
85 |
86 | float b2GetMillisecondsAndReset( uint64_t* ticks )
87 | {
88 | uint64_t ticksNow = b2GetTicks();
89 | float ms = (float)( (ticksNow - *ticks) / 1000000.0 );
90 | *ticks = ticksNow;
91 | return ms;
92 | }
93 |
94 | void b2Yield( void )
95 | {
96 | sched_yield();
97 | }
98 |
99 | #elif defined( __APPLE__ )
100 |
101 | #include
102 | #include
103 | #include
104 |
105 | static double s_invFrequency = 0.0;
106 |
107 | uint64_t b2GetTicks( void )
108 | {
109 | return mach_absolute_time();
110 | }
111 |
112 | float b2GetMilliseconds( uint64_t ticks )
113 | {
114 | if ( s_invFrequency == 0 )
115 | {
116 | mach_timebase_info_data_t timebase;
117 | mach_timebase_info( &timebase );
118 |
119 | // convert to ns then to ms
120 | s_invFrequency = 1e-6 * (double)timebase.numer / (double)timebase.denom;
121 | }
122 |
123 | uint64_t ticksNow = b2GetTicks();
124 | return (float)( s_invFrequency * (ticksNow - ticks) );
125 | }
126 |
127 | float b2GetMillisecondsAndReset( uint64_t* ticks )
128 | {
129 | if ( s_invFrequency == 0 )
130 | {
131 | mach_timebase_info_data_t timebase;
132 | mach_timebase_info( &timebase );
133 |
134 | // convert to ns then to ms
135 | s_invFrequency = 1e-6 * (double)timebase.numer / (double)timebase.denom;
136 | }
137 |
138 | uint64_t ticksNow = b2GetTicks();
139 | float ms = (float)( s_invFrequency * ( ticksNow - *ticks ) );
140 | *ticks = ticksNow;
141 | return ms;
142 | }
143 |
144 | void b2Yield( void )
145 | {
146 | sched_yield();
147 | }
148 |
149 | #else
150 |
151 | uint64_t b2GetTicks( void )
152 | {
153 | return 0;
154 | }
155 |
156 | float b2GetMilliseconds( uint64_t ticks )
157 | {
158 | ( (void)( ticks ) );
159 | return 0.0f;
160 | }
161 |
162 | float b2GetMillisecondsAndReset( uint64_t* ticks )
163 | {
164 | ( (void)( ticks ) );
165 | return 0.0f;
166 | }
167 |
168 | void b2Yield( void )
169 | {
170 | }
171 |
172 | #endif
173 |
174 | // djb2 hash
175 | // https://en.wikipedia.org/wiki/List_of_hash_functions
176 | uint32_t b2Hash( uint32_t hash, const uint8_t* data, int count )
177 | {
178 | uint32_t result = hash;
179 | for ( int i = 0; i < count; i++ )
180 | {
181 | result = ( result << 5 ) + result + data[i];
182 | }
183 |
184 | return result;
185 | }
186 |
--------------------------------------------------------------------------------
/src/types.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "box2d/types.h"
5 |
6 | #include "constants.h"
7 | #include "core.h"
8 |
9 | b2WorldDef b2DefaultWorldDef( void )
10 | {
11 | b2WorldDef def = { 0 };
12 | def.gravity.x = 0.0f;
13 | def.gravity.y = -10.0f;
14 | def.hitEventThreshold = 1.0f * b2_lengthUnitsPerMeter;
15 | def.restitutionThreshold = 1.0f * b2_lengthUnitsPerMeter;
16 | def.maxContactPushSpeed = 3.0f * b2_lengthUnitsPerMeter;
17 | def.contactHertz = 30.0;
18 | def.contactDampingRatio = 10.0f;
19 | def.jointHertz = 60.0;
20 | def.jointDampingRatio = 2.0f;
21 | // 400 meters per second, faster than the speed of sound
22 | def.maximumLinearSpeed = 400.0f * b2_lengthUnitsPerMeter;
23 | def.enableSleep = true;
24 | def.enableContinuous = true;
25 | def.internalValue = B2_SECRET_COOKIE;
26 | return def;
27 | }
28 |
29 | b2BodyDef b2DefaultBodyDef( void )
30 | {
31 | b2BodyDef def = { 0 };
32 | def.type = b2_staticBody;
33 | def.rotation = b2Rot_identity;
34 | def.sleepThreshold = 0.05f * b2_lengthUnitsPerMeter;
35 | def.gravityScale = 1.0f;
36 | def.enableSleep = true;
37 | def.isAwake = true;
38 | def.isEnabled = true;
39 | def.internalValue = B2_SECRET_COOKIE;
40 | return def;
41 | }
42 |
43 | b2Filter b2DefaultFilter( void )
44 | {
45 | b2Filter filter = { B2_DEFAULT_CATEGORY_BITS, B2_DEFAULT_MASK_BITS, 0 };
46 | return filter;
47 | }
48 |
49 | b2QueryFilter b2DefaultQueryFilter( void )
50 | {
51 | b2QueryFilter filter = { B2_DEFAULT_CATEGORY_BITS, B2_DEFAULT_MASK_BITS };
52 | return filter;
53 | }
54 |
55 | b2ShapeDef b2DefaultShapeDef( void )
56 | {
57 | b2ShapeDef def = { 0 };
58 | def.material.friction = 0.6f;
59 | def.density = 1.0f;
60 | def.filter = b2DefaultFilter();
61 | def.updateBodyMass = true;
62 | def.invokeContactCreation = true;
63 | def.internalValue = B2_SECRET_COOKIE;
64 | return def;
65 | }
66 |
67 | b2SurfaceMaterial b2DefaultSurfaceMaterial( void )
68 | {
69 | b2SurfaceMaterial material = {
70 | .friction = 0.6f,
71 | };
72 |
73 | return material;
74 | }
75 |
76 | b2ChainDef b2DefaultChainDef( void )
77 | {
78 | static b2SurfaceMaterial defaultMaterial = {
79 | .friction = 0.6f,
80 | };
81 |
82 | b2ChainDef def = { 0 };
83 | def.materials = &defaultMaterial;
84 | def.materialCount = 1;
85 | def.filter = b2DefaultFilter();
86 | def.internalValue = B2_SECRET_COOKIE;
87 | return def;
88 | }
89 |
90 | static void b2EmptyDrawPolygon( const b2Vec2* vertices, int vertexCount, b2HexColor color, void* context )
91 | {
92 | B2_UNUSED( vertices, vertexCount, color, context );
93 | }
94 |
95 | static void b2EmptyDrawSolidPolygon( b2Transform transform, const b2Vec2* vertices, int vertexCount, float radius,
96 | b2HexColor color, void* context )
97 | {
98 | B2_UNUSED( transform, vertices, vertexCount, radius, color, context );
99 | }
100 |
101 | static void b2EmptyDrawCircle( b2Vec2 center, float radius, b2HexColor color, void* context )
102 | {
103 | B2_UNUSED( center, radius, color, context );
104 | }
105 |
106 | static void b2EmptyDrawSolidCircle( b2Transform transform, float radius, b2HexColor color, void* context )
107 | {
108 | B2_UNUSED( transform, radius, color, context );
109 | }
110 |
111 | static void b2EmptyDrawSolidCapsule( b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color, void* context )
112 | {
113 | B2_UNUSED( p1, p2, radius, color, context );
114 | }
115 |
116 | static void b2EmptyDrawSegment( b2Vec2 p1, b2Vec2 p2, b2HexColor color, void* context )
117 | {
118 | B2_UNUSED( p1, p2, color, context );
119 | }
120 |
121 | static void b2EmptyDrawTransform( b2Transform transform, void* context )
122 | {
123 | B2_UNUSED( transform, context );
124 | }
125 |
126 | static void b2EmptyDrawPoint( b2Vec2 p, float size, b2HexColor color, void* context )
127 | {
128 | B2_UNUSED( p, size, color, context );
129 | }
130 |
131 | static void b2EmptyDrawString( b2Vec2 p, const char* s, b2HexColor color, void* context )
132 | {
133 | B2_UNUSED( p, s, color, context );
134 | }
135 |
136 | b2DebugDraw b2DefaultDebugDraw( void )
137 | {
138 | b2DebugDraw draw = { 0 };
139 |
140 | // These allow the user to skip some implementations and not hit null exceptions.
141 | draw.DrawPolygonFcn = b2EmptyDrawPolygon;
142 | draw.DrawSolidPolygonFcn = b2EmptyDrawSolidPolygon;
143 | draw.DrawCircleFcn = b2EmptyDrawCircle;
144 | draw.DrawSolidCircleFcn = b2EmptyDrawSolidCircle;
145 | draw.DrawSolidCapsuleFcn = b2EmptyDrawSolidCapsule;
146 | draw.DrawSegmentFcn = b2EmptyDrawSegment;
147 | draw.DrawTransformFcn = b2EmptyDrawTransform;
148 | draw.DrawPointFcn = b2EmptyDrawPoint;
149 | draw.DrawStringFcn = b2EmptyDrawString;
150 | return draw;
151 | }
152 |
--------------------------------------------------------------------------------
/test/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Box2D unit test app
2 |
3 | set(BOX2D_TEST_FILES
4 | main.c
5 | test_bitset.c
6 | test_collision.c
7 | test_determinism.c
8 | test_distance.c
9 | test_id.c
10 | test_macros.h
11 | test_math.c
12 | test_shape.c
13 | test_table.c
14 | test_world.c
15 | )
16 |
17 | add_executable(test ${BOX2D_TEST_FILES})
18 |
19 | set_target_properties(test PROPERTIES
20 | C_STANDARD 17
21 | C_STANDARD_REQUIRED YES
22 | C_EXTENSIONS NO
23 | )
24 |
25 | if (BOX2D_COMPILE_WARNING_AS_ERROR)
26 | set_target_properties(test PROPERTIES COMPILE_WARNING_AS_ERROR ON)
27 | endif()
28 |
29 | # Special access to Box2D internals for testing
30 | target_include_directories(test PRIVATE ${CMAKE_SOURCE_DIR}/src)
31 |
32 | target_link_libraries(test PRIVATE box2d shared enkiTS)
33 |
34 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "" FILES ${BOX2D_TEST_FILES})
35 |
--------------------------------------------------------------------------------
/test/main.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "test_macros.h"
5 |
6 | #if defined( _MSC_VER )
7 | #include
8 |
9 | // int MyAllocHook(int allocType, void* userData, size_t size, int blockType, long requestNumber, const unsigned char* filename,
10 | // int lineNumber)
11 | //{
12 | // if (size == 16416)
13 | // {
14 | // size += 0;
15 | // }
16 | //
17 | // return 1;
18 | // }
19 | #endif
20 |
21 | extern int BitSetTest( void );
22 | extern int CollisionTest( void );
23 | extern int DeterminismTest( void );
24 | extern int DistanceTest( void );
25 | extern int IdTest( void );
26 | extern int MathTest( void );
27 | extern int ShapeTest( void );
28 | extern int TableTest( void );
29 | extern int WorldTest( void );
30 |
31 | int main( void )
32 | {
33 | #if defined( _MSC_VER )
34 | // Enable memory-leak reports
35 |
36 | // How to break at the leaking allocation, in the watch window enter this variable
37 | // and set it to the allocation number in {}. Do this at the first line in main.
38 | // {,,ucrtbased.dll}_crtBreakAlloc = 3970
39 | // Note:
40 | // Just _crtBreakAlloc in static link
41 | // Tracy Profile server leaks
42 |
43 | _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE );
44 | _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
45 | //_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
46 | //_CrtSetAllocHook(MyAllocHook);
47 | //_CrtSetBreakAlloc(196);
48 | #endif
49 |
50 | printf( "Starting Box2D unit tests\n" );
51 | printf( "======================================\n" );
52 |
53 | RUN_TEST( BitSetTest );
54 | RUN_TEST( CollisionTest );
55 | RUN_TEST( DeterminismTest );
56 | RUN_TEST( DistanceTest );
57 | RUN_TEST( IdTest );
58 | RUN_TEST( MathTest );
59 | RUN_TEST( ShapeTest );
60 | RUN_TEST( TableTest );
61 | RUN_TEST( WorldTest );
62 |
63 | printf( "======================================\n" );
64 | printf( "All Box2D tests passed!\n" );
65 |
66 | #if defined( _MSC_VER )
67 | if ( _CrtDumpMemoryLeaks() )
68 | {
69 | return 1;
70 | }
71 | #endif
72 |
73 | return 0;
74 | }
75 |
--------------------------------------------------------------------------------
/test/test_bitset.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "bitset.h"
5 | #include "test_macros.h"
6 |
7 | #define COUNT 169
8 |
9 | int BitSetTest( void )
10 | {
11 | b2BitSet bitSet = b2CreateBitSet( COUNT );
12 |
13 | b2SetBitCountAndClear( &bitSet, COUNT );
14 | bool values[COUNT] = { false };
15 |
16 | int32_t i1 = 0, i2 = 1;
17 | b2SetBit( &bitSet, i1 );
18 | values[i1] = true;
19 |
20 | while ( i2 < COUNT )
21 | {
22 | b2SetBit( &bitSet, i2 );
23 | values[i2] = true;
24 | int32_t next = i1 + i2;
25 | i1 = i2;
26 | i2 = next;
27 | }
28 |
29 | for ( int32_t i = 0; i < COUNT; ++i )
30 | {
31 | bool value = b2GetBit( &bitSet, i );
32 | ENSURE( value == values[i] );
33 | }
34 |
35 | b2DestroyBitSet( &bitSet );
36 |
37 | return 0;
38 | }
39 |
--------------------------------------------------------------------------------
/test/test_collision.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "aabb.h"
5 | #include "test_macros.h"
6 |
7 | #include "box2d/math_functions.h"
8 |
9 | static int AABBTest( void )
10 | {
11 | b2AABB a;
12 | a.lowerBound = ( b2Vec2 ){ -1.0f, -1.0f };
13 | a.upperBound = ( b2Vec2 ){ -2.0f, -2.0f };
14 |
15 | ENSURE( b2IsValidAABB( a ) == false );
16 |
17 | a.upperBound = ( b2Vec2 ){ 1.0f, 1.0f };
18 | ENSURE( b2IsValidAABB( a ) == true );
19 |
20 | b2AABB b = { { 2.0f, 2.0f }, { 4.0f, 4.0f } };
21 | ENSURE( b2AABB_Overlaps( a, b ) == false );
22 | ENSURE( b2AABB_Contains( a, b ) == false );
23 |
24 | b2Vec2 p1 = ( b2Vec2 ){ -2.0f, 0.0f };
25 | b2Vec2 p2 = ( b2Vec2 ){ 2.0f, 0.0f };
26 |
27 | b2CastOutput output = b2AABB_RayCast( a, p1, p2 );
28 | ENSURE( output.hit == true );
29 | ENSURE( 0.1f < output.fraction && output.fraction < 0.9f );
30 |
31 | return 0;
32 | }
33 |
34 | int CollisionTest( void )
35 | {
36 | RUN_SUBTEST( AABBTest );
37 |
38 | return 0;
39 | }
40 |
--------------------------------------------------------------------------------
/test/test_determinism.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "TaskScheduler_c.h"
5 | #include "determinism.h"
6 | #include "test_macros.h"
7 |
8 | #include "box2d/box2d.h"
9 | #include "box2d/types.h"
10 |
11 | #include
12 | #include
13 |
14 | #ifdef BOX2D_PROFILE
15 | #include
16 | #else
17 | #define TracyCFrameMark
18 | #endif
19 |
20 | #define EXPECTED_SLEEP_STEP 323
21 | #define EXPECTED_HASH 0xdf9ee1fb
22 |
23 | enum
24 | {
25 | e_maxTasks = 128,
26 | };
27 |
28 | typedef struct TaskData
29 | {
30 | b2TaskCallback* box2dTask;
31 | void* box2dContext;
32 | } TaskData;
33 |
34 | enkiTaskScheduler* scheduler;
35 | enkiTaskSet* tasks[e_maxTasks];
36 | TaskData taskData[e_maxTasks];
37 | int taskCount;
38 |
39 | static void ExecuteRangeTask( uint32_t start, uint32_t end, uint32_t threadIndex, void* context )
40 | {
41 | TaskData* data = context;
42 | data->box2dTask( start, end, threadIndex, data->box2dContext );
43 | }
44 |
45 | static void* EnqueueTask( b2TaskCallback* box2dTask, int itemCount, int minRange, void* box2dContext, void* userContext )
46 | {
47 | MAYBE_UNUSED( userContext );
48 |
49 | if ( taskCount < e_maxTasks )
50 | {
51 | enkiTaskSet* task = tasks[taskCount];
52 | TaskData* data = taskData + taskCount;
53 | data->box2dTask = box2dTask;
54 | data->box2dContext = box2dContext;
55 |
56 | struct enkiParamsTaskSet params;
57 | params.minRange = minRange;
58 | params.setSize = itemCount;
59 | params.pArgs = data;
60 | params.priority = 0;
61 |
62 | enkiSetParamsTaskSet( task, params );
63 | enkiAddTaskSet( scheduler, task );
64 |
65 | ++taskCount;
66 |
67 | return task;
68 | }
69 |
70 | box2dTask( 0, itemCount, 0, box2dContext );
71 | return NULL;
72 | }
73 |
74 | static void FinishTask( void* userTask, void* userContext )
75 | {
76 | MAYBE_UNUSED( userContext );
77 |
78 | enkiTaskSet* task = userTask;
79 | enkiWaitForTaskSet( scheduler, task );
80 | }
81 |
82 | static int SingleMultithreadingTest( int workerCount )
83 | {
84 | scheduler = enkiNewTaskScheduler();
85 | struct enkiTaskSchedulerConfig config = enkiGetTaskSchedulerConfig( scheduler );
86 | config.numTaskThreadsToCreate = workerCount - 1;
87 | enkiInitTaskSchedulerWithConfig( scheduler, config );
88 |
89 | for ( int i = 0; i < e_maxTasks; ++i )
90 | {
91 | tasks[i] = enkiCreateTaskSet( scheduler, ExecuteRangeTask );
92 | }
93 |
94 | b2WorldDef worldDef = b2DefaultWorldDef();
95 | worldDef.enqueueTask = EnqueueTask;
96 | worldDef.finishTask = FinishTask;
97 | worldDef.workerCount = workerCount;
98 |
99 | b2WorldId worldId = b2CreateWorld( &worldDef );
100 |
101 | FallingHingeData data = CreateFallingHinges( worldId );
102 |
103 | float timeStep = 1.0f / 60.0f;
104 |
105 | bool done = false;
106 | while ( done == false )
107 | {
108 | int subStepCount = 4;
109 | b2World_Step( worldId, timeStep, subStepCount );
110 | TracyCFrameMark;
111 |
112 | done = UpdateFallingHinges( worldId, &data );
113 | }
114 |
115 | b2DestroyWorld( worldId );
116 |
117 | for ( int i = 0; i < e_maxTasks; ++i )
118 | {
119 | enkiDeleteTaskSet( scheduler, tasks[i] );
120 | }
121 |
122 | enkiDeleteTaskScheduler( scheduler );
123 |
124 | ENSURE( data.sleepStep == EXPECTED_SLEEP_STEP );
125 | ENSURE( data.hash == EXPECTED_HASH );
126 |
127 | DestroyFallingHinges( &data );
128 |
129 | return 0;
130 | }
131 |
132 | // Test multithreaded determinism.
133 | static int MultithreadingTest( void )
134 | {
135 | for ( int workerCount = 1; workerCount < 6; ++workerCount )
136 | {
137 | int result = SingleMultithreadingTest( workerCount );
138 | ENSURE( result == 0 );
139 | }
140 |
141 | return 0;
142 | }
143 |
144 | // Test cross-platform determinism.
145 | static int CrossPlatformTest( void )
146 | {
147 | b2WorldDef worldDef = b2DefaultWorldDef();
148 | b2WorldId worldId = b2CreateWorld( &worldDef );
149 |
150 | FallingHingeData data = CreateFallingHinges( worldId );
151 |
152 | float timeStep = 1.0f / 60.0f;
153 |
154 | bool done = false;
155 | while ( done == false )
156 | {
157 | int subStepCount = 4;
158 | b2World_Step( worldId, timeStep, subStepCount );
159 | TracyCFrameMark;
160 |
161 | done = UpdateFallingHinges( worldId, &data );
162 | }
163 |
164 | ENSURE( data.sleepStep == EXPECTED_SLEEP_STEP );
165 | ENSURE( data.hash == EXPECTED_HASH );
166 |
167 | DestroyFallingHinges( &data );
168 |
169 | b2DestroyWorld( worldId );
170 |
171 | return 0;
172 | }
173 |
174 | int DeterminismTest( void )
175 | {
176 | RUN_SUBTEST( MultithreadingTest );
177 | RUN_SUBTEST( CrossPlatformTest );
178 |
179 | return 0;
180 | }
181 |
--------------------------------------------------------------------------------
/test/test_distance.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "test_macros.h"
5 |
6 | #include "box2d/collision.h"
7 | #include "box2d/math_functions.h"
8 |
9 | #include
10 |
11 | static int SegmentDistanceTest( void )
12 | {
13 | b2Vec2 p1 = { -1.0f, -1.0f };
14 | b2Vec2 q1 = { -1.0f, 1.0f };
15 | b2Vec2 p2 = { 2.0f, 0.0f };
16 | b2Vec2 q2 = { 1.0f, 0.0f };
17 |
18 | b2SegmentDistanceResult result = b2SegmentDistance( p1, q1, p2, q2 );
19 |
20 | ENSURE_SMALL( result.fraction1 - 0.5f, FLT_EPSILON );
21 | ENSURE_SMALL( result.fraction2 - 1.0f, FLT_EPSILON );
22 | ENSURE_SMALL( result.closest1.x + 1.0f, FLT_EPSILON );
23 | ENSURE_SMALL( result.closest1.y, FLT_EPSILON );
24 | ENSURE_SMALL( result.closest2.x - 1.0f, FLT_EPSILON );
25 | ENSURE_SMALL( result.closest2.y, FLT_EPSILON );
26 | ENSURE_SMALL( result.distanceSquared - 4.0f, FLT_EPSILON );
27 |
28 | return 0;
29 | }
30 |
31 | static int ShapeDistanceTest( void )
32 | {
33 | b2Vec2 vas[] = { ( b2Vec2 ){ -1.0f, -1.0f }, ( b2Vec2 ){ 1.0f, -1.0f }, ( b2Vec2 ){ 1.0f, 1.0f }, ( b2Vec2 ){ -1.0f, 1.0f } };
34 |
35 | b2Vec2 vbs[] = {
36 | ( b2Vec2 ){ 2.0f, -1.0f },
37 | ( b2Vec2 ){ 2.0f, 1.0f },
38 | };
39 |
40 | b2DistanceInput input;
41 | input.proxyA = b2MakeProxy( vas, ARRAY_COUNT( vas ), 0.0f );
42 | input.proxyB = b2MakeProxy( vbs, ARRAY_COUNT( vbs ), 0.0f );
43 | input.transformA = b2Transform_identity;
44 | input.transformB = b2Transform_identity;
45 | input.useRadii = false;
46 |
47 | b2SimplexCache cache = { 0 };
48 | b2DistanceOutput output = b2ShapeDistance(&input, &cache, NULL, 0 );
49 |
50 | ENSURE_SMALL( output.distance - 1.0f, FLT_EPSILON );
51 |
52 | return 0;
53 | }
54 |
55 | static int ShapeCastTest( void )
56 | {
57 | b2Vec2 vas[] = { ( b2Vec2 ){ -1.0f, -1.0f }, ( b2Vec2 ){ 1.0f, -1.0f }, ( b2Vec2 ){ 1.0f, 1.0f }, ( b2Vec2 ){ -1.0f, 1.0f } };
58 |
59 | b2Vec2 vbs[] = {
60 | ( b2Vec2 ){ 2.0f, -1.0f },
61 | ( b2Vec2 ){ 2.0f, 1.0f },
62 | };
63 |
64 | b2ShapeCastPairInput input;
65 | input.proxyA = b2MakeProxy( vas, ARRAY_COUNT( vas ), 0.0f );
66 | input.proxyB = b2MakeProxy( vbs, ARRAY_COUNT( vbs ), 0.0f );
67 | input.transformA = b2Transform_identity;
68 | input.transformB = b2Transform_identity;
69 | input.translationB = ( b2Vec2 ){ -2.0f, 0.0f };
70 | input.maxFraction = 1.0f;
71 |
72 | b2CastOutput output = b2ShapeCast( &input );
73 |
74 | ENSURE( output.hit );
75 | ENSURE_SMALL( output.fraction - 0.5f, 0.005f );
76 |
77 | return 0;
78 | }
79 |
80 | static int TimeOfImpactTest( void )
81 | {
82 | b2Vec2 vas[] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { 1.0f, 1.0f }, { -1.0f, 1.0f } };
83 |
84 | b2Vec2 vbs[] = {
85 | { 2.0f, -1.0f },
86 | { 2.0f, 1.0f },
87 | };
88 |
89 | b2TOIInput input;
90 | input.proxyA = b2MakeProxy( vas, ARRAY_COUNT( vas ), 0.0f );
91 | input.proxyB = b2MakeProxy( vbs, ARRAY_COUNT( vbs ), 0.0f );
92 | input.sweepA = ( b2Sweep ){ b2Vec2_zero, b2Vec2_zero, b2Vec2_zero, b2Rot_identity, b2Rot_identity };
93 | input.sweepB = ( b2Sweep ){ b2Vec2_zero, b2Vec2_zero, ( b2Vec2 ){ -2.0f, 0.0f }, b2Rot_identity, b2Rot_identity };
94 | input.maxFraction = 1.0f;
95 |
96 | b2TOIOutput output = b2TimeOfImpact( &input );
97 |
98 | ENSURE( output.state == b2_toiStateHit );
99 | ENSURE_SMALL( output.fraction - 0.5f, 0.005f );
100 |
101 | return 0;
102 | }
103 |
104 | int DistanceTest( void )
105 | {
106 | RUN_SUBTEST( SegmentDistanceTest );
107 | RUN_SUBTEST( ShapeDistanceTest );
108 | RUN_SUBTEST( ShapeCastTest );
109 | RUN_SUBTEST( TimeOfImpactTest );
110 |
111 | return 0;
112 | }
113 |
--------------------------------------------------------------------------------
/test/test_id.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "test_macros.h"
5 |
6 | #include "box2d/id.h"
7 |
8 | int IdTest( void )
9 | {
10 | uint64_t x = 0x0123456789ABCDEFull;
11 |
12 | {
13 | b2BodyId id = b2LoadBodyId( x );
14 | uint64_t y = b2StoreBodyId( id );
15 | ENSURE( x == y );
16 | }
17 |
18 | {
19 | b2ShapeId id = b2LoadShapeId( x );
20 | uint64_t y = b2StoreShapeId( id );
21 | ENSURE( x == y );
22 | }
23 |
24 | {
25 | b2ChainId id = b2LoadChainId( x );
26 | uint64_t y = b2StoreChainId( id );
27 | ENSURE( x == y );
28 | }
29 |
30 | {
31 | b2JointId id = b2LoadJointId( x );
32 | uint64_t y = b2StoreJointId( id );
33 | ENSURE( x == y );
34 | }
35 |
36 | return 0;
37 | }
38 |
--------------------------------------------------------------------------------
/test/test_math.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "test_macros.h"
5 |
6 | #include "box2d/math_functions.h"
7 |
8 | #include
9 | #include
10 |
11 | // 0.0023 degrees
12 | #define ATAN_TOL 0.00004f
13 |
14 | int MathTest( void )
15 | {
16 | for ( float t = -10.0f; t < 10.0f; t += 0.01f )
17 | {
18 | float angle = B2_PI * t;
19 | b2Rot r = b2MakeRot( angle );
20 | float c = cosf( angle );
21 | float s = sinf( angle );
22 |
23 | // The cosine and sine approximations are accurate to about 0.1 degrees (0.002 radians)
24 | // printf( "%g %g\n", r.c - c, r.s - s );
25 | ENSURE_SMALL( r.c - c, 0.002f );
26 | ENSURE_SMALL( r.s - s, 0.002f );
27 |
28 | float xn = b2UnwindAngle( angle );
29 | ENSURE( -B2_PI <= xn && xn <= B2_PI );
30 |
31 | float a = b2Atan2( s, c );
32 | ENSURE( b2IsValidFloat( a ) );
33 |
34 | float diff = b2AbsFloat( a - xn );
35 |
36 | // The two results can be off by 360 degrees (-pi and pi)
37 | if ( diff > B2_PI )
38 | {
39 | diff -= 2.0f * B2_PI;
40 | }
41 |
42 | // The approximate atan2 is quite accurate
43 | ENSURE_SMALL( diff, ATAN_TOL );
44 | }
45 |
46 | for ( float y = -1.0f; y <= 1.0f; y += 0.01f )
47 | {
48 | for ( float x = -1.0f; x <= 1.0f; x += 0.01f )
49 | {
50 | float a1 = b2Atan2( y, x );
51 | float a2 = atan2f( y, x );
52 | float diff = b2AbsFloat( a1 - a2 );
53 | ENSURE( b2IsValidFloat( a1 ) );
54 | ENSURE_SMALL( diff, ATAN_TOL );
55 | }
56 | }
57 |
58 | {
59 | float a1 = b2Atan2( 1.0f, 0.0f );
60 | float a2 = atan2f( 1.0f, 0.0f );
61 | float diff = b2AbsFloat( a1 - a2 );
62 | ENSURE( b2IsValidFloat( a1 ) );
63 | ENSURE_SMALL( diff, ATAN_TOL );
64 | }
65 |
66 | {
67 | float a1 = b2Atan2( -1.0f, 0.0f );
68 | float a2 = atan2f( -1.0f, 0.0f );
69 | float diff = b2AbsFloat( a1 - a2 );
70 | ENSURE( b2IsValidFloat( a1 ) );
71 | ENSURE_SMALL( diff, ATAN_TOL );
72 | }
73 |
74 | {
75 | float a1 = b2Atan2( 0.0f, 1.0f );
76 | float a2 = atan2f( 0.0f, 1.0f );
77 | float diff = b2AbsFloat( a1 - a2 );
78 | ENSURE( b2IsValidFloat( a1 ) );
79 | ENSURE_SMALL( diff, ATAN_TOL );
80 | }
81 |
82 | {
83 | float a1 = b2Atan2( 0.0f, -1.0f );
84 | float a2 = atan2f( 0.0f, -1.0f );
85 | float diff = b2AbsFloat( a1 - a2 );
86 | ENSURE( b2IsValidFloat( a1 ) );
87 | ENSURE_SMALL( diff, ATAN_TOL );
88 | }
89 |
90 | {
91 | float a1 = b2Atan2( 0.0f, 0.0f );
92 | float a2 = atan2f( 0.0f, 0.0f );
93 | float diff = b2AbsFloat( a1 - a2 );
94 | ENSURE( b2IsValidFloat( a1 ) );
95 | ENSURE_SMALL( diff, ATAN_TOL );
96 | }
97 |
98 | b2Vec2 zero = b2Vec2_zero;
99 | b2Vec2 one = { 1.0f, 1.0f };
100 | b2Vec2 two = { 2.0f, 2.0f };
101 |
102 | b2Vec2 v = b2Add( one, two );
103 | ENSURE( v.x == 3.0f && v.y == 3.0f );
104 |
105 | v = b2Sub( zero, two );
106 | ENSURE( v.x == -2.0f && v.y == -2.0f );
107 |
108 | v = b2Add( two, two );
109 | ENSURE( v.x != 5.0f && v.y != 5.0f );
110 |
111 | b2Transform transform1 = { { -2.0f, 3.0f }, b2MakeRot( 1.0f ) };
112 | b2Transform transform2 = { { 1.0f, 0.0f }, b2MakeRot( -2.0f ) };
113 |
114 | b2Transform transform = b2MulTransforms( transform2, transform1 );
115 |
116 | v = b2TransformPoint( transform2, b2TransformPoint( transform1, two ) );
117 |
118 | b2Vec2 u = b2TransformPoint( transform, two );
119 |
120 | ENSURE_SMALL( u.x - v.x, 10.0f * FLT_EPSILON );
121 | ENSURE_SMALL( u.y - v.y, 10.0f * FLT_EPSILON );
122 |
123 | v = b2TransformPoint( transform1, two );
124 | v = b2InvTransformPoint( transform1, v );
125 |
126 | ENSURE_SMALL( v.x - two.x, 8.0f * FLT_EPSILON );
127 | ENSURE_SMALL( v.y - two.y, 8.0f * FLT_EPSILON );
128 |
129 | v = b2Normalize(( b2Vec2 ){ 0.2f, -0.5f });
130 | for ( float y = -1.0f; y <= 1.0f; y += 0.01f )
131 | {
132 | for ( float x = -1.0f; x <= 1.0f; x += 0.01f )
133 | {
134 | if (x == 0.0f && y == 0.0f)
135 | {
136 | continue;
137 | }
138 |
139 | u = b2Normalize( ( b2Vec2 ){ x, y } );
140 |
141 | b2Rot r = b2ComputeRotationBetweenUnitVectors( v, u );
142 |
143 | b2Vec2 w = b2RotateVector( r, v );
144 | ENSURE_SMALL( w.x - u.x, 4.0f * FLT_EPSILON );
145 | ENSURE_SMALL( w.y - u.y, 4.0f * FLT_EPSILON );
146 | }
147 | }
148 |
149 | // NLerp of b2Rot has an error of over 4 degrees.
150 | // 2D quaternions should have an error under 1 degree.
151 | b2Rot q1 = b2Rot_identity;
152 | b2Rot q2 = b2MakeRot(0.5f * B2_PI);
153 | int n = 100;
154 | for (int i = 0; i <= n; ++i)
155 | {
156 | float alpha = (float)i / (float)n;
157 | b2Rot q = b2NLerp(q1, q2, alpha);
158 | float angle = b2Rot_GetAngle(q);
159 | ENSURE_SMALL( alpha * 0.5f * B2_PI - angle, 5.0f * B2_PI / 180.0f );
160 | //printf("angle = [%g %g %g]\n", alpha, alpha * 0.5f * B2_PI, angle);
161 | }
162 |
163 | return 0;
164 | }
165 |
--------------------------------------------------------------------------------
/test/test_shape.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "test_macros.h"
5 |
6 | #include "box2d/collision.h"
7 | #include "box2d/math_functions.h"
8 |
9 | #include
10 |
11 | static b2Capsule capsule = { { -1.0f, 0.0f }, { 1.0f, 0.0f }, 1.0f };
12 | static b2Circle circle = { { 1.0f, 0.0f }, 1.0f };
13 | static b2Polygon box;
14 | static b2Segment segment = { { 0.0f, 1.0f }, { 0.0f, -1.0f } };
15 |
16 | #define N 4
17 |
18 | static int ShapeMassTest( void )
19 | {
20 | {
21 | b2MassData md = b2ComputeCircleMass( &circle, 1.0f );
22 | ENSURE_SMALL( md.mass - B2_PI, FLT_EPSILON );
23 | ENSURE( md.center.x == 1.0f && md.center.y == 0.0f );
24 | ENSURE_SMALL( md.rotationalInertia - 1.5f * B2_PI, FLT_EPSILON );
25 | }
26 |
27 | {
28 | float radius = capsule.radius;
29 | float length = b2Distance( capsule.center1, capsule.center2 );
30 |
31 | b2MassData md = b2ComputeCapsuleMass( &capsule, 1.0f );
32 |
33 | // Box that full contains capsule
34 | b2Polygon r = b2MakeBox( radius, radius + 0.5f * length );
35 | b2MassData mdr = b2ComputePolygonMass( &r, 1.0f );
36 |
37 | // Approximate capsule using convex hull
38 | b2Vec2 points[2 * N];
39 | float d = B2_PI / ( N - 1.0f );
40 | float angle = -0.5f * B2_PI;
41 | for ( int i = 0; i < N; ++i )
42 | {
43 | points[i].x = 1.0f + radius * cosf( angle );
44 | points[i].y = radius * sinf( angle );
45 | angle += d;
46 | }
47 |
48 | angle = 0.5f * B2_PI;
49 | for ( int i = N; i < 2 * N; ++i )
50 | {
51 | points[i].x = -1.0f + radius * cosf( angle );
52 | points[i].y = radius * sinf( angle );
53 | angle += d;
54 | }
55 |
56 | b2Hull hull = b2ComputeHull( points, 2 * N );
57 | b2Polygon ac = b2MakePolygon( &hull, 0.0f );
58 | b2MassData ma = b2ComputePolygonMass( &ac, 1.0f );
59 |
60 | ENSURE( ma.mass < md.mass && md.mass < mdr.mass );
61 | ENSURE( ma.rotationalInertia < md.rotationalInertia && md.rotationalInertia < mdr.rotationalInertia );
62 | }
63 |
64 | {
65 | b2MassData md = b2ComputePolygonMass( &box, 1.0f );
66 | ENSURE_SMALL( md.mass - 4.0f, FLT_EPSILON );
67 | ENSURE_SMALL( md.center.x, FLT_EPSILON );
68 | ENSURE_SMALL( md.center.y, FLT_EPSILON );
69 | ENSURE_SMALL( md.rotationalInertia - 8.0f / 3.0f, 2.0f * FLT_EPSILON );
70 | }
71 |
72 | return 0;
73 | }
74 |
75 | static int ShapeAABBTest( void )
76 | {
77 | {
78 | b2AABB b = b2ComputeCircleAABB( &circle, b2Transform_identity );
79 | ENSURE_SMALL( b.lowerBound.x, FLT_EPSILON );
80 | ENSURE_SMALL( b.lowerBound.y + 1.0f, FLT_EPSILON );
81 | ENSURE_SMALL( b.upperBound.x - 2.0f, FLT_EPSILON );
82 | ENSURE_SMALL( b.upperBound.y - 1.0f, FLT_EPSILON );
83 | }
84 |
85 | {
86 | b2AABB b = b2ComputePolygonAABB( &box, b2Transform_identity );
87 | ENSURE_SMALL( b.lowerBound.x + 1.0f, FLT_EPSILON );
88 | ENSURE_SMALL( b.lowerBound.y + 1.0f, FLT_EPSILON );
89 | ENSURE_SMALL( b.upperBound.x - 1.0f, FLT_EPSILON );
90 | ENSURE_SMALL( b.upperBound.y - 1.0f, FLT_EPSILON );
91 | }
92 |
93 | {
94 | b2AABB b = b2ComputeSegmentAABB( &segment, b2Transform_identity );
95 | ENSURE_SMALL( b.lowerBound.x, FLT_EPSILON );
96 | ENSURE_SMALL( b.lowerBound.y + 1.0f, FLT_EPSILON );
97 | ENSURE_SMALL( b.upperBound.x, FLT_EPSILON );
98 | ENSURE_SMALL( b.upperBound.y - 1.0f, FLT_EPSILON );
99 | }
100 |
101 | return 0;
102 | }
103 |
104 | static int PointInShapeTest( void )
105 | {
106 | b2Vec2 p1 = { 0.5f, 0.5f };
107 | b2Vec2 p2 = { 4.0f, -4.0f };
108 |
109 | {
110 | bool hit;
111 | hit = b2PointInCircle( p1, &circle );
112 | ENSURE( hit == true );
113 | hit = b2PointInCircle( p2, &circle );
114 | ENSURE( hit == false );
115 | }
116 |
117 | {
118 | bool hit;
119 | hit = b2PointInPolygon( p1, &box );
120 | ENSURE( hit == true );
121 | hit = b2PointInPolygon( p2, &box );
122 | ENSURE( hit == false );
123 | }
124 |
125 | return 0;
126 | }
127 |
128 | static int RayCastShapeTest( void )
129 | {
130 | b2RayCastInput input = { { -4.0f, 0.0f }, { 8.0f, 0.0f }, 1.0f };
131 |
132 | {
133 | b2CastOutput output = b2RayCastCircle( &input, &circle );
134 | ENSURE( output.hit );
135 | ENSURE_SMALL( output.normal.x + 1.0f, FLT_EPSILON );
136 | ENSURE_SMALL( output.normal.y, FLT_EPSILON );
137 | ENSURE_SMALL( output.fraction - 0.5f, FLT_EPSILON );
138 | }
139 |
140 | {
141 | b2CastOutput output = b2RayCastPolygon( &input, &box );
142 | ENSURE( output.hit );
143 | ENSURE_SMALL( output.normal.x + 1.0f, FLT_EPSILON );
144 | ENSURE_SMALL( output.normal.y, FLT_EPSILON );
145 | ENSURE_SMALL( output.fraction - 3.0f / 8.0f, FLT_EPSILON );
146 | }
147 |
148 | {
149 | b2CastOutput output = b2RayCastSegment( &input, &segment, true );
150 | ENSURE( output.hit );
151 | ENSURE_SMALL( output.normal.x + 1.0f, FLT_EPSILON );
152 | ENSURE_SMALL( output.normal.y, FLT_EPSILON );
153 | ENSURE_SMALL( output.fraction - 0.5f, FLT_EPSILON );
154 | }
155 |
156 | return 0;
157 | }
158 |
159 | int ShapeTest( void )
160 | {
161 | box = b2MakeBox( 1.0f, 1.0f );
162 |
163 | RUN_SUBTEST( ShapeMassTest );
164 | RUN_SUBTEST( ShapeAABBTest );
165 | RUN_SUBTEST( PointInShapeTest );
166 | RUN_SUBTEST( RayCastShapeTest );
167 |
168 | return 0;
169 | }
170 |
--------------------------------------------------------------------------------
/test/test_table.c:
--------------------------------------------------------------------------------
1 | // SPDX-FileCopyrightText: 2023 Erin Catto
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "atomic.h"
5 | #include "core.h"
6 | #include "ctz.h"
7 | #include "table.h"
8 | #include "test_macros.h"
9 |
10 | #include "box2d/base.h"
11 |
12 | #define SET_SPAN 317
13 | #define ITEM_COUNT ( ( SET_SPAN * SET_SPAN - SET_SPAN ) / 2 )
14 |
15 | int TableTest( void )
16 | {
17 | int power = b2BoundingPowerOf2( 3008 );
18 | ENSURE( power == 12 );
19 |
20 | int nextPowerOf2 = b2RoundUpPowerOf2( 3008 );
21 | ENSURE( nextPowerOf2 == ( 1 << power ) );
22 |
23 | const int32_t N = SET_SPAN;
24 | const uint32_t itemCount = ITEM_COUNT;
25 | bool removed[ITEM_COUNT] = { 0 };
26 |
27 | for ( int32_t iter = 0; iter < 1; ++iter )
28 | {
29 | b2HashSet set = b2CreateSet( 16 );
30 |
31 | // Fill set
32 | for ( int32_t i = 0; i < N; ++i )
33 | {
34 | for ( int32_t j = i + 1; j < N; ++j )
35 | {
36 | uint64_t key = B2_SHAPE_PAIR_KEY( i, j );
37 | b2AddKey( &set, key );
38 | }
39 | }
40 |
41 | ENSURE( set.count == itemCount );
42 |
43 | // Remove a portion of the set
44 | int32_t k = 0;
45 | uint32_t removeCount = 0;
46 | for ( int32_t i = 0; i < N; ++i )
47 | {
48 | for ( int32_t j = i + 1; j < N; ++j )
49 | {
50 | if ( j == i + 1 )
51 | {
52 | uint64_t key = B2_SHAPE_PAIR_KEY( i, j );
53 | b2RemoveKey( &set, key );
54 | removed[k++] = true;
55 | removeCount += 1;
56 | }
57 | else
58 | {
59 | removed[k++] = false;
60 | }
61 | }
62 | }
63 |
64 | ENSURE( set.count == ( itemCount - removeCount ) );
65 |
66 | #if B2_SNOOP_TABLE_COUNTERS
67 | extern b2AtomicInt b2_probeCount;
68 | b2AtomicStoreInt( &b2_probeCount, 0 );
69 | #endif
70 |
71 | // Test key search
72 | // ~5ns per search on an AMD 7950x
73 | uint64_t ticks = b2GetTicks();
74 |
75 | k = 0;
76 | for ( int32_t i = 0; i < N; ++i )
77 | {
78 | for ( int32_t j = i + 1; j < N; ++j )
79 | {
80 | uint64_t key = B2_SHAPE_PAIR_KEY( j, i );
81 | ENSURE( b2ContainsKey( &set, key ) || removed[k] );
82 | k += 1;
83 | }
84 | }
85 |
86 | // uint64_t ticks = b2GetTicks(&timer);
87 | // printf("set ticks = %llu\n", ticks);
88 |
89 | float ms = b2GetMilliseconds( ticks );
90 | printf( "set: count = %d, b2ContainsKey = %.5f ms, ave = %.5f us\n", itemCount, ms, 1000.0f * ms / itemCount );
91 |
92 | #if B2_SNOOP_TABLE_COUNTERS
93 | int probeCount = b2AtomicLoadInt( &b2_probeCount );
94 | float aveProbeCount = (float)probeCount / (float)itemCount;
95 | printf( "item count = %d, probe count = %d, ave probe count %.2f\n", itemCount, probeCount, aveProbeCount );
96 | #endif
97 |
98 | // Remove all keys from set
99 | for ( int32_t i = 0; i < N; ++i )
100 | {
101 | for ( int32_t j = i + 1; j < N; ++j )
102 | {
103 | uint64_t key = B2_SHAPE_PAIR_KEY( i, j );
104 | b2RemoveKey( &set, key );
105 | }
106 | }
107 |
108 | ENSURE( set.count == 0 );
109 |
110 | b2DestroySet( &set );
111 | }
112 |
113 | return 0;
114 | }
115 |
--------------------------------------------------------------------------------