├── .clang-format
├── .gitattributes
├── .github
├── actions
│ ├── build
│ │ └── action.yml
│ └── deps
│ │ └── action.yml
└── workflows
│ ├── android_builds.yml
│ ├── ios_builds.yml
│ ├── linux_builds.yml
│ ├── macos_builds.yml
│ ├── runner.yml
│ ├── static_checks.yml
│ └── windows_builds.yml
├── .gitignore
├── .gitmodules
├── LICENSE.txt
├── README.md
├── SConstruct
├── demo
├── icon.svg
├── icon.svg.import
├── physics_server_box2d.gdextension
├── project.godot
└── world.tscn
├── scripts
├── clang-format.sh
└── clang-tidy.sh
└── src
├── b2_user_settings.h
├── bodies
├── box2d_area.cpp
├── box2d_area.h
├── box2d_body.cpp
├── box2d_body.h
├── box2d_collision_object.cpp
├── box2d_collision_object.h
├── box2d_direct_body_state.cpp
└── box2d_direct_body_state.h
├── box2d_type_conversions.cpp
├── box2d_type_conversions.h
├── joints
├── box2d_joint.cpp
└── box2d_joint.h
├── register_types.cpp
├── register_types.h
├── servers
├── physics_server_box2d.cpp
└── physics_server_box2d.h
├── shapes
├── box2d_shape.cpp
├── box2d_shape.h
├── box2d_shape_capsule.cpp
├── box2d_shape_capsule.h
├── box2d_shape_circle.cpp
├── box2d_shape_circle.h
├── box2d_shape_concave_polygon.cpp
├── box2d_shape_concave_polygon.h
├── box2d_shape_convex_polygon.cpp
├── box2d_shape_convex_polygon.h
├── box2d_shape_rectangle.cpp
├── box2d_shape_rectangle.h
├── box2d_shape_segment.cpp
├── box2d_shape_segment.h
├── box2d_shape_separation_ray.cpp
├── box2d_shape_separation_ray.h
├── box2d_shape_world_boundary.cpp
└── box2d_shape_world_boundary.h
└── spaces
├── box2d_direct_space_state.cpp
├── box2d_direct_space_state.h
├── box2d_query_callback.cpp
├── box2d_query_callback.h
├── box2d_ray_cast_callback.cpp
├── box2d_ray_cast_callback.h
├── box2d_space.cpp
├── box2d_space.h
├── box2d_space_contact_filter.cpp
├── box2d_space_contact_filter.h
├── box2d_space_contact_listener.cpp
└── box2d_space_contact_listener.h
/.clang-format:
--------------------------------------------------------------------------------
1 | # Commented out parameters are those with the same value as base LLVM style.
2 | # We can uncomment them if we want to change their value, or enforce the
3 | # chosen value in case the base style changes (last sync: Clang 14.0).
4 | ---
5 | ### General config, applies to all languages ###
6 | BasedOnStyle: LLVM
7 | AccessModifierOffset: -4
8 | AlignAfterOpenBracket: DontAlign
9 | # AlignArrayOfStructures: None
10 | # AlignConsecutiveMacros: None
11 | # AlignConsecutiveAssignments: None
12 | # AlignConsecutiveBitFields: None
13 | # AlignConsecutiveDeclarations: None
14 | # AlignEscapedNewlines: Right
15 | AlignOperands: DontAlign
16 | AlignTrailingComments: false
17 | # AllowAllArgumentsOnNextLine: true
18 | AllowAllParametersOfDeclarationOnNextLine: false
19 | # AllowShortEnumsOnASingleLine: true
20 | # AllowShortBlocksOnASingleLine: Never
21 | # AllowShortCaseLabelsOnASingleLine: false
22 | # AllowShortFunctionsOnASingleLine: All
23 | # AllowShortLambdasOnASingleLine: All
24 | # AllowShortIfStatementsOnASingleLine: Never
25 | # AllowShortLoopsOnASingleLine: false
26 | # AlwaysBreakAfterDefinitionReturnType: None
27 | # AlwaysBreakAfterReturnType: None
28 | # AlwaysBreakBeforeMultilineStrings: false
29 | # AlwaysBreakTemplateDeclarations: MultiLine
30 | # AttributeMacros:
31 | # - __capability
32 | # BinPackArguments: true
33 | # BinPackParameters: true
34 | # BraceWrapping:
35 | # AfterCaseLabel: false
36 | # AfterClass: false
37 | # AfterControlStatement: Never
38 | # AfterEnum: false
39 | # AfterFunction: false
40 | # AfterNamespace: false
41 | # AfterObjCDeclaration: false
42 | # AfterStruct: false
43 | # AfterUnion: false
44 | # AfterExternBlock: false
45 | # BeforeCatch: false
46 | # BeforeElse: false
47 | # BeforeLambdaBody: false
48 | # BeforeWhile: false
49 | # IndentBraces: false
50 | # SplitEmptyFunction: true
51 | # SplitEmptyRecord: true
52 | # SplitEmptyNamespace: true
53 | # BreakBeforeBinaryOperators: None
54 | # BreakBeforeConceptDeclarations: true
55 | # BreakBeforeBraces: Attach
56 | # BreakBeforeInheritanceComma: false
57 | # BreakInheritanceList: BeforeColon
58 | # BreakBeforeTernaryOperators: true
59 | # BreakConstructorInitializersBeforeComma: false
60 | BreakConstructorInitializers: AfterColon
61 | # BreakStringLiterals: true
62 | ColumnLimit: 0
63 | # CommentPragmas: '^ IWYU pragma:'
64 | # QualifierAlignment: Leave
65 | # CompactNamespaces: false
66 | ConstructorInitializerIndentWidth: 8
67 | ContinuationIndentWidth: 8
68 | Cpp11BracedListStyle: false
69 | # DeriveLineEnding: true
70 | # DerivePointerAlignment: false
71 | # DisableFormat: false
72 | # EmptyLineAfterAccessModifier: Never
73 | # EmptyLineBeforeAccessModifier: LogicalBlock
74 | # ExperimentalAutoDetectBinPacking: false
75 | # PackConstructorInitializers: BinPack
76 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
77 | # AllowAllConstructorInitializersOnNextLine: true
78 | # FixNamespaceComments: true
79 | # ForEachMacros:
80 | # - foreach
81 | # - Q_FOREACH
82 | # - BOOST_FOREACH
83 | # IfMacros:
84 | # - KJ_IF_MAYBE
85 | # IncludeBlocks: Preserve
86 | IncludeCategories:
87 | - Regex: '".*"'
88 | Priority: 1
89 | - Regex: '^<.*\.h>'
90 | Priority: 2
91 | - Regex: '^<.*'
92 | Priority: 3
93 | # IncludeIsMainRegex: '(Test)?$'
94 | # IncludeIsMainSourceRegex: ''
95 | # IndentAccessModifiers: false
96 | IndentCaseLabels: true
97 | # IndentCaseBlocks: false
98 | # IndentGotoLabels: true
99 | # IndentPPDirectives: None
100 | # IndentExternBlock: AfterExternBlock
101 | # IndentRequires: false
102 | IndentWidth: 4
103 | # IndentWrappedFunctionNames: false
104 | # InsertTrailingCommas: None
105 | # JavaScriptQuotes: Leave
106 | # JavaScriptWrapImports: true
107 | KeepEmptyLinesAtTheStartOfBlocks: false
108 | # LambdaBodyIndentation: Signature
109 | # MacroBlockBegin: ''
110 | # MacroBlockEnd: ''
111 | # MaxEmptyLinesToKeep: 1
112 | # NamespaceIndentation: None
113 | # PenaltyBreakAssignment: 2
114 | # PenaltyBreakBeforeFirstCallParameter: 19
115 | # PenaltyBreakComment: 300
116 | # PenaltyBreakFirstLessLess: 120
117 | # PenaltyBreakOpenParenthesis: 0
118 | # PenaltyBreakString: 1000
119 | # PenaltyBreakTemplateDeclaration: 10
120 | # PenaltyExcessCharacter: 1000000
121 | # PenaltyReturnTypeOnItsOwnLine: 60
122 | # PenaltyIndentedWhitespace: 0
123 | # PointerAlignment: Right
124 | # PPIndentWidth: -1
125 | # ReferenceAlignment: Pointer
126 | # ReflowComments: true
127 | # RemoveBracesLLVM: false
128 | # SeparateDefinitionBlocks: Leave
129 | # ShortNamespaceLines: 1
130 | # SortIncludes: CaseSensitive
131 | # SortJavaStaticImport: Before
132 | # SortUsingDeclarations: true
133 | # SpaceAfterCStyleCast: false
134 | # SpaceAfterLogicalNot: false
135 | # SpaceAfterTemplateKeyword: true
136 | # SpaceBeforeAssignmentOperators: true
137 | # SpaceBeforeCaseColon: false
138 | # SpaceBeforeCpp11BracedList: false
139 | # SpaceBeforeCtorInitializerColon: true
140 | # SpaceBeforeInheritanceColon: true
141 | # SpaceBeforeParens: ControlStatements
142 | # SpaceBeforeParensOptions:
143 | # AfterControlStatements: true
144 | # AfterForeachMacros: true
145 | # AfterFunctionDefinitionName: false
146 | # AfterFunctionDeclarationName: false
147 | # AfterIfMacros: true
148 | # AfterOverloadedOperator: false
149 | # BeforeNonEmptyParentheses: false
150 | # SpaceAroundPointerQualifiers: Default
151 | # SpaceBeforeRangeBasedForLoopColon: true
152 | # SpaceInEmptyBlock: false
153 | # SpaceInEmptyParentheses: false
154 | # SpacesBeforeTrailingComments: 1
155 | # SpacesInAngles: Never
156 | # SpacesInConditionalStatement: false
157 | # SpacesInContainerLiterals: true
158 | # SpacesInCStyleCastParentheses: false
159 | ## Godot TODO: We'll want to use a min of 1, but we need to see how to fix
160 | ## our comment capitalization at the same time.
161 | SpacesInLineCommentPrefix:
162 | Minimum: 0
163 | Maximum: -1
164 | # SpacesInParentheses: false
165 | # SpacesInSquareBrackets: false
166 | # SpaceBeforeSquareBrackets: false
167 | # BitFieldColonSpacing: Both
168 | # StatementAttributeLikeMacros:
169 | # - Q_EMIT
170 | # StatementMacros:
171 | # - Q_UNUSED
172 | # - QT_REQUIRE_VERSION
173 | TabWidth: 4
174 | # UseCRLF: false
175 | UseTab: Always
176 | # WhitespaceSensitiveMacros:
177 | # - STRINGIZE
178 | # - PP_STRINGIZE
179 | # - BOOST_PP_STRINGIZE
180 | # - NS_SWIFT_NAME
181 | # - CF_SWIFT_NAME
182 | ---
183 | ### C++ specific config ###
184 | Language: Cpp
185 | Standard: c++17
186 | ---
187 | ### ObjC specific config ###
188 | Language: ObjC
189 | # ObjCBinPackProtocolList: Auto
190 | ObjCBlockIndentWidth: 4
191 | # ObjCBreakBeforeNestedBlockParam: true
192 | # ObjCSpaceAfterProperty: false
193 | # ObjCSpaceBeforeProtocolList: true
194 | ---
195 | ### Java specific config ###
196 | Language: Java
197 | # BreakAfterJavaFieldAnnotations: false
198 | JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax']
199 | ...
200 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize EOL for all files that Git considers text files.
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/.github/actions/build/action.yml:
--------------------------------------------------------------------------------
1 | name: Physics Server Box2D Build
2 | description: Build Godot Cpp and the Physics Server 2D Extension. Also uploads the artifacts.
3 |
4 | inputs:
5 | platform:
6 | required: true
7 | description: Target platform.
8 | arch:
9 | default: ''
10 | description: Target architecture.
11 | target:
12 | required: true
13 | description: Build target (editor, template_release, template_debug).
14 | sconsflags:
15 | required: true
16 | description: Extra flags
17 |
18 | runs:
19 | using: composite
20 | steps:
21 | - name: Cache .scons_cache
22 | uses: actions/cache@v3
23 | with:
24 | path: |
25 | ${{ github.workspace }}/.scons-cache/
26 | ${{ github.workspace }}/godot-cpp/.scons-cache/
27 | key: ${{ inputs.platform }}_${{ inputs.arch }}_${{ inputs.target }}_cache
28 | - name: Setup python and scons
29 | uses: ./.github/actions/deps
30 | - name: Lint
31 | shell: sh
32 | run:
33 | ./scripts/clang-format.sh
34 | ./scripts/clang-tidy.sh
35 | - name: Build Godot Cpp
36 | shell: sh
37 | env:
38 | SCONS_CACHE: .scons-cache
39 | SCONS_CACHE_DIR: .scons-cache
40 | run: |
41 | cd godot-cpp && scons target=${{ inputs.target }} platform=${{ inputs.platform }} arch=${{ inputs.arch }} generate_bindings=yes ${{ inputs.sconsflags }}
42 | - name: Build Physics Server Box2D
43 | shell: sh
44 | env:
45 | SCONS_CACHE: .scons-cache
46 | SCONS_CACHE_DIR: .scons-cache
47 | run: |
48 | scons target=${{ inputs.target }} platform=${{ inputs.platform }} arch=${{ inputs.arch }} generate_bindings=no ${{ inputs.sconsflags }}
49 | - name: Upload Artifact
50 | uses: actions/upload-artifact@v3
51 | with:
52 | name: Physics Server Box2D - ${{ matrix.os }}
53 | path: |
54 | ${{ github.workspace }}/demo/bin/
55 | ${{ github.workspace }}/demo/physics_server_box2d.gdextension
56 | retention-days: 14
57 |
--------------------------------------------------------------------------------
/.github/actions/deps/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup python and scons
2 | description: Setup python, install the pip version of scons.
3 |
4 | inputs:
5 | python-version:
6 | description: The python version to use.
7 | default: "3.x"
8 | python-arch:
9 | description: The python architecture.
10 | default: "x64"
11 |
12 | runs:
13 | using: "composite"
14 | steps:
15 | # Use python 3.x release (works cross platform)
16 | - name: Set up Python 3.x
17 | uses: actions/setup-python@v4
18 | with:
19 | # Semantic version range syntax or exact version of a Python version
20 | python-version: ${{ inputs.python-version }}
21 | # Optional - x64 or x86 architecture, defaults to x64
22 | architecture: ${{ inputs.python-arch }}
23 |
24 | - name: Setup scons
25 | shell: bash
26 | run: |
27 | python -c "import sys; print(sys.version)"
28 | python -m pip install scons==4.4.0
29 | scons --version
30 |
31 | - name: Setup clang-format
32 | shell: bash
33 | run: |
34 | python -m pip install clang-format
35 |
--------------------------------------------------------------------------------
/.github/workflows/android_builds.yml:
--------------------------------------------------------------------------------
1 | name: 🤖 Android Builds
2 | on:
3 | workflow_call:
4 |
5 | # Global Settings
6 | env:
7 | SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no
8 |
9 | jobs:
10 | android:
11 | runs-on: "ubuntu-20.04"
12 | name: Android Build ${{ matrix.target }} ${{ matrix.arch }}
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | target: [template_debug, template_release]
17 | arch: [arm64, x86_64]
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | with:
22 | submodules: true
23 | - name: Set up Java 11
24 | uses: actions/setup-java@v3
25 | with:
26 | distribution: temurin
27 | java-version: 11
28 |
29 | - name: Build ${{ matrix.target }} ${{ matrix.arch }}
30 | uses: ./.github/actions/build
31 | with:
32 | sconsflags: ${{ env.SCONSFLAGS }}
33 | arch: ${{ matrix.arch }}
34 | platform: android
35 | target: ${{ matrix.target }}
36 |
--------------------------------------------------------------------------------
/.github/workflows/ios_builds.yml:
--------------------------------------------------------------------------------
1 | name: 🍏 iOS Builds
2 | on:
3 | workflow_call:
4 |
5 | # Global Settings
6 | env:
7 | SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no
8 |
9 | jobs:
10 | ios:
11 | runs-on: "macos-latest"
12 | name: iOS Build ${{ matrix.target }} ${{ matrix.arch }}
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | target: [template_debug, template_release]
17 | arch: [arm64]
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | with:
22 | submodules: true
23 | - name: Build ${{ matrix.target }} ${{ matrix.arch }}
24 | uses: ./.github/actions/build
25 | with:
26 | sconsflags: ${{ env.SCONSFLAGS }}
27 | arch: ${{ matrix.arch }}
28 | platform: ios
29 | target: ${{ matrix.target }}
30 |
--------------------------------------------------------------------------------
/.github/workflows/linux_builds.yml:
--------------------------------------------------------------------------------
1 | name: 🐧 Linux Builds
2 | on:
3 | workflow_call:
4 |
5 | # Global Settings
6 | env:
7 | SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no
8 |
9 | jobs:
10 | ios:
11 | runs-on: "ubuntu-20.04"
12 | name: Linux Build ${{ matrix.target }} ${{ matrix.arch }}
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | target: [template_debug, template_release]
17 | arch: [x86_64]
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | with:
22 | submodules: true
23 |
24 | - name: Build ${{ matrix.target }} ${{ matrix.arch }}
25 | uses: ./.github/actions/build
26 | with:
27 | sconsflags: ${{ env.SCONSFLAGS }}
28 | arch: ${{ matrix.arch }}
29 | platform: linux
30 | target: ${{ matrix.target }}
31 |
--------------------------------------------------------------------------------
/.github/workflows/macos_builds.yml:
--------------------------------------------------------------------------------
1 | name: 🍎 macOS Builds
2 | on:
3 | workflow_call:
4 |
5 | # Global Settings
6 | env:
7 | SCONSFLAGS: verbose=yes warnings=extra werror=yes debug_symbols=no
8 |
9 | jobs:
10 | ios:
11 | runs-on: "macos-latest"
12 | name: macOS Build ${{ matrix.target }} ${{ matrix.arch }}
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | target: [template_debug, template_release]
17 | arch: [universal]
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | with:
22 | submodules: true
23 |
24 | - name: Build ${{ matrix.target }} ${{ matrix.arch }}
25 | uses: ./.github/actions/build
26 | with:
27 | sconsflags: ${{ env.SCONSFLAGS }}
28 | arch: ${{ matrix.arch }}
29 | platform: macos
30 | target: ${{ matrix.target }}
31 |
--------------------------------------------------------------------------------
/.github/workflows/runner.yml:
--------------------------------------------------------------------------------
1 | name: 🔗 Builds
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | static-checks:
6 | name: 📊 Static checks
7 | uses: ./.github/workflows/static_checks.yml
8 |
9 | android-build:
10 | name: 🤖 Android
11 | needs: static-checks
12 | uses: ./.github/workflows/android_builds.yml
13 |
14 | ios-build:
15 | name: 🍏 iOS
16 | needs: static-checks
17 | uses: ./.github/workflows/ios_builds.yml
18 |
19 | linux-build:
20 | name: 🐧 Linux
21 | needs: static-checks
22 | uses: ./.github/workflows/linux_builds.yml
23 |
24 | macos-build:
25 | name: 🍎 macOS
26 | needs: static-checks
27 | uses: ./.github/workflows/macos_builds.yml
28 |
29 | windows-build:
30 | name: 🏁 Windows
31 | needs: static-checks
32 | uses: ./.github/workflows/windows_builds.yml
33 |
--------------------------------------------------------------------------------
/.github/workflows/static_checks.yml:
--------------------------------------------------------------------------------
1 | name: 📊 Static Checks
2 | on:
3 | workflow_call:
4 |
5 | jobs:
6 | static-checks:
7 | name: Code style
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | with:
12 | submodules: true
13 | - name: Clang Format
14 | shell: sh
15 | run: ./scripts/clang-format.sh
16 |
--------------------------------------------------------------------------------
/.github/workflows/windows_builds.yml:
--------------------------------------------------------------------------------
1 | name: 🏁 Windows Builds
2 | on:
3 | workflow_call:
4 |
5 | # Global Settings
6 | env:
7 | SCONSFLAGS: verbose=yes warnings=extra werror=yes
8 |
9 | jobs:
10 | build-windows:
11 | runs-on: "windows-latest"
12 | name: Windows Build ${{ matrix.target }} ${{ matrix.arch }}
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | target: [template_debug, template_release]
17 | arch: [x86_32, x86_64]
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | with:
22 | submodules: true
23 | - name: Setup MSVC problem matcher
24 | uses: ammaraskar/msvc-problem-matcher@master
25 |
26 | - name: Build ${{ matrix.target }} ${{ matrix.arch }}
27 | uses: ./.github/actions/build
28 | with:
29 | sconsflags: ${{ env.SCONSFLAGS }}
30 | arch: ${{ matrix.arch }}
31 | platform: windows
32 | target: ${{ matrix.target }}
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | demo/.godot/
3 |
4 | # Binaries
5 | *.os
6 | *.dblite
7 |
8 | # Generated directories with binaries
9 | demo/bin/
10 |
11 | .DS_Store
12 | .vscode
13 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "godot-cpp"]
2 | path = godot-cpp
3 | url = https://github.com/godotengine/godot-cpp
4 | [submodule "box2d"]
5 | path = box2d
6 | url = https://github.com/erincatto/box2d
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022-present Ricardo Buring and Dragos Daian
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 | # PhysicsServerBox2D
2 |
3 | An unofficial [**Box2D**](https://github.com/erincatto/box2d) physics server for [**Godot Engine**](https://github.com/godotengine/godot) 4.0, implemented as a GDExtension.
4 |
5 | The goal of the project is to be a drop-in solution for 2D physics in Godot 4.0. In your Godot project you can load the GDExtension, change the (advanced) project setting `physics/2d/physics_engine` to `Box2D`, and it will work with Godot's original 2D physics nodes such as `RigidBody2D` and `StaticBody2D`.
6 |
7 | ## Current state
8 |
9 | ⚠ This project is a work in progress, still in a very early stage. ⚠
10 |
11 | Runtime errors of the form `Required virtual method ... must be overridden before calling` reflect this unfinished state, and they hint at which functionality is still missing.
12 |
13 | ## Building from source
14 |
15 | 1. Clone the git repository https://github.com/rburing/physics_server_box2d, including its `box2d` and `godot-cpp` submodules.
16 |
17 | 2. Open a terminal application and change its working directory to the `physics_server_box2d` git repository.
18 |
19 | 3. Compile `godot-cpp` for the desired `target` (`template_debug` or `template_release`):
20 |
21 | cd godot-cpp
22 | scons target=template_debug generate_bindings=yes
23 |
24 | 4. Compile the GDExtension for the same `target` as above:
25 |
26 | cd ..
27 | scons target=template_debug generate_bindings=no
28 |
29 | *Note*: The `template_debug` target can also be loaded in the Godot editor.
30 |
31 | ## Demo
32 |
33 | The Godot project in the `demo` subdirectory is an example of how to load the GDExtension.
34 |
--------------------------------------------------------------------------------
/SConstruct:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | env = SConscript("godot-cpp/SConstruct")
6 |
7 | box2d_folder = "box2d/"
8 | box2d_include = [
9 | "include/",
10 | "src/"
11 | ]
12 | box2d_src = [
13 | "collision/b2_broad_phase.cpp",
14 | "collision/b2_chain_shape.cpp",
15 | "collision/b2_circle_shape.cpp",
16 | "collision/b2_collide_circle.cpp",
17 | "collision/b2_collide_edge.cpp",
18 | "collision/b2_collide_polygon.cpp",
19 | "collision/b2_collision.cpp",
20 | "collision/b2_distance.cpp",
21 | "collision/b2_dynamic_tree.cpp",
22 | "collision/b2_edge_shape.cpp",
23 | "collision/b2_polygon_shape.cpp",
24 | "collision/b2_time_of_impact.cpp",
25 | "common/b2_block_allocator.cpp",
26 | "common/b2_draw.cpp",
27 | "common/b2_math.cpp",
28 | "common/b2_settings.cpp",
29 | "common/b2_stack_allocator.cpp",
30 | "common/b2_timer.cpp",
31 | "dynamics/b2_body.cpp",
32 | "dynamics/b2_chain_circle_contact.cpp",
33 | "dynamics/b2_chain_polygon_contact.cpp",
34 | "dynamics/b2_circle_contact.cpp",
35 | "dynamics/b2_contact.cpp",
36 | "dynamics/b2_contact_manager.cpp",
37 | "dynamics/b2_contact_solver.cpp",
38 | "dynamics/b2_distance_joint.cpp",
39 | "dynamics/b2_edge_circle_contact.cpp",
40 | "dynamics/b2_edge_polygon_contact.cpp",
41 | "dynamics/b2_fixture.cpp",
42 | "dynamics/b2_friction_joint.cpp",
43 | "dynamics/b2_gear_joint.cpp",
44 | "dynamics/b2_island.cpp",
45 | "dynamics/b2_joint.cpp",
46 | "dynamics/b2_motor_joint.cpp",
47 | "dynamics/b2_mouse_joint.cpp",
48 | "dynamics/b2_polygon_circle_contact.cpp",
49 | "dynamics/b2_polygon_contact.cpp",
50 | "dynamics/b2_prismatic_joint.cpp",
51 | "dynamics/b2_pulley_joint.cpp",
52 | "dynamics/b2_revolute_joint.cpp",
53 | "dynamics/b2_weld_joint.cpp",
54 | "dynamics/b2_wheel_joint.cpp",
55 | "dynamics/b2_world.cpp",
56 | "dynamics/b2_world_callbacks.cpp",
57 | "rope/b2_rope.cpp",
58 | ]
59 |
60 | env.Prepend(CPPPATH=[box2d_folder + folder for folder in box2d_include])
61 |
62 | # For the reference:
63 | # - CCFLAGS are compilation flags shared between C and C++
64 | # - CFLAGS are for C-specific compilation flags
65 | # - CXXFLAGS are for C++-specific compilation flags
66 | # - CPPFLAGS are for pre-processor flags
67 | # - CPPDEFINES are for pre-processor defines
68 | # - LINKFLAGS are for linking flags
69 |
70 | # Make Box2D include "b2_user_settings.h"
71 | env.Append(CPPDEFINES="B2_USER_SETTINGS")
72 |
73 | # tweak this if you want to use different folders, or more folders, to store your source code in.
74 | env.Append(CPPPATH=["src/"])
75 | sources = [Glob("src/*.cpp"),Glob("src/bodies/*.cpp"),Glob("src/joints/*.cpp"),Glob("src/servers/*.cpp"),Glob("src/shapes/*.cpp"),Glob("src/spaces/*.cpp")]
76 | sources.extend([box2d_folder + 'src/' + box2d_src_file for box2d_src_file in box2d_src])
77 |
78 | if env["platform"] == "macos":
79 | library = env.SharedLibrary(
80 | "demo/bin/libphysics_server_box2d.{}.{}.framework/libphysics_server_box2d.{}.{}".format(
81 | env["platform"], env["target"], env["platform"], env["target"]
82 | ),
83 | source=sources,
84 | )
85 | else:
86 | library = env.SharedLibrary(
87 | "demo/bin/libphysics_server_box2d{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
88 | source=sources,
89 | )
90 |
91 | Default(library)
92 |
--------------------------------------------------------------------------------
/demo/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/demo/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d3vm5kkva7oiy"
6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/demo/physics_server_box2d.gdextension:
--------------------------------------------------------------------------------
1 | [configuration]
2 |
3 | entry_symbol = "physics_server_box2d_library_init"
4 |
5 | [libraries]
6 |
7 | macos.debug = "res://bin/libphysics_server_box2d.macos.template_debug.framework"
8 | macos.release = "res://bin/libphysics_server_box2d.macos.template_release.framework"
9 | ios.debug = "res://bin/libphysics_server_box2d.ios.template_debug.arm64.dylib"
10 | ios.release = "res://bin/libphysics_server_box2d.ios.template_release.arm64.dylib"
11 | windows.debug.x86_32 = "res://bin/libphysics_server_box2d.windows.template_debug.x86_32.dll"
12 | windows.release.x86_32 = "res://bin/libphysics_server_box2d.windows.template_release.x86_32.dll"
13 | windows.debug.x86_64 = "res://bin/libphysics_server_box2d.windows.template_debug.x86_64.dll"
14 | windows.release.x86_64 = "res://bin/libphysics_server_box2d.windows.template_release.x86_64.dll"
15 | linux.debug.x86_64 = "res://bin/libphysics_server_box2d.linux.template_debug.x86_64.so"
16 | linux.release.x86_64 = "res://bin/libphysics_server_box2d.linux.template_release.x86_64.so"
17 | linux.debug.arm64 = "res://bin/libphysics_server_box2d.linux.template_debug.arm64.so"
18 | linux.release.arm64 = "res://bin/libphysics_server_box2d.linux.template_release.arm64.so"
19 | linux.debug.rv64 = "res://bin/libphysics_server_box2d.linux.template_debug.rv64.so"
20 | linux.release.rv64 = "res://bin/libphysics_server_box2d.linux.template_release.rv64.so"
21 | android.debug.x86_64 = "res://bin/libphysics_server_box2d.android.template_debug.x86_64.so"
22 | android.release.x86_64 = "res://bin/libphysics_server_box2d.android.template_release.x86_64.so"
23 | android.debug.arm64 = "res://bin/libphysics_server_box2d.android.template_debug.arm64.so"
24 | android.release.arm64 = "res://bin/libphysics_server_box2d.android.template_release.arm64.so"
25 |
--------------------------------------------------------------------------------
/demo/project.godot:
--------------------------------------------------------------------------------
1 | ; Engine configuration file.
2 | ; It's best edited using the editor UI and not directly,
3 | ; since the parameters that go here are not all obvious.
4 | ;
5 | ; Format:
6 | ; [section] ; section goes between []
7 | ; param=value ; assign values to parameters
8 |
9 | config_version=5
10 |
11 | [application]
12 |
13 | config/name="PhysicsServerBox2D demo"
14 | run/main_scene="res://world.tscn"
15 | config/features=PackedStringArray("4.0")
16 | config/icon="res://icon.svg"
17 |
18 | [physics]
19 |
20 | 2d/physics_engine="Box2D"
21 |
--------------------------------------------------------------------------------
/demo/world.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://bkd3jvy83di6t"]
2 |
3 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_0osea"]
4 | size = Vector2(500, 20)
5 |
6 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_ruwi5"]
7 |
8 | [node name="World" type="Node2D"]
9 |
10 | [node name="StaticBody2D" type="StaticBody2D" parent="."]
11 |
12 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"]
13 | shape = SubResource("RectangleShape2D_0osea")
14 |
15 | [node name="RigidBody2D" type="RigidBody2D" parent="."]
16 | position = Vector2(0, -62)
17 |
18 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"]
19 | shape = SubResource("RectangleShape2D_ruwi5")
20 |
21 | [node name="Camera2D" type="Camera2D" parent="RigidBody2D"]
22 |
--------------------------------------------------------------------------------
/scripts/clang-format.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # This is based on Godot's clang-format.sh
3 |
4 | # This script runs clang-format on all relevant files in the repo.
5 | # This is the primary script responsible for fixing style violations.
6 |
7 | set -uo pipefail
8 |
9 | # Loop through all code files tracked by Git.
10 | files=$(git ls-files -- '*.h' '*.cpp')
11 |
12 |
13 | if [ ! -z "$files" ]; then
14 | clang-format --Wno-error=unknown -i $files
15 | fi
16 |
17 | diff=$(git diff --color)
18 |
19 | # If no diff has been generated all is OK, clean up, and exit.
20 | if [ -z "$diff" ] ; then
21 | printf "\e[1;32m*** Files in this commit comply with the clang-format style rules.\e[0m\n"
22 | exit 0
23 | fi
24 |
25 | # A diff has been created, notify the user, clean up, and exit.
26 | printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n"
27 | # Perl commands replace trailing spaces with `·` and tabs with ``.
28 | printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="" x length($2); sprintf("$1$tabs$3")/ge'
29 |
30 | printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i '\e[0m\n"
31 | exit 1
32 |
--------------------------------------------------------------------------------
/scripts/clang-tidy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # This is based on Godot's clang-tidy.sh
3 |
4 | # This script runs clang-tidy on all relevant files in the repo.
5 | # This is more thorough than clang-format and thus slower; it should only be run manually.
6 |
7 | set -uo pipefail
8 |
9 | # Loops through all code files tracked by Git.
10 |
11 | git ls-files -- '*.h' '*.cpp' |
12 | while read -r f; do
13 | # Run clang-tidy.
14 | clang-tidy --quiet --fix "$f" &> /dev/null
15 |
16 | # Run clang-format. This also fixes the output of clang-tidy.
17 | clang-format --Wno-error=unknown -i "$f"
18 | done
19 |
20 | diff=$(git diff --color)
21 |
22 | # If no diff has been generated all is OK, clean up, and exit.
23 | if [ -z "$diff" ] ; then
24 | printf "\e[1;32m*** Files in this commit comply with the clang-tidy style rules.\e[0m\n"
25 | exit 0
26 | fi
27 |
28 | # A diff has been created, notify the user, clean up, and exit.
29 | printf "\n\e[1;33m*** The following changes must be made to comply with the formatting rules:\e[0m\n\n"
30 | # Perl commands replace trailing spaces with `·` and tabs with ``.
31 | printf "$diff\n" | perl -pe 's/(.*[^ ])( +)(\e\[m)$/my $spaces="·" x length($2); sprintf("$1$spaces$3")/ge' | perl -pe 's/(.*[^\t])(\t+)(\e\[m)$/my $tabs="" x length($2); sprintf("$1$tabs$3")/ge'
32 |
33 | printf "\n\e[1;91m*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i '\e[0m\n"
34 | exit 1
35 |
--------------------------------------------------------------------------------
/src/b2_user_settings.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 |
8 | // Tunable Constants
9 |
10 | /// You can use this to change the length scale used by your game.
11 | /// For example for inches you could use 39.4.
12 | #define b2_lengthUnitsPerMeter 1.0f
13 |
14 | /// The maximum number of vertices on a convex polygon. You cannot increase
15 | /// this too much because b2BlockAllocator has a maximum object size.
16 | #define b2_maxPolygonVertices 8
17 |
18 | // User data
19 |
20 | class Box2DCollisionObject;
21 |
22 | struct B2_API b2BodyUserData {
23 | b2BodyUserData() {
24 | collision_object = nullptr;
25 | }
26 |
27 | Box2DCollisionObject *collision_object;
28 | };
29 |
30 | struct B2_API b2FixtureUserData {
31 | b2FixtureUserData() {
32 | shape_idx = -1;
33 | box2d_fixture_idx = 0;
34 | }
35 |
36 | int shape_idx;
37 | int box2d_fixture_idx;
38 | };
39 |
40 | /// You can define this to inject whatever data you want in b2Joint
41 | struct B2_API b2JointUserData {
42 | b2JointUserData() {
43 | pointer = 0;
44 | }
45 |
46 | /// For legacy compatibility
47 | uintptr_t pointer;
48 | };
49 |
50 | // Memory Allocation using Godot's functions
51 |
52 | inline void *b2Alloc(int32 size) {
53 | return memalloc(size);
54 | }
55 |
56 | inline void b2Free(void *mem) {
57 | memfree(mem);
58 | }
59 |
60 | /// Default logging function
61 | B2_API void b2Log_Default(const char *string, va_list args);
62 |
63 | /// Implement this to use your own logging.
64 | inline void b2Log(const char *string, ...) {
65 | va_list args;
66 | va_start(args, string);
67 | b2Log_Default(string, args);
68 | va_end(args);
69 | }
70 |
--------------------------------------------------------------------------------
/src/bodies/box2d_area.cpp:
--------------------------------------------------------------------------------
1 | #include "box2d_area.h"
2 | #include "box2d_body.h"
3 |
4 | // Physics Server
5 | void Box2DArea::set_monitorable(bool p_monitorable) {
6 | monitorable = p_monitorable;
7 | }
8 | bool Box2DArea::get_monitorable() {
9 | return monitorable;
10 | }
11 | bool Box2DArea::get_monitoring() {
12 | return area_monitor_callback.is_valid();
13 | }
14 | void Box2DArea::set_monitor_callback(const Callable &p_callback) {
15 | monitor_callback = p_callback;
16 | }
17 | void Box2DArea::set_area_monitor_callback(const Callable &p_callback) {
18 | area_monitor_callback = p_callback;
19 | }
20 | void Box2DArea::call_area_monitor(Box2DArea *area, PhysicsServer2D::AreaBodyStatus status, const RID &p_area, ObjectID p_instance, int area_shape_idx, int self_shape_idx) {
21 | if (get_monitoring() && area->monitorable) {
22 | area_monitor_callback.callv(Array::make(status, p_area, p_instance, area_shape_idx, self_shape_idx));
23 | }
24 | }
25 | void Box2DArea::call_monitor(Box2DCollisionObject *body, PhysicsServer2D::AreaBodyStatus status, const RID &p_body, ObjectID p_instance, int32_t area_shape_idx, int32_t self_shape_idx) {
26 | if (monitor_callback.is_valid()) {
27 | monitor_callback.callv(Array::make(status, p_body, p_instance, area_shape_idx, self_shape_idx));
28 | if (status == PhysicsServer2D::AreaBodyStatus::AREA_BODY_ADDED) {
29 | add_body(body);
30 | } else {
31 | remove_body(body);
32 | }
33 | }
34 | }
35 |
36 | void Box2DArea::set_transform(const Transform2D &p_transform) {
37 | // TODO: add to moved list?
38 |
39 | _set_transform(p_transform);
40 | // _set_inv_transform(p_transform.affine_inverse());
41 | }
42 |
43 | void Box2DArea::set_space(Box2DSpace *p_space) {
44 | // TODO: remove from monitor query list, remove from moved list?
45 |
46 | //monitored_bodies.clear();
47 | //monitored_areas.clear();
48 |
49 | _set_space(p_space);
50 | }
51 |
52 | void Box2DArea::set_priority(real_t p_priority) {
53 | if (collision.priority == p_priority) {
54 | return;
55 | }
56 | collision.priority = p_priority;
57 | for (Box2DCollisionObject *body : bodies) {
58 | body->sort_areas();
59 | body->recalculate_total_gravity();
60 | body->recalculate_total_angular_damp();
61 | body->recalculate_total_linear_damp();
62 | }
63 | }
64 |
65 | void Box2DArea::set_gravity_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_value) {
66 | if (gravity.override_mode == p_value) {
67 | return;
68 | }
69 | gravity.override_mode = p_value;
70 | for (Box2DCollisionObject *body : bodies) {
71 | body->recalculate_total_gravity();
72 | }
73 | }
74 | void Box2DArea::set_gravity(real_t p_value) {
75 | if (gravity.gravity == godot_to_box2d(p_value)) {
76 | return;
77 | }
78 | gravity.gravity = godot_to_box2d(p_value);
79 | for (Box2DCollisionObject *body : bodies) {
80 | body->recalculate_total_gravity();
81 | }
82 | }
83 | void Box2DArea::set_gravity_vector(Vector2 p_value) {
84 | if (gravity.vector == b2Vec2(p_value.x, p_value.y)) {
85 | return;
86 | }
87 | gravity.vector = b2Vec2(p_value.x, p_value.y);
88 | for (Box2DCollisionObject *body : bodies) {
89 | body->recalculate_total_gravity();
90 | }
91 | }
92 | void Box2DArea::set_gravity_is_point(bool p_value) {
93 | if (gravity.is_point == p_value) {
94 | return;
95 | }
96 | gravity.is_point = p_value;
97 | for (Box2DCollisionObject *body : bodies) {
98 | body->recalculate_total_gravity();
99 | }
100 | }
101 | void Box2DArea::set_gravity_point_unit_distance(double p_value) {
102 | if (gravity.point_unit_distance == p_value) {
103 | return;
104 | }
105 | gravity.point_unit_distance = p_value;
106 | for (Box2DCollisionObject *body : bodies) {
107 | body->recalculate_total_gravity();
108 | }
109 | }
110 | void Box2DArea::set_linear_damp_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_value) {
111 | if (linear_damp_override_mode == p_value) {
112 | return;
113 | }
114 | linear_damp_override_mode = p_value;
115 | for (Box2DCollisionObject *body : bodies) {
116 | body->recalculate_total_linear_damp();
117 | }
118 | }
119 | void Box2DArea::set_angular_damp_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_value) {
120 | if (angular_damp_override_mode == p_value) {
121 | return;
122 | }
123 | angular_damp_override_mode = p_value;
124 | for (Box2DCollisionObject *body : bodies) {
125 | body->recalculate_total_angular_damp();
126 | }
127 | }
128 |
129 | void Box2DArea::set_linear_damp(real_t p_linear_damp) {
130 | if (damping.linear_damp == p_linear_damp) {
131 | return;
132 | }
133 | damping.linear_damp = p_linear_damp;
134 | for (Box2DCollisionObject *body : bodies) {
135 | body->recalculate_total_linear_damp();
136 | }
137 | }
138 | void Box2DArea::set_angular_damp(real_t p_angular_damp) {
139 | if (damping.angular_damp == p_angular_damp) {
140 | return;
141 | }
142 | damping.angular_damp = p_angular_damp;
143 | for (Box2DCollisionObject *body : bodies) {
144 | body->recalculate_total_angular_damp();
145 | }
146 | }
147 |
148 | PhysicsServer2D::AreaSpaceOverrideMode Box2DArea::get_gravity_override_mode() const {
149 | return gravity.override_mode;
150 | }
151 | double Box2DArea::get_gravity() const {
152 | return box2d_to_godot(gravity.gravity);
153 | }
154 |
155 | b2Vec2 Box2DArea::get_b2_gravity(Transform2D transform) const {
156 | if (!gravity.is_point) {
157 | return gravity.gravity * gravity.vector;
158 | }
159 |
160 | const Vector2 point = get_transform().xform(box2d_to_godot(gravity.vector));
161 | const Vector2 to_point = point - transform.get_origin();
162 | const float to_point_dist_sq = std::max(to_point.length_squared(), CMP_EPSILON);
163 | const Vector2 to_point_dir = to_point / Math::sqrt(to_point_dist_sq);
164 |
165 | const float gravity_dist_sq = gravity.point_unit_distance * gravity.point_unit_distance;
166 |
167 | return godot_to_box2d((to_point_dir * (get_gravity() * gravity_dist_sq / to_point_dist_sq)));
168 | }
169 | Vector2 Box2DArea::get_gravity_vector() const {
170 | return Vector2(gravity.vector.x, gravity.vector.y);
171 | }
172 | bool Box2DArea::get_gravity_is_point() const {
173 | return gravity.is_point;
174 | }
175 | double Box2DArea::get_gravity_point_unit_distance() const {
176 | return gravity.point_unit_distance;
177 | }
178 | PhysicsServer2D::AreaSpaceOverrideMode Box2DArea::get_linear_damp_override_mode() const {
179 | return linear_damp_override_mode;
180 | }
181 | PhysicsServer2D::AreaSpaceOverrideMode Box2DArea::get_angular_damp_override_mode() const {
182 | return angular_damp_override_mode;
183 | }
184 | void Box2DArea::add_body(Box2DCollisionObject *p_body) {
185 | bodies.append(p_body);
186 | p_body->add_area(this);
187 | }
188 | void Box2DArea::remove_body(Box2DCollisionObject *p_body) {
189 | bodies.erase(p_body);
190 | p_body->remove_area(this);
191 | }
192 |
193 | Box2DArea::Box2DArea() :
194 | Box2DCollisionObject(TYPE_AREA) {
195 | damping.linear_damp = 0.1;
196 | damping.angular_damp = 1;
197 | // areas are sensors and dynamic bodies, but don't move.
198 | // b2_staticBody don't collide with b2_staticBody or b2_kinematicBody
199 | // b2_kinematicBody don't collide with b2_staticBody or b2_kinematicBody
200 | // and areas have to be able to intersect with both kinematic and static bodies
201 | body_def->type = b2_dynamicBody;
202 | //_set_static(true); //areas are not active by default
203 | }
204 |
205 | Box2DArea::~Box2DArea() {
206 | for (Box2DCollisionObject *body : bodies) {
207 | if (body) {
208 | body->remove_area(this);
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/bodies/box2d_area.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "../spaces/box2d_space.h"
10 | #include "box2d_collision_object.h"
11 |
12 | using namespace godot;
13 |
14 | class Box2DBody;
15 |
16 | class Box2DArea : public Box2DCollisionObject {
17 | bool monitorable = false;
18 | Callable monitor_callback;
19 | Callable area_monitor_callback;
20 | struct Gravity {
21 | PhysicsServer2D::AreaSpaceOverrideMode override_mode = PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_DISABLED;
22 | real_t gravity = 9.8;
23 | b2Vec2 vector = b2Vec2(0, 1);
24 | bool is_point = false;
25 | real_t point_unit_distance = 0;
26 | };
27 | Gravity gravity;
28 | PhysicsServer2D::AreaSpaceOverrideMode linear_damp_override_mode = PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_DISABLED;
29 | PhysicsServer2D::AreaSpaceOverrideMode angular_damp_override_mode = PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_DISABLED;
30 | Vector bodies;
31 |
32 | void update_bodies();
33 |
34 | public:
35 | virtual void set_linear_damp(real_t p_linear_damp) override;
36 | virtual void set_angular_damp(real_t p_angular_damp) override;
37 | virtual void set_priority(real_t p_priority) override;
38 | // Physics Server
39 | void set_monitorable(bool monitorable);
40 | bool get_monitorable();
41 | bool get_monitoring();
42 | void set_monitor_callback(const Callable &callback);
43 | void set_area_monitor_callback(const Callable &callback);
44 | void call_area_monitor(Box2DArea *area, PhysicsServer2D::AreaBodyStatus status, const RID &p_area, ObjectID p_instance, int area_shape_idx, int self_shape_idx);
45 | void call_monitor(Box2DCollisionObject *body, PhysicsServer2D::AreaBodyStatus status, const RID &p_body, ObjectID p_instance, int32_t area_shape_idx, int32_t self_shape_idx);
46 |
47 | virtual void set_transform(const Transform2D &p_transform) override;
48 |
49 | virtual void set_space(Box2DSpace *p_space) override;
50 |
51 | void set_gravity_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_value);
52 | void set_gravity(real_t p_value);
53 | void set_gravity_vector(Vector2 p_value);
54 | void set_gravity_is_point(bool p_value);
55 | void set_gravity_point_unit_distance(double p_value);
56 | void set_linear_damp_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_value);
57 | void set_angular_damp_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_value);
58 |
59 | PhysicsServer2D::AreaSpaceOverrideMode get_gravity_override_mode() const;
60 | double get_gravity() const;
61 | b2Vec2 get_b2_gravity(Transform2D transform) const;
62 | Vector2 get_gravity_vector() const;
63 | bool get_gravity_is_point() const;
64 | double get_gravity_point_unit_distance() const;
65 | PhysicsServer2D::AreaSpaceOverrideMode get_linear_damp_override_mode() const;
66 | PhysicsServer2D::AreaSpaceOverrideMode get_angular_damp_override_mode() const;
67 |
68 | void add_body(Box2DCollisionObject *p_body);
69 | void remove_body(Box2DCollisionObject *p_body);
70 |
71 | Box2DArea();
72 | ~Box2DArea();
73 | };
74 |
--------------------------------------------------------------------------------
/src/bodies/box2d_body.cpp:
--------------------------------------------------------------------------------
1 | #include "box2d_body.h"
2 | #include "../box2d_type_conversions.h"
3 | #include "box2d_direct_body_state.h"
4 |
5 | bool Box2DBody::is_active() const { return active; }
6 |
7 | // Physics Server
8 |
9 | void Box2DBody::set_max_contacts_reported(int32 p_max_contacts_reported) {
10 | max_contacts_reported = p_max_contacts_reported;
11 | }
12 |
13 | int32 Box2DBody::get_max_contacts_reported() {
14 | return max_contacts_reported;
15 | }
16 |
17 | void Box2DBody::wakeup() {
18 | if ((!get_space()) || mode == PhysicsServer2D::BODY_MODE_STATIC || mode == PhysicsServer2D::BODY_MODE_KINEMATIC) {
19 | return;
20 | }
21 | set_active(true);
22 | }
23 |
24 | void Box2DBody::set_state_sync_callback(const Callable &p_callable) {
25 | body_state_callback = p_callable;
26 | }
27 |
28 | Box2DDirectBodyState *Box2DBody::get_direct_state() {
29 | if (!direct_state) {
30 | direct_state = memnew(Box2DDirectBodyState);
31 | direct_state->body = this;
32 | }
33 | return direct_state;
34 | }
35 |
36 | void Box2DBody::set_active(bool p_active) {
37 | if (active == p_active) {
38 | return;
39 | }
40 |
41 | active = p_active;
42 |
43 | if (active) {
44 | if (mode == PhysicsServer2D::BODY_MODE_STATIC) {
45 | // Static bodies can't be active.
46 | active = false;
47 | } else if (get_space()) {
48 | set_sleep_state(true);
49 | get_space()->body_add_to_active_list(&active_list);
50 | }
51 | } else if (get_space()) {
52 | set_sleep_state(false);
53 | get_space()->body_remove_from_active_list(&active_list);
54 | }
55 | }
56 |
57 | void Box2DBody::set_mode(PhysicsServer2D::BodyMode p_mode) {
58 | if (mode == p_mode) {
59 | return;
60 | }
61 | mode = p_mode;
62 | switch (p_mode) {
63 | case PhysicsServer2D::BODY_MODE_STATIC: {
64 | // TODO: other stuff
65 | body_def->type = b2_staticBody;
66 | body_def->fixedRotation = false;
67 | set_active(false);
68 | } break;
69 | case PhysicsServer2D::BODY_MODE_KINEMATIC: {
70 | // TODO: other stuff
71 | body_def->type = b2_kinematicBody;
72 | body_def->fixedRotation = false;
73 | set_active(true); // TODO: consider contacts
74 | } break;
75 | case PhysicsServer2D::BODY_MODE_RIGID: {
76 | body_def->type = b2_dynamicBody;
77 | body_def->fixedRotation = false;
78 | set_active(true);
79 | } break;
80 | case PhysicsServer2D::BODY_MODE_RIGID_LINEAR: {
81 | // TODO: (inverse) mass calculation?
82 | //_set_static(false);
83 | body_def->type = b2_dynamicBody;
84 | body_def->fixedRotation = true;
85 | set_active(true);
86 | } break;
87 | }
88 | if (body) {
89 | body->SetType(body_def->type);
90 | body->SetFixedRotation(body_def->fixedRotation);
91 | body->SetMassData(&mass_data);
92 | }
93 | }
94 |
95 | PhysicsServer2D::BodyMode Box2DBody::get_mode() const {
96 | return mode;
97 | }
98 |
99 | void Box2DBody::set_state(PhysicsServer2D::BodyState p_state, const Variant &p_variant) {
100 | switch (p_state) {
101 | case PhysicsServer2D::BODY_STATE_TRANSFORM: {
102 | if (mode == PhysicsServer2D::BODY_MODE_KINEMATIC) {
103 | // TODO
104 | } else if (mode == PhysicsServer2D::BODY_MODE_STATIC) {
105 | _set_transform(p_variant);
106 | //_set_inv_transform(get_transform().affine_inverse());
107 | //wakeup_neighbours();
108 | } else { // rigid body
109 | Transform2D t = p_variant;
110 | t.orthonormalize();
111 | new_transform = get_transform(); // used as old to compute motion
112 | if (t == new_transform) {
113 | break;
114 | }
115 | _set_transform(t);
116 | //_set_inv_transform(get_transform().inverse());
117 | //_update_transform_dependent();
118 | }
119 | wakeup();
120 | } break;
121 | case PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY: {
122 | Vector2 linear_velocity = p_variant;
123 | set_linear_velocity(linear_velocity);
124 | wakeup();
125 | } break;
126 | case PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY: {
127 | float angular_velocity = godot_to_box2d(variant_to_number(p_variant));
128 | set_angular_velocity(angular_velocity);
129 | wakeup();
130 | } break;
131 | case PhysicsServer2D::BODY_STATE_SLEEPING: {
132 | if (mode == PhysicsServer2D::BODY_MODE_STATIC || mode == PhysicsServer2D::BODY_MODE_KINEMATIC) {
133 | break;
134 | }
135 | bool do_sleep = p_variant;
136 | if (do_sleep) {
137 | set_linear_velocity(Vector2());
138 | set_angular_velocity(0);
139 | set_active(false);
140 | } else {
141 | if (mode != PhysicsServer2D::BODY_MODE_STATIC) {
142 | set_active(true);
143 | }
144 | }
145 | } break;
146 | case PhysicsServer2D::BODY_STATE_CAN_SLEEP: {
147 | can_sleep = p_variant;
148 | if (body) {
149 | body->SetSleepingAllowed(can_sleep);
150 | }
151 | if (mode >= PhysicsServer2D::BODY_MODE_RIGID && !active && !can_sleep) {
152 | set_active(true);
153 | }
154 | } break;
155 | }
156 | }
157 |
158 | Variant Box2DBody::get_state(PhysicsServer2D::BodyState p_state) const {
159 | switch (p_state) {
160 | case PhysicsServer2D::BODY_STATE_TRANSFORM: {
161 | return get_transform();
162 | } break;
163 | case PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY: {
164 | return get_linear_velocity();
165 | } break;
166 | case PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY: {
167 | return get_angular_velocity();
168 | } break;
169 | case PhysicsServer2D::BODY_STATE_SLEEPING: {
170 | return !is_active();
171 | }
172 | case PhysicsServer2D::BODY_STATE_CAN_SLEEP: {
173 | return can_sleep;
174 | }
175 | }
176 | return Variant();
177 | }
178 |
179 | void Box2DBody::set_space(Box2DSpace *p_space) {
180 | if (get_space()) {
181 | // TODO: clean up more
182 | if (active_list.in_list()) {
183 | get_space()->body_remove_from_active_list(&active_list);
184 | }
185 | if (direct_state_query_list.in_list()) {
186 | get_space()->body_remove_from_state_query_list(&direct_state_query_list);
187 | }
188 | }
189 |
190 | _set_space(p_space);
191 |
192 | if (get_space()) {
193 | // TODO: do more
194 | if (body) {
195 | body->SetAwake(active);
196 | }
197 | if (active) {
198 | get_space()->body_add_to_active_list(&active_list);
199 | }
200 | }
201 | }
202 |
203 | void Box2DBody::after_step() {
204 | if (body_state_callback.is_valid()) {
205 | get_space()->body_add_to_state_query_list(&direct_state_query_list);
206 | }
207 | }
208 |
209 | void Box2DBody::call_queries() {
210 | Variant direct_state = get_direct_state();
211 | if (body_state_callback.is_valid()) {
212 | body_state_callback.callv(Array::make(direct_state));
213 | }
214 | }
215 |
216 | void Box2DBody::set_continuous_collision_detection_mode(PhysicsServer2D::CCDMode p_mode) {
217 | if (collision_mode == p_mode) {
218 | return;
219 | }
220 | collision_mode = p_mode;
221 | switch (collision_mode) {
222 | case PhysicsServer2D::CCD_MODE_DISABLED: {
223 | body_def->bullet = false;
224 | } break;
225 | case PhysicsServer2D::CCD_MODE_CAST_RAY:
226 | case PhysicsServer2D::CCD_MODE_CAST_SHAPE:
227 | body_def->bullet = true;
228 | break;
229 | }
230 | if (body) {
231 | body->SetBullet(body_def->bullet);
232 | }
233 | }
234 | PhysicsServer2D::CCDMode Box2DBody::get_continuous_collision_detection_mode() const {
235 | return collision_mode;
236 | }
237 |
238 | void Box2DBody::add_joint(Box2DJoint *p_joint) {
239 | joints.insert(p_joint);
240 | }
241 | void Box2DBody::remove_joint(Box2DJoint *p_joint) {
242 | joints.erase(p_joint);
243 | }
244 |
245 | HashSet Box2DBody::get_joints() {
246 | return joints;
247 | }
248 |
249 | Box2DBody::Box2DBody() :
250 | Box2DCollisionObject(TYPE_BODY),
251 | active_list(this),
252 | direct_state_query_list(this) {
253 | }
254 |
255 | Box2DBody::~Box2DBody() {
256 | if (direct_state) {
257 | memdelete(direct_state);
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/bodies/box2d_body.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "../joints/box2d_joint.h"
10 | #include "../spaces/box2d_space.h"
11 | #include "box2d_collision_object.h"
12 |
13 | using namespace godot;
14 |
15 | class Box2DDirectBodyState;
16 |
17 | class Box2DBody : public Box2DCollisionObject {
18 | PhysicsServer2D::BodyMode mode = PhysicsServer2D::BODY_MODE_RIGID;
19 |
20 | SelfList active_list;
21 | SelfList direct_state_query_list;
22 |
23 | bool active = true;
24 | bool can_sleep = true;
25 | PhysicsServer2D::CCDMode collision_mode = PhysicsServer2D::CCD_MODE_DISABLED;
26 |
27 | Transform2D new_transform;
28 |
29 | Callable body_state_callback;
30 |
31 | Box2DDirectBodyState *direct_state = nullptr;
32 | HashSet joints;
33 | int32 max_contacts_reported = 0;
34 |
35 | public:
36 | // Physics Server
37 | void set_max_contacts_reported(int32 p_max_contacts_reported);
38 |
39 | int32 get_max_contacts_reported();
40 |
41 | void set_space(Box2DSpace *p_space) override;
42 |
43 | void set_state_sync_callback(const Callable &p_callable);
44 |
45 | Box2DDirectBodyState *get_direct_state();
46 |
47 | void set_active(bool p_active);
48 | bool is_active() const;
49 |
50 | void wakeup();
51 |
52 | void set_mode(PhysicsServer2D::BodyMode p_mode);
53 | PhysicsServer2D::BodyMode get_mode() const;
54 |
55 | void set_state(PhysicsServer2D::BodyState p_state, const Variant &p_variant);
56 | Variant get_state(PhysicsServer2D::BodyState p_state) const;
57 |
58 | void set_continuous_collision_detection_mode(PhysicsServer2D::CCDMode mode);
59 | PhysicsServer2D::CCDMode get_continuous_collision_detection_mode() const;
60 |
61 | void add_joint(Box2DJoint *p_joint);
62 | void remove_joint(Box2DJoint *p_joint);
63 |
64 | virtual HashSet get_joints() override;
65 |
66 | void after_step();
67 | void call_queries();
68 |
69 | Box2DBody();
70 | ~Box2DBody();
71 | };
72 |
--------------------------------------------------------------------------------
/src/bodies/box2d_collision_object.cpp:
--------------------------------------------------------------------------------
1 | #include "box2d_collision_object.h"
2 |
3 | #include "../b2_user_settings.h"
4 |
5 | #include "../box2d_type_conversions.h"
6 | #include "../spaces/box2d_direct_space_state.h"
7 | #include "box2d_area.h"
8 |
9 | #include
10 |
11 | #include
12 | #include
13 |
14 | // Mass
15 |
16 | void Box2DCollisionObject::reset_mass_properties() {
17 | mass_data.mass = 1;
18 | mass_data.I = 0;
19 | mass_data.center = b2Vec2();
20 | if (body) {
21 | body->SetMassData(&mass_data);
22 | }
23 | }
24 |
25 | void Box2DCollisionObject::set_mass(real_t p_mass) {
26 | if (mass_data.mass == p_mass) {
27 | return;
28 | }
29 | mass_data.mass = p_mass;
30 | if (body) {
31 | body->SetMassData(&mass_data);
32 | }
33 | }
34 | double Box2DCollisionObject::get_mass() const {
35 | return mass_data.mass; // no need to convert
36 | }
37 | void Box2DCollisionObject::set_inertia(real_t p_inertia) {
38 | if (mass_data.I == p_inertia) {
39 | return;
40 | }
41 | mass_data.I = p_inertia;
42 | if (body) {
43 | body->SetMassData(&mass_data);
44 | }
45 | }
46 | double Box2DCollisionObject::get_inertia() const {
47 | return mass_data.I; // no need to convert
48 | }
49 | void Box2DCollisionObject::set_center_of_mass(Vector2 p_center_of_mass) {
50 | if (godot_to_box2d(p_center_of_mass) == mass_data.center) {
51 | return;
52 | }
53 | godot_to_box2d(p_center_of_mass, mass_data.center);
54 | if (body) {
55 | body->SetMassData(&mass_data);
56 | }
57 | }
58 | Vector2 Box2DCollisionObject::get_center_of_mass() const {
59 | if (body) {
60 | return box2d_to_godot(mass_data.center + body->GetPosition());
61 | } else {
62 | return box2d_to_godot(mass_data.center + body_def->position);
63 | }
64 | }
65 |
66 | // Damping
67 |
68 | void Box2DCollisionObject::set_linear_damp_mode(PhysicsServer2D::BodyDampMode p_linear_damp) {
69 | if (damping.linear_damp_mode == p_linear_damp) {
70 | return;
71 | }
72 | damping.linear_damp_mode = p_linear_damp;
73 | recalculate_total_linear_damp();
74 | }
75 | void Box2DCollisionObject::set_angular_damp_mode(PhysicsServer2D::BodyDampMode p_angular_damp) {
76 | if (damping.angular_damp_mode == p_angular_damp) {
77 | return;
78 | }
79 | damping.angular_damp_mode = p_angular_damp;
80 | recalculate_total_angular_damp();
81 | }
82 |
83 | void Box2DCollisionObject::set_linear_damp(real_t p_linear_damp) {
84 | if (damping.linear_damp == p_linear_damp) {
85 | return;
86 | }
87 | damping.linear_damp = p_linear_damp;
88 | recalculate_total_linear_damp();
89 | }
90 | void Box2DCollisionObject::set_angular_damp(real_t p_angular_damp) {
91 | if (damping.angular_damp == p_angular_damp) {
92 | return;
93 | }
94 | damping.angular_damp = p_angular_damp;
95 | recalculate_total_angular_damp();
96 | }
97 | PhysicsServer2D::BodyDampMode Box2DCollisionObject::get_linear_damp_mode() const {
98 | return damping.linear_damp_mode;
99 | }
100 | PhysicsServer2D::BodyDampMode Box2DCollisionObject::get_angular_damp_mode() const {
101 | return damping.angular_damp_mode;
102 | }
103 | double Box2DCollisionObject::get_linear_damp() const {
104 | return damping.linear_damp;
105 | }
106 | double Box2DCollisionObject::get_angular_damp() const {
107 | return damping.angular_damp;
108 | }
109 |
110 | void Box2DCollisionObject::recalculate_total_linear_damp() {
111 | total_linear_damp = damping.linear_damp;
112 | if (get_linear_damp_mode() == PhysicsServer2D::BodyDampMode::BODY_DAMP_MODE_REPLACE) {
113 | body_def->linearDamping = total_linear_damp;
114 | if (body) {
115 | body->SetLinearDamping(body_def->linearDamping);
116 | }
117 | // replace linear damp with body one
118 | return;
119 | }
120 | bool keep_computing = true;
121 | for (Box2DArea *area : areas) {
122 | if (!keep_computing) {
123 | break;
124 | }
125 | real_t linear_damp = area->damping.linear_damp;
126 | switch (area->get_linear_damp_override_mode()) {
127 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_COMBINE: {
128 | total_linear_damp += linear_damp;
129 | } break;
130 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: {
131 | total_linear_damp += linear_damp;
132 | keep_computing = false;
133 | } break;
134 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_REPLACE: {
135 | total_linear_damp = linear_damp;
136 | keep_computing = false;
137 | } break;
138 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: {
139 | total_linear_damp = linear_damp;
140 | } break;
141 | default: {
142 | }
143 | }
144 | }
145 | body_def->linearDamping = total_linear_damp;
146 | if (body) {
147 | body->SetLinearDamping(body_def->linearDamping);
148 | }
149 | }
150 |
151 | void Box2DCollisionObject::recalculate_total_angular_damp() {
152 | total_angular_damp = damping.angular_damp;
153 | if (get_angular_damp_mode() == PhysicsServer2D::BodyDampMode::BODY_DAMP_MODE_REPLACE) {
154 | body_def->angularDamping = total_angular_damp;
155 | if (body) {
156 | body->SetAngularDamping(body_def->angularDamping);
157 | }
158 | // replace angular damp with body one
159 | return;
160 | }
161 | // compute angular damp from areas
162 | bool keep_computing = true;
163 | for (Box2DArea *area : areas) {
164 | if (!keep_computing) {
165 | break;
166 | }
167 | real_t angular_damp = area->damping.angular_damp;
168 | switch (area->get_angular_damp_override_mode()) {
169 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_COMBINE: {
170 | total_angular_damp += angular_damp;
171 | } break;
172 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: {
173 | total_angular_damp += angular_damp;
174 | keep_computing = false;
175 | } break;
176 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_REPLACE: {
177 | total_angular_damp = angular_damp;
178 | keep_computing = false;
179 | } break;
180 | case PhysicsServer2D::AreaSpaceOverrideMode::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: {
181 | total_angular_damp = angular_damp;
182 | } break;
183 | default: {
184 | }
185 | }
186 | }
187 | body_def->angularDamping = total_angular_damp;
188 | if (body) {
189 | body->SetAngularDamping(body_def->angularDamping);
190 | }
191 | }
192 |
193 | // Collision
194 |
195 | void Box2DCollisionObject::set_priority(real_t p_priority) {
196 | collision.priority = p_priority;
197 | }
198 |
199 | double Box2DCollisionObject::get_priority() const {
200 | return collision.priority;
201 | }
202 |
203 | // Physics Material
204 |
205 | void Box2DCollisionObject::set_bounce(real_t p_bounce) {
206 | if (physics_material.bounce == p_bounce) {
207 | return;
208 | }
209 | physics_material.bounce = p_bounce;
210 | if (body) {
211 | _update_shapes();
212 | }
213 | }
214 | void Box2DCollisionObject::set_friction(real_t p_friction) {
215 | if (physics_material.friction == p_friction) {
216 | return;
217 | }
218 | physics_material.friction = p_friction;
219 | if (body) {
220 | _update_shapes();
221 | }
222 | }
223 |
224 | double Box2DCollisionObject::get_bounce() const {
225 | return physics_material.bounce;
226 | }
227 | double Box2DCollisionObject::get_friction() const {
228 | return physics_material.friction;
229 | }
230 |
231 | // Direct Body API
232 |
233 | Vector2 Box2DCollisionObject::get_total_gravity() const {
234 | return Vector2(total_gravity.x, total_gravity.y);
235 | }
236 |
237 | double Box2DCollisionObject::get_total_linear_damp() const {
238 | return box2d_to_godot(body_def->linearDamping);
239 | }
240 |
241 | double Box2DCollisionObject::get_total_angular_damp() const {
242 | return box2d_to_godot(body_def->linearDamping);
243 | }
244 |
245 | Vector2 Box2DCollisionObject::get_center_of_mass_local() const {
246 | return box2d_to_godot(mass_data.center);
247 | }
248 |
249 | double Box2DCollisionObject::get_inverse_mass() const {
250 | if (mass_data.mass <= 0) {
251 | return 0;
252 | }
253 | return 1.0 / mass_data.mass;
254 | }
255 | double Box2DCollisionObject::get_inverse_inertia() const {
256 | if (mass_data.I <= 0) {
257 | return 0;
258 | }
259 | return 1.0 / mass_data.I;
260 | }
261 | void Box2DCollisionObject::set_linear_velocity(const Vector2 &p_linear_velocity) {
262 | b2Vec2 box2d_linear_velocity = godot_to_box2d(p_linear_velocity);
263 | body_def->linearVelocity = box2d_linear_velocity;
264 | if (body) {
265 | body->SetLinearVelocity(box2d_linear_velocity);
266 | }
267 | }
268 |
269 | Vector2 Box2DCollisionObject::get_linear_velocity() const {
270 | if (body) {
271 | return box2d_to_godot(body->GetLinearVelocity());
272 | }
273 | return Vector2();
274 | }
275 | void Box2DCollisionObject::set_angular_velocity(double p_velocity) {
276 | float angularVelocity = godot_to_box2d(p_velocity);
277 | body_def->angularVelocity = angularVelocity;
278 | if (body) {
279 | body->SetAngularVelocity(angularVelocity);
280 | }
281 | }
282 | double Box2DCollisionObject::get_angular_velocity() const {
283 | if (body) {
284 | return box2d_to_godot(body->GetAngularVelocity());
285 | }
286 | return 0;
287 | }
288 | void Box2DCollisionObject::set_transform(const Transform2D &transform) {
289 | _set_transform(transform);
290 | }
291 | Transform2D Box2DCollisionObject::get_transform() const {
292 | if (body) {
293 | return Transform2D(body->GetAngle(), box2d_to_godot(body->GetPosition()));
294 | } else {
295 | return Transform2D(body_def->angle, box2d_to_godot(body_def->position));
296 | }
297 | }
298 | Vector2 Box2DCollisionObject::get_velocity_at_local_position(const Vector2 &p_local_position) const {
299 | if (body) {
300 | b2Vec2 velocity = body->GetLinearVelocityFromLocalPoint(godot_to_box2d(p_local_position));
301 | return box2d_to_godot(velocity);
302 | }
303 | return Vector2();
304 | }
305 | void Box2DCollisionObject::apply_central_impulse(const Vector2 &impulse) {
306 | if (body) {
307 | body->ApplyLinearImpulseToCenter(body->GetMass() * godot_to_box2d(impulse), true);
308 | }
309 | }
310 | void Box2DCollisionObject::apply_impulse(const Vector2 &impulse, const Vector2 &position) {
311 | if (body) {
312 | body->ApplyLinearImpulse(body->GetMass() * godot_to_box2d(impulse), godot_to_box2d(position), true);
313 | }
314 | }
315 | void Box2DCollisionObject::apply_torque_impulse(double impulse) {
316 | if (body) {
317 | body->ApplyTorque(body->GetMass() * godot_to_box2d(impulse), true);
318 | }
319 | }
320 | void Box2DCollisionObject::apply_central_force(const Vector2 &force) {
321 | if (body) {
322 | body->ApplyForceToCenter(body->GetMass() * godot_to_box2d(force), true);
323 | }
324 | }
325 | void Box2DCollisionObject::apply_force(const Vector2 &force, const Vector2 &position) {
326 | if (body) {
327 | body->ApplyForce(body->GetMass() * godot_to_box2d(force), godot_to_box2d(position), true);
328 | }
329 | }
330 | void Box2DCollisionObject::apply_torque(double torque) {
331 | if (body) {
332 | body->ApplyTorque(body->GetMass() * godot_to_box2d(torque), true);
333 | }
334 | }
335 |
336 | // Constant Forces
337 |
338 | void Box2DCollisionObject::add_constant_central_force(const Vector2 &force) {
339 | constant_forces.constant_force += godot_to_box2d(force);
340 | // TODO set position to center
341 | //constant_force_position = position;
342 | }
343 | void Box2DCollisionObject::add_constant_force(const Vector2 &force, const Vector2 &position) {
344 | constant_forces.constant_force += godot_to_box2d(force);
345 | constant_forces.constant_force_position = godot_to_box2d(position);
346 | }
347 | void Box2DCollisionObject::add_constant_torque(double torque) {
348 | constant_forces.constant_torque += godot_to_box2d(torque);
349 | }
350 | void Box2DCollisionObject::set_constant_force(const Vector2 &force) {
351 | constant_forces.constant_force = godot_to_box2d(force);
352 | }
353 | Vector2 Box2DCollisionObject::get_constant_force() const {
354 | return box2d_to_godot(constant_forces.constant_force);
355 | }
356 | void Box2DCollisionObject::set_constant_torque(double torque) {
357 | constant_forces.constant_torque = godot_to_box2d(torque);
358 | }
359 | double Box2DCollisionObject::get_constant_torque() const {
360 | return box2d_to_godot(constant_forces.constant_torque);
361 | }
362 | void Box2DCollisionObject::set_sleep_state(bool enabled) {
363 | if (body) {
364 | body->SetAwake(!enabled);
365 | }
366 | }
367 | bool Box2DCollisionObject::is_sleeping() const {
368 | return !body->IsAwake();
369 | }
370 | Box2DCollisionObject::ContactEdgeData Box2DCollisionObject::_get_contact_edge_data(int32_t contact_idx) const {
371 | if (!body) {
372 | return ContactEdgeData();
373 | }
374 | b2ContactEdge *contacts = body->GetContactList();
375 | int32 contacts_count = 0;
376 | b2WorldManifold worldManifold;
377 | while (contacts) {
378 | contacts_count += contacts->contact->GetManifold()->pointCount;
379 | if (contacts_count > contact_idx) {
380 | return ContactEdgeData{ contacts, contacts_count - contact_idx - 1 };
381 | }
382 | contacts = contacts->next;
383 | }
384 | return ContactEdgeData();
385 | }
386 |
387 | int32_t Box2DCollisionObject::get_contact_count() const {
388 | if (!body) {
389 | return 0;
390 | }
391 | b2ContactEdge *contacts = body->GetContactList();
392 | int32 contacts_count = 0;
393 | while (contacts) {
394 | contacts_count += contacts->contact->GetManifold()->pointCount;
395 | contacts = contacts->next;
396 | }
397 | return contacts_count;
398 | }
399 | Vector2 Box2DCollisionObject::get_contact_local_position(int32_t contact_idx) const {
400 | ContactEdgeData data = _get_contact_edge_data(contact_idx);
401 | if (!data.edge) {
402 | return Vector2();
403 | }
404 | b2WorldManifold worldManifold;
405 | data.edge->contact->GetWorldManifold(&worldManifold);
406 | return box2d_to_godot(worldManifold.points[data.point_idx]);
407 | }
408 | Vector2 Box2DCollisionObject::get_contact_local_normal(int32_t contact_idx) const {
409 | ContactEdgeData data = _get_contact_edge_data(contact_idx);
410 | if (!data.edge) {
411 | return Vector2();
412 | }
413 | b2Vec2 normal = data.edge->contact->GetManifold()->localNormal;
414 | return Vector2(normal.x, normal.y);
415 | }
416 | int32_t Box2DCollisionObject::get_contact_local_shape(int32_t contact_idx) const {
417 | ContactEdgeData data = _get_contact_edge_data(contact_idx);
418 | if (!data.edge) {
419 | return -1;
420 | }
421 | return data.edge->contact->GetFixtureA()->GetUserData().shape_idx;
422 | }
423 | RID Box2DCollisionObject::get_contact_collider(int32_t contact_idx) const {
424 | ContactEdgeData data = _get_contact_edge_data(contact_idx);
425 | if (!data.edge) {
426 | return RID();
427 | }
428 | b2BodyUserData *user_data = (b2BodyUserData *)&data.edge->other->GetUserData();
429 | return user_data->collision_object->get_self();
430 | }
431 | Vector2 Box2DCollisionObject::get_contact_collider_position(int32_t contact_idx) const {
432 | return get_contact_local_position(contact_idx);
433 | }
434 | uint64_t Box2DCollisionObject::get_contact_collider_id(int32_t contact_idx) const {
435 | ContactEdgeData data = _get_contact_edge_data(contact_idx);
436 | if (!data.edge) {
437 | return 0;
438 | }
439 | b2BodyUserData *user_data = (b2BodyUserData *)&data.edge->other->GetUserData();
440 | return user_data->collision_object->get_object_instance_id();
441 | }
442 | Object *Box2DCollisionObject::get_contact_collider_object(int32_t contact_idx) const {
443 | ObjectID id = ObjectID(get_contact_collider_id(contact_idx));
444 | return ObjectDB::get_instance(id);
445 | }
446 | int32_t Box2DCollisionObject::get_contact_collider_shape(int32_t contact_idx) const {
447 | ContactEdgeData data = _get_contact_edge_data(contact_idx);
448 | if (!data.edge) {
449 | return -1;
450 | }
451 | return data.edge->contact->GetFixtureB()->GetUserData().shape_idx;
452 | }
453 | Vector2 Box2DCollisionObject::get_contact_collider_velocity_at_position(int32_t contact_idx) const {
454 | ContactEdgeData data = _get_contact_edge_data(contact_idx);
455 | if (!data.edge) {
456 | return Vector2();
457 | }
458 | b2WorldManifold worldManifold;
459 | data.edge->contact->GetWorldManifold(&worldManifold);
460 | b2Vec2 world_point = worldManifold.points[data.point_idx];
461 | return box2d_to_godot(data.edge->other->GetLinearVelocityFromWorldPoint(world_point));
462 | }
463 | Vector2 Box2DCollisionObject::get_contact_impulse(int32_t contact_idx) const {
464 | return get_contact_local_normal(contact_idx) * get_step();
465 | }
466 | double Box2DCollisionObject::get_step() const {
467 | Box2DSpace *space = get_space();
468 | if (!space) {
469 | return 0;
470 | }
471 | return space->get_step();
472 | }
473 | void Box2DCollisionObject::integrate_forces() {
474 | }
475 |
476 | PhysicsDirectSpaceState2D *Box2DCollisionObject::get_space_state() {
477 | if (!direct_space) {
478 | direct_space = memnew(Box2DDirectSpaceState);
479 | direct_space->space = space;
480 | }
481 | return direct_space;
482 | }
483 |
484 | // Physics Server
485 |
486 | void Box2DCollisionObject::set_collision_layer(uint32_t layer) {
487 | filter.categoryBits = layer;
488 | if (body) {
489 | _update_shapes();
490 | }
491 | }
492 |
493 | uint32_t Box2DCollisionObject::get_collision_layer() const {
494 | return filter.categoryBits;
495 | }
496 | void Box2DCollisionObject::set_collision_mask(uint32_t layer) {
497 | filter.maskBits = layer;
498 | if (body) {
499 | _update_shapes();
500 | }
501 | }
502 |
503 | uint32_t Box2DCollisionObject::get_collision_mask() const {
504 | return filter.maskBits;
505 | }
506 |
507 | void Box2DCollisionObject::set_pickable(bool p_pickable) {
508 | collision.pickable = p_pickable;
509 | if (body) {
510 | _update_shapes();
511 | }
512 | }
513 |
514 | void Box2DCollisionObject::set_object_instance_id(const ObjectID &p_instance_id) {
515 | object_instance_id = p_instance_id;
516 | }
517 | ObjectID Box2DCollisionObject::get_object_instance_id() const { return object_instance_id; }
518 |
519 | Object *Box2DCollisionObject::get_object() const {
520 | ObjectID id = ObjectID(object_instance_id);
521 | return ObjectDB::get_instance(id);
522 | }
523 | Object *Box2DCollisionObject::get_object_unsafe() const {
524 | return reinterpret_cast