├── .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 │ ├── joint_grid.csv │ ├── large_pyramid.csv │ ├── many_pyramids.csv │ ├── smash.csv │ └── tumbler.csv ├── amd7950x_sse2 │ ├── joint_grid.csv │ ├── large_pyramid.csv │ ├── many_pyramids.csv │ ├── smash.csv │ └── tumbler.csv ├── benchmark_results.html ├── joint_grid.c ├── large_pyramid.c ├── m2air │ ├── joint_grid.csv │ ├── large_pyramid.csv │ ├── many_pyramids.csv │ ├── smash.csv │ └── tumbler.csv ├── main.c ├── many_pyramids.c ├── smash.c └── tumbler.c ├── deploy_docs.sh ├── docs ├── CMakeLists.txt ├── FAQ.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 │ ├── overlap_test.svg │ ├── prismatic_joint.svg │ ├── raycast.svg │ ├── revolute_joint.svg │ ├── samples.png │ ├── self_intersect.svg │ ├── skin_collision.svg │ ├── tunneling1.svg │ ├── tunneling2.svg │ ├── wheel_joint.svg │ └── winding.svg ├── layout.xml ├── loose_ends.md ├── migration.md ├── overview.md ├── reading.md ├── samples.md └── simulation.md ├── extern ├── glad │ ├── include │ │ ├── KHR │ │ │ └── khrplatform.h │ │ └── glad │ │ │ └── glad.h │ └── src │ │ └── glad.c ├── jsmn │ └── jsmn.h └── simde │ ├── CMakeLists.txt │ ├── check.h │ ├── debug-trap.h │ ├── hedley.h │ ├── simde-aes.h │ ├── simde-align.h │ ├── simde-arch.h │ ├── simde-common.h │ ├── simde-constify.h │ ├── simde-detect-clang.h │ ├── simde-diagnostic.h │ ├── simde-f16.h │ ├── simde-features.h │ ├── simde-math.h │ └── x86 │ ├── aes.h │ ├── avx.h │ ├── avx2.h │ ├── f16c.h │ ├── fma.h │ ├── mmx.h │ ├── sse.h │ ├── sse2.h │ ├── sse3.h │ ├── sse4.1.h │ ├── sse4.2.h │ └── ssse3.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 │ ├── 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 ├── human.cpp ├── human.h ├── main.cpp ├── sample.cpp ├── sample.h ├── sample_benchmark.cpp ├── sample_bodies.cpp ├── sample_collision.cpp ├── sample_continuous.cpp ├── sample_events.cpp ├── sample_geometry.cpp ├── sample_joints.cpp ├── sample_robustness.cpp ├── sample_shapes.cpp ├── sample_stacking.cpp ├── sample_world.cpp ├── settings.cpp ├── settings.h ├── shader.cpp └── shader.h ├── src ├── CMakeLists.txt ├── aabb.c ├── aabb.h ├── allocate.c ├── allocate.h ├── array.c ├── array.h ├── bitset.c ├── bitset.h ├── block_array.c ├── block_array.h ├── body.c ├── body.h ├── box2d.natvis ├── broad_phase.c ├── broad_phase.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 ├── prismatic_joint.c ├── revolute_joint.c ├── shape.c ├── shape.h ├── solver.c ├── solver.h ├── solver_set.c ├── solver_set.h ├── stack_allocator.c ├── stack_allocator.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_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: Allman 11 | BreakBeforeBraces: Custom 12 | BraceWrapping: 13 | AfterCaseLabel: true 14 | AfterUnion: true 15 | BeforeWhile: true 16 | # BreakBeforeBinaryOperators: All 17 | 18 | ColumnLimit: 130 19 | PointerAlignment: Left 20 | UseTab: Always 21 | BreakConstructorInitializers: BeforeComma 22 | 23 | InsertNewlineAtEOF: true 24 | IncludeBlocks: Regroup 25 | 26 | IncludeCategories: 27 | - Regex: '^"(box2d\/)' 28 | Priority: 2 29 | - Regex: '^<.*' 30 | Priority: 3 31 | - Regex: '^".*' 32 | Priority: 1 33 | 34 | IndentExternBlock: NoIndent 35 | IndentCaseLabels: true 36 | IndentPPDirectives: BeforeHash 37 | IndentAccessModifiers: false 38 | AccessModifierOffset: -4 39 | 40 | # AlignArrayOfStructures: Left 41 | # AlignAfterOpenBracket: BlockIndent 42 | 43 | SpacesInParens: Custom 44 | SpacesInParensOptions: 45 | Other: true 46 | -------------------------------------------------------------------------------- /.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: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 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 | 14 | github: [erincatto] 15 | -------------------------------------------------------------------------------- /.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) and/or [reddit](https://www.reddit.com/r/box2d) 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 | However, pull requests for build system improvements are often accepted. 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | BUILD_TYPE: Debug 11 | 12 | jobs: 13 | build-ubuntu-gcc: 14 | name: ubuntu-gcc 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 4 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Configure CMake 21 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF 22 | 23 | - name: Build 24 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 25 | 26 | - name: Test 27 | working-directory: ${{github.workspace}}/build 28 | run: ./bin/test 29 | 30 | build-ubuntu-clang: 31 | name: ubuntu-clang 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - name: Configure CMake 38 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_C_COMPILER=clang -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF 39 | 40 | - name: Build 41 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 42 | 43 | - name: Test 44 | working-directory: ${{github.workspace}}/build 45 | run: ./bin/test 46 | 47 | build-macos: 48 | name: macos 49 | runs-on: macos-latest 50 | 51 | steps: 52 | - uses: actions/checkout@v4 53 | 54 | - name: Configure CMake 55 | # some problem with simde 56 | # run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF 57 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF 58 | 59 | - name: Build 60 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 61 | 62 | - name: Test 63 | working-directory: ${{github.workspace}}/build 64 | run: ./bin/test 65 | 66 | build-windows: 67 | name: windows 68 | runs-on: windows-latest 69 | steps: 70 | 71 | - uses: actions/checkout@v4 72 | 73 | - name: Setup MSVC dev command prompt 74 | uses: TheMrMilchmann/setup-msvc-dev@v3 75 | with: 76 | arch: x64 77 | 78 | - name: Configure CMake 79 | # enkiTS is failing ASAN on windows 80 | # run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBOX2D_SANITIZE=ON -DBUILD_SHARED_LIBS=OFF 81 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBOX2D_SAMPLES=OFF -DBUILD_SHARED_LIBS=OFF 82 | 83 | - name: Build 84 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 85 | 86 | - name: Test 87 | working-directory: ${{github.workspace}}/build 88 | run: ./bin/${{env.BUILD_TYPE}}/test 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | imgui.ini 3 | settings.ini 4 | .vscode/ 5 | .vs/ 6 | .cap 7 | .DS_Store 8 | CMakeUserPresets.json 9 | .cache/ 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | include(FetchContent) 3 | 4 | project(box2d 5 | VERSION 3.0.0 6 | DESCRIPTION "A 2D physics engine for games" 7 | HOMEPAGE_URL "https://box2d.org" 8 | LANGUAGES C CXX 9 | ) 10 | 11 | # stuff to help debug cmake 12 | # message(STATUS "cmake source dir: ${CMAKE_SOURCE_DIR}") 13 | # message(STATUS "library postfix: ${CMAKE_DEBUG_POSTFIX}") 14 | message(STATUS "CMake C compiler: ${CMAKE_C_COMPILER_ID}") 15 | message(STATUS "CMake C++ compiler: ${CMAKE_CXX_COMPILER_ID}") 16 | message(STATUS "CMake system name: ${CMAKE_SYSTEM_NAME}") 17 | message(STATUS "CMake host system processor: ${CMAKE_HOST_SYSTEM_PROCESSOR}") 18 | 19 | if (MSVC OR APPLE) 20 | option(BOX2D_SANITIZE "Enable sanitizers for some builds" OFF) 21 | if(BOX2D_SANITIZE) 22 | message(STATUS "Box2D Sanitize") 23 | # sanitizers need to apply to all compiled libraries to work well 24 | if(MSVC) 25 | # address sanitizer only in the debug build 26 | add_compile_options("$<$:/fsanitize=address>") 27 | add_link_options("$<$:/INCREMENTAL:NO>") 28 | elseif(APPLE) 29 | # more sanitizers on Apple clang 30 | # add_compile_options(-fsanitize=thread -fno-omit-frame-pointer) 31 | add_compile_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined) 32 | add_link_options(-fsanitize=address -fsanitize-address-use-after-scope -fsanitize=undefined) 33 | endif() 34 | endif() 35 | endif() 36 | 37 | if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") 38 | option(BOX2D_AVX2 "Enable AVX2 (faster)" ON) 39 | endif() 40 | 41 | if(PROJECT_IS_TOP_LEVEL) 42 | # Needed for samples.exe to find box2d.dll 43 | # set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") 44 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") 45 | endif() 46 | 47 | # C++17 needed for imgui 48 | set(CMAKE_CXX_STANDARD 17) 49 | set(CMAKE_COMPILE_WARNING_AS_ERROR ON) 50 | 51 | # set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 52 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 53 | set(CMAKE_VERBOSE_MAKEFILE ON) 54 | 55 | # The Box2D library uses simde https://github.com/simd-everywhere/simde 56 | add_subdirectory(extern/simde) 57 | add_subdirectory(src) 58 | 59 | # This hides samples, test, and doxygen from apps that use box2d via FetchContent 60 | if(PROJECT_IS_TOP_LEVEL) 61 | option(BOX2D_SAMPLES "Build the Box2D samples" ON) 62 | option(BOX2D_BENCHMARKS "Build the Box2D benchmarks" OFF) 63 | option(BOX2D_DOCS "Build the Box2D documentation" OFF) 64 | option(BOX2D_PROFILE "Enable profiling with Tracy" OFF) 65 | option(BOX2D_VALIDATE "Enable heavy validation" ON) 66 | option(BOX2D_UNIT_TESTS "Build the Box2D unit tests" ON) 67 | 68 | if(MSVC AND WIN32) 69 | # Enable edit and continue only in debug due to perf hit in release 70 | # add_link_options($<$:/INCREMENTAL>) 71 | # add_compile_options($<$:/ZI>) 72 | # add_compile_options(/fsanitize=address) 73 | # set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/GS- /Gy /O2 /Oi /Ot") 74 | # set(CMAKE_CXX_FLAGS_RELEASE "/GS- /Gy /O2 /Oi /Ot") 75 | # set(CMAKE_C_FLAGS_RELWITHDEBINFO "/GS- /Gy /O2 /Oi /Ot") 76 | # set(CMAKE_C_FLAGS_RELEASE "/GS- /Gy /O2 /Oi /Ot") 77 | endif() 78 | 79 | # enkiTS is used by all the test apps, but not directly by the Box2D library 80 | if(BOX2D_UNIT_TESTS OR BOX2D_SAMPLES OR BOX2D_BENCHMARKS) 81 | SET(ENKITS_BUILD_EXAMPLES OFF CACHE BOOL "Build enkiTS examples") 82 | 83 | # Used in tests and samples 84 | FetchContent_Declare( 85 | enkits 86 | GIT_REPOSITORY https://github.com/dougbinks/enkiTS.git 87 | GIT_TAG master 88 | GIT_SHALLOW TRUE 89 | GIT_PROGRESS TRUE 90 | ) 91 | FetchContent_MakeAvailable(enkits) 92 | endif() 93 | 94 | # Tests need static linkage because they test internal Box2D functions 95 | if(NOT BUILD_SHARED_LIBS AND BOX2D_UNIT_TESTS) 96 | message(STATUS "Adding Box2D unit tests") 97 | add_subdirectory(test) 98 | else() 99 | message(STATUS "Skipping Box2D unit tests") 100 | endif() 101 | 102 | if(BOX2D_SAMPLES) 103 | add_subdirectory(samples) 104 | 105 | # default startup project for Visual Studio 106 | if(MSVC) 107 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT samples) 108 | set_property(TARGET samples PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") 109 | endif() 110 | endif() 111 | 112 | if(BOX2D_BENCHMARKS) 113 | add_subdirectory(benchmark) 114 | endif() 115 | 116 | if(BOX2D_DOCS) 117 | add_subdirectory(docs) 118 | endif() 119 | endif() 120 | 121 | # # Building on clang in windows 122 | # cmake -S .. -B . -G "Visual Studio 17 2022" -A x64 -T ClangCL 123 | # https://clang.llvm.org/docs/UsersManual.html#clang-cl 124 | -------------------------------------------------------------------------------- /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 | # THIS REPO IS NO LONGER USED!!! 2 | Version 3 is now live at the [main repo](https://github.com/erincatto/box2d) 3 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Box2D benchmark app 2 | 3 | add_executable(benchmark 4 | main.c 5 | joint_grid.c 6 | large_pyramid.c 7 | many_pyramids.c 8 | smash.c 9 | tumbler.c 10 | ) 11 | 12 | set_target_properties(benchmark PROPERTIES 13 | C_STANDARD 17 14 | C_STANDARD_REQUIRED YES 15 | C_EXTENSIONS NO 16 | ) 17 | 18 | if(MSVC) 19 | # target_compile_options(benchmark PRIVATE /experimental:c11atomics) 20 | endif() 21 | 22 | target_link_libraries(benchmark PRIVATE box2d enkiTS simde) 23 | -------------------------------------------------------------------------------- /benchmark/amd7950x/joint_grid.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,331.343 3 | 2,638.04 4 | 3,932.731 5 | 4,1200.15 6 | 5,1480.23 7 | 6,1718.79 8 | 7,1930.12 9 | 8,2133.65 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x/large_pyramid.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,325.705 3 | 2,616.127 4 | 3,886.575 5 | 4,1118.85 6 | 5,1331.22 7 | 6,1498.6 8 | 7,1685.28 9 | 8,1728.1 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x/many_pyramids.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,82.8619 3 | 2,160.906 4 | 3,236.027 5 | 4,300.688 6 | 5,368.315 7 | 6,429.822 8 | 7,498.81 9 | 8,549.271 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x/smash.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,173.898 3 | 2,277.19 4 | 3,357.566 5 | 4,430.528 6 | 5,483.446 7 | 6,525.652 8 | 7,566.859 9 | 8,598.553 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x/tumbler.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,373.066 3 | 2,581.852 4 | 3,764.444 5 | 4,902.898 6 | 5,1044.99 7 | 6,1143.44 8 | 7,1229.87 9 | 8,1299.61 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x_sse2/joint_grid.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,357.551 3 | 2,691.193 4 | 3,1010.45 5 | 4,1317.42 6 | 5,1590.65 7 | 6,1858.78 8 | 7,2074.2 9 | 8,2261.67 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x_sse2/large_pyramid.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,186.185 3 | 2,351.045 4 | 3,511.316 5 | 4,636.035 6 | 5,765.404 7 | 6,875.296 8 | 7,991.353 9 | 8,961.402 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x_sse2/many_pyramids.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,48.5561 3 | 2,92.6231 4 | 3,137.175 5 | 4,176.644 6 | 5,214.941 7 | 6,253.39 8 | 7,288.631 9 | 8,312.527 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x_sse2/smash.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,142.532 3 | 2,228.987 4 | 3,299.951 5 | 4,364.679 6 | 5,413.564 7 | 6,453.351 8 | 7,489.239 9 | 8,519.379 10 | -------------------------------------------------------------------------------- /benchmark/amd7950x_sse2/tumbler.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,276.905 3 | 2,453.522 4 | 3,592.946 5 | 4,702.383 6 | 5,826.52 7 | 6,919.179 8 | 7,1009.05 9 | 8,1062.61 10 | -------------------------------------------------------------------------------- /benchmark/joint_grid.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "box2d/box2d.h" 5 | #include "box2d/math_functions.h" 6 | 7 | #include 8 | 9 | b2WorldId JointGrid( b2WorldDef* worldDef ) 10 | { 11 | // Turning gravity off to isolate joint performance. 12 | worldDef->gravity = b2Vec2_zero; 13 | 14 | b2WorldId worldId = b2CreateWorld( worldDef ); 15 | 16 | #ifdef NDEBUG 17 | int N = 100; 18 | #else 19 | int N = 10; 20 | #endif 21 | 22 | // Allocate to avoid huge stack usage 23 | b2BodyId* bodies = malloc( N * N * sizeof( b2BodyId ) ); 24 | int index = 0; 25 | 26 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 27 | shapeDef.density = 1.0f; 28 | shapeDef.filter.categoryBits = 2; 29 | shapeDef.filter.maskBits = ~2u; 30 | 31 | b2Circle circle = { { 0.0f, 0.0f }, 0.4f }; 32 | 33 | b2RevoluteJointDef jd = b2DefaultRevoluteJointDef(); 34 | b2BodyDef bodyDef = b2DefaultBodyDef(); 35 | bodyDef.enableSleep = false; 36 | 37 | for ( int k = 0; k < N; ++k ) 38 | { 39 | for ( int i = 0; i < N; ++i ) 40 | { 41 | float fk = (float)k; 42 | float fi = (float)i; 43 | 44 | if ( k >= N / 2 - 3 && k <= N / 2 + 3 && i == 0 ) 45 | { 46 | bodyDef.type = b2_staticBody; 47 | } 48 | else 49 | { 50 | bodyDef.type = b2_dynamicBody; 51 | } 52 | 53 | bodyDef.position = ( b2Vec2 ){ fk, -fi }; 54 | 55 | b2BodyId body = b2CreateBody( worldId, &bodyDef ); 56 | 57 | b2CreateCircleShape( body, &shapeDef, &circle ); 58 | 59 | if ( i > 0 ) 60 | { 61 | jd.bodyIdA = bodies[index - 1]; 62 | jd.bodyIdB = body; 63 | jd.localAnchorA = ( b2Vec2 ){ 0.0f, -0.5f }; 64 | jd.localAnchorB = ( b2Vec2 ){ 0.0f, 0.5f }; 65 | b2CreateRevoluteJoint( worldId, &jd ); 66 | } 67 | 68 | if ( k > 0 ) 69 | { 70 | jd.bodyIdA = bodies[index - N]; 71 | jd.bodyIdB = body; 72 | jd.localAnchorA = ( b2Vec2 ){ 0.5f, 0.0f }; 73 | jd.localAnchorB = ( b2Vec2 ){ -0.5f, 0.0f }; 74 | b2CreateRevoluteJoint( worldId, &jd ); 75 | } 76 | 77 | bodies[index++] = body; 78 | } 79 | } 80 | 81 | free( bodies ); 82 | 83 | return worldId; 84 | } 85 | -------------------------------------------------------------------------------- /benchmark/large_pyramid.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "box2d/box2d.h" 5 | #include "box2d/math_functions.h" 6 | 7 | b2WorldId LargePyramid( b2WorldDef* worldDef ) 8 | { 9 | #ifdef NDEBUG 10 | int baseCount = 100; 11 | #else 12 | int baseCount = 20; 13 | #endif 14 | 15 | b2WorldId worldId = b2CreateWorld( worldDef ); 16 | 17 | { 18 | b2BodyDef bodyDef = b2DefaultBodyDef(); 19 | bodyDef.position = ( b2Vec2 ){ 0.0f, -1.0f }; 20 | b2BodyId groundId = b2CreateBody( worldId, &bodyDef ); 21 | 22 | b2Polygon box = b2MakeBox( 100.0f, 1.0f ); 23 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 24 | b2CreatePolygonShape( groundId, &shapeDef, &box ); 25 | } 26 | 27 | b2BodyDef bodyDef = b2DefaultBodyDef(); 28 | bodyDef.type = b2_dynamicBody; 29 | bodyDef.enableSleep = false; 30 | 31 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 32 | shapeDef.density = 1.0f; 33 | 34 | float h = 0.5f; 35 | b2Polygon box = b2MakeSquare( h ); 36 | 37 | float shift = 1.0f * h; 38 | 39 | for ( int i = 0; i < baseCount; ++i ) 40 | { 41 | float y = ( 2.0f * i + 1.0f ) * shift; 42 | 43 | for ( int j = i; j < baseCount; ++j ) 44 | { 45 | float x = ( i + 1.0f ) * shift + 2.0f * ( j - i ) * shift - h * baseCount; 46 | 47 | bodyDef.position = ( b2Vec2 ){ x, y }; 48 | 49 | b2BodyId bodyId = b2CreateBody( worldId, &bodyDef ); 50 | b2CreatePolygonShape( bodyId, &shapeDef, &box ); 51 | } 52 | } 53 | 54 | return worldId; 55 | } 56 | -------------------------------------------------------------------------------- /benchmark/m2air/joint_grid.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,510.67 3 | 2,955.752 4 | 3,1384.14 5 | 4,1651.69 6 | -------------------------------------------------------------------------------- /benchmark/m2air/large_pyramid.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,284.342 3 | 2,526.955 4 | 3,728.772 5 | 4,911.715 6 | -------------------------------------------------------------------------------- /benchmark/m2air/many_pyramids.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,73.9053 3 | 2,139.551 4 | 3,193.414 5 | 4,234.215 6 | -------------------------------------------------------------------------------- /benchmark/m2air/smash.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,161.17 3 | 2,256.422 4 | 3,321.137 5 | 4,385.797 6 | -------------------------------------------------------------------------------- /benchmark/m2air/tumbler.csv: -------------------------------------------------------------------------------- 1 | threads,fps 2 | 1,340.404 3 | 2,538.587 4 | 3,661.721 5 | 4,781.784 6 | -------------------------------------------------------------------------------- /benchmark/many_pyramids.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "box2d/box2d.h" 5 | #include "box2d/math_functions.h" 6 | 7 | static void CreatePyramid( b2WorldId worldId, int baseCount, float extent, float centerX, float baseY ) 8 | { 9 | b2BodyDef bodyDef = b2DefaultBodyDef(); 10 | bodyDef.type = b2_dynamicBody; 11 | // bodyDef.enableSleep = false; 12 | 13 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 14 | 15 | b2Polygon box = b2MakeSquare( extent ); 16 | 17 | for ( int i = 0; i < baseCount; ++i ) 18 | { 19 | float y = ( 2.0f * i + 1.0f ) * extent + baseY; 20 | 21 | for ( int j = i; j < baseCount; ++j ) 22 | { 23 | float x = ( i + 1.0f ) * extent + 2.0f * ( j - i ) * extent + centerX - 0.5f; 24 | bodyDef.position = ( b2Vec2 ){ x, y }; 25 | 26 | b2BodyId bodyId = b2CreateBody( worldId, &bodyDef ); 27 | b2CreatePolygonShape( bodyId, &shapeDef, &box ); 28 | } 29 | } 30 | } 31 | 32 | b2WorldId ManyPyramids( b2WorldDef* worldDef ) 33 | { 34 | int baseCount = 10; 35 | float extent = 0.5f; 36 | #ifdef NDEBUG 37 | int rowCount = 20; 38 | int columnCount = 20; 39 | #else 40 | int rowCount = 5; 41 | int columnCount = 5; 42 | #endif 43 | 44 | worldDef->enableSleep = false; 45 | 46 | b2WorldId worldId = b2CreateWorld( worldDef ); 47 | 48 | b2BodyDef bodyDef = b2DefaultBodyDef(); 49 | b2BodyId groundId = b2CreateBody( worldId, &bodyDef ); 50 | 51 | float groundDeltaY = 2.0f * extent * ( baseCount + 1.0f ); 52 | float groundWidth = 2.0f * extent * columnCount * ( baseCount + 1.0f ); 53 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 54 | 55 | float groundY = 0.0f; 56 | 57 | for ( int i = 0; i < rowCount; ++i ) 58 | { 59 | b2Segment segment = { { -0.5f * 2.0f * groundWidth, groundY }, { 0.5f * 2.0f * groundWidth, groundY } }; 60 | b2CreateSegmentShape( groundId, &shapeDef, &segment ); 61 | groundY += groundDeltaY; 62 | } 63 | 64 | float baseWidth = 2.0f * extent * baseCount; 65 | float baseY = 0.0f; 66 | 67 | for ( int i = 0; i < rowCount; ++i ) 68 | { 69 | for ( int j = 0; j < columnCount; ++j ) 70 | { 71 | float centerX = -0.5f * groundWidth + j * ( baseWidth + 2.0f * extent ) + extent; 72 | CreatePyramid( worldId, baseCount, extent, centerX, baseY ); 73 | } 74 | 75 | baseY += groundDeltaY; 76 | } 77 | 78 | return worldId; 79 | } 80 | -------------------------------------------------------------------------------- /benchmark/smash.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "box2d/box2d.h" 5 | #include "box2d/math_functions.h" 6 | 7 | b2WorldId Smash( b2WorldDef* worldDef ) 8 | { 9 | b2WorldId worldId = b2CreateWorld( worldDef ); 10 | 11 | { 12 | b2Polygon box = b2MakeBox( 4.0f, 4.0f ); 13 | 14 | b2BodyDef bodyDef = b2DefaultBodyDef(); 15 | bodyDef.type = b2_dynamicBody; 16 | bodyDef.position = ( b2Vec2 ){ -20.0f, 0.0f }; 17 | bodyDef.linearVelocity = ( b2Vec2 ){ 40.0f, 0.0f }; 18 | b2BodyId bodyId = b2CreateBody( worldId, &bodyDef ); 19 | 20 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 21 | shapeDef.density = 8.0f; 22 | b2CreatePolygonShape( bodyId, &shapeDef, &box ); 23 | } 24 | 25 | float d = 0.4f; 26 | b2Polygon box = b2MakeSquare( 0.5f * d ); 27 | 28 | b2BodyDef bodyDef = b2DefaultBodyDef(); 29 | bodyDef.type = b2_dynamicBody; 30 | bodyDef.isAwake = false; 31 | 32 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 33 | 34 | #ifdef NDEBUG 35 | int columns = 120; 36 | int rows = 80; 37 | #else 38 | int columns = 20; 39 | int rows = 10; 40 | #endif 41 | 42 | for ( int i = 0; i < columns; ++i ) 43 | { 44 | for ( int j = 0; j < rows; ++j ) 45 | { 46 | bodyDef.position.x = i * d + 30.0f; 47 | bodyDef.position.y = ( j - rows / 2.0f ) * d; 48 | b2BodyId bodyId = b2CreateBody( worldId, &bodyDef ); 49 | b2CreatePolygonShape( bodyId, &shapeDef, &box ); 50 | } 51 | } 52 | 53 | return worldId; 54 | } 55 | -------------------------------------------------------------------------------- /benchmark/tumbler.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "box2d/box2d.h" 5 | #include "box2d/math_functions.h" 6 | 7 | b2WorldId Tumbler( b2WorldDef* worldDef ) 8 | { 9 | b2WorldId worldId = b2CreateWorld( worldDef ); 10 | 11 | b2BodyId groundId; 12 | { 13 | b2BodyDef bodyDef = b2DefaultBodyDef(); 14 | groundId = b2CreateBody( worldId, &bodyDef ); 15 | } 16 | 17 | { 18 | b2BodyDef bodyDef = b2DefaultBodyDef(); 19 | bodyDef.type = b2_dynamicBody; 20 | bodyDef.position = ( b2Vec2 ){ 0.0f, 10.0f }; 21 | b2BodyId bodyId = b2CreateBody( worldId, &bodyDef ); 22 | 23 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 24 | shapeDef.density = 50.0f; 25 | 26 | b2Polygon polygon; 27 | polygon = b2MakeOffsetBox( 0.5f, 10.0f, ( b2Vec2 ){ 10.0f, 0.0f }, 0.0 ); 28 | b2CreatePolygonShape( bodyId, &shapeDef, &polygon ); 29 | polygon = b2MakeOffsetBox( 0.5f, 10.0f, ( b2Vec2 ){ -10.0f, 0.0f }, 0.0 ); 30 | b2CreatePolygonShape( bodyId, &shapeDef, &polygon ); 31 | polygon = b2MakeOffsetBox( 10.0f, 0.5f, ( b2Vec2 ){ 0.0f, 10.0f }, 0.0 ); 32 | b2CreatePolygonShape( bodyId, &shapeDef, &polygon ); 33 | polygon = b2MakeOffsetBox( 10.0f, 0.5f, ( b2Vec2 ){ 0.0f, -10.0f }, 0.0 ); 34 | b2CreatePolygonShape( bodyId, &shapeDef, &polygon ); 35 | 36 | float motorSpeed = 25.0f; 37 | 38 | b2RevoluteJointDef jd = b2DefaultRevoluteJointDef(); 39 | jd.bodyIdA = groundId; 40 | jd.bodyIdB = bodyId; 41 | jd.localAnchorA = ( b2Vec2 ){ 0.0f, 10.0f }; 42 | jd.localAnchorB = ( b2Vec2 ){ 0.0f, 0.0f }; 43 | jd.referenceAngle = 0.0f; 44 | jd.motorSpeed = ( b2_pi / 180.0f ) * motorSpeed; 45 | jd.maxMotorTorque = 1e8f; 46 | jd.enableMotor = true; 47 | 48 | b2CreateRevoluteJoint( worldId, &jd ); 49 | } 50 | 51 | #ifdef NDEBUG 52 | int gridCount = 45; 53 | #else 54 | int gridCount = 20; 55 | #endif 56 | 57 | b2Polygon polygon = b2MakeBox( 0.125f, 0.125f ); 58 | b2BodyDef bodyDef = b2DefaultBodyDef(); 59 | bodyDef.type = b2_dynamicBody; 60 | b2ShapeDef shapeDef = b2DefaultShapeDef(); 61 | 62 | float y = -0.2f * gridCount + 10.0f; 63 | for ( int i = 0; i < gridCount; ++i ) 64 | { 65 | float x = -0.2f * gridCount; 66 | 67 | for ( int j = 0; j < gridCount; ++j ) 68 | { 69 | bodyDef.position = ( b2Vec2 ){ x, y }; 70 | b2BodyId bodyId = b2CreateBody( worldId, &bodyDef ); 71 | 72 | b2CreatePolygonShape( bodyId, &shapeDef, &polygon ); 73 | 74 | x += 0.4f; 75 | } 76 | 77 | y += 0.4f; 78 | } 79 | 80 | return worldId; 81 | } 82 | -------------------------------------------------------------------------------- /deploy_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copies documentation to blog 4 | cp -R build/docs/html/. ../box2d_blog/public/documentation_v3/ 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 | "reading.md" 49 | "faq.md" 50 | "migration.md" 51 | ALL 52 | COMMENT "Generate HTML documentation") 53 | -------------------------------------------------------------------------------- /docs/foundation.md: -------------------------------------------------------------------------------- 1 | # Foundations 2 | Box2D provides minimal base functionality for allocation hooks and vector math. The C interface 3 | allows most runtime data and types to be defined internally in the `src` folder. 4 | 5 | ## Assertions 6 | Box2D will assert on bad input. This includes things like sending in NaN or infinity for values. It will assert if 7 | you use negative values for things that should only be positive, such as density. 8 | 9 | Box2D will also assert if an internal bug is detected. For this reason, it is advisable to build Box2D from source. 10 | The Box2D library compiles in about a second on my computer. 11 | 12 | You may wish to capture assertions in your application. In this case you can use `b2SetAssertFcn()`. This allows you 13 | to override the debugger break and/or perform your own error handling. 14 | 15 | ## Allocation 16 | Box2D uses memory efficiently and minimizes per frame allocations by pooling memory. The engine quickly adapts to the 17 | simulation size. After the first step or two of simulation, there should be no further per frame allocations. 18 | 19 | As bodies, shapes, and joints are created and destroyed, their memory will be recycled. Internally all this data is stored in contiguous arrays. When an object is destroyed, the array element will be marked as empty. And when an object is created it will use empty slots in the array using an efficient free list. 20 | 21 | Once the internal memory pools are initially filled, the only allocations should be for sleeping islands since their data is copied out of the main simulation. Generally, these allocations should be infrequent. 22 | 23 | You can provide a custom allocator using `b2SetAllocator()` and you can get the number of bytes allocated using `b2GetByteCount()`. 24 | 25 | ## Version 26 | The b2Version structure holds the current version so you can query this 27 | at run-time using `b2GetVersion()`. 28 | 29 | ```c 30 | b2Version version = b2GetVersion(); 31 | printf("Box2D version %d.%d.%d\n", version.major, version.minor, version.patch); 32 | ``` 33 | 34 | ## Vector Math 35 | Box2D includes a small vector math library including types `b2Vec2`, `b2Rot`, `b2Transform`, and `b2AABB`. This has been 36 | designed to suit the internal needs of Box2D and the interface. All the 37 | members are exposed, so you may use them freely in your application. 38 | 39 | The math library is kept simple to make Box2D easy to port and maintain. 40 | 41 | ## Multithreading {#multi} 42 | Box2D has been highly optimized for multithreading. Multithreading is not required and by default Box2D will run single-threaded. If performance is important for your application, you should consider using the multithreading interface. 43 | 44 | Box2D multithreading has been designed to work with your application's task system. Box2D does 45 | not create threads. The Samples application shows how to do this using the open source tasks system [enkiTS](https://github.com/dougbinks/enkiTS). 46 | 47 | Multithreading is established for each Box2D world you create and must be hooked up to 48 | the world definition. See `b2TaskCallback()`, `b2EnqueueTaskCallback()`, and `b2FinishTaskCallback()` for more details. Also see `b2WorldDef::workerCount`, `b2WorldDef::enqueueTask`, and `b2WorldDef::finishTask`. 49 | 50 | The multithreading design for Box2D is focused on [data parallelism](https://en.wikipedia.org/wiki/Data_parallelism). The idea is to use multiple cores to complete the world simulation as fast as possible. Box2D multithreading is not designed for [task parallelism](https://en.wikipedia.org/wiki/Task_parallelism). Often in games you may have a render thread and an audio thread that do work in isolation from the main thread. Those are examples of task parallelism. 51 | 52 | So when you design your game loop, you should let Box2D *go wide* and use multiple cores to finish its work quickly, without other threads trying to interact with the Box2D world. 53 | 54 | > **Caution**: 55 | > While Box2D is designed for multithreading, its interface is *not* thread-safe. Modifying 56 | > the Box2D world during simulation or from multiple threads will result in a [race condition](https://en.wikipedia.org/wiki/Race_condition). 57 | 58 | It *is safe* to do ray-casts, shape-casts, and overlap tests from multiple threads outside of `b2World_Step()`. Generally, any read-only operation is safe to do multithreaded outside of `b2World_Step()`. This can be very useful if you have multithreaded game logic. 59 | -------------------------------------------------------------------------------- /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/samples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erincatto/box2c/694b7f3c875cde2eb2a6db1a5dcc3b26f0916728/docs/images/samples.png -------------------------------------------------------------------------------- /docs/loose_ends.md: -------------------------------------------------------------------------------- 1 | # Loose Ends 2 | 3 | ## User Data 4 | Bodies, shapes, and joints allow you to attach user data 5 | as a `void*`. This is handy when you are examining Box2D data 6 | structures and you want to determine how they relate to the objects in 7 | your game engine. 8 | 9 | For example, it is typical to attach an entity pointer to the rigid body 10 | on that entity. This sets up a circular reference. If you have the entity, 11 | you can get the body. If you have the body, you can get the entity. 12 | 13 | ```c 14 | GameEntity* entity = GameCreateEntity(); 15 | b2BodyDef bodyDef = b2DefaultBodyDef(); 16 | bodyDef.userData = entity; 17 | entity->bodyId = b2CreateBody(myWorldId, &bodyDef); 18 | ``` 19 | 20 | Here are some examples of cases where you would need the user data: 21 | - Applying damage to an entity using a collision result. 22 | - Playing a scripted event if the player is inside an axis-aligned box. 23 | - Accessing a game structure when Box2D notifies you that a joint is 24 | going to be destroyed. 25 | 26 | Keep in mind that user data is optional and you can put anything in it. 27 | However, you should be consistent. For example, if you want to store an 28 | entity pointer on one body, you should keep an entity pointer on all 29 | bodies. Don't store a `GameEntity` pointer on one body, and a `ParticleSystem` 30 | pointer on another body. Casting a `GameEntity` to a `ParticleSystem` pointer 31 | may lead to a crash. 32 | 33 | ## Pixels and Coordinate Systems 34 | I recommend using MKS (meters, kilograms, and seconds) units and 35 | radians for angles. You may have trouble working with meters because 36 | your game is expressed in terms of pixels. To deal with this in the 37 | sample I have the whole *game* world in meters and just use an OpenGL 38 | viewport transformation to scale the world into screen space. 39 | 40 | You use code like this to scale your graphics. 41 | 42 | ```c 43 | float lowerX = -25.0f, upperX = 25.0f, lowerY = -5.0f, upperY = 25.0f; 44 | gluOrtho2D(lowerX, upperX, lowerY, upperY); 45 | ``` 46 | 47 | If your game must work in pixel units then you could convert your 48 | length units from pixels to meters when passing values from Box2D. 49 | Likewise you should convert the values received from Box2D from meters 50 | to pixels. This will improve the stability of the physics simulation. 51 | 52 | You have to come up with a reasonable conversion factor. I suggest 53 | making this choice based on the size of your characters. Suppose you 54 | have determined to use 50 pixels per meter (because your character is 75 55 | pixels tall). Then you can convert from pixels to meters using these 56 | formulas: 57 | 58 | ```cpp 59 | xMeters = 0.02f * xPixels; 60 | yMeters = 0.02f * yPixels; 61 | ``` 62 | 63 | In reverse: 64 | 65 | ```cpp 66 | xPixels = 50.0f * xMeters; 67 | yPixels = 50.0f * yMeters; 68 | ``` 69 | 70 | You should consider using MKS units in your game code and just convert 71 | to pixels when you render. This will simplify your game logic and reduce 72 | the chance for errors since the rendering conversion can be isolated to 73 | a small amount of code. 74 | 75 | If you use a conversion factor, you should try tweaking it globally to 76 | make sure nothing breaks. You can also try adjusting it to improve 77 | stability. 78 | 79 | If this conversion is not possible, you can set the length units used 80 | by Box2D using `b2SetLengthUnitsPerMeter()`. This is experimental and not 81 | well tested. 82 | 83 | ## Debug Drawing 84 | You can implement the function pointers in `b2DebugDraw` struct to get detailed 85 | drawing of the Box2D world. Debug draw provides: 86 | - shapes 87 | - joints 88 | - broad-phase axis-aligned bounding boxes (AABBs) 89 | - center of mass 90 | - contact points 91 | 92 | This is the preferred method of drawing the Box2D simulation, rather 93 | than accessing the data directly. The reason is that much of the 94 | necessary data is internal and subject to change. 95 | 96 | The samples application draws the Box2D world using the `b2DebugDraw`. 97 | 98 | ## Limitations 99 | Box2D uses several approximations to simulate rigid body physics 100 | efficiently. This brings some limitations. 101 | 102 | Here are the current limitations: 103 | 1. Extreme mass ratios may cause joint stretching and collision overlap. 104 | 2. Box2D uses soft constraints to improve robustness. This can lead to joint and contact flexing. 105 | 3. Continuous collision does not handle all situations. For example, general dynamic versus dynamic continuous collision is not handled. [Bullets](#bullets) handle this in a limited way. This is done for performance reasons. 106 | 4. Continuous collision does not handle joints. So you may see joint stretching on fast moving objects. Usually the joints recover after a few time steps. 107 | 5. Box2D uses the [semi-implicit Euler method](https://en.wikipedia.org/wiki/Semi-implicit_Euler_method) to solve the [equations of motion](https://en.wikipedia.org/wiki/Equations_of_motion). It does not reproduce exactly the parabolic motion of projectiles and has only first-order accuracy. However it is fast and has good stability. 108 | 6. Box2D uses a the [Gauss-Seidel method](https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method) to solve constraints and achieve real-time performance. You will not get precisely rigid collisions or pixel perfect accuracy. Increasing the sub-step count will improve accuracy. 109 | -------------------------------------------------------------------------------- /docs/reading.md: -------------------------------------------------------------------------------- 1 | # Further Reading 2 | - [Erin Catto's Publications](https://box2d.org/publications/) 3 | - Collision Detection in Interactive 3D Environments, Gino van den Bergen, 2004 4 | - Real-Time Collision Detection, Christer Ericson, 2005 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /extern/simde/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is a reduced version of https://github.com/simd-everywhere/simde 2 | 3 | add_library( 4 | simde INTERFACE 5 | check.h 6 | debug-trap.h 7 | hedley.h 8 | simde-aes.h 9 | simde-align.h 10 | simde-arch.h 11 | simde-common.h 12 | simde-constify.h 13 | simde-detect-clang.h 14 | simde-diagnostic.h 15 | simde-f16.h 16 | simde-features.h 17 | simde-math.h 18 | x86/aes.h 19 | x86/avx.h 20 | x86/avx2.h 21 | x86/f16c.h 22 | x86/fma.h 23 | x86/mmx.h 24 | x86/sse.h 25 | x86/sse2.h 26 | x86/sse3.h 27 | x86/sse4.1.h 28 | x86/sse4.2.h 29 | x86/ssse3.h 30 | ) 31 | 32 | target_include_directories(simde INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 33 | -------------------------------------------------------------------------------- /extern/simde/debug-trap.h: -------------------------------------------------------------------------------- 1 | /* Debugging assertions and traps 2 | * Portable Snippets - https://github.com/nemequ/portable-snippets 3 | * Created by Evan Nemerson 4 | * 5 | * To the extent possible under law, the authors have waived all 6 | * copyright and related or neighboring rights to this code. For 7 | * details, see the Creative Commons Zero 1.0 Universal license at 8 | * https://creativecommons.org/publicdomain/zero/1.0/ 9 | * 10 | * SPDX-License-Identifier: CC0-1.0 11 | */ 12 | 13 | #if !defined(SIMDE_DEBUG_TRAP_H) 14 | #define SIMDE_DEBUG_TRAP_H 15 | 16 | #if !defined(SIMDE_NDEBUG) && defined(NDEBUG) && !defined(SIMDE_DEBUG) 17 | # define SIMDE_NDEBUG 1 18 | #endif 19 | 20 | #if defined(__has_builtin) && !defined(__ibmxl__) 21 | # if __has_builtin(__builtin_debugtrap) 22 | # define simde_trap() __builtin_debugtrap() 23 | # elif __has_builtin(__debugbreak) 24 | # define simde_trap() __debugbreak() 25 | # endif 26 | #endif 27 | #if !defined(simde_trap) 28 | # if defined(_MSC_VER) || defined(__INTEL_COMPILER) 29 | # define simde_trap() __debugbreak() 30 | # elif defined(__ARMCC_VERSION) 31 | # define simde_trap() __breakpoint(42) 32 | # elif defined(__ibmxl__) || defined(__xlC__) 33 | # include 34 | # define simde_trap() __trap(42) 35 | # elif defined(__DMC__) && defined(_M_IX86) 36 | static inline void simde_trap(void) { __asm int 3h; } 37 | # elif defined(__i386__) || defined(__x86_64__) 38 | static inline void simde_trap(void) { __asm__ __volatile__("int $03"); } 39 | # elif defined(__thumb__) 40 | static inline void simde_trap(void) { __asm__ __volatile__(".inst 0xde01"); } 41 | # elif defined(__aarch64__) 42 | static inline void simde_trap(void) { __asm__ __volatile__(".inst 0xd4200000"); } 43 | # elif defined(__arm__) 44 | static inline void simde_trap(void) { __asm__ __volatile__(".inst 0xe7f001f0"); } 45 | # elif defined (__alpha__) && !defined(__osf__) 46 | static inline void simde_trap(void) { __asm__ __volatile__("bpt"); } 47 | # elif defined(_54_) 48 | static inline void simde_trap(void) { __asm__ __volatile__("ESTOP"); } 49 | # elif defined(_55_) 50 | static inline void simde_trap(void) { __asm__ __volatile__(";\n .if (.MNEMONIC)\n ESTOP_1\n .else\n ESTOP_1()\n .endif\n NOP"); } 51 | # elif defined(_64P_) 52 | static inline void simde_trap(void) { __asm__ __volatile__("SWBP 0"); } 53 | # elif defined(_6x_) 54 | static inline void simde_trap(void) { __asm__ __volatile__("NOP\n .word 0x10000000"); } 55 | # elif defined(__STDC_HOSTED__) && (__STDC_HOSTED__ == 0) && defined(__GNUC__) 56 | # define simde_trap() __builtin_trap() 57 | # else 58 | # include 59 | # if defined(SIGTRAP) 60 | # define simde_trap() raise(SIGTRAP) 61 | # else 62 | # define simde_trap() raise(SIGABRT) 63 | # endif 64 | # endif 65 | #endif 66 | 67 | #if defined(HEDLEY_LIKELY) 68 | # define SIMDE_DBG_LIKELY(expr) HEDLEY_LIKELY(expr) 69 | #elif defined(__GNUC__) && (__GNUC__ >= 3) 70 | # define SIMDE_DBG_LIKELY(expr) __builtin_expect(!!(expr), 1) 71 | #else 72 | # define SIMDE_DBG_LIKELY(expr) (!!(expr)) 73 | #endif 74 | 75 | #if !defined(SIMDE_NDEBUG) || (SIMDE_NDEBUG == 0) 76 | # define simde_dbg_assert(expr) do { \ 77 | if (!SIMDE_DBG_LIKELY(expr)) { \ 78 | simde_trap(); \ 79 | } \ 80 | } while (0) 81 | #else 82 | # define simde_dbg_assert(expr) 83 | #endif 84 | 85 | #endif /* !defined(SIMDE_DEBUG_TRAP_H) */ 86 | -------------------------------------------------------------------------------- /include/box2d/base.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | // Shared library macros 9 | #if defined( _MSC_VER ) && defined( box2d_EXPORTS ) 10 | // build the Windows DLL 11 | #define BOX2D_EXPORT __declspec( dllexport ) 12 | #elif defined( _MSC_VER ) && defined( BOX2D_DLL ) 13 | // using the Windows DLL 14 | #define BOX2D_EXPORT __declspec( dllimport ) 15 | #elif defined( box2d_EXPORTS ) 16 | // building or using the Box2D shared library 17 | #define BOX2D_EXPORT __attribute__( ( visibility( "default" ) ) ) 18 | #else 19 | // static library 20 | #define BOX2D_EXPORT 21 | #endif 22 | 23 | // C++ macros 24 | // clang-format off 25 | #ifdef __cplusplus 26 | #define B2_API extern "C" BOX2D_EXPORT 27 | #define B2_INLINE inline 28 | #define B2_LITERAL(T) T 29 | #define B2_ZERO_INIT {} 30 | #else 31 | #define B2_API BOX2D_EXPORT 32 | #define B2_INLINE static inline 33 | /// Used for C literals like (b2Vec2){1.0f, 2.0f} where C++ requires b2Vec2{1.0f, 2.0f} 34 | #define B2_LITERAL(T) (T) 35 | #define B2_ZERO_INIT {0} 36 | #endif 37 | // clang-format on 38 | 39 | /** 40 | * @defgroup base Base 41 | * Base functionality 42 | * @{ 43 | */ 44 | 45 | /// Prototype for user allocation function 46 | /// @param size the allocation size in bytes 47 | /// @param alignment the required alignment, guaranteed to be a power of 2 48 | typedef void* b2AllocFcn( unsigned int size, int alignment ); 49 | 50 | /// Prototype for user free function 51 | /// @param mem the memory previously allocated through `b2AllocFcn` 52 | typedef void b2FreeFcn( void* mem ); 53 | 54 | /// Prototype for the user assert callback. Return 0 to skip the debugger break. 55 | typedef int b2AssertFcn( const char* condition, const char* fileName, int lineNumber ); 56 | 57 | /// This allows the user to override the allocation functions. These should be 58 | /// set during application startup. 59 | B2_API void b2SetAllocator( b2AllocFcn* allocFcn, b2FreeFcn* freeFcn ); 60 | 61 | /// @return the total bytes allocated by Box2D 62 | B2_API int b2GetByteCount( void ); 63 | 64 | /// Override the default assert callback 65 | /// @param assertFcn a non-null assert callback 66 | B2_API void b2SetAssertFcn( b2AssertFcn* assertFcn ); 67 | 68 | /// Version numbering scheme. 69 | /// See https://semver.org/ 70 | typedef struct b2Version 71 | { 72 | /// Significant changes 73 | int major; 74 | 75 | /// Incremental changes 76 | int minor; 77 | 78 | /// Bug fixes 79 | int revision; 80 | } b2Version; 81 | 82 | /// Get the current version of Box2D 83 | B2_API b2Version b2GetVersion( void ); 84 | 85 | /**@}*/ 86 | 87 | //! @cond 88 | // Timer for profiling. This has platform specific code and may not work on every platform. 89 | typedef struct b2Timer 90 | { 91 | #if defined( _WIN32 ) 92 | int64_t start; 93 | #elif defined( __linux__ ) || defined( __APPLE__ ) 94 | unsigned long long start_sec; 95 | unsigned long long start_usec; 96 | #else 97 | int32_t dummy; 98 | #endif 99 | } b2Timer; 100 | 101 | B2_API b2Timer b2CreateTimer( void ); 102 | B2_API int64_t b2GetTicks( b2Timer* timer ); 103 | B2_API float b2GetMilliseconds( const b2Timer* timer ); 104 | B2_API float b2GetMillisecondsAndReset( b2Timer* timer ); 105 | B2_API void b2SleepMilliseconds( int milliseconds ); 106 | B2_API void b2Yield( void ); 107 | //! @endcond 108 | -------------------------------------------------------------------------------- /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 revision; 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 revision; 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 revision; 57 | } b2ShapeId; 58 | 59 | /// Joint id references a joint instance. This should be treated as an opaque handle. 60 | typedef struct b2JointId 61 | { 62 | int32_t index1; 63 | uint16_t world0; 64 | uint16_t revision; 65 | } b2JointId; 66 | 67 | /// Chain id references a chain instances. This should be treated as an opaque handle. 68 | typedef struct b2ChainId 69 | { 70 | int32_t index1; 71 | uint16_t world0; 72 | uint16_t revision; 73 | } b2ChainId; 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 b2JointId b2_nullJointId = B2_ZERO_INIT; 81 | static const b2ChainId b2_nullChainId = 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.revision == id2.revision ) 91 | 92 | /**@}*/ 93 | -------------------------------------------------------------------------------- /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 master 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 | FetchContent_Populate(imgui 32 | URL https://github.com/ocornut/imgui/archive/docking.zip 33 | SOURCE_DIR ${CMAKE_SOURCE_DIR}/build/imgui 34 | ) 35 | 36 | set(IMGUI_DIR ${CMAKE_SOURCE_DIR}/build/imgui) 37 | 38 | add_library(imgui STATIC 39 | ${IMGUI_DIR}/imgui.cpp 40 | ${IMGUI_DIR}/imgui_draw.cpp 41 | ${IMGUI_DIR}/imgui_demo.cpp 42 | ${IMGUI_DIR}/imgui_tables.cpp 43 | ${IMGUI_DIR}/imgui_widgets.cpp 44 | 45 | ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp 46 | ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp 47 | ) 48 | target_link_libraries(imgui PUBLIC glfw glad) 49 | target_include_directories(imgui PUBLIC ${IMGUI_DIR} ${IMGUI_DIR}/backends) 50 | target_compile_definitions(imgui PUBLIC IMGUI_DISABLE_OBSOLETE_FUNCTIONS) 51 | 52 | # jsmn for json 53 | set(JSMN_DIR ${CMAKE_SOURCE_DIR}/extern/jsmn) 54 | 55 | add_library(jsmn INTERFACE ${JSMN_DIR}/jsmn.h) 56 | target_include_directories(jsmn INTERFACE ${JSMN_DIR}) 57 | 58 | add_executable(samples 59 | car.cpp 60 | car.h 61 | donut.cpp 62 | donut.h 63 | doohickey.cpp 64 | doohickey.h 65 | draw.cpp 66 | draw.h 67 | human.cpp 68 | human.h 69 | main.cpp 70 | sample.cpp 71 | sample.h 72 | sample_benchmark.cpp 73 | sample_bodies.cpp 74 | sample_collision.cpp 75 | sample_continuous.cpp 76 | sample_events.cpp 77 | sample_geometry.cpp 78 | sample_joints.cpp 79 | sample_robustness.cpp 80 | sample_shapes.cpp 81 | sample_stacking.cpp 82 | sample_world.cpp 83 | settings.cpp 84 | settings.h 85 | shader.cpp 86 | shader.h 87 | ) 88 | 89 | set_target_properties(samples PROPERTIES 90 | CXX_STANDARD 17 91 | CXX_STANDARD_REQUIRED YES 92 | CXX_EXTENSIONS NO 93 | ) 94 | 95 | target_include_directories(samples PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${JSMN_DIR}) 96 | target_link_libraries(samples PUBLIC box2d imgui glfw glad enkiTS) 97 | 98 | # target_compile_definitions(samples PRIVATE "$<$:SAMPLES_DEBUG>") 99 | # message(STATUS "runtime = ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") 100 | # message(STATUS "binary = ${CMAKE_CURRENT_BINARY_DIR}") 101 | 102 | # Copy font files, etc 103 | add_custom_command( 104 | TARGET samples POST_BUILD 105 | COMMAND ${CMAKE_COMMAND} -E copy_directory 106 | ${CMAKE_CURRENT_SOURCE_DIR}/data/ 107 | ${CMAKE_CURRENT_BINARY_DIR}/data/) 108 | 109 | # source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${BOX2D_SAMPLES}) 110 | -------------------------------------------------------------------------------- /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.04; 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/box2c/694b7f3c875cde2eb2a6db1a5dcc3b26f0916728/samples/data/droid_sans.ttf -------------------------------------------------------------------------------- /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 < e_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::Spawn( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, void* userData ) 23 | { 24 | assert( m_isSpawned == false ); 25 | 26 | for ( int i = 0; i < e_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 / e_sides; 34 | float length = 2.0f * b2_pi * radius / e_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.density = 1.0f; 46 | shapeDef.filter.groupIndex = -groupIndex; 47 | shapeDef.friction = 0.3f; 48 | 49 | // Create bodies 50 | float angle = 0.0f; 51 | for ( int i = 0; i < e_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[e_sides - 1]; 70 | for ( int i = 0; i < e_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::Despawn() 85 | { 86 | assert( m_isSpawned == true ); 87 | 88 | for ( int i = 0; i < e_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 | enum 11 | { 12 | e_sides = 7 13 | }; 14 | 15 | public: 16 | Donut(); 17 | 18 | void Spawn( b2WorldId worldId, b2Vec2 position, float scale, int groupIndex, void* userData ); 19 | void Despawn(); 20 | 21 | b2BodyId m_bodyIds[e_sides]; 22 | b2JointId m_jointIds[e_sides]; 23 | bool m_isSpawned; 24 | }; 25 | -------------------------------------------------------------------------------- /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 | // 9 | struct Camera 10 | { 11 | Camera(); 12 | 13 | void ResetView(); 14 | b2Vec2 ConvertScreenToWorld( b2Vec2 screenPoint ); 15 | b2Vec2 ConvertWorldToScreen( b2Vec2 worldPoint ); 16 | void BuildProjectionMatrix( float* m, float zBias ); 17 | b2AABB GetViewBounds(); 18 | 19 | b2Vec2 m_center; 20 | float m_zoom; 21 | int32_t m_width; 22 | int32_t m_height; 23 | }; 24 | 25 | // This class implements Box2D debug drawing callbacks 26 | class Draw 27 | { 28 | public: 29 | Draw(); 30 | ~Draw(); 31 | 32 | void Create(); 33 | void Destroy(); 34 | 35 | void DrawPolygon( const b2Vec2* vertices, int32_t vertexCount, b2HexColor color ); 36 | void DrawSolidPolygon( b2Transform transform, const b2Vec2* vertices, int32_t vertexCount, float radius, b2HexColor color ); 37 | 38 | void DrawCircle( b2Vec2 center, float radius, b2HexColor color ); 39 | void DrawSolidCircle( b2Transform transform, b2Vec2 center, float radius, b2HexColor color ); 40 | 41 | void DrawCapsule( b2Vec2 p1, b2Vec2 p2, float radius, b2HexColor color ); 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 | bool m_showUI; 60 | struct GLBackground* m_background; 61 | struct GLPoints* m_points; 62 | struct GLLines* m_lines; 63 | struct GLTriangles* m_triangles; 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 | 71 | extern Draw g_draw; 72 | extern Camera g_camera; 73 | extern struct GLFWwindow* g_mainWindow; 74 | -------------------------------------------------------------------------------- /samples/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 | struct Bone 9 | { 10 | enum 11 | { 12 | e_hip = 0, 13 | e_torso = 1, 14 | e_head = 2, 15 | e_upperLeftLeg = 3, 16 | e_lowerLeftLeg = 4, 17 | e_upperRightLeg = 5, 18 | e_lowerRightLeg = 6, 19 | e_upperLeftArm = 7, 20 | e_lowerLeftArm = 8, 21 | e_upperRightArm = 9, 22 | e_lowerRightArm = 10, 23 | e_count = 11, 24 | }; 25 | 26 | b2BodyId bodyId; 27 | b2JointId jointId; 28 | float frictionScale; 29 | int parentIndex; 30 | }; 31 | 32 | class Human 33 | { 34 | public: 35 | Human(); 36 | 37 | void Spawn( b2WorldId worldId, b2Vec2 position, float scale, float frictionTorque, float hertz, float dampingRatio, 38 | int groupIndex, void* userData, bool colorize ); 39 | void Despawn(); 40 | 41 | void ApplyRandomAngularImpulse( float magnitude ); 42 | void SetJointFrictionTorque( float torque ); 43 | void SetJointSpringHertz( float hertz ); 44 | void SetJointDampingRatio( float dampingRatio ); 45 | 46 | Bone m_bones[Bone::e_count]; 47 | float m_scale; 48 | bool m_isSpawned; 49 | }; 50 | -------------------------------------------------------------------------------- /samples/sample.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/id.h" 8 | #include "box2d/types.h" 9 | 10 | // todo this include is slow 11 | #include "TaskScheduler.h" 12 | 13 | #include 14 | 15 | #define ARRAY_COUNT( A ) (int)( sizeof( A ) / sizeof( A[0] ) ) 16 | 17 | struct Settings; 18 | 19 | #ifdef NDEBUG 20 | constexpr bool g_sampleDebug = false; 21 | #else 22 | constexpr bool g_sampleDebug = true; 23 | #endif 24 | 25 | constexpr int32_t k_maxContactPoints = 12 * 2048; 26 | 27 | struct ContactPoint 28 | { 29 | b2ShapeId shapeIdA; 30 | b2ShapeId shapeIdB; 31 | b2Vec2 normal; 32 | b2Vec2 position; 33 | bool persisted; 34 | float normalImpulse; 35 | float tangentImpulse; 36 | float separation; 37 | int32_t constraintIndex; 38 | int32_t color; 39 | }; 40 | 41 | class SampleTask : public enki::ITaskSet 42 | { 43 | public: 44 | SampleTask() = default; 45 | 46 | void ExecuteRange( enki::TaskSetPartition range, uint32_t threadIndex ) override 47 | { 48 | m_task( range.start, range.end, threadIndex, m_taskContext ); 49 | } 50 | 51 | b2TaskCallback* m_task = nullptr; 52 | void* m_taskContext = nullptr; 53 | }; 54 | 55 | constexpr int32_t maxTasks = 64; 56 | constexpr int32_t maxThreads = 64; 57 | 58 | class Sample 59 | { 60 | public: 61 | explicit Sample( Settings& settings ); 62 | virtual ~Sample(); 63 | 64 | void DrawTitle( const char* string ); 65 | virtual void Step( Settings& settings ); 66 | virtual void UpdateUI() 67 | { 68 | } 69 | virtual void Keyboard( int ) 70 | { 71 | } 72 | virtual void MouseDown( b2Vec2 p, int button, int mod ); 73 | virtual void MouseUp( b2Vec2 p, int button ); 74 | virtual void MouseMove( b2Vec2 p ); 75 | 76 | void ResetProfile(); 77 | void ShiftOrigin( b2Vec2 newOrigin ); 78 | 79 | friend class DestructionListener; 80 | friend class BoundaryListener; 81 | friend class ContactListener; 82 | 83 | enki::TaskScheduler m_scheduler; 84 | SampleTask m_tasks[maxTasks]; 85 | int32_t m_taskCount; 86 | int m_threadCount; 87 | 88 | b2BodyId m_groundBodyId; 89 | 90 | // DestructionListener m_destructionListener; 91 | int32_t m_textLine; 92 | b2WorldId m_worldId; 93 | b2JointId m_mouseJointId; 94 | int32_t m_stepCount; 95 | int32_t m_textIncrement; 96 | b2Profile m_maxProfile; 97 | b2Profile m_totalProfile; 98 | }; 99 | 100 | typedef Sample* SampleCreateFcn( Settings& settings ); 101 | 102 | int RegisterSample( const char* category, const char* name, SampleCreateFcn* fcn ); 103 | 104 | struct SampleEntry 105 | { 106 | const char* category; 107 | const char* name; 108 | SampleCreateFcn* createFcn; 109 | }; 110 | 111 | #define MAX_SAMPLES 256 112 | extern SampleEntry g_sampleEntries[MAX_SAMPLES]; 113 | extern int g_sampleCount; 114 | 115 | #define RAND_LIMIT 32767 116 | 117 | /// Random integer in range [lo, hi) 118 | inline float RandomInt( int lo, int hi ) 119 | { 120 | return lo + rand() % ( hi - lo ); 121 | } 122 | 123 | /// Random number in range [-1,1] 124 | inline float RandomFloat() 125 | { 126 | float r = (float)( rand() & ( RAND_LIMIT ) ); 127 | r /= RAND_LIMIT; 128 | r = 2.0f * r - 1.0f; 129 | return r; 130 | } 131 | 132 | /// Random floating point number in range [lo, hi] 133 | inline float RandomFloat( float lo, float hi ) 134 | { 135 | float r = (float)( rand() & ( RAND_LIMIT ) ); 136 | r /= RAND_LIMIT; 137 | r = ( hi - lo ) * r + lo; 138 | return r; 139 | } 140 | 141 | /// Random vector with coordinates in range [lo, hi] 142 | inline b2Vec2 RandomVec2( float lo, float hi ) 143 | { 144 | b2Vec2 v; 145 | v.x = RandomFloat( lo, hi ); 146 | v.y = RandomFloat( lo, hi ); 147 | return v; 148 | } 149 | 150 | b2Polygon RandomPolygon( float extent ); 151 | -------------------------------------------------------------------------------- /samples/sample_geometry.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "draw.h" 5 | #include "sample.h" 6 | #include "settings.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_maxPolygonVertices 18 | }; 19 | 20 | explicit ConvexHull( Settings& settings ) 21 | : Sample( settings ) 22 | { 23 | if ( settings.restart == false ) 24 | { 25 | g_camera.m_center = { 0.5f, 0.0f }; 26 | g_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( Settings& settings ) override 113 | { 114 | Sample::Step( settings ); 115 | 116 | g_draw.DrawString( 5, m_textLine, "Options: generate(g), auto(a), bulk(b)" ); 117 | m_textLine += m_textIncrement; 118 | 119 | b2Hull hull; 120 | bool valid = false; 121 | float milliseconds = 0.0f; 122 | 123 | if ( m_bulk ) 124 | { 125 | #if 1 126 | // defect hunting 127 | for ( int i = 0; i < 10000; ++i ) 128 | { 129 | Generate(); 130 | hull = b2ComputeHull( m_points, m_count ); 131 | if ( hull.count == 0 ) 132 | { 133 | // m_bulk = false; 134 | // break; 135 | continue; 136 | } 137 | 138 | valid = b2ValidateHull( &hull ); 139 | if ( valid == false || m_bulk == false ) 140 | { 141 | m_bulk = false; 142 | break; 143 | } 144 | } 145 | #else 146 | // performance 147 | Generate(); 148 | b2Timer timer; 149 | for ( int i = 0; i < 1000000; ++i ) 150 | { 151 | hull = b2ComputeHull( m_points, m_count ); 152 | } 153 | valid = hull.count > 0; 154 | milliseconds = timer.GetMilliseconds(); 155 | #endif 156 | } 157 | else 158 | { 159 | if ( m_auto ) 160 | { 161 | Generate(); 162 | } 163 | 164 | hull = b2ComputeHull( m_points, m_count ); 165 | if ( hull.count > 0 ) 166 | { 167 | valid = b2ValidateHull( &hull ); 168 | if ( valid == false ) 169 | { 170 | m_auto = false; 171 | } 172 | } 173 | } 174 | 175 | if ( valid == false ) 176 | { 177 | g_draw.DrawString( 5, m_textLine, "generation = %d, FAILED", m_generation ); 178 | m_textLine += m_textIncrement; 179 | } 180 | else 181 | { 182 | g_draw.DrawString( 5, m_textLine, "generation = %d, count = %d", m_generation, hull.count ); 183 | m_textLine += m_textIncrement; 184 | } 185 | 186 | if ( milliseconds > 0.0f ) 187 | { 188 | g_draw.DrawString( 5, m_textLine, "milliseconds = %g", milliseconds ); 189 | m_textLine += m_textIncrement; 190 | } 191 | 192 | m_textLine += m_textIncrement; 193 | 194 | g_draw.DrawPolygon( hull.points, hull.count, b2_colorGray ); 195 | 196 | for ( int32_t i = 0; i < m_count; ++i ) 197 | { 198 | g_draw.DrawPoint( m_points[i], 5.0f, b2_colorBlue ); 199 | g_draw.DrawString( b2Add( m_points[i], { 0.1f, 0.1f } ), "%d", i ); 200 | } 201 | 202 | for ( int32_t i = 0; i < hull.count; ++i ) 203 | { 204 | g_draw.DrawPoint( hull.points[i], 6.0f, b2_colorGreen ); 205 | } 206 | } 207 | 208 | static Sample* Create( Settings& settings ) 209 | { 210 | return new ConvexHull( settings ); 211 | } 212 | 213 | b2Vec2 m_points[b2_maxPolygonVertices]; 214 | int32_t m_count; 215 | int32_t m_generation; 216 | bool m_auto; 217 | bool m_bulk; 218 | }; 219 | 220 | static int sampleIndex = RegisterSample( "Geometry", "Convex Hull", ConvexHull::Create ); 221 | -------------------------------------------------------------------------------- /samples/settings.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #define _CRT_SECURE_NO_WARNINGS 5 | #include "settings.h" 6 | 7 | // todo consider using https://github.com/skeeto/pdjson 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static const char* fileName = "settings.ini"; 15 | 16 | // Load a file. You must free the character array. 17 | static bool ReadFile( char*& data, int& size, const char* filename ) 18 | { 19 | FILE* file = fopen( filename, "rb" ); 20 | if ( file == nullptr ) 21 | { 22 | return false; 23 | } 24 | 25 | fseek( file, 0, SEEK_END ); 26 | size = (int)ftell( file ); 27 | fseek( file, 0, SEEK_SET ); 28 | 29 | if ( size == 0 ) 30 | { 31 | return false; 32 | } 33 | 34 | data = (char*)malloc( size + 1 ); 35 | fread( data, size, 1, file ); 36 | fclose( file ); 37 | data[size] = 0; 38 | 39 | return true; 40 | } 41 | 42 | void Settings::Save() 43 | { 44 | FILE* file = fopen( fileName, "w" ); 45 | fprintf( file, "{\n" ); 46 | fprintf( file, " \"sampleIndex\": %d,\n", sampleIndex ); 47 | fprintf( file, " \"drawShapes\": %s,\n", drawShapes ? "true" : "false" ); 48 | fprintf( file, " \"drawJoints\": %s,\n", drawJoints ? "true" : "false" ); 49 | fprintf( file, " \"drawAABBs\": %s,\n", drawAABBs ? "true" : "false" ); 50 | fprintf( file, " \"drawContactPoints\": %s,\n", drawContactPoints ? "true" : "false" ); 51 | fprintf( file, " \"drawContactNormals\": %s,\n", drawContactNormals ? "true" : "false" ); 52 | fprintf( file, " \"drawContactImpulses\": %s,\n", drawContactImpulses ? "true" : "false" ); 53 | fprintf( file, " \"drawFrictionImpulse\": %s,\n", drawFrictionImpulses ? "true" : "false" ); 54 | fprintf( file, " \"drawMass\": %s,\n", drawMass ? "true" : "false" ); 55 | fprintf( file, " \"drawCounters\": %s,\n", drawCounters ? "true" : "false" ); 56 | fprintf( file, " \"drawProfile\": %s,\n", drawProfile ? "true" : "false" ); 57 | fprintf( file, " \"enableWarmStarting\": %s,\n", enableWarmStarting ? "true" : "false" ); 58 | fprintf( file, " \"enableContinuous\": %s,\n", enableContinuous ? "true" : "false" ); 59 | fprintf( file, " \"enableSleep\": %s\n", enableSleep ? "true" : "false" ); 60 | fprintf( file, "}\n" ); 61 | fclose( file ); 62 | } 63 | 64 | static int jsoneq( const char* json, jsmntok_t* tok, const char* s ) 65 | { 66 | if ( tok->type == JSMN_STRING && (int)strlen( s ) == tok->end - tok->start && 67 | strncmp( json + tok->start, s, tok->end - tok->start ) == 0 ) 68 | { 69 | return 0; 70 | } 71 | return -1; 72 | } 73 | 74 | #define MAX_TOKENS 32 75 | 76 | void Settings::Load() 77 | { 78 | char* data = nullptr; 79 | int size = 0; 80 | bool found = ReadFile( data, size, fileName ); 81 | if ( found == false ) 82 | { 83 | return; 84 | } 85 | 86 | jsmn_parser parser; 87 | jsmntok_t tokens[MAX_TOKENS]; 88 | 89 | jsmn_init( &parser ); 90 | 91 | // js - pointer to JSON string 92 | // tokens - an array of tokens available 93 | // 10 - number of tokens available 94 | int tokenCount = jsmn_parse( &parser, data, size, tokens, MAX_TOKENS ); 95 | char buffer[32]; 96 | 97 | for ( int i = 0; i < tokenCount; ++i ) 98 | { 99 | if ( jsoneq( data, &tokens[i], "sampleIndex" ) == 0 ) 100 | { 101 | int count = tokens[i + 1].end - tokens[i + 1].start; 102 | assert( count < 32 ); 103 | const char* s = data + tokens[i + 1].start; 104 | strncpy( buffer, s, count ); 105 | buffer[count] = 0; 106 | char* dummy; 107 | sampleIndex = (int)strtol( buffer, &dummy, 10 ); 108 | } 109 | else if ( jsoneq( data, &tokens[i], "drawShapes" ) == 0 ) 110 | { 111 | const char* s = data + tokens[i + 1].start; 112 | if ( strncmp( s, "true", 4 ) == 0 ) 113 | { 114 | drawShapes = true; 115 | } 116 | else if ( strncmp( s, "false", 5 ) == 0 ) 117 | { 118 | drawShapes = false; 119 | } 120 | } 121 | else if ( jsoneq( data, &tokens[i], "drawJoints" ) == 0 ) 122 | { 123 | const char* s = data + tokens[i + 1].start; 124 | if ( strncmp( s, "true", 4 ) == 0 ) 125 | { 126 | drawJoints = true; 127 | } 128 | else if ( strncmp( s, "false", 5 ) == 0 ) 129 | { 130 | drawJoints = false; 131 | } 132 | } 133 | } 134 | 135 | free( data ); 136 | } 137 | -------------------------------------------------------------------------------- /samples/settings.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | // todo add camera and draw and remove globals 7 | struct Settings 8 | { 9 | void Save(); 10 | void Load(); 11 | 12 | int sampleIndex = 0; 13 | // int windowWidth = 3840; 14 | // int windowHeight = 2160; 15 | int windowWidth = 1920; 16 | int windowHeight = 1080; 17 | // int windowWidth = 1280; 18 | // int windowHeight = 720; 19 | // int windowWidth = 800; 20 | // int windowHeight = 600; 21 | float hertz = 60.0f; 22 | int subStepCount = 4; 23 | int workerCount = 1; 24 | bool useCameraBounds = false; 25 | bool drawShapes = true; 26 | bool drawJoints = true; 27 | bool drawJointExtras = false; 28 | bool drawAABBs = false; 29 | bool drawContactPoints = false; 30 | bool drawContactNormals = false; 31 | bool drawContactImpulses = false; 32 | bool drawFrictionImpulses = false; 33 | bool drawMass = false; 34 | bool drawGraphColors = false; 35 | bool drawCounters = false; 36 | bool drawProfile = false; 37 | bool enableWarmStarting = true; 38 | bool enableContinuous = true; 39 | bool enableSleep = true; 40 | bool pause = false; 41 | bool singleStep = false; 42 | bool restart = false; 43 | }; 44 | -------------------------------------------------------------------------------- /samples/shader.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #define _CRT_SECURE_NO_WARNINGS 5 | 6 | #include "shader.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #if defined( _WIN32 ) 13 | #define _CRTDBG_MAP_ALLOC 14 | #include 15 | #include 16 | #else 17 | #include 18 | #endif 19 | 20 | void DumpInfoGL() 21 | { 22 | const char* renderer = (const char*)glGetString( GL_RENDERER ); 23 | const char* vendor = (const char*)glGetString( GL_VENDOR ); 24 | const char* version = (const char*)glGetString( GL_VERSION ); 25 | const char* glslVersion = (const char*)glGetString( GL_SHADING_LANGUAGE_VERSION ); 26 | 27 | int major, minor; 28 | glGetIntegerv( GL_MAJOR_VERSION, &major ); 29 | glGetIntegerv( GL_MINOR_VERSION, &minor ); 30 | 31 | printf( "-------------------------------------------------------------\n" ); 32 | printf( "GL Vendor : %s\n", vendor ); 33 | printf( "GL Renderer : %s\n", renderer ); 34 | printf( "GL Version : %s\n", version ); 35 | printf( "GL Version : %d.%d\n", major, minor ); 36 | printf( "GLSL Version : %s\n", glslVersion ); 37 | printf( "-------------------------------------------------------------\n" ); 38 | } 39 | 40 | void CheckErrorGL() 41 | { 42 | GLenum errCode = glGetError(); 43 | if ( errCode != GL_NO_ERROR ) 44 | { 45 | printf( "OpenGL error = %d\n", errCode ); 46 | assert( false ); 47 | } 48 | } 49 | 50 | void PrintLogGL( uint32_t object ) 51 | { 52 | GLint log_length = 0; 53 | if ( glIsShader( object ) ) 54 | { 55 | glGetShaderiv( object, GL_INFO_LOG_LENGTH, &log_length ); 56 | } 57 | else if ( glIsProgram( object ) ) 58 | { 59 | glGetProgramiv( object, GL_INFO_LOG_LENGTH, &log_length ); 60 | } 61 | else 62 | { 63 | printf( "PrintLogGL: Not a shader or a program\n" ); 64 | return; 65 | } 66 | 67 | char* log = (char*)malloc( log_length ); 68 | 69 | if ( glIsShader( object ) ) 70 | { 71 | glGetShaderInfoLog( object, log_length, nullptr, log ); 72 | } 73 | else if ( glIsProgram( object ) ) 74 | { 75 | glGetProgramInfoLog( object, log_length, nullptr, log ); 76 | } 77 | 78 | printf( "PrintLogGL: %s", log ); 79 | free( log ); 80 | } 81 | 82 | static GLuint sCreateShaderFromString( const char* source, GLenum type ) 83 | { 84 | GLuint shader = glCreateShader( type ); 85 | const char* sources[] = { source }; 86 | 87 | glShaderSource( shader, 1, sources, nullptr ); 88 | glCompileShader( shader ); 89 | 90 | int success = GL_FALSE; 91 | glGetShaderiv( shader, GL_COMPILE_STATUS, &success ); 92 | 93 | if ( success == GL_FALSE ) 94 | { 95 | printf( "Error compiling shader of type %d!\n", type ); 96 | PrintLogGL( shader ); 97 | glDeleteShader( shader ); 98 | return 0; 99 | } 100 | 101 | return shader; 102 | } 103 | 104 | uint32_t CreateProgramFromStrings( const char* vertexString, const char* fragmentString ) 105 | { 106 | GLuint vertex = sCreateShaderFromString( vertexString, GL_VERTEX_SHADER ); 107 | if ( vertex == 0 ) 108 | { 109 | return 0; 110 | } 111 | 112 | GLuint fragment = sCreateShaderFromString( fragmentString, GL_FRAGMENT_SHADER ); 113 | if ( fragment == 0 ) 114 | { 115 | return 0; 116 | } 117 | 118 | GLuint program = glCreateProgram(); 119 | glAttachShader( program, vertex ); 120 | glAttachShader( program, fragment ); 121 | 122 | glLinkProgram( program ); 123 | 124 | int success = GL_FALSE; 125 | glGetProgramiv( program, GL_LINK_STATUS, &success ); 126 | if ( success == GL_FALSE ) 127 | { 128 | printf( "glLinkProgram:" ); 129 | PrintLogGL( program ); 130 | return 0; 131 | } 132 | 133 | glDeleteShader( vertex ); 134 | glDeleteShader( fragment ); 135 | 136 | return program; 137 | } 138 | 139 | static GLuint sCreateShaderFromFile( const char* filename, GLenum type ) 140 | { 141 | FILE* file = fopen( filename, "rb" ); 142 | if ( file == nullptr ) 143 | { 144 | fprintf( stderr, "Error opening %s\n", filename ); 145 | return 0; 146 | } 147 | 148 | fseek( file, 0, SEEK_END ); 149 | long size = ftell( file ); 150 | fseek( file, 0, SEEK_SET ); 151 | 152 | char* source = static_cast( malloc( size + 1 ) ); 153 | fread( source, size, 1, file ); 154 | fclose( file ); 155 | 156 | source[size] = 0; 157 | 158 | GLuint shader = glCreateShader( type ); 159 | const char* sources[] = { source }; 160 | 161 | glShaderSource( shader, 1, sources, nullptr ); 162 | glCompileShader( shader ); 163 | 164 | int success = GL_FALSE; 165 | glGetShaderiv( shader, GL_COMPILE_STATUS, &success ); 166 | 167 | if ( success == GL_FALSE ) 168 | { 169 | fprintf( stderr, "Error compiling shader of type %d!\n", type ); 170 | PrintLogGL( shader ); 171 | } 172 | 173 | free( source ); 174 | return shader; 175 | } 176 | 177 | uint32_t CreateProgramFromFiles( const char* vertexPath, const char* fragmentPath ) 178 | { 179 | GLuint vertex = sCreateShaderFromFile( vertexPath, GL_VERTEX_SHADER ); 180 | if ( vertex == 0 ) 181 | { 182 | return 0; 183 | } 184 | 185 | GLuint fragment = sCreateShaderFromFile( fragmentPath, GL_FRAGMENT_SHADER ); 186 | if ( fragment == 0 ) 187 | { 188 | return 0; 189 | } 190 | 191 | GLuint program = glCreateProgram(); 192 | glAttachShader( program, vertex ); 193 | glAttachShader( program, fragment ); 194 | 195 | glLinkProgram( program ); 196 | 197 | int success = GL_FALSE; 198 | glGetProgramiv( program, GL_LINK_STATUS, &success ); 199 | if ( success == GL_FALSE ) 200 | { 201 | printf( "glLinkProgram:" ); 202 | PrintLogGL( program ); 203 | return 0; 204 | } 205 | 206 | glDeleteShader( vertex ); 207 | glDeleteShader( fragment ); 208 | 209 | return program; 210 | } 211 | -------------------------------------------------------------------------------- /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 CheckErrorGL(); 12 | void DumpInfoGL(); 13 | void PrintLogGL( int object ); 14 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(BOX2D_SOURCE_FILES 2 | aabb.c 3 | aabb.h 4 | allocate.c 5 | allocate.h 6 | array.c 7 | array.h 8 | bitset.c 9 | bitset.h 10 | block_array.c 11 | block_array.h 12 | body.c 13 | body.h 14 | broad_phase.c 15 | broad_phase.h 16 | constraint_graph.c 17 | constraint_graph.h 18 | contact.c 19 | contact.h 20 | contact_solver.c 21 | contact_solver.h 22 | core.c 23 | core.h 24 | ctz.h 25 | distance.c 26 | distance_joint.c 27 | dynamic_tree.c 28 | geometry.c 29 | hull.c 30 | id_pool.c 31 | id_pool.h 32 | island.c 33 | island.h 34 | joint.c 35 | joint.h 36 | manifold.c 37 | math_functions.c 38 | motor_joint.c 39 | mouse_joint.c 40 | prismatic_joint.c 41 | revolute_joint.c 42 | shape.c 43 | shape.h 44 | solver.c 45 | solver.h 46 | solver_set.c 47 | solver_set.h 48 | stack_allocator.c 49 | stack_allocator.h 50 | table.c 51 | table.h 52 | timer.c 53 | types.c 54 | weld_joint.c 55 | wheel_joint.c 56 | world.c 57 | world.h 58 | ) 59 | 60 | set(BOX2D_API_FILES 61 | ../include/box2d/base.h 62 | ../include/box2d/box2d.h 63 | ../include/box2d/collision.h 64 | ../include/box2d/id.h 65 | ../include/box2d/math_functions.h 66 | ../include/box2d/types.h 67 | ) 68 | 69 | # Hide internal functions 70 | # todo need to investigate this more 71 | # https://gcc.gnu.org/wiki/Visibility 72 | set(CMAKE_C_VISIBILITY_PRESET hidden) 73 | set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) 74 | 75 | add_library(box2d ${BOX2D_SOURCE_FILES} ${BOX2D_API_FILES}) 76 | 77 | # Generate box2d_export.h to handles shared library builds 78 | # turned this off to make Box2D easier to use without cmake 79 | # include(GenerateExportHeader) 80 | # generate_export_header(box2d) 81 | 82 | target_include_directories(box2d 83 | PUBLIC 84 | $ 85 | $ 86 | $ 87 | PRIVATE 88 | ${CMAKE_CURRENT_SOURCE_DIR} 89 | ) 90 | 91 | # SIMDE is used to support SIMD math on multiple platforms 92 | target_link_libraries(box2d PRIVATE simde) 93 | 94 | # Box2D uses C17 95 | set_target_properties(box2d PROPERTIES 96 | C_STANDARD 17 97 | C_STANDARD_REQUIRED YES 98 | C_EXTENSIONS YES 99 | VERSION ${PROJECT_VERSION} 100 | SOVERSION ${PROJECT_VERSION_MAJOR} 101 | ) 102 | 103 | if (BOX2D_PROFILE) 104 | target_compile_definitions(box2d PRIVATE BOX2D_PROFILE) 105 | 106 | FetchContent_Declare( 107 | tracy 108 | GIT_REPOSITORY https://github.com/wolfpld/tracy.git 109 | GIT_TAG master 110 | GIT_SHALLOW TRUE 111 | GIT_PROGRESS TRUE 112 | ) 113 | FetchContent_MakeAvailable(tracy) 114 | 115 | target_link_libraries(box2d PUBLIC TracyClient) 116 | endif() 117 | 118 | if (BOX2D_VALIDATE) 119 | target_compile_definitions(box2d PRIVATE BOX2D_VALIDATE) 120 | endif() 121 | 122 | if (MSVC) 123 | message(STATUS "Box2D on MSVC") 124 | if (BUILD_SHARED_LIBS) 125 | # this is needed by DLL users to import Box2D symbols 126 | target_compile_definitions(box2d INTERFACE BOX2D_DLL) 127 | endif() 128 | 129 | # Visual Studio won't load the natvis unless it is in the project 130 | target_sources(box2d PRIVATE box2d.natvis) 131 | 132 | # Enable asserts in release with debug info 133 | target_compile_definitions(box2d PUBLIC "$<$:B2_ENABLE_ASSERT>") 134 | 135 | # Atomics are still considered experimental in Visual Studio 17.8 136 | target_compile_options(box2d PRIVATE /experimental:c11atomics) 137 | 138 | if (BOX2D_AVX2) 139 | message(STATUS "Box2D using AVX2") 140 | target_compile_options(box2d PRIVATE /arch:AVX2) 141 | endif() 142 | 143 | elseif (MINGW) 144 | message(STATUS "Box2D on MinGW") 145 | if (BOX2D_AVX2) 146 | message(STATUS "Box2D using AVX2") 147 | target_compile_options(box2d PRIVATE -mavx2) 148 | else() 149 | # see SIMDE_DIAGNOSTIC_DISABLE_PSABI_ 150 | message(STATUS "Box2D disabling ABI warning") 151 | target_compile_options(box2d PRIVATE -Wno-psabi) 152 | endif() 153 | elseif (APPLE) 154 | message(STATUS "Box2D on Apple") 155 | elseif (EMSCRIPTEN) 156 | message(STATUS "Box2D on Emscripten") 157 | # see SIMDE_DIAGNOSTIC_DISABLE_PSABI_ 158 | message(STATUS "Box2D disabling ABI warning") 159 | target_compile_options(box2d PRIVATE -Wno-psabi) 160 | elseif (UNIX) 161 | message(STATUS "Box2D using Unix") 162 | if ("${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "aarch64") 163 | # raspberry pi 164 | # -mfpu=neon 165 | # target_compile_options(box2d PRIVATE) 166 | else() 167 | # x64 168 | if (BOX2D_AVX2) 169 | message(STATUS "Box2D using AVX2") 170 | # FMA -mfma -mavx -march=native 171 | target_compile_options(box2d PRIVATE -mavx2) 172 | else() 173 | # see SIMDE_DIAGNOSTIC_DISABLE_PSABI_ 174 | message(STATUS "Box2D disabling ABI warning") 175 | target_compile_options(box2d PRIVATE -Wno-psabi) 176 | endif() 177 | endif() 178 | endif() 179 | 180 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "src" FILES ${BOX2D_SOURCE_FILES}) 181 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" PREFIX "include" FILES ${BOX2D_API_FILES}) 182 | 183 | install(TARGETS box2d) 184 | -------------------------------------------------------------------------------- /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 b2AABB_IsValid( 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 && b2Vec2_IsValid( a.lowerBound ) && b2Vec2_IsValid( 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 | 134 | #if 0 135 | bool b2TestOverlap( const b2Shape* shapeA, int32_t indexA, 136 | const b2Shape* shapeB, int32_t indexB, 137 | b2Transform xfA, b2Transform xfB) 138 | { 139 | b2DistanceInput input; 140 | input->proxyA.Set(shapeA, indexA); 141 | input->proxyB.Set(shapeB, indexB); 142 | input->transformA = xfA; 143 | input->transformB = xfB; 144 | input->useRadii = true; 145 | 146 | b2DistanceCache cache; 147 | cache.count = 0; 148 | 149 | b2DistanceOutput output; 150 | 151 | b2Distance(&output, &cache, &input); 152 | 153 | return output.distance < 10.0f * b2_epsilon; 154 | } 155 | #endif 156 | -------------------------------------------------------------------------------- /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 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 | 51 | static inline bool b2AABB_ContainsWithMargin( b2AABB a, b2AABB b, float margin ) 52 | { 53 | bool s = ( a.lowerBound.x <= b.lowerBound.x - margin ) & ( a.lowerBound.y <= b.lowerBound.y - margin ) & 54 | ( b.upperBound.x + margin <= a.upperBound.x ) & ( b.upperBound.y + margin <= a.upperBound.y ); 55 | return s; 56 | } 57 | 58 | /// Do a and b overlap 59 | static inline bool b2AABB_Overlaps( b2AABB a, b2AABB b ) 60 | { 61 | b2Vec2 d1 = { b.lowerBound.x - a.upperBound.x, b.lowerBound.y - a.upperBound.y }; 62 | b2Vec2 d2 = { a.lowerBound.x - b.upperBound.x, a.lowerBound.y - b.upperBound.y }; 63 | 64 | if ( d1.x > 0.0f || d1.y > 0.0f ) 65 | return false; 66 | 67 | if ( d2.x > 0.0f || d2.y > 0.0f ) 68 | return false; 69 | 70 | return true; 71 | } 72 | -------------------------------------------------------------------------------- /src/allocate.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "allocate.h" 5 | 6 | #include "core.h" 7 | 8 | #include "box2d/base.h" 9 | 10 | #if defined( B2_COMPILER_MSVC ) 11 | #define _CRTDBG_MAP_ALLOC 12 | #include 13 | #include 14 | #else 15 | #include 16 | #endif 17 | 18 | #include 19 | #include 20 | 21 | #ifdef BOX2D_PROFILE 22 | 23 | #include 24 | #define b2TracyCAlloc( ptr, size ) TracyCAlloc( ptr, size ) 25 | #define b2TracyCFree( ptr ) TracyCFree( ptr ) 26 | 27 | #else 28 | 29 | #define b2TracyCAlloc( ptr, size ) 30 | #define b2TracyCFree( ptr ) 31 | 32 | #endif 33 | 34 | static b2AllocFcn* b2_allocFcn = NULL; 35 | static b2FreeFcn* b2_freeFcn = NULL; 36 | 37 | static _Atomic int b2_byteCount; 38 | 39 | void b2SetAllocator( b2AllocFcn* allocFcn, b2FreeFcn* freeFcn ) 40 | { 41 | b2_allocFcn = allocFcn; 42 | b2_freeFcn = freeFcn; 43 | } 44 | 45 | // Use 32 byte alignment for everything. Works with 256bit SIMD. 46 | #define B2_ALIGNMENT 32 47 | 48 | void* b2Alloc( uint32_t size ) 49 | { 50 | // This could cause some sharing issues, however Box2D rarely calls b2Alloc. 51 | atomic_fetch_add_explicit( &b2_byteCount, size, memory_order_relaxed ); 52 | 53 | // Allocation must be a multiple of 32 or risk a seg fault 54 | // https://en.cppreference.com/w/c/memory/aligned_alloc 55 | uint32_t size32 = ( ( size - 1 ) | 0x1F ) + 1; 56 | 57 | if ( b2_allocFcn != NULL ) 58 | { 59 | void* ptr = b2_allocFcn( size32, B2_ALIGNMENT ); 60 | b2TracyCAlloc( ptr, size ); 61 | 62 | B2_ASSERT( ptr != NULL ); 63 | B2_ASSERT( ( (uintptr_t)ptr & 0x1F ) == 0 ); 64 | 65 | return ptr; 66 | } 67 | 68 | #ifdef B2_PLATFORM_WINDOWS 69 | void* ptr = _aligned_malloc( size32, B2_ALIGNMENT ); 70 | #else 71 | void* ptr = aligned_alloc( B2_ALIGNMENT, size32 ); 72 | #endif 73 | 74 | b2TracyCAlloc( ptr, size ); 75 | 76 | B2_ASSERT( ptr != NULL ); 77 | B2_ASSERT( ( (uintptr_t)ptr & 0x1F ) == 0 ); 78 | 79 | return ptr; 80 | } 81 | 82 | void b2Free( void* mem, uint32_t size ) 83 | { 84 | if ( mem == NULL ) 85 | { 86 | return; 87 | } 88 | 89 | b2TracyCFree( mem ); 90 | 91 | if ( b2_freeFcn != NULL ) 92 | { 93 | b2_freeFcn( mem ); 94 | } 95 | else 96 | { 97 | #ifdef B2_PLATFORM_WINDOWS 98 | _aligned_free( mem ); 99 | #else 100 | free( mem ); 101 | #endif 102 | } 103 | 104 | atomic_fetch_sub_explicit( &b2_byteCount, size, memory_order_relaxed ); 105 | } 106 | 107 | int b2GetByteCount( void ) 108 | { 109 | return atomic_load_explicit( &b2_byteCount, memory_order_relaxed ); 110 | } 111 | -------------------------------------------------------------------------------- /src/allocate.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | #include 6 | 7 | void* b2Alloc( uint32_t size ); 8 | void b2Free( void* mem, uint32_t size ); 9 | -------------------------------------------------------------------------------- /src/array.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "array.h" 5 | 6 | #include "allocate.h" 7 | #include "core.h" 8 | 9 | #include 10 | 11 | void* b2CreateArray( int elementSize, int capacity ) 12 | { 13 | void* result = (b2ArrayHeader*)b2Alloc( sizeof( b2ArrayHeader ) + elementSize * capacity ) + 1; 14 | b2Array( result ).count = 0; 15 | b2Array( result ).capacity = capacity; 16 | return result; 17 | } 18 | 19 | void b2DestroyArray( void* a, int elementSize ) 20 | { 21 | int capacity = b2Array( a ).capacity; 22 | int size = sizeof( b2ArrayHeader ) + elementSize * capacity; 23 | b2Free( ( (b2ArrayHeader*)a ) - 1, size ); 24 | } 25 | 26 | void b2Array_Grow( void** a, int elementSize ) 27 | { 28 | int capacity = b2Array( *a ).capacity; 29 | B2_ASSERT( capacity == b2Array( *a ).count ); 30 | 31 | // grow by 50% 32 | int newCapacity = capacity + ( capacity >> 1 ); 33 | newCapacity = newCapacity >= 2 ? newCapacity : 2; 34 | void* tmp = *a; 35 | *a = (b2ArrayHeader*)b2Alloc( sizeof( b2ArrayHeader ) + elementSize * newCapacity ) + 1; 36 | b2Array( *a ).capacity = newCapacity; 37 | b2Array( *a ).count = capacity; 38 | memcpy( *a, tmp, capacity * elementSize ); 39 | b2DestroyArray( tmp, elementSize ); 40 | } 41 | 42 | void b2Array_Resize( void** a, int elementSize, int count ) 43 | { 44 | int capacity = b2Array( *a ).capacity; 45 | if ( capacity >= count ) 46 | { 47 | b2Array( *a ).count = count; 48 | return; 49 | } 50 | 51 | int originalCount = b2Array( *a ).count; 52 | 53 | // grow by 50% 54 | int newCapacity = count + ( count >> 1 ); 55 | newCapacity = newCapacity >= 2 ? newCapacity : 2; 56 | void* tmp = *a; 57 | *a = (b2ArrayHeader*)b2Alloc( sizeof( b2ArrayHeader ) + elementSize * newCapacity ) + 1; 58 | b2Array( *a ).capacity = newCapacity; 59 | b2Array( *a ).count = count; 60 | 61 | // copy existing elements 62 | memcpy( *a, tmp, originalCount * elementSize ); 63 | b2DestroyArray( tmp, elementSize ); 64 | } 65 | -------------------------------------------------------------------------------- /src/array.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include "core.h" 7 | 8 | // todo compare with https://github.com/skeeto/growable-buf 9 | 10 | typedef struct b2ArrayHeader 11 | { 12 | int count; 13 | int capacity; 14 | } b2ArrayHeader; 15 | 16 | #define b2Array( a ) ( (b2ArrayHeader*)( a ) )[-1] 17 | 18 | void* b2CreateArray( int elementSize, int capacity ); 19 | void b2DestroyArray( void* a, int elementSize ); 20 | void b2Array_Grow( void** a, int elementSize ); 21 | void b2Array_Resize( void** a, int elementSize, int count ); 22 | 23 | #define b2CheckIndex( a, index ) B2_ASSERT( 0 <= index && index < b2Array( a ).count ) 24 | #define b2CheckId( ARRAY, ID ) B2_ASSERT( 0 <= ID && ID < b2Array( ARRAY ).count && ARRAY[ID].id == ID ) 25 | #define b2CheckIdAndRevision( ARRAY, ID, REV ) \ 26 | B2_ASSERT( 0 <= ID && ID < b2Array( ARRAY ).count && ARRAY[ID].id == ID && ARRAY[ID].revision == REV ) 27 | 28 | #define b2Array_Clear( a ) b2Array( a ).count = 0 29 | 30 | #define b2Array_Push( a, element ) \ 31 | if ( b2Array( a ).count == b2Array( a ).capacity ) \ 32 | b2Array_Grow( (void**)&a, sizeof( element ) ); \ 33 | B2_ASSERT( b2Array( a ).count < b2Array( a ).capacity ); \ 34 | a[b2Array( a ).count++] = element 35 | 36 | #define b2Array_RemoveSwap( a, index ) \ 37 | B2_ASSERT( 0 <= index && index < b2Array( a ).count ); \ 38 | a[index] = a[b2Array( a ).count - 1]; \ 39 | b2Array( a ).count-- 40 | 41 | #define b2Array_Last( a ) ( a )[b2Array( a ).count - 1]; 42 | 43 | #define b2Array_Pop( a ) \ 44 | B2_ASSERT( 0 < b2Array( a ).count ); \ 45 | b2Array( a ).count-- 46 | 47 | #define b2GetArrayCount( a ) ( b2Array( a ).count ) 48 | #define b2GetArrayCapacity( a ) ( b2Array( a ).capacity ) 49 | #define b2GetArrayBytes( a, elementSize ) ( (int)elementSize * b2Array( a ).capacity ) 50 | -------------------------------------------------------------------------------- /src/bitset.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "bitset.h" 5 | 6 | #include "allocate.h" 7 | 8 | #include 9 | 10 | b2BitSet b2CreateBitSet( uint32_t bitCapacity ) 11 | { 12 | b2BitSet bitSet = { 0 }; 13 | 14 | bitSet.blockCapacity = ( bitCapacity + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 ); 15 | bitSet.blockCount = 0; 16 | bitSet.bits = b2Alloc( bitSet.blockCapacity * sizeof( uint64_t ) ); 17 | memset( bitSet.bits, 0, bitSet.blockCapacity * sizeof( uint64_t ) ); 18 | return bitSet; 19 | } 20 | 21 | void b2DestroyBitSet( b2BitSet* bitSet ) 22 | { 23 | b2Free( bitSet->bits, bitSet->blockCapacity * sizeof( uint64_t ) ); 24 | bitSet->blockCapacity = 0; 25 | bitSet->blockCount = 0; 26 | bitSet->bits = NULL; 27 | } 28 | 29 | void b2SetBitCountAndClear( b2BitSet* bitSet, uint32_t bitCount ) 30 | { 31 | uint32_t blockCount = ( bitCount + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 ); 32 | if ( bitSet->blockCapacity < blockCount ) 33 | { 34 | b2DestroyBitSet( bitSet ); 35 | uint32_t newBitCapacity = bitCount + ( bitCount >> 1 ); 36 | *bitSet = b2CreateBitSet( newBitCapacity ); 37 | } 38 | 39 | bitSet->blockCount = blockCount; 40 | memset( bitSet->bits, 0, bitSet->blockCount * sizeof( uint64_t ) ); 41 | } 42 | 43 | void b2GrowBitSet( b2BitSet* bitSet, uint32_t blockCount ) 44 | { 45 | B2_ASSERT( blockCount > bitSet->blockCount ); 46 | if ( blockCount > bitSet->blockCapacity ) 47 | { 48 | uint32_t oldCapacity = bitSet->blockCapacity; 49 | bitSet->blockCapacity = blockCount + blockCount / 2; 50 | uint64_t* newBits = b2Alloc( bitSet->blockCapacity * sizeof( uint64_t ) ); 51 | memset( newBits, 0, bitSet->blockCapacity * sizeof( uint64_t ) ); 52 | memcpy( newBits, bitSet->bits, bitSet->blockCount * sizeof( uint64_t ) ); 53 | b2Free( bitSet->bits, oldCapacity * sizeof( uint64_t ) ); 54 | bitSet->bits = newBits; 55 | } 56 | 57 | bitSet->blockCount = blockCount; 58 | } 59 | 60 | void b2InPlaceUnion( b2BitSet* restrict setA, const b2BitSet* restrict setB ) 61 | { 62 | B2_ASSERT( setA->blockCount == setB->blockCount ); 63 | uint32_t blockCount = setA->blockCount; 64 | for ( uint32_t i = 0; i < blockCount; ++i ) 65 | { 66 | setA->bits[i] |= setB->bits[i]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /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/block_array.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | typedef struct b2BodySim b2BodySim; 7 | typedef struct b2BodyState b2BodyState; 8 | typedef struct b2ContactSim b2ContactSim; 9 | typedef struct b2IslandSim b2IslandSim; 10 | typedef struct b2JointSim b2JointSim; 11 | 12 | typedef struct b2BodySimArray 13 | { 14 | b2BodySim* data; 15 | int count; 16 | int capacity; 17 | } b2BodySimArray; 18 | 19 | typedef struct b2BodyStateArray 20 | { 21 | b2BodyState* data; 22 | int count; 23 | int capacity; 24 | } b2BodyStateArray; 25 | 26 | typedef struct b2ContactArray 27 | { 28 | b2ContactSim* data; 29 | int count; 30 | int capacity; 31 | } b2ContactArray; 32 | 33 | typedef struct b2IslandArray 34 | { 35 | b2IslandSim* data; 36 | int count; 37 | int capacity; 38 | } b2IslandArray; 39 | 40 | typedef struct b2JointArray 41 | { 42 | b2JointSim* data; 43 | int count; 44 | int capacity; 45 | } b2JointArray; 46 | 47 | // These provide a way to create an array with a specified capacity. If the capacity is not 48 | // known, you may use zero initialization. 49 | b2BodySimArray b2CreateBodySimArray( int capacity ); 50 | b2BodyStateArray b2CreateBodyStateArray( int capacity ); 51 | b2ContactArray b2CreateContactArray( int capacity ); 52 | b2IslandArray b2CreateIslandArray( int capacity ); 53 | b2JointArray b2CreateJointArray( int capacity ); 54 | 55 | void b2DestroyBodySimArray( b2BodySimArray* array ); 56 | void b2DestroyBodyStateArray( b2BodyStateArray* array ); 57 | void b2DestroyContactArray( b2ContactArray* array ); 58 | void b2DestroyIslandArray( b2IslandArray* array ); 59 | void b2DestroyJointArray( b2JointArray* array ); 60 | 61 | b2BodySim* b2AddBodySim( b2BodySimArray* array ); 62 | b2BodyState* b2AddBodyState( b2BodyStateArray* array ); 63 | b2ContactSim* b2AddContact( b2ContactArray* array ); 64 | b2IslandSim* b2AddIsland( b2IslandArray* array ); 65 | b2JointSim* b2AddJoint( b2JointArray* array ); 66 | 67 | // Returns the index of the element moved into the empty slot (or B2_NULL_INDEX) 68 | // todo have these return the id directly? 69 | int b2RemoveBodySim( b2BodySimArray* array, int index ); 70 | int b2RemoveBodyState( b2BodyStateArray* array, int index ); 71 | int b2RemoveContact( b2ContactArray* array, int index ); 72 | int b2RemoveIsland( b2IslandArray* array, int index ); 73 | int b2RemoveJoint( b2JointArray* array, int index ); 74 | -------------------------------------------------------------------------------- /src/body.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include "box2d/math_functions.h" 7 | #include "box2d/types.h" 8 | 9 | typedef struct b2Polygon b2Polygon; 10 | typedef struct b2World b2World; 11 | typedef struct b2JointSim b2JointSim; 12 | typedef struct b2ContactSim b2ContactSim; 13 | typedef struct b2Shape b2Shape; 14 | typedef struct b2Body b2Body; 15 | 16 | // Body organizational details that are not used in the solver. 17 | typedef struct b2Body 18 | { 19 | void* userData; 20 | 21 | // index of solver set stored in b2World 22 | // may be B2_NULL_INDEX 23 | int setIndex; 24 | 25 | // body sim and state index within set 26 | // may be B2_NULL_INDEX 27 | int localIndex; 28 | 29 | // [31 : contactId | 1 : edgeIndex] 30 | int headContactKey; 31 | int contactCount; 32 | 33 | // todo maybe move this to the body sim 34 | int headShapeId; 35 | int shapeCount; 36 | 37 | int headChainId; 38 | 39 | // [31 : jointId | 1 : edgeIndex] 40 | int headJointKey; 41 | int jointCount; 42 | 43 | // All enabled dynamic and kinematic bodies are in an island. 44 | int islandId; 45 | 46 | // doubly-linked island list 47 | int islandPrev; 48 | int islandNext; 49 | 50 | float sleepThreshold; 51 | float sleepTime; 52 | 53 | // this is used to adjust the fellAsleep flag in the body move array 54 | int bodyMoveIndex; 55 | 56 | int id; 57 | 58 | b2BodyType type; 59 | 60 | // This is monotonically advanced when a body is allocated in this slot 61 | // Used to check for invalid b2BodyId 62 | uint16_t revision; 63 | 64 | bool enableSleep; 65 | bool fixedRotation; 66 | bool isSpeedCapped; 67 | bool isMarked; 68 | bool automaticMass; 69 | } b2Body; 70 | 71 | // The body state is designed for fast conversion to and from SIMD via scatter-gather. 72 | // Only awake dynamic and kinematic bodies have a body state. 73 | // This is used in the performance critical constraint solver 74 | // 75 | // 32 bytes 76 | typedef struct b2BodyState 77 | { 78 | b2Vec2 linearVelocity; // 8 79 | float angularVelocity; // 4 80 | int flags; // 4 81 | 82 | // Using delta position reduces round-off error far from the origin 83 | b2Vec2 deltaPosition; // 8 84 | 85 | // Using delta rotation because I cannot access the full rotation on static bodies in 86 | // the solver and must use zero delta rotation for static bodies (c,s) = (1,0) 87 | b2Rot deltaRotation; // 8 88 | } b2BodyState; 89 | 90 | // Identity body state, notice the deltaRotation is {1, 0} 91 | static const b2BodyState b2_identityBodyState = { { 0.0f, 0.0f }, 0.0f, 0, { 0.0f, 0.0f }, { 1.0f, 0.0f } }; 92 | 93 | // Body simulation data used for integration of position and velocity 94 | // Transform data used for collision and solver preparation. 95 | typedef struct b2BodySim 96 | { 97 | // todo better to have transform in sim or in base body? Try both! 98 | // transform for body origin 99 | b2Transform transform; 100 | 101 | // center of mass position in world space 102 | b2Vec2 center; 103 | 104 | // previous rotation and COM for TOI 105 | b2Rot rotation0; 106 | b2Vec2 center0; 107 | 108 | // location of center of mass relative to the body origin 109 | b2Vec2 localCenter; 110 | 111 | b2Vec2 force; 112 | float torque; 113 | 114 | float mass, invMass; 115 | 116 | // Rotational inertia about the center of mass. 117 | float inertia, invInertia; 118 | 119 | float minExtent; 120 | float maxExtent; 121 | float linearDamping; 122 | float angularDamping; 123 | float gravityScale; 124 | 125 | // body data can be moved around, the id is stable (used in b2BodyId) 126 | int bodyId; 127 | 128 | // todo eliminate 129 | bool isFast; 130 | bool isBullet; 131 | bool isSpeedCapped; 132 | bool enlargeAABB; 133 | } b2BodySim; 134 | 135 | b2Body* b2GetBodyFullId( b2World* world, b2BodyId bodyId ); 136 | 137 | b2Body* b2GetBody( b2World* world, int bodyId ); 138 | 139 | // Get a validated body from a world using an id. 140 | b2Body* b2GetBodyFullId( b2World* world, b2BodyId bodyId ); 141 | 142 | b2Transform b2GetBodyTransformQuick( b2World* world, b2Body* body ); 143 | b2Transform b2GetBodyTransform( b2World* world, int bodyId ); 144 | 145 | // Create a b2BodyId from a raw id. 146 | b2BodyId b2MakeBodyId( b2World* world, int bodyId ); 147 | 148 | bool b2ShouldBodiesCollide( b2World* world, b2Body* bodyA, b2Body* bodyB ); 149 | bool b2IsBodyAwake( b2World* world, b2Body* body ); 150 | 151 | b2BodySim* b2GetBodySim( b2World* world, b2Body* body ); 152 | b2BodyState* b2GetBodyState( b2World* world, b2Body* body ); 153 | 154 | // careful calling this because it can invalidate body, state, joint, and contact pointers 155 | bool b2WakeBody( b2World* world, b2Body* body ); 156 | 157 | void b2UpdateBodyMassData( b2World* world, b2Body* body ); 158 | 159 | static inline b2Sweep b2MakeSweep( const b2BodySim* bodySim ) 160 | { 161 | b2Sweep s; 162 | s.c1 = bodySim->center0; 163 | s.c2 = bodySim->center; 164 | s.q1 = bodySim->rotation0; 165 | s.q2 = bodySim->transform.q; 166 | s.localCenter = bodySim->localCenter; 167 | return s; 168 | } 169 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 b2StackAllocator b2StackAllocator; 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 | int proxyCount; 30 | 31 | // The move set and array are used to track shapes that have moved significantly 32 | // and need a pair query for new contacts. The array has a deterministic order. 33 | // todo perhaps just a move set? 34 | // todo implement a 32bit hash set for faster lookup 35 | // todo moveSet can grow quite large on the first time step and remain large 36 | b2HashSet moveSet; 37 | int* moveArray; 38 | 39 | // These are the results from the pair query and are used to create new contacts 40 | // in deterministic order. 41 | // todo these could be in the step context 42 | b2MoveResult* moveResults; 43 | b2MovePair* movePairs; 44 | int movePairCapacity; 45 | _Atomic int movePairIndex; 46 | 47 | // Tracks shape pairs that have a b2Contact 48 | // todo pairSet can grow quite large on the first time step and remain large 49 | b2HashSet pairSet; 50 | 51 | } b2BroadPhase; 52 | 53 | void b2CreateBroadPhase( b2BroadPhase* bp ); 54 | void b2DestroyBroadPhase( b2BroadPhase* bp ); 55 | 56 | int b2BroadPhase_CreateProxy( b2BroadPhase* bp, b2BodyType proxyType, b2AABB aabb, uint32_t categoryBits, int shapeIndex, 57 | bool forcePairCreation ); 58 | void b2BroadPhase_DestroyProxy( b2BroadPhase* bp, int proxyKey ); 59 | 60 | void b2BroadPhase_MoveProxy( b2BroadPhase* bp, int proxyKey, b2AABB aabb ); 61 | void b2BroadPhase_EnlargeProxy( b2BroadPhase* bp, int proxyKey, b2AABB aabb ); 62 | 63 | void b2BroadPhase_RebuildTrees( b2BroadPhase* bp ); 64 | 65 | int b2BroadPhase_GetShapeIndex( b2BroadPhase* bp, int proxyKey ); 66 | 67 | void b2UpdateBroadPhasePairs( b2World* world ); 68 | bool b2BroadPhase_TestOverlap( const b2BroadPhase* bp, int proxyKeyA, int proxyKeyB ); 69 | 70 | void b2ValidateBroadphase( const b2BroadPhase* bp ); 71 | void b2ValidateNoEnlarged( const b2BroadPhase* bp ); 72 | 73 | // This is what triggers new contact pairs to be created 74 | // Warning: this must be called in deterministic order 75 | static inline void b2BufferMove( b2BroadPhase* bp, int queryProxy ) 76 | { 77 | // Adding 1 because 0 is the sentinel 78 | bool alreadyAdded = b2AddKey( &bp->moveSet, queryProxy + 1 ); 79 | if ( alreadyAdded == false ) 80 | { 81 | b2Array_Push( bp->moveArray, queryProxy ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/constraint_graph.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include "bitset.h" 7 | #include "block_array.h" 8 | 9 | typedef struct b2Body b2Body; 10 | typedef struct b2ContactSim b2ContactSim; 11 | typedef struct b2Contact b2Contact; 12 | typedef struct b2ContactConstraint b2ContactConstraint; 13 | typedef struct b2ContactConstraintSIMD b2ContactConstraintSIMD; 14 | typedef struct b2JointSim b2JointSim; 15 | typedef struct b2Joint b2Joint; 16 | typedef struct b2StepContext b2StepContext; 17 | typedef struct b2World b2World; 18 | 19 | // This holds constraints that cannot fit the graph color limit. This happens when a single dynamic body 20 | // is touching many other bodies. 21 | #define b2_overflowIndex b2_graphColorCount - 1 22 | 23 | typedef struct b2GraphColor 24 | { 25 | // base on bodyId so this is over-sized to encompass static bodies 26 | // however I never traverse these bits or use the bit count for anything 27 | b2BitSet bodySet; 28 | 29 | // cache friendly arrays 30 | b2ContactArray contacts; 31 | b2JointArray joints; 32 | 33 | // transient 34 | union 35 | { 36 | b2ContactConstraintSIMD* simdConstraints; 37 | b2ContactConstraint* overflowConstraints; 38 | }; 39 | } b2GraphColor; 40 | 41 | typedef struct b2ConstraintGraph 42 | { 43 | // including overflow at the end 44 | b2GraphColor colors[b2_graphColorCount]; 45 | } b2ConstraintGraph; 46 | 47 | void b2CreateGraph( b2ConstraintGraph* graph, int bodyCapacity ); 48 | void b2DestroyGraph( b2ConstraintGraph* graph ); 49 | 50 | void b2AddContactToGraph( b2World* world, b2ContactSim* contactSim, b2Contact* contact ); 51 | void b2RemoveContactFromGraph( b2World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex ); 52 | 53 | b2JointSim* b2CreateJointInGraph( b2World* world, b2Joint* joint ); 54 | void b2AddJointToGraph( b2World* world, b2JointSim* jointSim, b2Joint* joint ); 55 | void b2RemoveJointFromGraph( b2World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex ); 56 | -------------------------------------------------------------------------------- /src/contact.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/collision.h" 9 | #include "box2d/types.h" 10 | 11 | typedef struct b2Shape b2Shape; 12 | typedef struct b2World b2World; 13 | 14 | enum b2ContactFlags 15 | { 16 | // Set when the solid shapes are touching. 17 | b2_contactTouchingFlag = 0x00000001, 18 | 19 | // Contact has a hit event 20 | b2_contactHitEventFlag = 0x00000002, 21 | 22 | // One of the shapes is a sensor 23 | b2_contactSensorFlag = 0x0000004, 24 | 25 | // Set when a sensor is touching 26 | // todo this is not used, perhaps have b2Body_GetSensorContactData() 27 | b2_contactSensorTouchingFlag = 0x00000008, 28 | 29 | // This contact wants sensor events 30 | b2_contactEnableSensorEvents = 0x00000010, 31 | 32 | // This contact wants contact events 33 | b2_contactEnableContactEvents = 0x00000020, 34 | }; 35 | 36 | // A contact edge is used to connect bodies and contacts together 37 | // in a contact graph where each body is a node and each contact 38 | // is an edge. A contact edge belongs to a doubly linked list 39 | // maintained in each attached body. Each contact has two contact 40 | // edges, one for each attached body. 41 | typedef struct b2ContactEdge 42 | { 43 | int bodyId; 44 | int prevKey; 45 | int nextKey; 46 | } b2ContactEdge; 47 | 48 | // Cold contact data. Used as a persistent handle and for persistent island 49 | // connectivity. 50 | typedef struct b2Contact 51 | { 52 | // index of simulation set stored in b2World 53 | // B2_NULL_INDEX when slot is free 54 | int setIndex; 55 | 56 | // index into the constraint graph color array 57 | // B2_NULL_INDEX for non-touching or sleeping contacts 58 | // B2_NULL_INDEX when slot is free 59 | int colorIndex; 60 | 61 | // contact index within set or graph color 62 | // B2_NULL_INDEX when slot is free 63 | int localIndex; 64 | 65 | b2ContactEdge edges[2]; 66 | int shapeIdA; 67 | int shapeIdB; 68 | 69 | // A contact only belongs to an island if touching, otherwise B2_NULL_INDEX. 70 | int islandPrev; 71 | int islandNext; 72 | int islandId; 73 | 74 | int contactId; 75 | 76 | // b2ContactFlags 77 | uint32_t flags; 78 | 79 | bool isMarked; 80 | } b2Contact; 81 | 82 | // Shifted to be distinct from b2ContactFlags 83 | enum b2ContactSimFlags 84 | { 85 | // Set when the shapes are touching, including sensors 86 | b2_simTouchingFlag = 0x00010000, 87 | 88 | // This contact no longer has overlapping AABBs 89 | b2_simDisjoint = 0x00020000, 90 | 91 | // This contact started touching 92 | b2_simStartedTouching = 0x00040000, 93 | 94 | // This contact stopped touching 95 | b2_simStoppedTouching = 0x00080000, 96 | 97 | // This contact has a hit event 98 | b2_simEnableHitEvent = 0x00100000, 99 | 100 | // This contact wants pre-solve events 101 | b2_simEnablePreSolveEvents = 0x00200000, 102 | }; 103 | 104 | /// The class manages contact between two shapes. A contact exists for each overlapping 105 | /// AABB in the broad-phase (except if filtered). Therefore a contact object may exist 106 | /// that has no contact points. 107 | typedef struct b2ContactSim 108 | { 109 | int contactId; 110 | 111 | #if B2_VALIDATE 112 | int bodyIdA; 113 | int bodyIdB; 114 | #endif 115 | 116 | int bodySimIndexA; 117 | int bodySimIndexB; 118 | 119 | int shapeIdA; 120 | int shapeIdB; 121 | 122 | float invMassA; 123 | float invIA; 124 | 125 | float invMassB; 126 | float invIB; 127 | 128 | b2Manifold manifold; 129 | 130 | // Mixed friction and restitution 131 | float friction; 132 | float restitution; 133 | 134 | // todo for conveyor belts 135 | float tangentSpeed; 136 | 137 | // b2ContactSimFlags 138 | uint32_t simFlags; 139 | 140 | b2DistanceCache cache; 141 | } b2ContactSim; 142 | 143 | void b2InitializeContactRegisters( void ); 144 | 145 | void b2CreateContact( b2World* world, b2Shape* shapeA, b2Shape* shapeB ); 146 | void b2DestroyContact( b2World* world, b2Contact* contact, bool wakeBodies ); 147 | 148 | b2ContactSim* b2GetContactSim( b2World* world, b2Contact* contact ); 149 | 150 | bool b2ShouldShapesCollide( b2Filter filterA, b2Filter filterB ); 151 | 152 | bool b2UpdateContact( b2World* world, b2ContactSim* contactSim, b2Shape* shapeA, b2Transform transformA, b2Vec2 centerOffsetA, 153 | b2Shape* shapeB, b2Transform transformB, b2Vec2 centerOffsetB ); 154 | -------------------------------------------------------------------------------- /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 | #include "x86/avx.h" 8 | 9 | typedef struct b2ContactSim b2ContactSim; 10 | 11 | typedef struct b2ContactConstraintPoint 12 | { 13 | b2Vec2 anchorA, anchorB; 14 | float baseSeparation; 15 | float relativeVelocity; 16 | float normalImpulse; 17 | float tangentImpulse; 18 | float maxNormalImpulse; 19 | float normalMass; 20 | float tangentMass; 21 | } b2ContactConstraintPoint; 22 | 23 | typedef struct b2ContactConstraint 24 | { 25 | int indexA; 26 | int indexB; 27 | b2ContactConstraintPoint points[2]; 28 | b2Vec2 normal; 29 | float invMassA, invMassB; 30 | float invIA, invIB; 31 | float friction; 32 | float restitution; 33 | b2Softness softness; 34 | int pointCount; 35 | } b2ContactConstraint; 36 | 37 | // Wide float 38 | typedef simde__m256 b2FloatW; 39 | 40 | // Wide vec2 41 | typedef struct b2Vec2W 42 | { 43 | b2FloatW X, Y; 44 | } b2Vec2W; 45 | 46 | // Wide rotation 47 | typedef struct b2RotW 48 | { 49 | b2FloatW S, C; 50 | } b2RotW; 51 | 52 | typedef struct b2ContactConstraintSIMD 53 | { 54 | int indexA[8]; 55 | int indexB[8]; 56 | 57 | b2FloatW invMassA, invMassB; 58 | b2FloatW invIA, invIB; 59 | b2Vec2W normal; 60 | b2FloatW friction; 61 | b2FloatW biasRate; 62 | b2FloatW massScale; 63 | b2FloatW impulseScale; 64 | b2Vec2W anchorA1, anchorB1; 65 | b2FloatW normalMass1, tangentMass1; 66 | b2FloatW baseSeparation1; 67 | b2FloatW normalImpulse1; 68 | b2FloatW maxNormalImpulse1; 69 | b2FloatW tangentImpulse1; 70 | b2Vec2W anchorA2, anchorB2; 71 | b2FloatW baseSeparation2; 72 | b2FloatW normalImpulse2; 73 | b2FloatW maxNormalImpulse2; 74 | b2FloatW tangentImpulse2; 75 | b2FloatW normalMass2, tangentMass2; 76 | b2FloatW restitution; 77 | b2FloatW relativeVelocity1, relativeVelocity2; 78 | } b2ContactConstraintSIMD; 79 | 80 | // Overflow contacts don't fit into the constraint graph coloring 81 | void b2PrepareOverflowContacts( b2StepContext* context ); 82 | void b2WarmStartOverflowContacts( b2StepContext* context ); 83 | void b2SolveOverflowContacts( b2StepContext* context, bool useBias ); 84 | void b2ApplyOverflowRestitution( b2StepContext* context ); 85 | void b2StoreOverflowImpulses( b2StepContext* context ); 86 | 87 | // Contacts that live within the constraint graph coloring 88 | void b2PrepareContactsTask( int startIndex, int endIndex, b2StepContext* context ); 89 | void b2WarmStartContactsTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex ); 90 | void b2SolveContactsTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex, bool useBias ); 91 | void b2ApplyRestitutionTask( int startIndex, int endIndex, b2StepContext* context, int colorIndex ); 92 | void b2StoreImpulsesTask( int startIndex, int endIndex, b2StepContext* context ); 93 | -------------------------------------------------------------------------------- /src/core.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "core.h" 5 | 6 | #include "box2d/math_functions.h" 7 | 8 | #include 9 | 10 | float b2_lengthUnitsPerMeter = 1.0f; 11 | 12 | void b2SetLengthUnitsPerMeter( float lengthUnits ) 13 | { 14 | B2_ASSERT( b2IsValid( lengthUnits ) && lengthUnits > 0.0f ); 15 | b2_lengthUnitsPerMeter = lengthUnits; 16 | } 17 | 18 | float b2GetLengthUnitsPerMeter( void ) 19 | { 20 | return b2_lengthUnitsPerMeter; 21 | } 22 | 23 | static int b2DefaultAssertFcn( const char* condition, const char* fileName, int lineNumber ) 24 | { 25 | printf( "BOX2D ASSERTION: %s, %s, line %d\n", condition, fileName, lineNumber ); 26 | 27 | // return non-zero to break to debugger 28 | return 1; 29 | } 30 | 31 | b2AssertFcn* b2AssertHandler = b2DefaultAssertFcn; 32 | 33 | void b2SetAssertFcn( b2AssertFcn* assertFcn ) 34 | { 35 | B2_ASSERT( assertFcn != NULL ); 36 | b2AssertHandler = assertFcn; 37 | } 38 | 39 | b2Version b2GetVersion( void ) 40 | { 41 | return ( b2Version ){ 3, 0, 0 }; 42 | } 43 | -------------------------------------------------------------------------------- /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 | #include "array.h" 7 | 8 | b2IdPool b2CreateIdPool() 9 | { 10 | b2IdPool pool = { 0 }; 11 | pool.freeArray = b2CreateArray( sizeof( int ), 32 ); 12 | return pool; 13 | } 14 | 15 | void b2DestroyIdPool( b2IdPool* pool ) 16 | { 17 | b2DestroyArray( pool->freeArray, sizeof( int ) ); 18 | *pool = ( b2IdPool ){ 0 }; 19 | } 20 | 21 | int b2AllocId( b2IdPool* pool ) 22 | { 23 | if ( b2Array( pool->freeArray ).count > 0 ) 24 | { 25 | int id = b2Array_Last( pool->freeArray ); 26 | b2Array_Pop( pool->freeArray ); 27 | return id; 28 | } 29 | 30 | int id = pool->nextIndex; 31 | pool->nextIndex += 1; 32 | return id; 33 | } 34 | 35 | void b2FreeId( b2IdPool* pool, int id ) 36 | { 37 | B2_ASSERT( pool->nextIndex > 0 ); 38 | B2_ASSERT( 0 <= id && id < pool->nextIndex ); 39 | 40 | if ( id == pool->nextIndex ) 41 | { 42 | pool->nextIndex -= 1; 43 | return; 44 | } 45 | 46 | b2Array_Push( pool->freeArray, id ); 47 | } 48 | 49 | #if B2_VALIDATE 50 | 51 | void b2ValidateFreeId( b2IdPool* pool, int id ) 52 | { 53 | int freeCount = b2Array( pool->freeArray ).count; 54 | for ( int i = 0; i < freeCount; ++i ) 55 | { 56 | if ( pool->freeArray[i] == id ) 57 | { 58 | return; 59 | } 60 | } 61 | 62 | B2_ASSERT( 0 ); 63 | } 64 | 65 | #else 66 | 67 | void b2ValidateFreeId( b2IdPool* pool, int id ) 68 | { 69 | B2_MAYBE_UNUSED( pool ); 70 | B2_MAYBE_UNUSED( id ); 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /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 | int* freeArray; 11 | int nextIndex; 12 | } b2IdPool; 13 | 14 | b2IdPool b2CreateIdPool(); 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 | 21 | static inline int b2GetIdCount( b2IdPool* pool ) 22 | { 23 | return pool->nextIndex - b2Array( pool->freeArray ).count; 24 | } 25 | 26 | static inline int b2GetIdCapacity( b2IdPool* pool ) 27 | { 28 | return pool->nextIndex; 29 | } 30 | 31 | static inline int b2GetIdBytes( b2IdPool* pool ) 32 | { 33 | return b2GetArrayBytes( pool->freeArray, sizeof( int ) ); 34 | } 35 | -------------------------------------------------------------------------------- /src/island.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | typedef struct b2Body b2Body; 9 | typedef struct b2Contact b2Contact; 10 | typedef struct b2Joint b2Joint; 11 | typedef struct b2StepContext b2StepContext; 12 | typedef struct b2World b2World; 13 | 14 | // Deterministic solver 15 | // 16 | // Collide all awake contacts 17 | // Use bit array to emit start/stop touching events in defined order, per thread. Try using contact index, assuming contacts are 18 | // created in a deterministic order. bit-wise OR together bit arrays and issue changes: 19 | // - start touching: merge islands - temporary linked list - mark root island dirty - wake all - largest island is root 20 | // - stop touching: mark island dirty - wake island 21 | // Reserve island jobs 22 | // - island job does a DFS to merge/split islands. Mutex to allocate new islands. Split islands sent to different jobs. 23 | 24 | // Persistent island for awake bodies, joints, and contacts 25 | // https://en.wikipedia.org/wiki/Component_(graph_theory) 26 | // https://en.wikipedia.org/wiki/Dynamic_connectivity 27 | // map from int to solver set and index 28 | typedef struct b2Island 29 | { 30 | // index of solver set stored in b2World 31 | // may be B2_NULL_INDEX 32 | int setIndex; 33 | 34 | // island index within set 35 | // may be B2_NULL_INDEX 36 | int localIndex; 37 | 38 | int islandId; 39 | 40 | int headBody; 41 | int tailBody; 42 | int bodyCount; 43 | 44 | int headContact; 45 | int tailContact; 46 | int contactCount; 47 | 48 | int headJoint; 49 | int tailJoint; 50 | int jointCount; 51 | 52 | // Union find 53 | int parentIsland; 54 | 55 | // Keeps track of how many contacts have been removed from this island. 56 | int constraintRemoveCount; 57 | } b2Island; 58 | 59 | typedef struct b2IslandSim 60 | { 61 | int islandId; 62 | 63 | } b2IslandSim; 64 | 65 | b2Island* b2CreateIsland( b2World* world, int setIndex ); 66 | void b2DestroyIsland( b2World* world, int islandId ); 67 | 68 | b2Island* b2GetIsland( b2World* world, int islandId ); 69 | 70 | // Link contacts into the island graph when it starts having contact points 71 | void b2LinkContact( b2World* world, b2Contact* contact ); 72 | 73 | // Unlink contact from the island graph when it stops having contact points 74 | void b2UnlinkContact( b2World* world, b2Contact* contact ); 75 | 76 | // Link a joint into the island graph when it is created 77 | void b2LinkJoint( b2World* world, b2Joint* joint ); 78 | 79 | // Unlink a joint from the island graph when it is destroyed 80 | void b2UnlinkJoint( b2World* world, b2Joint* joint ); 81 | 82 | void b2MergeAwakeIslands( b2World* world ); 83 | 84 | void b2SplitIsland( b2World* world, int baseId ); 85 | void b2SplitIslandTask( int startIndex, int endIndex, uint32_t threadIndex, void* context ); 86 | 87 | void b2ValidateIsland( b2World* world, int islandId ); 88 | -------------------------------------------------------------------------------- /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 "core.h" 7 | 8 | #include 9 | 10 | bool b2IsValid( 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 b2Vec2_IsValid( 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 b2Rot_IsValid( 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 b2IsNormalized( q ); 53 | } 54 | 55 | b2Vec2 b2Normalize( b2Vec2 v ) 56 | { 57 | float length = b2Length( v ); 58 | if ( length < FLT_EPSILON ) 59 | { 60 | return b2Vec2_zero; 61 | } 62 | 63 | float invLength = 1.0f / length; 64 | b2Vec2 n = { invLength * v.x, invLength * v.y }; 65 | return n; 66 | } 67 | 68 | b2Vec2 b2NormalizeChecked( b2Vec2 v ) 69 | { 70 | float length = b2Length( v ); 71 | if ( length < FLT_EPSILON ) 72 | { 73 | B2_ASSERT( false ); 74 | return b2Vec2_zero; 75 | } 76 | 77 | float invLength = 1.0f / length; 78 | b2Vec2 n = { invLength * v.x, invLength * v.y }; 79 | return n; 80 | } 81 | 82 | b2Vec2 b2GetLengthAndNormalize( float* length, b2Vec2 v ) 83 | { 84 | *length = b2Length( v ); 85 | if ( *length < FLT_EPSILON ) 86 | { 87 | return b2Vec2_zero; 88 | } 89 | 90 | float invLength = 1.0f / *length; 91 | b2Vec2 n = { invLength * v.x, invLength * v.y }; 92 | return n; 93 | } 94 | -------------------------------------------------------------------------------- /src/shape.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include "world.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 | b2ShapeType type; 20 | float density; 21 | float friction; 22 | float restitution; 23 | 24 | b2AABB aabb; 25 | b2AABB fatAABB; 26 | b2Vec2 localCentroid; 27 | int proxyKey; 28 | 29 | b2Filter filter; 30 | void* userData; 31 | uint32_t customColor; 32 | 33 | union 34 | { 35 | b2Capsule capsule; 36 | b2Circle circle; 37 | b2Polygon polygon; 38 | b2Segment segment; 39 | b2SmoothSegment smoothSegment; 40 | }; 41 | 42 | uint16_t revision; 43 | bool isSensor; 44 | bool enableSensorEvents; 45 | bool enableContactEvents; 46 | bool enableHitEvents; 47 | bool enablePreSolveEvents; 48 | bool enlargedAABB; 49 | bool isFast; 50 | } b2Shape; 51 | 52 | typedef struct b2ChainShape 53 | { 54 | int id; 55 | int bodyId; 56 | int nextChainId; 57 | int* shapeIndices; 58 | int count; 59 | uint16_t revision; 60 | } b2ChainShape; 61 | 62 | typedef struct b2ShapeExtent 63 | { 64 | float minExtent; 65 | float maxExtent; 66 | } b2ShapeExtent; 67 | 68 | void b2CreateShapeProxy( b2Shape* shape, b2BroadPhase* bp, b2BodyType type, b2Transform transform, bool forcePairCreation ); 69 | void b2DestroyShapeProxy( b2Shape* shape, b2BroadPhase* bp ); 70 | 71 | b2MassData b2ComputeShapeMass( const b2Shape* shape ); 72 | b2ShapeExtent b2ComputeShapeExtent( const b2Shape* shape, b2Vec2 localCenter ); 73 | b2AABB b2ComputeShapeAABB( const b2Shape* shape, b2Transform transform ); 74 | b2Vec2 b2GetShapeCentroid( const b2Shape* shape ); 75 | float b2GetShapePerimeter( const b2Shape* shape ); 76 | 77 | b2DistanceProxy b2MakeShapeDistanceProxy( const b2Shape* shape ); 78 | 79 | b2CastOutput b2RayCastShape( const b2RayCastInput* input, const b2Shape* shape, b2Transform transform ); 80 | b2CastOutput b2ShapeCastShape( const b2ShapeCastInput* input, const b2Shape* shape, b2Transform transform ); 81 | 82 | b2Transform b2GetOwnerTransform( b2World* world, b2Shape* shape ); 83 | -------------------------------------------------------------------------------- /src/solver.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include "block_array.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 | _Atomic int 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 | _Atomic int 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 | 95 | struct b2World* world; 96 | struct b2ConstraintGraph* graph; 97 | 98 | // shortcut to body states from awake set 99 | b2BodyState* states; 100 | 101 | // shortcut to body sims from awake set 102 | b2BodySim* sims; 103 | 104 | // array of all shape ids for shapes that have enlarged AABBs 105 | int* enlargedShapes; 106 | int enlargedShapeCount; 107 | 108 | // Array of fast bodies that need continuous collision handling 109 | int* fastBodies; 110 | _Atomic int fastBodyCount; 111 | 112 | // Array of bullet bodies that need continuous collision handling 113 | int* bulletBodies; 114 | _Atomic int bulletBodyCount; 115 | 116 | // joint pointers for simplified parallel-for access. 117 | b2JointSim** joints; 118 | 119 | // contact pointers for simplified parallel-for access. 120 | // - parallel-for collide with no gaps 121 | // - parallel-for prepare and store contacts with NULL gaps for SIMD remainders 122 | // despite being an array of pointers, these are contiguous sub-arrays corresponding 123 | // to constraint graph colors 124 | b2ContactSim** contacts; 125 | 126 | struct b2ContactConstraintSIMD* simdContactConstraints; 127 | int activeColorCount; 128 | int workerCount; 129 | 130 | b2SolverStage* stages; 131 | int stageCount; 132 | bool enableWarmStarting; 133 | 134 | // todo padding to prevent false sharing 135 | char dummy1[64]; 136 | 137 | // sync index (16-bits) | stage type (16-bits) 138 | _Atomic unsigned int atomicSyncBits; 139 | 140 | char dummy2[64]; 141 | 142 | } b2StepContext; 143 | 144 | static inline b2Softness b2MakeSoft( float hertz, float zeta, float h ) 145 | { 146 | if ( hertz == 0.0f ) 147 | { 148 | return ( b2Softness ){ 0.0f, 1.0f, 0.0f }; 149 | } 150 | 151 | float omega = 2.0f * b2_pi * hertz; 152 | float a1 = 2.0f * zeta + h * omega; 153 | float a2 = h * omega * a1; 154 | float a3 = 1.0f / ( 1.0f + a2 ); 155 | return ( b2Softness ){ omega / a1, a2 * a3, a3 }; 156 | } 157 | 158 | void b2Solve( b2World* world, b2StepContext* stepContext ); 159 | -------------------------------------------------------------------------------- /src/solver_set.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include "block_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 sims; 23 | 24 | // Body state only exists for active set 25 | b2BodyStateArray states; 26 | 27 | // This holds sleeping/disabled joints. Empty for static/active set. 28 | b2JointArray joints; 29 | 30 | // This holds all contacts for sleeping sets. 31 | // This holds non-touching contacts for the awake set. 32 | b2ContactArray contacts; 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 | b2IslandArray islands; 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 | -------------------------------------------------------------------------------- /src/stack_allocator.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "stack_allocator.h" 5 | 6 | #include "allocate.h" 7 | #include "array.h" 8 | #include "core.h" 9 | 10 | #include 11 | 12 | typedef struct b2StackEntry 13 | { 14 | char* data; 15 | const char* name; 16 | int size; 17 | bool usedMalloc; 18 | } b2StackEntry; 19 | 20 | b2StackAllocator b2CreateStackAllocator( int capacity ) 21 | { 22 | B2_ASSERT( capacity >= 0 ); 23 | b2StackAllocator allocator = { 0 }; 24 | allocator.capacity = capacity; 25 | allocator.data = b2Alloc( capacity ); 26 | allocator.allocation = 0; 27 | allocator.maxAllocation = 0; 28 | allocator.index = 0; 29 | allocator.entries = b2CreateArray( sizeof( b2StackEntry ), 32 ); 30 | return allocator; 31 | } 32 | 33 | void b2DestroyStackAllocator( b2StackAllocator* allocator ) 34 | { 35 | b2DestroyArray( allocator->entries, sizeof( b2StackEntry ) ); 36 | b2Free( allocator->data, allocator->capacity ); 37 | } 38 | 39 | void* b2AllocateStackItem( b2StackAllocator* alloc, int size, const char* name ) 40 | { 41 | // ensure allocation is 32 byte aligned to support 256-bit SIMD 42 | int size32 = ( ( size - 1 ) | 0x1F ) + 1; 43 | 44 | b2StackEntry entry; 45 | entry.size = size32; 46 | entry.name = name; 47 | if ( alloc->index + size32 > alloc->capacity ) 48 | { 49 | // fall back to the heap (undesirable) 50 | entry.data = b2Alloc( size32 ); 51 | entry.usedMalloc = true; 52 | 53 | B2_ASSERT( ( (uintptr_t)entry.data & 0x1F ) == 0 ); 54 | } 55 | else 56 | { 57 | entry.data = alloc->data + alloc->index; 58 | entry.usedMalloc = false; 59 | alloc->index += size32; 60 | 61 | B2_ASSERT( ( (uintptr_t)entry.data & 0x1F ) == 0 ); 62 | } 63 | 64 | alloc->allocation += size32; 65 | if ( alloc->allocation > alloc->maxAllocation ) 66 | { 67 | alloc->maxAllocation = alloc->allocation; 68 | } 69 | 70 | b2Array_Push( alloc->entries, entry ); 71 | return entry.data; 72 | } 73 | 74 | void b2FreeStackItem( b2StackAllocator* alloc, void* mem ) 75 | { 76 | int entryCount = b2Array( alloc->entries ).count; 77 | B2_ASSERT( entryCount > 0 ); 78 | b2StackEntry* entry = alloc->entries + ( entryCount - 1 ); 79 | B2_ASSERT( mem == entry->data ); 80 | if ( entry->usedMalloc ) 81 | { 82 | b2Free( mem, entry->size ); 83 | } 84 | else 85 | { 86 | alloc->index -= entry->size; 87 | } 88 | alloc->allocation -= entry->size; 89 | b2Array_Pop( alloc->entries ); 90 | } 91 | 92 | void b2GrowStack( b2StackAllocator* alloc ) 93 | { 94 | // Stack must not be in use 95 | B2_ASSERT( alloc->allocation == 0 ); 96 | 97 | if ( alloc->maxAllocation > alloc->capacity ) 98 | { 99 | b2Free( alloc->data, alloc->capacity ); 100 | alloc->capacity = alloc->maxAllocation + alloc->maxAllocation / 2; 101 | alloc->data = b2Alloc( alloc->capacity ); 102 | } 103 | } 104 | 105 | int b2GetStackCapacity( b2StackAllocator* alloc ) 106 | { 107 | return alloc->capacity; 108 | } 109 | 110 | int b2GetStackAllocation( b2StackAllocator* alloc ) 111 | { 112 | return alloc->allocation; 113 | } 114 | 115 | int b2GetMaxStackAllocation( b2StackAllocator* alloc ) 116 | { 117 | return alloc->maxAllocation; 118 | } 119 | -------------------------------------------------------------------------------- /src/stack_allocator.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | // This is a stack-like arena allocator used for fast per step allocations. 7 | // You must nest allocate/free pairs. The code will B2_ASSERT 8 | // if you try to interleave multiple allocate/free pairs. 9 | // This allocator uses the heap if space is insufficient. 10 | // I could remove the need to free entries individually. 11 | typedef struct b2StackAllocator 12 | { 13 | char* data; 14 | int capacity; 15 | int index; 16 | 17 | int allocation; 18 | int maxAllocation; 19 | 20 | struct b2StackEntry* entries; 21 | } b2StackAllocator; 22 | 23 | b2StackAllocator b2CreateStackAllocator( int capacity ); 24 | void b2DestroyStackAllocator( b2StackAllocator* allocator ); 25 | 26 | void* b2AllocateStackItem( b2StackAllocator* alloc, int size, const char* name ); 27 | void b2FreeStackItem( b2StackAllocator* alloc, void* mem ); 28 | 29 | // Grow the stack based on usage 30 | void b2GrowStack( b2StackAllocator* alloc ); 31 | 32 | int b2GetStackCapacity( b2StackAllocator* alloc ); 33 | int b2GetStackAllocation( b2StackAllocator* alloc ); 34 | int b2GetMaxStackAllocation( b2StackAllocator* alloc ); 35 | -------------------------------------------------------------------------------- /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( int32_t 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 | #if defined( _WIN32 ) 7 | 8 | #ifndef WIN32_LEAN_AND_MEAN 9 | #define WIN32_LEAN_AND_MEAN 10 | #endif 11 | 12 | #include 13 | 14 | static double s_invFrequency = 0.0; 15 | 16 | b2Timer b2CreateTimer( void ) 17 | { 18 | LARGE_INTEGER largeInteger; 19 | 20 | if ( s_invFrequency == 0.0 ) 21 | { 22 | QueryPerformanceFrequency( &largeInteger ); 23 | 24 | s_invFrequency = (double)largeInteger.QuadPart; 25 | if ( s_invFrequency > 0.0 ) 26 | { 27 | s_invFrequency = 1000.0 / s_invFrequency; 28 | } 29 | } 30 | 31 | QueryPerformanceCounter( &largeInteger ); 32 | b2Timer timer; 33 | timer.start = largeInteger.QuadPart; 34 | return timer; 35 | } 36 | 37 | int64_t b2GetTicks( b2Timer* timer ) 38 | { 39 | LARGE_INTEGER largeInteger; 40 | QueryPerformanceCounter( &largeInteger ); 41 | int64_t ticks = largeInteger.QuadPart; 42 | int64_t count = ticks - timer->start; 43 | timer->start = ticks; 44 | return count; 45 | } 46 | 47 | float b2GetMilliseconds( const b2Timer* timer ) 48 | { 49 | LARGE_INTEGER largeInteger; 50 | QueryPerformanceCounter( &largeInteger ); 51 | int64_t count = largeInteger.QuadPart; 52 | float ms = (float)( s_invFrequency * ( count - timer->start ) ); 53 | return ms; 54 | } 55 | 56 | float b2GetMillisecondsAndReset( b2Timer* timer ) 57 | { 58 | LARGE_INTEGER largeInteger; 59 | QueryPerformanceCounter( &largeInteger ); 60 | int64_t count = largeInteger.QuadPart; 61 | float ms = (float)( s_invFrequency * ( count - timer->start ) ); 62 | timer->start = count; 63 | return ms; 64 | } 65 | 66 | void b2SleepMilliseconds( int milliseconds ) 67 | { 68 | // also SwitchToThread() 69 | Sleep( (DWORD)milliseconds ); 70 | } 71 | 72 | void b2Yield() 73 | { 74 | SwitchToThread(); 75 | } 76 | 77 | #elif defined( __linux__ ) || defined( __APPLE__ ) 78 | 79 | #include 80 | #include 81 | #include 82 | 83 | b2Timer b2CreateTimer( void ) 84 | { 85 | b2Timer timer; 86 | struct timeval t; 87 | gettimeofday( &t, 0 ); 88 | timer.start_sec = t.tv_sec; 89 | timer.start_usec = t.tv_usec; 90 | return timer; 91 | } 92 | 93 | float b2GetMilliseconds( const b2Timer* timer ) 94 | { 95 | struct timeval t; 96 | gettimeofday( &t, 0 ); 97 | time_t start_sec = timer->start_sec; 98 | suseconds_t start_usec = (suseconds_t)timer->start_usec; 99 | 100 | // http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html 101 | if ( t.tv_usec < start_usec ) 102 | { 103 | int nsec = ( start_usec - t.tv_usec ) / 1000000 + 1; 104 | start_usec -= 1000000 * nsec; 105 | start_sec += nsec; 106 | } 107 | 108 | if ( t.tv_usec - start_usec > 1000000 ) 109 | { 110 | int nsec = ( t.tv_usec - start_usec ) / 1000000; 111 | start_usec += 1000000 * nsec; 112 | start_sec -= nsec; 113 | } 114 | return 1000.0f * ( t.tv_sec - start_sec ) + 0.001f * ( t.tv_usec - start_usec ); 115 | } 116 | 117 | float b2GetMillisecondsAndReset( b2Timer* timer ) 118 | { 119 | struct timeval t; 120 | gettimeofday( &t, 0 ); 121 | time_t start_sec = timer->start_sec; 122 | suseconds_t start_usec = (suseconds_t)timer->start_usec; 123 | 124 | // http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html 125 | if ( t.tv_usec < start_usec ) 126 | { 127 | int nsec = ( start_usec - t.tv_usec ) / 1000000 + 1; 128 | start_usec -= 1000000 * nsec; 129 | start_sec += nsec; 130 | } 131 | 132 | if ( t.tv_usec - start_usec > 1000000 ) 133 | { 134 | int nsec = ( t.tv_usec - start_usec ) / 1000000; 135 | start_usec += 1000000 * nsec; 136 | start_sec -= nsec; 137 | } 138 | 139 | timer->start_sec = t.tv_sec; 140 | timer->start_usec = t.tv_usec; 141 | 142 | return 1000.0f * ( t.tv_sec - start_sec ) + 0.001f * ( t.tv_usec - start_usec ); 143 | } 144 | 145 | void b2SleepMilliseconds( int milliseconds ) 146 | { 147 | struct timespec ts; 148 | ts.tv_sec = milliseconds / 1000; 149 | ts.tv_nsec = ( milliseconds % 1000 ) * 1000000; 150 | nanosleep( &ts, NULL ); 151 | } 152 | 153 | void b2Yield() 154 | { 155 | sched_yield(); 156 | } 157 | 158 | #else 159 | 160 | b2Timer b2CreateTimer( void ) 161 | { 162 | b2Timer timer = { 0 }; 163 | return timer; 164 | } 165 | 166 | float b2GetMilliseconds( const b2Timer* timer ) 167 | { 168 | ( (void)( timer ) ); 169 | return 0.0f; 170 | } 171 | 172 | float b2GetMillisecondsAndReset( b2Timer* timer ) 173 | { 174 | ( (void)( timer ) ); 175 | return 0.0f; 176 | } 177 | 178 | void b2SleepMilliseconds( int milliseconds ) 179 | { 180 | ( (void)( milliseconds ) ); 181 | } 182 | 183 | void b2Yield() 184 | { 185 | } 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /src/types.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "box2d/types.h" 5 | 6 | #include "core.h" 7 | 8 | b2WorldDef b2DefaultWorldDef( void ) 9 | { 10 | b2WorldDef def = { 0 }; 11 | def.gravity.x = 0.0f; 12 | def.gravity.y = -10.0f; 13 | def.hitEventThreshold = 1.0f * b2_lengthUnitsPerMeter; 14 | def.restitutionThreshold = 1.0f * b2_lengthUnitsPerMeter; 15 | def.contactPushoutVelocity = 3.0f * b2_lengthUnitsPerMeter; 16 | def.contactHertz = 30.0; 17 | def.contactDampingRatio = 10.0f; 18 | def.jointHertz = 60.0; 19 | def.jointDampingRatio = 2.0f; 20 | def.enableSleep = true; 21 | def.enableContinous = true; 22 | def.internalValue = B2_SECRET_COOKIE; 23 | return def; 24 | } 25 | 26 | b2BodyDef b2DefaultBodyDef( void ) 27 | { 28 | b2BodyDef def = { 0 }; 29 | def.type = b2_staticBody; 30 | def.rotation = b2Rot_identity; 31 | def.sleepThreshold = 0.05f * b2_lengthUnitsPerMeter; 32 | def.gravityScale = 1.0f; 33 | def.enableSleep = true; 34 | def.isAwake = true; 35 | def.isEnabled = true; 36 | def.automaticMass = true; 37 | def.internalValue = B2_SECRET_COOKIE; 38 | return def; 39 | } 40 | 41 | b2Filter b2DefaultFilter( void ) 42 | { 43 | b2Filter filter = { 0x00000001, 0xFFFFFFFF, 0 }; 44 | return filter; 45 | } 46 | 47 | b2QueryFilter b2DefaultQueryFilter( void ) 48 | { 49 | b2QueryFilter filter = { 0x00000001, 0xFFFFFFFF }; 50 | return filter; 51 | } 52 | 53 | b2ShapeDef b2DefaultShapeDef( void ) 54 | { 55 | b2ShapeDef def = { 0 }; 56 | def.friction = 0.6f; 57 | def.density = 1.0f; 58 | def.filter = b2DefaultFilter(); 59 | def.enableSensorEvents = true; 60 | def.enableContactEvents = true; 61 | def.internalValue = B2_SECRET_COOKIE; 62 | return def; 63 | } 64 | 65 | b2ChainDef b2DefaultChainDef( void ) 66 | { 67 | b2ChainDef def = { 0 }; 68 | def.friction = 0.6f; 69 | def.filter = b2DefaultFilter(); 70 | def.internalValue = B2_SECRET_COOKIE; 71 | return def; 72 | } 73 | -------------------------------------------------------------------------------- /src/world.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include "bitset.h" 7 | #include "broad_phase.h" 8 | #include "constraint_graph.h" 9 | #include "id_pool.h" 10 | #include "island.h" 11 | #include "stack_allocator.h" 12 | 13 | #include "box2d/types.h" 14 | 15 | typedef struct b2ContactSim b2ContactSim; 16 | 17 | enum b2SetType 18 | { 19 | b2_staticSet = 0, 20 | b2_disabledSet = 1, 21 | b2_awakeSet = 2, 22 | b2_firstSleepingSet = 3, 23 | }; 24 | 25 | // Per thread task storage 26 | typedef struct b2TaskContext 27 | { 28 | // These bits align with the b2ConstraintGraph::contactBlocks and signal a change in contact status 29 | b2BitSet contactStateBitSet; 30 | 31 | // Used to track bodies with shapes that have enlarged AABBs. This avoids having a bit array 32 | // that is very large when there are many static shapes. 33 | b2BitSet enlargedSimBitSet; 34 | 35 | // Used to put islands to sleep 36 | b2BitSet awakeIslandBitSet; 37 | 38 | // Per worker split island candidate 39 | float splitSleepTime; 40 | int splitIslandId; 41 | 42 | } b2TaskContext; 43 | 44 | /// The world class manages all physics entities, dynamic simulation, 45 | /// and asynchronous queries. The world also contains efficient memory 46 | /// management facilities. 47 | typedef struct b2World 48 | { 49 | b2StackAllocator stackAllocator; 50 | b2BroadPhase broadPhase; 51 | b2ConstraintGraph constraintGraph; 52 | 53 | // The body id pool is used to allocate and recycle body ids. Body ids 54 | // provide a stable identifier for users, but incur caches misses when used 55 | // to access body data. Aligns with b2Body. 56 | b2IdPool bodyIdPool; 57 | 58 | // This is a sparse array that maps body ids to the body data 59 | // stored in solver sets. As sims move within a set or across set. 60 | // Indices come from id pool. 61 | struct b2Body* bodyArray; 62 | 63 | // Provides free list for solver sets. 64 | b2IdPool solverSetIdPool; 65 | 66 | // Solvers sets allow sims to be stored in contiguous arrays. The first 67 | // set is all static sims. The second set is active sims. The third set is disabled 68 | // sims. The remaining sets are sleeping islands. 69 | struct b2SolverSet* solverSetArray; 70 | 71 | // Used to create stable ids for joints 72 | b2IdPool jointIdPool; 73 | 74 | // This is a sparse array that maps joint ids to the joint data stored in the constraint graph 75 | // or in the solver sets. 76 | struct b2Joint* jointArray; 77 | 78 | // Used to create stable ids for contacts 79 | b2IdPool contactIdPool; 80 | 81 | // This is a sparse array that maps contact ids to the contact data stored in the constraint graph 82 | // or in the solver sets. 83 | struct b2Contact* contactArray; 84 | 85 | // Used to create stable ids for islands 86 | b2IdPool islandIdPool; 87 | 88 | // This is a sparse array that maps island ids to the island data stored in the solver sets. 89 | struct b2Island* islandArray; 90 | 91 | b2IdPool shapeIdPool; 92 | b2IdPool chainIdPool; 93 | 94 | // These are sparse arrays that point into the pools above 95 | struct b2Shape* shapeArray; 96 | struct b2ChainShape* chainArray; 97 | 98 | // Per thread storage 99 | b2TaskContext* taskContextArray; 100 | 101 | struct b2BodyMoveEvent* bodyMoveEventArray; 102 | struct b2SensorBeginTouchEvent* sensorBeginEventArray; 103 | struct b2SensorEndTouchEvent* sensorEndEventArray; 104 | struct b2ContactBeginTouchEvent* contactBeginArray; 105 | struct b2ContactEndTouchEvent* contactEndArray; 106 | struct b2ContactHitEvent* contactHitArray; 107 | 108 | // Used to track debug draw 109 | b2BitSet debugBodySet; 110 | b2BitSet debugJointSet; 111 | b2BitSet debugContactSet; 112 | 113 | // Id that is incremented every time step 114 | uint64_t stepIndex; 115 | 116 | // Identify islands for splitting as follows: 117 | // - I want to split islands so smaller islands can sleep 118 | // - when a body comes to rest and its sleep timer trips, I can look at the island and flag it for splitting 119 | // if it has removed constraints 120 | // - islands that have removed constraints must be put split first because I don't want to wake bodies incorrectly 121 | // - otherwise I can use the awake islands that have bodies wanting to sleep as the splitting candidates 122 | // - if no bodies want to sleep then there is no reason to perform island splitting 123 | int splitIslandId; 124 | 125 | b2Vec2 gravity; 126 | float hitEventThreshold; 127 | float restitutionThreshold; 128 | float contactPushoutVelocity; 129 | float contactHertz; 130 | float contactDampingRatio; 131 | float jointHertz; 132 | float jointDampingRatio; 133 | 134 | uint16_t revision; 135 | 136 | b2Profile profile; 137 | 138 | b2PreSolveFcn* preSolveFcn; 139 | void* preSolveContext; 140 | 141 | b2CustomFilterFcn* customFilterFcn; 142 | void* customFilterContext; 143 | 144 | int workerCount; 145 | b2EnqueueTaskCallback* enqueueTaskFcn; 146 | b2FinishTaskCallback* finishTaskFcn; 147 | void* userTaskContext; 148 | void* userTreeTask; 149 | 150 | // Remember type step used for reporting forces and torques 151 | float inv_h; 152 | 153 | int activeTaskCount; 154 | int taskCount; 155 | 156 | uint16_t worldId; 157 | 158 | bool enableSleep; 159 | bool locked; 160 | bool enableWarmStarting; 161 | bool enableContinuous; 162 | bool inUse; 163 | } b2World; 164 | 165 | b2World* b2GetWorldFromId( b2WorldId id ); 166 | b2World* b2GetWorld( int index ); 167 | b2World* b2GetWorldLocked( int index ); 168 | 169 | void b2ValidateConnectivity( b2World* world ); 170 | void b2ValidateSolverSets( b2World* world ); 171 | void b2ValidateContacts( b2World* world ); 172 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Box2D unit test app 2 | 3 | add_executable(test 4 | main.c 5 | test_bitset.c 6 | test_collision.c 7 | test_determinism.c 8 | test_distance.c 9 | test_macros.h 10 | test_math.c 11 | test_shape.c 12 | test_table.c 13 | test_world.c 14 | ) 15 | 16 | set_target_properties(test PROPERTIES 17 | C_STANDARD 17 18 | C_STANDARD_REQUIRED YES 19 | C_EXTENSIONS NO 20 | ) 21 | 22 | # Special access to Box2D internals for testing 23 | target_include_directories(test PRIVATE ${CMAKE_SOURCE_DIR}/src) 24 | 25 | if(MSVC) 26 | target_compile_options(test PRIVATE /experimental:c11atomics) 27 | endif() 28 | 29 | target_link_libraries(test PRIVATE box2d enkiTS simde) 30 | 31 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "" FILES ${BOX2D_TESTS}) 32 | -------------------------------------------------------------------------------- /test/main.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Erin Catto 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "test_macros.h" 5 | 6 | #if defined( _WIN32 ) 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 MathTest( void ); 23 | extern int CollisionTest( void ); 24 | extern int DeterminismTest( void ); 25 | extern int DistanceTest( void ); 26 | extern int WorldTest( void ); 27 | extern int ShapeTest( void ); 28 | extern int TableTest( void ); 29 | 30 | int main( void ) 31 | { 32 | #if defined( _WIN32 ) 33 | // Enable memory-leak reports 34 | 35 | // How to break at the leaking allocation, in the watch window enter this variable 36 | // and set it to the allocation number in {}. Do this at the first line in main. 37 | // {,,ucrtbased.dll}_crtBreakAlloc = 3970 38 | 39 | _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE ); 40 | _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR ); 41 | //_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)); 42 | //_CrtSetAllocHook(MyAllocHook); 43 | //_CrtSetBreakAlloc(196); 44 | #endif 45 | 46 | printf( "Starting Box2D unit tests\n" ); 47 | printf( "======================================\n" ); 48 | 49 | RUN_TEST( MathTest ); 50 | RUN_TEST( CollisionTest ); 51 | RUN_TEST( DeterminismTest ); 52 | RUN_TEST( DistanceTest ); 53 | RUN_TEST( WorldTest ); 54 | RUN_TEST( ShapeTest ); 55 | RUN_TEST( TableTest ); 56 | RUN_TEST( BitSetTest ); 57 | 58 | printf( "======================================\n" ); 59 | printf( "All Box2D tests passed!\n" ); 60 | 61 | #if defined( _WIN32 ) 62 | if ( _CrtDumpMemoryLeaks() ) 63 | { 64 | return 1; 65 | } 66 | #endif 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /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( b2AABB_IsValid( a ) == false ); 16 | 17 | a.upperBound = ( b2Vec2 ){ 1.0f, 1.0f }; 18 | ENSURE( b2AABB_IsValid( 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 "test_macros.h" 6 | 7 | #include "box2d/box2d.h" 8 | #include "box2d/math_functions.h" 9 | #include "box2d/types.h" 10 | 11 | #include 12 | 13 | #ifdef BOX2D_PROFILE 14 | #include 15 | #else 16 | #define TracyCFrameMark 17 | #endif 18 | 19 | enum 20 | { 21 | e_columns = 10, 22 | e_rows = 10, 23 | e_count = e_columns * e_rows, 24 | e_maxTasks = 128, 25 | }; 26 | 27 | b2Vec2 finalPositions[2][e_count]; 28 | b2Rot finalRotations[2][e_count]; 29 | 30 | typedef struct TaskData 31 | { 32 | b2TaskCallback* box2dTask; 33 | void* box2dContext; 34 | } TaskData; 35 | 36 | enkiTaskScheduler* scheduler; 37 | enkiTaskSet* tasks[e_maxTasks]; 38 | TaskData taskData[e_maxTasks]; 39 | int taskCount; 40 | 41 | void ExecuteRangeTask( uint32_t start, uint32_t end, uint32_t threadIndex, void* context ) 42 | { 43 | TaskData* data = context; 44 | data->box2dTask( start, end, threadIndex, data->box2dContext ); 45 | } 46 | 47 | static void* EnqueueTask( b2TaskCallback* box2dTask, int itemCount, int minRange, void* box2dContext, void* userContext ) 48 | { 49 | MAYBE_UNUSED( userContext ); 50 | 51 | if ( taskCount < e_maxTasks ) 52 | { 53 | enkiTaskSet* task = tasks[taskCount]; 54 | TaskData* data = taskData + taskCount; 55 | data->box2dTask = box2dTask; 56 | data->box2dContext = box2dContext; 57 | 58 | struct enkiParamsTaskSet params; 59 | params.minRange = minRange; 60 | params.setSize = itemCount; 61 | params.pArgs = data; 62 | params.priority = 0; 63 | 64 | enkiSetParamsTaskSet( task, params ); 65 | enkiAddTaskSet( scheduler, task ); 66 | 67 | ++taskCount; 68 | 69 | return task; 70 | } 71 | else 72 | { 73 | box2dTask( 0, itemCount, 0, box2dContext ); 74 | 75 | return NULL; 76 | } 77 | } 78 | 79 | static void FinishTask( void* userTask, void* userContext ) 80 | { 81 | MAYBE_UNUSED( userContext ); 82 | 83 | enkiTaskSet* task = userTask; 84 | enkiWaitForTaskSet( scheduler, task ); 85 | } 86 | 87 | void TiltedStacks( int testIndex, int workerCount ) 88 | { 89 | scheduler = enkiNewTaskScheduler(); 90 | struct enkiTaskSchedulerConfig config = enkiGetTaskSchedulerConfig( scheduler ); 91 | config.numTaskThreadsToCreate = workerCount - 1; 92 | enkiInitTaskSchedulerWithConfig( scheduler, config ); 93 | 94 | for ( int i = 0; i < e_maxTasks; ++i ) 95 | { 96 | tasks[i] = enkiCreateTaskSet( scheduler, ExecuteRangeTask ); 97 | } 98 | 99 | // Define the gravity vector. 100 | b2Vec2 gravity = { 0.0f, -10.0f }; 101 | 102 | // Construct a world object, which will hold and simulate the rigid bodies. 103 | b2WorldDef worldDef = b2DefaultWorldDef(); 104 | worldDef.gravity = gravity; 105 | worldDef.enqueueTask = EnqueueTask; 106 | worldDef.finishTask = FinishTask; 107 | worldDef.workerCount = workerCount; 108 | worldDef.enableSleep = false; 109 | 110 | b2WorldId worldId = b2CreateWorld( &worldDef ); 111 | 112 | b2BodyId bodies[e_count]; 113 | 114 | { 115 | b2BodyDef bd = b2DefaultBodyDef(); 116 | bd.position = ( b2Vec2 ){ 0.0f, -1.0f }; 117 | b2BodyId groundId = b2CreateBody( worldId, &bd ); 118 | 119 | b2Polygon box = b2MakeBox( 1000.0f, 1.0f ); 120 | b2ShapeDef sd = b2DefaultShapeDef(); 121 | b2CreatePolygonShape( groundId, &sd, &box ); 122 | } 123 | 124 | b2Polygon box = b2MakeRoundedBox( 0.45f, 0.45f, 0.05f ); 125 | b2ShapeDef sd = b2DefaultShapeDef(); 126 | sd.density = 1.0f; 127 | sd.friction = 0.3f; 128 | 129 | float offset = 0.2f; 130 | float dx = 5.0f; 131 | float xroot = -0.5f * dx * ( e_columns - 1.0f ); 132 | 133 | for ( int j = 0; j < e_columns; ++j ) 134 | { 135 | float x = xroot + j * dx; 136 | 137 | for ( int i = 0; i < e_rows; ++i ) 138 | { 139 | b2BodyDef bd = b2DefaultBodyDef(); 140 | bd.type = b2_dynamicBody; 141 | 142 | int n = j * e_rows + i; 143 | 144 | bd.position = ( b2Vec2 ){ x + offset * i, 0.5f + 1.0f * i }; 145 | b2BodyId bodyId = b2CreateBody( worldId, &bd ); 146 | bodies[n] = bodyId; 147 | 148 | b2CreatePolygonShape( bodyId, &sd, &box ); 149 | } 150 | } 151 | 152 | float timeStep = 1.0f / 60.0f; 153 | int subStepCount = 3; 154 | 155 | for ( int i = 0; i < 100; ++i ) 156 | { 157 | b2World_Step( worldId, timeStep, subStepCount ); 158 | taskCount = 0; 159 | TracyCFrameMark; 160 | } 161 | 162 | for ( int i = 0; i < e_count; ++i ) 163 | { 164 | finalPositions[testIndex][i] = b2Body_GetPosition( bodies[i] ); 165 | finalRotations[testIndex][i] = b2Body_GetRotation( bodies[i] ); 166 | } 167 | 168 | b2DestroyWorld( worldId ); 169 | 170 | for ( int i = 0; i < e_maxTasks; ++i ) 171 | { 172 | enkiDeleteTaskSet( scheduler, tasks[i] ); 173 | } 174 | 175 | enkiDeleteTaskScheduler( scheduler ); 176 | } 177 | 178 | // Test multithreaded determinism. 179 | int DeterminismTest( void ) 180 | { 181 | // Test 1 : 4 threads 182 | TiltedStacks( 0, 4 ); 183 | 184 | // Test 2 : 1 thread 185 | TiltedStacks( 1, 1 ); 186 | 187 | // Both runs should produce identical results 188 | for ( int i = 0; i < e_count; ++i ) 189 | { 190 | b2Vec2 p1 = finalPositions[0][i]; 191 | b2Vec2 p2 = finalPositions[1][i]; 192 | b2Rot rot1 = finalRotations[0][i]; 193 | b2Rot rot2 = finalRotations[1][i]; 194 | 195 | ENSURE( p1.x == p2.x ); 196 | ENSURE( p1.y == p2.y ); 197 | ENSURE( rot1.c == rot2.c ); 198 | ENSURE( rot1.s == rot2.s ); 199 | } 200 | 201 | return 0; 202 | } 203 | -------------------------------------------------------------------------------- /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 | b2DistanceCache cache = { 0 }; 48 | b2DistanceOutput output = b2ShapeDistance( &cache, &input, 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.tMax = 1.0f; 95 | 96 | b2TOIOutput output = b2TimeOfImpact( &input ); 97 | 98 | ENSURE( output.state == b2_toiStateHit ); 99 | ENSURE_SMALL( output.t - 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_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 | 10 | int MathTest( void ) 11 | { 12 | b2Vec2 zero = b2Vec2_zero; 13 | b2Vec2 one = { 1.0f, 1.0f }; 14 | b2Vec2 two = { 2.0f, 2.0f }; 15 | 16 | b2Vec2 v = b2Add( one, two ); 17 | ENSURE( v.x == 3.0f && v.y == 3.0f ); 18 | 19 | v = b2Sub( zero, two ); 20 | ENSURE( v.x == -2.0f && v.y == -2.0f ); 21 | 22 | v = b2Add( two, two ); 23 | ENSURE( v.x != 5.0f && v.y != 5.0f ); 24 | 25 | b2Transform transform1 = { { -2.0f, 3.0f }, b2MakeRot( 1.0f ) }; 26 | b2Transform transform2 = { { 1.0f, 0.0f }, b2MakeRot( -2.0f ) }; 27 | 28 | b2Transform transform = b2MulTransforms( transform2, transform1 ); 29 | 30 | v = b2TransformPoint( transform2, b2TransformPoint( transform1, two ) ); 31 | 32 | b2Vec2 u = b2TransformPoint( transform, two ); 33 | 34 | ENSURE_SMALL( u.x - v.x, 10.0f * FLT_EPSILON ); 35 | ENSURE_SMALL( u.y - v.y, 10.0f * FLT_EPSILON ); 36 | 37 | v = b2TransformPoint( transform1, two ); 38 | v = b2InvTransformPoint( transform1, v ); 39 | 40 | ENSURE_SMALL( v.x - two.x, 8.0f * FLT_EPSILON ); 41 | ENSURE_SMALL( v.y - two.y, 8.0f * FLT_EPSILON ); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /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 "ctz.h" 5 | #include "table.h" 6 | #include "test_macros.h" 7 | 8 | #include "box2d/base.h" 9 | 10 | #include 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 | #ifndef NDEBUG 67 | extern _Atomic int g_probeCount; 68 | g_probeCount = 0; 69 | #endif 70 | 71 | // Test key search 72 | // ~5ns per search on an AMD 7950x 73 | b2Timer timer = b2CreateTimer(); 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( &timer ); 90 | printf( "set: count = %d, b2ContainsKey = %.5f ms, ave = %.5f us\n", itemCount, ms, 1000.0f * ms / itemCount ); 91 | 92 | #if !NDEBUG 93 | float aveProbeCount = (float)g_probeCount / (float)itemCount; 94 | printf( "item count = %d, probe count = %d, ave probe count %.2f\n", itemCount, g_probeCount, aveProbeCount ); 95 | #endif 96 | 97 | // Remove all keys from set 98 | for ( int32_t i = 0; i < N; ++i ) 99 | { 100 | for ( int32_t j = i + 1; j < N; ++j ) 101 | { 102 | uint64_t key = B2_SHAPE_PAIR_KEY( i, j ); 103 | b2RemoveKey( &set, key ); 104 | } 105 | } 106 | 107 | ENSURE( set.count == 0 ); 108 | 109 | b2DestroySet( &set ); 110 | } 111 | 112 | return 0; 113 | } 114 | --------------------------------------------------------------------------------