├── .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 | ![Box2D Logo](https://box2d.org/images/logo.svg) 2 | 3 | # Build Status 4 | [![Build Status](https://github.com/erincatto/box2d/actions/workflows/build.yml/badge.svg)](https://github.com/erincatto/box2d/actions) 5 | 6 | # Box2D 7 | Box2D is a 2D physics engine for games. 8 | 9 | [![Box2D Version 3.0 Release Demo](https://img.youtube.com/vi/dAoM-xjOWtA/0.jpg)](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 | ![Character Mover](images/mover.png) 42 | -------------------------------------------------------------------------------- /docs/images/captured_toi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 30 | 31 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 70 | 77 | 85 | 93 | t=0 104 | t=1 115 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/images/convex_concave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 53 | 54 | 56 | 61 | convex 73 | 77 | 81 | concave 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 49 | 51 | 52 | 54 | image/svg+xml 55 | 57 | 58 | 59 | 60 | 61 | 66 | 75 | 82 | 89 | 96 | 104 | 111 | 118 | 125 | 126 | 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 | ![Box2D Samples](images/samples.png) 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 | 16 | 36 | 52 | 53 | 55 | 60 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /samples/data/map02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 59 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /samples/data/map03.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 59 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /samples/data/ramp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 60 | 65 | 66 | 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 | --------------------------------------------------------------------------------