From 83aee02540ceeb140f78cbacbf8c41e7344ea9a7 Mon Sep 17 00:00:00 2001
From: yhh <359807859@qq.com>
Date: Wed, 3 Dec 2025 16:24:08 +0800
Subject: [PATCH] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=E7=AC=AC=E4=B8=89?=
=?UTF-8?q?=E6=96=B9=E4=BE=9D=E8=B5=96=E5=BA=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
thirdparty/rapier.js/.github/FUNDING.yml | 12 +
.../rapier.js/.github/workflows/main.yml | 154 ++
thirdparty/rapier.js/.gitignore | 21 +
thirdparty/rapier.js/.prettierignore | 7 +
thirdparty/rapier.js/.prettierrc | 5 +
thirdparty/rapier.js/CHANGELOG.md | 937 ++++++++
thirdparty/rapier.js/Cargo.lock | 1711 ++++++++++++++
thirdparty/rapier.js/Cargo.toml | 23 +
thirdparty/rapier.js/LICENSE | 201 ++
thirdparty/rapier.js/README.md | 82 +
.../builds/prepare_builds/Cargo.toml | 9 +
.../rapier.js/builds/prepare_builds/README.md | 17 +
.../prepare_builds/build_all_projects.sh | 19 +
.../prepare_builds/prepare_all_projects.sh | 11 +
.../builds/prepare_builds/src/main.rs | 182 ++
.../prepare_builds/templates/Cargo.toml.tera | 60 +
.../builds/prepare_builds/templates/LICENSE | 201 ++
.../prepare_builds/templates/README.md.tera | 70 +
.../templates/build_rust.sh.tera | 15 +
.../templates/build_typescript.sh.tera | 13 +
.../templates/package.json.tera | 25 +
.../prepare_builds/templates/tsconfig.json | 13 +
.../templates/tsconfig_typedoc.json | 10 +
.../templates/typedoc.json.tera | 42 +
thirdparty/rapier.js/package.json | 12 +
thirdparty/rapier.js/publish_all_canary.sh | 19 +
thirdparty/rapier.js/publish_all_prod.sh | 19 +
thirdparty/rapier.js/rapier-compat/README.md | 16 +
.../rapier.js/rapier-compat/build-rust.sh | 50 +
.../rapier.js/rapier-compat/fix_raw_file.sh | 11 +
thirdparty/rapier.js/rapier-compat/gen_src.sh | 58 +
.../rapier.js/rapier-compat/package.json | 59 +
.../rapier.js/rapier-compat/rollup.config.js | 85 +
.../rapier.js/rapier-compat/src2d/init.ts | 60 +
.../rapier.js/rapier-compat/src2d/raw.ts | 1 +
.../rapier.js/rapier-compat/src3d/init.ts | 12 +
.../rapier.js/rapier-compat/src3d/raw.ts | 1 +
.../rapier-compat/tests/World2d.test.ts | 23 +
.../rapier-compat/tests/World3d.test.ts | 23 +
.../rapier-compat/tests/math2d.test.ts | 15 +
.../rapier-compat/tests/math3d.test.ts | 17 +
.../rapier-compat/tsconfig.common.json | 15 +
.../rapier.js/rapier-compat/tsconfig.json | 10 +
.../rapier-compat/tsconfig.pkg2d.json | 8 +
.../rapier-compat/tsconfig.pkg3d.json | 8 +
thirdparty/rapier.js/src.ts/coarena.ts | 70 +
.../src.ts/control/character_controller.ts | 386 ++++
thirdparty/rapier.js/src.ts/control/index.ts | 6 +
.../src.ts/control/pid_controller.ts | 207 ++
.../control/ray_cast_vehicle_controller.ts | 481 ++++
.../rapier.js/src.ts/dynamics/ccd_solver.ts | 25 +
.../dynamics/coefficient_combine_rule.ts | 13 +
.../src.ts/dynamics/impulse_joint.ts | 681 ++++++
.../src.ts/dynamics/impulse_joint_set.ts | 165 ++
thirdparty/rapier.js/src.ts/dynamics/index.ts | 10 +
.../src.ts/dynamics/integration_parameters.ts | 126 ++
.../src.ts/dynamics/island_manager.ts | 37 +
.../src.ts/dynamics/multibody_joint.ts | 222 ++
.../src.ts/dynamics/multibody_joint_set.ts | 157 ++
.../rapier.js/src.ts/dynamics/rigid_body.ts | 1691 ++++++++++++++
.../src.ts/dynamics/rigid_body_set.ts | 238 ++
thirdparty/rapier.js/src.ts/exports.ts | 27 +
.../rapier.js/src.ts/geometry/broad_phase.ts | 520 +++++
.../rapier.js/src.ts/geometry/collider.ts | 1983 +++++++++++++++++
.../rapier.js/src.ts/geometry/collider_set.ts | 213 ++
.../rapier.js/src.ts/geometry/contact.ts | 62 +
.../rapier.js/src.ts/geometry/feature.ts | 16 +
thirdparty/rapier.js/src.ts/geometry/index.ts | 11 +
.../src.ts/geometry/interaction_groups.ts | 18 +
.../rapier.js/src.ts/geometry/narrow_phase.ts | 203 ++
thirdparty/rapier.js/src.ts/geometry/point.ts | 98 +
thirdparty/rapier.js/src.ts/geometry/ray.ts | 192 ++
thirdparty/rapier.js/src.ts/geometry/shape.ts | 1558 +++++++++++++
thirdparty/rapier.js/src.ts/geometry/toi.ts | 105 +
thirdparty/rapier.js/src.ts/init.ts | 2 +
thirdparty/rapier.js/src.ts/math.ts | 263 +++
.../src.ts/pipeline/debug_render_pipeline.ts | 85 +
.../rapier.js/src.ts/pipeline/event_queue.ts | 158 ++
thirdparty/rapier.js/src.ts/pipeline/index.ts | 7 +
.../src.ts/pipeline/physics_hooks.ts | 54 +
.../src.ts/pipeline/physics_pipeline.ts | 85 +
.../src.ts/pipeline/query_pipeline.ts | 57 +
.../src.ts/pipeline/serialization_pipeline.ts | 84 +
thirdparty/rapier.js/src.ts/pipeline/world.ts | 1312 +++++++++++
thirdparty/rapier.js/src.ts/rapier.ts | 3 +
thirdparty/rapier.js/src.ts/raw.ts | 1 +
.../src/control/character_controller.rs | 290 +++
thirdparty/rapier.js/src/control/mod.rs | 11 +
.../rapier.js/src/control/pid_controller.rs | 264 +++
.../control/ray_cast_vehicle_controller.rs | 336 +++
.../rapier.js/src/dynamics/ccd_solver.rs | 13 +
.../rapier.js/src/dynamics/impulse_joint.rs | 215 ++
.../src/dynamics/impulse_joint_set.rs | 92 +
.../src/dynamics/integration_parameters.rs | 101 +
.../rapier.js/src/dynamics/island_manager.rs | 31 +
thirdparty/rapier.js/src/dynamics/joint.rs | 314 +++
thirdparty/rapier.js/src/dynamics/mod.rs | 20 +
.../rapier.js/src/dynamics/multibody_joint.rs | 196 ++
.../src/dynamics/multibody_joint_set.rs | 85 +
.../rapier.js/src/dynamics/rigid_body.rs | 753 +++++++
.../rapier.js/src/dynamics/rigid_body_set.rs | 237 ++
.../rapier.js/src/geometry/broad_phase.rs | 462 ++++
thirdparty/rapier.js/src/geometry/collider.rs | 993 +++++++++
.../rapier.js/src/geometry/collider_set.rs | 293 +++
thirdparty/rapier.js/src/geometry/contact.rs | 31 +
thirdparty/rapier.js/src/geometry/feature.rs | 47 +
thirdparty/rapier.js/src/geometry/mod.rs | 49 +
.../rapier.js/src/geometry/narrow_phase.rs | 217 ++
thirdparty/rapier.js/src/geometry/point.rs | 53 +
thirdparty/rapier.js/src/geometry/ray.rs | 74 +
thirdparty/rapier.js/src/geometry/shape.rs | 527 +++++
thirdparty/rapier.js/src/geometry/toi.rs | 65 +
thirdparty/rapier.js/src/lib.rs | 32 +
thirdparty/rapier.js/src/math.rs | 266 +++
.../src/pipeline/debug_render_pipeline.rs | 152 ++
.../rapier.js/src/pipeline/event_queue.rs | 140 ++
thirdparty/rapier.js/src/pipeline/mod.rs | 11 +
.../rapier.js/src/pipeline/physics_hooks.rs | 217 ++
.../src/pipeline/physics_pipeline.rs | 170 ++
.../src/pipeline/serialization_pipeline.rs | 145 ++
thirdparty/rapier.js/src/utils.rs | 79 +
thirdparty/rapier.js/testbed2d/.npmignore | 3 +
thirdparty/rapier.js/testbed2d/package.json | 32 +
thirdparty/rapier.js/testbed2d/publish.sh | 5 +
.../rapier.js/testbed2d/src/Graphics.ts | 269 +++
thirdparty/rapier.js/testbed2d/src/Gui.ts | 113 +
thirdparty/rapier.js/testbed2d/src/Testbed.ts | 189 ++
.../src/demos/characterController.ts | 89 +
.../testbed2d/src/demos/collisionGroups.ts | 79 +
.../testbed2d/src/demos/convexPolygons.ts | 68 +
.../rapier.js/testbed2d/src/demos/cubes.ts | 56 +
.../testbed2d/src/demos/heightfield.ts | 65 +
.../rapier.js/testbed2d/src/demos/keva.ts | 92 +
.../testbed2d/src/demos/lockedRotations.ts | 51 +
.../testbed2d/src/demos/pidController.ts | 100 +
.../rapier.js/testbed2d/src/demos/polyline.ts | 64 +
.../testbed2d/src/demos/revoluteJoints.ts | 59 +
.../rapier.js/testbed2d/src/demos/voxels.ts | 90 +
thirdparty/rapier.js/testbed2d/src/index.ts | 30 +
.../rapier.js/testbed2d/static/.htaccess | 3 +
.../rapier.js/testbed2d/static/index.html | 18 +
thirdparty/rapier.js/testbed2d/tsconfig.json | 11 +
.../rapier.js/testbed2d/webpack.config.js | 55 +
thirdparty/rapier.js/testbed3d/.npmignore | 3 +
thirdparty/rapier.js/testbed3d/package.json | 32 +
thirdparty/rapier.js/testbed3d/publish.sh | 6 +
.../rapier.js/testbed3d/src/Graphics.ts | 576 +++++
thirdparty/rapier.js/testbed3d/src/Gui.ts | 167 ++
thirdparty/rapier.js/testbed3d/src/Testbed.ts | 211 ++
.../rapier.js/testbed3d/src/demos/ccd.ts | 75 +
.../src/demos/characterController.ts | 106 +
.../testbed3d/src/demos/collisionGroups.ts | 73 +
.../testbed3d/src/demos/convexPolyhedron.ts | 111 +
.../rapier.js/testbed3d/src/demos/damping.ts | 42 +
.../rapier.js/testbed3d/src/demos/fountain.ts | 78 +
.../testbed3d/src/demos/glbToTrimesh.ts | 68 +
.../testbed3d/src/demos/glbtoConvexHull.ts | 68 +
.../testbed3d/src/demos/heightfield.ts | 126 ++
.../rapier.js/testbed3d/src/demos/joints.ts | 292 +++
.../rapier.js/testbed3d/src/demos/keva.ts | 139 ++
.../testbed3d/src/demos/lockedRotations.ts | 58 +
.../testbed3d/src/demos/pidController.ts | 127 ++
.../rapier.js/testbed3d/src/demos/platform.ts | 153 ++
.../rapier.js/testbed3d/src/demos/pyramid.ts | 51 +
.../rapier.js/testbed3d/src/demos/trimesh.ts | 143 ++
.../rapier.js/testbed3d/src/demos/voxels.ts | 130 ++
thirdparty/rapier.js/testbed3d/src/index.ts | 42 +
.../rapier.js/testbed3d/static/.htaccess | 3 +
.../rapier.js/testbed3d/static/index.html | 18 +
.../static/suzanne_blender_monkey.glb | Bin 0 -> 60820 bytes
thirdparty/rapier.js/testbed3d/tsconfig.json | 11 +
.../rapier.js/testbed3d/webpack.config.js | 55 +
172 files changed, 27480 insertions(+)
create mode 100644 thirdparty/rapier.js/.github/FUNDING.yml
create mode 100644 thirdparty/rapier.js/.github/workflows/main.yml
create mode 100644 thirdparty/rapier.js/.gitignore
create mode 100644 thirdparty/rapier.js/.prettierignore
create mode 100644 thirdparty/rapier.js/.prettierrc
create mode 100644 thirdparty/rapier.js/CHANGELOG.md
create mode 100644 thirdparty/rapier.js/Cargo.lock
create mode 100644 thirdparty/rapier.js/Cargo.toml
create mode 100644 thirdparty/rapier.js/LICENSE
create mode 100644 thirdparty/rapier.js/README.md
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/Cargo.toml
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/README.md
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/build_all_projects.sh
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/prepare_all_projects.sh
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/src/main.rs
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/Cargo.toml.tera
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/LICENSE
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/README.md.tera
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/build_rust.sh.tera
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/build_typescript.sh.tera
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/package.json.tera
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig.json
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig_typedoc.json
create mode 100644 thirdparty/rapier.js/builds/prepare_builds/templates/typedoc.json.tera
create mode 100644 thirdparty/rapier.js/package.json
create mode 100644 thirdparty/rapier.js/publish_all_canary.sh
create mode 100644 thirdparty/rapier.js/publish_all_prod.sh
create mode 100644 thirdparty/rapier.js/rapier-compat/README.md
create mode 100644 thirdparty/rapier.js/rapier-compat/build-rust.sh
create mode 100644 thirdparty/rapier.js/rapier-compat/fix_raw_file.sh
create mode 100644 thirdparty/rapier.js/rapier-compat/gen_src.sh
create mode 100644 thirdparty/rapier.js/rapier-compat/package.json
create mode 100644 thirdparty/rapier.js/rapier-compat/rollup.config.js
create mode 100644 thirdparty/rapier.js/rapier-compat/src2d/init.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/src2d/raw.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/src3d/init.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/src3d/raw.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/tests/World2d.test.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/tests/World3d.test.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/tests/math2d.test.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/tests/math3d.test.ts
create mode 100644 thirdparty/rapier.js/rapier-compat/tsconfig.common.json
create mode 100644 thirdparty/rapier.js/rapier-compat/tsconfig.json
create mode 100644 thirdparty/rapier.js/rapier-compat/tsconfig.pkg2d.json
create mode 100644 thirdparty/rapier.js/rapier-compat/tsconfig.pkg3d.json
create mode 100644 thirdparty/rapier.js/src.ts/coarena.ts
create mode 100644 thirdparty/rapier.js/src.ts/control/character_controller.ts
create mode 100644 thirdparty/rapier.js/src.ts/control/index.ts
create mode 100644 thirdparty/rapier.js/src.ts/control/pid_controller.ts
create mode 100644 thirdparty/rapier.js/src.ts/control/ray_cast_vehicle_controller.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/ccd_solver.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/coefficient_combine_rule.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/impulse_joint.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/impulse_joint_set.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/index.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/integration_parameters.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/island_manager.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/multibody_joint.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/multibody_joint_set.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/rigid_body.ts
create mode 100644 thirdparty/rapier.js/src.ts/dynamics/rigid_body_set.ts
create mode 100644 thirdparty/rapier.js/src.ts/exports.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/broad_phase.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/collider.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/collider_set.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/contact.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/feature.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/index.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/interaction_groups.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/narrow_phase.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/point.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/ray.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/shape.ts
create mode 100644 thirdparty/rapier.js/src.ts/geometry/toi.ts
create mode 100644 thirdparty/rapier.js/src.ts/init.ts
create mode 100644 thirdparty/rapier.js/src.ts/math.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/debug_render_pipeline.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/event_queue.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/index.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/physics_hooks.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/physics_pipeline.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/query_pipeline.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/serialization_pipeline.ts
create mode 100644 thirdparty/rapier.js/src.ts/pipeline/world.ts
create mode 100644 thirdparty/rapier.js/src.ts/rapier.ts
create mode 100644 thirdparty/rapier.js/src.ts/raw.ts
create mode 100644 thirdparty/rapier.js/src/control/character_controller.rs
create mode 100644 thirdparty/rapier.js/src/control/mod.rs
create mode 100644 thirdparty/rapier.js/src/control/pid_controller.rs
create mode 100644 thirdparty/rapier.js/src/control/ray_cast_vehicle_controller.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/ccd_solver.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/impulse_joint.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/impulse_joint_set.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/integration_parameters.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/island_manager.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/joint.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/mod.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/multibody_joint.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/multibody_joint_set.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/rigid_body.rs
create mode 100644 thirdparty/rapier.js/src/dynamics/rigid_body_set.rs
create mode 100644 thirdparty/rapier.js/src/geometry/broad_phase.rs
create mode 100644 thirdparty/rapier.js/src/geometry/collider.rs
create mode 100644 thirdparty/rapier.js/src/geometry/collider_set.rs
create mode 100644 thirdparty/rapier.js/src/geometry/contact.rs
create mode 100644 thirdparty/rapier.js/src/geometry/feature.rs
create mode 100644 thirdparty/rapier.js/src/geometry/mod.rs
create mode 100644 thirdparty/rapier.js/src/geometry/narrow_phase.rs
create mode 100644 thirdparty/rapier.js/src/geometry/point.rs
create mode 100644 thirdparty/rapier.js/src/geometry/ray.rs
create mode 100644 thirdparty/rapier.js/src/geometry/shape.rs
create mode 100644 thirdparty/rapier.js/src/geometry/toi.rs
create mode 100644 thirdparty/rapier.js/src/lib.rs
create mode 100644 thirdparty/rapier.js/src/math.rs
create mode 100644 thirdparty/rapier.js/src/pipeline/debug_render_pipeline.rs
create mode 100644 thirdparty/rapier.js/src/pipeline/event_queue.rs
create mode 100644 thirdparty/rapier.js/src/pipeline/mod.rs
create mode 100644 thirdparty/rapier.js/src/pipeline/physics_hooks.rs
create mode 100644 thirdparty/rapier.js/src/pipeline/physics_pipeline.rs
create mode 100644 thirdparty/rapier.js/src/pipeline/serialization_pipeline.rs
create mode 100644 thirdparty/rapier.js/src/utils.rs
create mode 100644 thirdparty/rapier.js/testbed2d/.npmignore
create mode 100644 thirdparty/rapier.js/testbed2d/package.json
create mode 100644 thirdparty/rapier.js/testbed2d/publish.sh
create mode 100644 thirdparty/rapier.js/testbed2d/src/Graphics.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/Gui.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/Testbed.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/characterController.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/collisionGroups.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/convexPolygons.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/cubes.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/heightfield.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/keva.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/lockedRotations.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/pidController.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/polyline.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/revoluteJoints.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/demos/voxels.ts
create mode 100644 thirdparty/rapier.js/testbed2d/src/index.ts
create mode 100644 thirdparty/rapier.js/testbed2d/static/.htaccess
create mode 100644 thirdparty/rapier.js/testbed2d/static/index.html
create mode 100644 thirdparty/rapier.js/testbed2d/tsconfig.json
create mode 100644 thirdparty/rapier.js/testbed2d/webpack.config.js
create mode 100644 thirdparty/rapier.js/testbed3d/.npmignore
create mode 100644 thirdparty/rapier.js/testbed3d/package.json
create mode 100644 thirdparty/rapier.js/testbed3d/publish.sh
create mode 100644 thirdparty/rapier.js/testbed3d/src/Graphics.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/Gui.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/Testbed.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/ccd.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/characterController.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/collisionGroups.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/convexPolyhedron.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/damping.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/fountain.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/glbToTrimesh.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/glbtoConvexHull.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/heightfield.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/joints.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/keva.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/lockedRotations.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/pidController.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/platform.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/pyramid.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/trimesh.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/demos/voxels.ts
create mode 100644 thirdparty/rapier.js/testbed3d/src/index.ts
create mode 100644 thirdparty/rapier.js/testbed3d/static/.htaccess
create mode 100644 thirdparty/rapier.js/testbed3d/static/index.html
create mode 100644 thirdparty/rapier.js/testbed3d/static/suzanne_blender_monkey.glb
create mode 100644 thirdparty/rapier.js/testbed3d/tsconfig.json
create mode 100644 thirdparty/rapier.js/testbed3d/webpack.config.js
diff --git a/thirdparty/rapier.js/.github/FUNDING.yml b/thirdparty/rapier.js/.github/FUNDING.yml
new file mode 100644
index 00000000..97039335
--- /dev/null
+++ b/thirdparty/rapier.js/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: ["dimforge"] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/thirdparty/rapier.js/.github/workflows/main.yml b/thirdparty/rapier.js/.github/workflows/main.yml
new file mode 100644
index 00000000..c7977f5e
--- /dev/null
+++ b/thirdparty/rapier.js/.github/workflows/main.yml
@@ -0,0 +1,154 @@
+name: main
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+ workflow_dispatch:
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ check-fmt:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "22.x"
+ - run: npm ci
+ - name: Check formatting
+ run: npm run fmt -- --check
+ - name: Check Rust formatting
+ run: cargo fmt -- --check
+ build:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ env:
+ RUSTFLAGS: -D warnings
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ - run: npm ci;
+ - name: Prepare no-compat builds
+ run: |
+ ./builds/prepare_builds/prepare_all_projects.sh
+ - name: Build all no-compat projects
+ run: |
+ ./builds/prepare_builds/build_all_projects.sh
+ # Install dependencies to check simd for builds
+ - name: Install wabt
+ run: |
+ sudo apt install wabt;
+ if: matrix.os == 'ubuntu-latest'
+ - name: Install wabt
+ run: |
+ brew install wabt;
+ if: matrix.os == 'macos-latest'
+ # Check simd for rust builds
+ - name: Check 2d simd rust build
+ run: |
+ if ! wasm-objdump -d builds/rapier2d-simd/pkg/rapier_wasm2d_bg.wasm | grep :\\sfd ; then
+ >&2 echo "ERROR: 2d simd compat build does not include simd opcode prefix." && exit 1
+ fi
+ - name: Check 3d simd compat build
+ run: |
+ if ! wasm-objdump -d builds/rapier3d-simd/pkg/rapier_wasm3d_bg.wasm | grep :\\sfd ; then
+ >&2 echo "ERROR: 3d simd compat build does not include simd opcode prefix." && exit 1
+ fi
+ - uses: actions/upload-artifact@v4
+ with:
+ name: pkg no-compat ${{ matrix.os }}
+ path: |
+ builds/*/pkg
+ overwrite: true
+ build-compat:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ env:
+ RUSTFLAGS: -D warnings
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ - run: npm ci;
+ - name: Prepare compat builds
+ run: |
+ ./builds/prepare_builds/prepare_all_projects.sh
+ - name: Build rapier-compat
+ run: |
+ cd rapier-compat;
+ npm ci;
+ npm run build;
+ npm run test;
+ # Install dependencies to check simd for builds
+ - name: Install wabt
+ run: |
+ sudo apt install wabt;
+ if: matrix.os == 'ubuntu-latest'
+ - name: Install wabt
+ run: |
+ brew install wabt;
+ if: matrix.os == 'macos-latest'
+ # Check simd for compat builds
+ - name: Check 2d simd compat build
+ run: |
+ if ! wasm-objdump -d rapier-compat/builds/2d-simd/pkg/rapier_wasm2d_bg.wasm | grep :\\sfd ; then
+ >&2 echo "ERROR: 2d simd compat build does not include simd opcode prefix." && exit 1;
+ fi
+ - name: Check 3d simd compat build
+ run: |
+ if ! wasm-objdump -d rapier-compat/builds/3d-simd/pkg/rapier_wasm3d_bg.wasm | grep :\\sfd ; then
+ >&2 echo "ERROR: 3d simd compat build does not include simd opcode prefix." && exit 1;
+ fi
+ # Upload
+ - uses: actions/upload-artifact@v4
+ with:
+ name: pkg compat ${{ matrix.os }}
+ path: |
+ rapier-compat/builds/*/pkg
+ overwrite: true
+ publish:
+ runs-on: ubuntu-latest
+ needs: [build, build-compat]
+ if: github.ref == 'refs/heads/master'
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: pkg no-compat ubuntu-latest
+ path: builds
+ - uses: actions/download-artifact@v4
+ with:
+ name: pkg compat ubuntu-latest
+ path: rapier-compat/builds
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "22.x"
+ registry-url: "https://registry.npmjs.org"
+ - name: Publish projects
+ run: |
+ ./publish_all_canary.sh
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/thirdparty/rapier.js/.gitignore b/thirdparty/rapier.js/.gitignore
new file mode 100644
index 00000000..b6a5a6f5
--- /dev/null
+++ b/thirdparty/rapier.js/.gitignore
@@ -0,0 +1,21 @@
+rapier2d/compat
+rapier3d/compat
+**/*.rs.bk
+node_modules
+target
+.idea
+.DS_Store
+dist
+pkg
+gen2d/
+gen3d/
+docs
+*.swp
+*.d.ts
+
+# Made by the prepare_builds crate
+builds/*
+!builds/prepare_builds
+
+# compat generated builds
+rapier-compat/builds/
\ No newline at end of file
diff --git a/thirdparty/rapier.js/.prettierignore b/thirdparty/rapier.js/.prettierignore
new file mode 100644
index 00000000..24ed3cdd
--- /dev/null
+++ b/thirdparty/rapier.js/.prettierignore
@@ -0,0 +1,7 @@
+pkg
+gen2d
+gen3d
+docs
+target
+**/CHANGELOG.md
+**/README.md
\ No newline at end of file
diff --git a/thirdparty/rapier.js/.prettierrc b/thirdparty/rapier.js/.prettierrc
new file mode 100644
index 00000000..5a3e1430
--- /dev/null
+++ b/thirdparty/rapier.js/.prettierrc
@@ -0,0 +1,5 @@
+{
+ "tabWidth": 4,
+ "bracketSpacing": false,
+ "trailingComma": "all"
+}
diff --git a/thirdparty/rapier.js/CHANGELOG.md b/thirdparty/rapier.js/CHANGELOG.md
new file mode 100644
index 00000000..413325bf
--- /dev/null
+++ b/thirdparty/rapier.js/CHANGELOG.md
@@ -0,0 +1,937 @@
+## 0.19.3 (05 Nov. 2025)
+
+- Significantly improve performances of `combineVoxelStates`.
+
+### 0.19.2 (17 Oct. 2025)
+
+- Fix bug where kinematic bodies would not wake up when setting its velocity.
+- Fix bug where slow-moving kinematic bodies would fall asleep.
+- Fix point-projection on voxels shapes.
+
+### 0.19.1 (03 Oct. 2025)
+
+### Modified
+
+- Update to Rapier 0.30.0. The only change is a [switch to a sparse storage](https://github.com/dimforge/parry/pull/380)
+ for the Voxels shapes. This allows support for orders of magnitudes larger maps without reaching the 4GB WASM memory
+ limit.
+
+### 0.19.0 (05 Sept. 2025)
+
+### Modified
+
+- Update to Rapier 0.29.0 which includes performance improvements for scenes involving a lot of contact constraints.
+ See https://github.com/dimforge/rapier/pull/876 for details.
+- Renamed the `RigidBody.invPrincipalInertiaSqrt` and `.effectiveWorldInvInertiaSqrt` methods to
+ `RigidBody.invPrincipalInertia` and `.effectiveWorldInvInertia` (removed the `Sqrt` suffix). These methods will now
+ return the actual inverse angular inertia matrix rather than its square root.
+- Removed methods related to the legacy PGS solver: `World.numAdditionalFrictionIterations`,
+ `switchToStandardPgsSolver`, `switchToSmallStepsPgsSolver`, `switchToSmallStepsPgsSolverWithoutWarmstart`.
+
+### 0.18.2 (13 August 2025)
+
+### Fixed
+
+- Fix rollup configuration adding `types: "./rapier.d.ts"` to the export config.
+
+### 0.18.1 (8 August 2025)
+
+### Modified
+
+- Update to Rapier 0.28.0 which includes performance improvements when CCD is active and when
+ the user applies modification to a collider or rigid-body.
+
+### Fix
+
+- Another attempt to fix bundlerless module import with rapier-compat.
+
+### 0.18.0 (24 July 2025)
+
+### Added
+
+- Add `World.timing*` functions to access the internal performances measurements if the internal
+ profiler is enabled with `World.profilerEnabled = true`.
+- Add `World.maxCcdSubsteps` to get/set the max number of CCD substeps run by the engine.
+
+### Fix
+
+- Fixed crash that would happen when removing colliders in a particular order (e.g. in the same order
+ as their insertion).
+
+### 0.18.0-beta.0 (12 July 2025)
+
+#### Modified
+
+- Update to Rapier 0.27.0-beta.1 which includes a fully reworked broad-phase tha supports scene queries.
+ This implies a performance gain on large scenes by avoiding the need to re-build the underlying acceleration
+ structure at each frame.
+- Un-deprecate methods for reading shape properties (for example `collider.radius()`). It turned out that these
+ methods are more convenient as they are guaranteed to always be in sync with rapier’s state on wasm.
+- Add `collider.translationWrtParent()` and `collider.rotationWrtParent()` to get the collider’s translation/rotation
+ relative to its parent rigid-body.
+
+#### Fix
+
+- rapier-compat top level javascript files extensions have been changed from `.cjs.js` and `.es.js` to `.cjs` and `mjs`
+ respectively. This results in better compatibility with NPM.
+
+### 0.17.3 (30 May 2025)
+
+#### Fix
+
+- The published package for 0.17.2 had a broken package.json. It is fixed on this release.
+
+### 0.17.2 (30 May 2025)
+
+#### Added
+
+- Added the function `RAPIER.reserveMemory` to instruct the internal allocator to pre-allocate more memory in preparation
+ for future operations. This typically called only once after initializing the WASM module.
+
+### 0.17.1 (23 May 2025)
+
+#### Added
+
+- Added optional arguments to `World.debugRender(filterFlags, filterPredicate)` to prevent some colliders from being
+ rendered.
+- Added `Collider.combineVoxelStates` to ensure two adjacent voxels colliders don’t suffer from the internal edges
+ problem, and `Collider.propagateVoxelChange` to maintain that coupling after modifications with `.setVoxel`.
+
+### 0.17.0 (16 May 2025)
+
+#### Fixed
+
+- Fix sensor events not triggering when hitting a voxels collider.
+
+#### Added
+
+- Added support for voxels colliders attached to dynamic rigid-bodies.
+- Added force calculation between colliding voxels/voxels and voxels/compound shapes.
+
+### 0.16.2 (5 May 2025)
+
+#### Fixed
+
+- Fixed infinite loop in `collider.setVoxel`.
+
+### 0.16.1 (2 May 2025)
+
+#### Added
+
+- Added `Collider.clearShapeCache` to release the reference to the JS shape stored in the collider, oor too force its
+ recomputation the next time the collider shape is accessed.
+- Added support for shape-casting involving Voxels colliders.
+- Added support for CCD involving Voxels colliders.
+
+### 0.16.0 (24 April 2025)
+
+#### Added
+
+- Added `ColliderDesc.voxels` to create a collider with a shape optimized for voxels.
+- Added `Collider.setVoxel` for adding or removing a voxel from a collider with a voxel shape.
+- Added the `Voxels` shape class.
+
+The support or voxels is still experimental. In particular the following features will currently **not** work on
+colliders with voxel shapes:
+
+- Voxels colliders attached to dynamic rigid-bodies will not run the automatic mass/angular inertia calculation.
+- Shape-casting on voxel shapes/colliders.
+- Collision-detection between two-voxels colliders, or a voxels collider and a mesh, polyline, or heightfield.
+
+See [parry#336](https://github.com/dimforge/parry/pull/336) for additional information.
+
+### 0.15.1 (10 April 2025)
+
+#### Added
+
+- Added `RigidBody.velocityAtPoint` function to retrieve the velocity of a given world-space point on a rigid-body.
+
+#### Modified
+
+- Update to Rapier 0.25. The main notable change is that the `TriMeshFlags.FIX_INTERNAL_EDGES` flag will no longer
+ imply that the `TriMeshFlags.ORIENTED` flag is set, avoiding edge-cases where enabling `FIX_INTERNAL_EDGES`
+ results in unexpected collisions between open meshes and balls.
+
+#### Fixed
+
+- Fixed `*-simd-compat` builds not actually emitting SIMD instructions.
+
+### 0.15.0 (06 March 2025)
+
+#### Added
+
+- Added `PidController`, `World.createPidController`, `World.removePidController` to create a Position-Integral-Derivative
+ controller; a building block for building velocity-based dynamic character controllers.
+
+#### Modified
+
+- Update to Rapier 0.24.
+- Package tree shaking has been disabled to avoid a crash on certain build configuration (#289).
+- Multiple packages with different feature sets are now released, and their build process has been automated (#309)
+ - Released packages are:
+ - rapier2d
+ - rapier2d-compat
+ - rapier2d-simd
+ - rapier2d-simd-compat
+ - rapier2d-deterministic
+ - rapier2d-deterministic-compat
+ - rapier3d
+ - rapier3d-compat
+ - rapier2d
+ - rapier2d-compat
+ - rapier3d-simd
+ - rapier3d-simd-compat
+ - rapier3d-deterministic
+ - rapier3d-deterministic-compat
+ - :warning: To this occasion, existing packages `rapier2d` and `rapier3d` are now built without `enhanced-determinism` feature enabled,
+ so if you rely on that feature, you should migrate to the new `-deterministic` flavor.
+
+
+### 0.14.0 (20 July 2024)
+
+#### Modified
+
+- Update from the rust library of rapier 0.19 to 0.22, see [rapier's changelog](https://github.com/dimforge/rapier/blob/master/CHANGELOG.md#v0210-23-june-2024) for more context.
+
+#### Added
+
+- Added `RigidBody.userForce` function to retrieve the constant force(s) the user added to a rigid-body
+- Added `RigidBody.userTorque` function to retrieve the constant torque(s) the user added to a rigid-body
+
+### 0.13.1 (2024-05-08)
+
+#### Fixed
+
+- Fix regression that could cause missed contact between a ball and another shape type.
+
+### 0.13.0 (2024-05-05)
+
+Several stability improvements are added as part of this release.
+See [rapier#625](https://github.com/dimforge/rapier/pull/625) for overviews of the most important improvements.
+
+#### Modified
+
+- The `castShape` and `castCollider` functions have been modified to add a `targetDistance` parameter. This parameter
+ indicates the distance below which a hit is detected.
+- Rename `RayIntersection.toi` to `.timeOfImpact` for better clarity.
+- Rename `RayColliderIntersection.toi` to `.timeOfImpact` for better clarity.
+- Rename `RayColliderToi` to `RayColliderHit`.
+- Rename `RayColliderHit.toi` to `.timeOfImpact` for better clarity.
+- Rename `ShapeTOI` to `ShapeCastHit`.
+- Rename `ShapeColliderTOI` to `ColliderShapeCastHit`.
+- Rename `ShapeCastHit.toi` to `.timeOfImpact`.
+
+#### Added
+
+- Fix the kinematic character controller getting stuck against vertical walls.
+- Add `KinematicCharacterController.normalNudgeFactor` and `.setNormalNudgeFactor` so set a coefficient used for
+ avoiding having the character controller get stuck on penetrations.
+- Add `RigidBody.softCcdPrediction`, `.setSoftCcdPrediction`, and `RigidBodyDesc.setSoftCcdPrediction` for configuring
+ soft-ccd on the rigid-body. See [rapier#625](https://github.com/dimforge/rapier/pull/625) for additional details on
+ soft-ccd.
+- 3D version only: add `TriMeshFlags::FIX_INTERNAL_EDGES` and `HeightFieldFlags::FIX_INTERNAL_EDGES` for enabling
+ internal edges correction (which is no longer enabled by default). The flags have been added as an optional parameter
+ when building the shapes.
+- Add `Collider.contactSkin`, `.setContactSkin`, and `ColliderDesc.setContactSkin` for configuring the collider’s
+ contact skin. See [rapier#625](https://github.com/dimforge/rapier/pull/625) for additional details on contact skins.
+- Add `World.lengthUnit` which can be used to indicate the typical size of dynamic objects (e.g. 100 pixels instead of
+ 1 meter). This helps the physics engine adjust internal thresholds for better results.
+
+#### Fixed
+
+- Fix an issue where the reported contact force are lower than their actual value.
+
+### 0.12.0 (2024-01-28)
+
+The main highlight of this release is the implementation of a new non-linear constraints solver for better stability
+and increased convergence rates. See [#579](https://github.com/dimforge/rapier/pull/579) for additional information.
+
+In order to adjust the number of iterations of the new solver, simply adjust `World.numSolverIterations`.
+If recovering the old solver behavior is useful to you, call `World.switchToStandardPgsSolver()`.
+
+It is now possible to specify some additional solver iteration for specific rigid-bodies (and everything interacting
+with it directly or indirectly through contacts and joints): `RigidBodyDesc.additionalSolverIterations` and
+`RigidBody::setAdditionalSolverIterations`. This allows for higher-accuracy on subsets of the physics scene
+without affecting performance of the other parts of the simulation.
+
+#### Modified
+
+- Renamed `CharacterController.translationApplied`, `.translationRemaining` and the `desiredTranslation`
+ method argument to `CharacterController.translationDeltaApplied`, `.translationDeltaRemaining` and the
+ `desiredTranslationDelta` to avoid confusion with the usage of the `translation` world in `RigidBody.translation()`.
+
+#### Added
+
+- Added `DynamicRayCastVehicleController` to simulate vehicles based on ray-casting.
+- Added `JointData.generic` (3D only) to create a generic 6-dof joint and manually specify the locked axes.
+
+### 0.11.2
+
+#### Fixed
+
+- Fix bug that made dynamic rigid-bodies behave like kinematic bodies after being disabled and then re-enabled.
+- Fix issue with convex polyhedron jitter due to missing contacts.
+- Fix character controller getting stuck against vertical walls.
+- Fix character controller’s snapping to ground not triggering sometimes.
+- Fix character controller’s horizontal offset being mostly ignored and some instances of vertical offset being ignored.
+
+#### Added
+
+- Add `JointData.spring` and `JointData.rope` joints.
+- Add access to the mass-properties of a rigid-body: `RigidBody.effectiveInvMass`, `.invMass()`, `.localCom()`,
+ `.worldCom()`, `.invPrincipalInertiaSqrt()`, `.principalInertia()`, `.principalInertiaLocalFrame()`,
+ `.effectiveWorldInvInertiaSqrt()`, `.effectiveAngularInertia()`.
+- Add `DynamicRayCastVehicleController.siteFrictionStiffness` to customize the side friction applied to the vehicle
+ controller’s wheel.
+
+#### Modified
+
+- Rename `World.contactsWith` to `World.contactPairsWith`.
+- Rename `World.intersectionsWith` to `World.intersectionPairsWith`.
+
+### 0.11.1 (2023-01-16)
+
+#### Fixed
+
+- Fix bug that disabled all colliders at construction time.
+
+### 0.11.0 (2023-01-15)
+
+#### Added
+
+- Add `World.propagateModifiedBodyPositionsToColliders` to propagate rigid-bodies position changes to their attached
+ colliders.
+- Add `World.updateSceneQueries` to update the scene queries data structures without stepping the whole simulation.
+- Add `RigidBody.isEnabled, RigidBody.setEnabled, RigidBodyDesc.setEnabled` to disable a rigid-body (and all its
+ attached colliders) without removing it from the physics world.
+- Add `Collider.isEnabled, Collider.setEnabled, ColliderDesc.setEnabled` to disable a collider without removing it
+ from the physics world.
+- Add shape-specific methods to modify a collider’s
+ size: `Collider.setRadius, setHalfExtents, setRoundRadius, setHalfHeight`.
+
+#### Modified
+
+- Add a boolean argument to `RigidBody.setBodyType` to indicate if the rigid-body should awaken after changing
+ its type.
+
+#### Fixed
+
+- Fix rigid-bodies automatically waking up at creation even if they were explicitly created sleeping.
+
+### 0.10.0 (2022-11-06)
+
+#### Added
+
+- Add `World.createCharacterController`, `World.removeCharacterController` to create/remove a kinematic character
+ controller.
+- Add a character-controller implementation with the `KinematicCharacterController` class and its method
+ `KinematicCharacterController.computeColliderMovement`. The character controller features currently supported are:
+ - Slide on uneven terrains
+ - Interaction with dynamic bodies.
+ - Climb stairs automatically.
+ - Automatically snap the body to the floor when going downstairs.
+ - Prevent sliding up slopes that are too steep
+ - Prevent sliding down slopes that are not steep enough
+ - Interactions with moving platforms.
+ - Report information on the obstacles it hit on its path.
+- Add the `HalfSpace` (infinite plane) shape. Colliders with this shape can be built using `ColliderDesc.halfspace`.
+
+#### Modified
+
+- Change the signature of `Collider.castShape` and World.castShape by adding a boolean argument `stop_at_penetration`
+ before the filter-related arguments. Set this argument to `true` to get the same result as before. If this is set to
+ `false` and the shape being cast starts it path already intersecting another shape, then a hit won’t be returned
+ with that intersecting shape unless the casting movement would result in more penetrations.
+- Reduce rounding errors in 3D when setting the rotation of a rigid-body or collider.
+
+#### Fixed
+
+- Fix incorrect application of torque if the torque is applies right after setting the rigid-body’s
+ position, but before calling `World.step`.
+
+### 0.9.0 (2022-10-07)
+
+#### Fixed
+
+- Fix unpredictable broad-phase panic when using small colliders in the simulation.
+- Fix collision events being incorrectly generated for any shape that produces multiple
+ contact manifolds (like triangle meshes).
+
+#### Modified
+
+- The `RigidBodyDesc.setAdditionalMass` method will now result in the additional angular inertia
+ being automatically computed based on the shapes of the colliders attached to the rigid-body.
+- Removed the method `RigidBodyDesc.setAdditionalPrincipalAngularInertia`. Use
+ `RigidBodyDesc.setAdditionalMassProperties` instead.
+- The methods of `RigidBodyDesc` and `ColliderDesc` will now always copy the object provided by
+ the user instead of storing the object itself.
+- The following method will now copy the user’s objects instead of storing it: `ColliderDesc.setRotation`,
+ `ColliderDesc.setMassProperties`, `RigidBodyDesc.setRotation`, `RigidBodyDesc.setAdditionalMassProperties`,
+ `RigidBodyDesc.setAngvel`.
+- Rename `RigidBody.restrictRotations` and `RigidBody.restrictTranslations` to
+ `RigidBody.setEnabledRotations` and `RigidBody.setEnabledTranslations`.
+- Rename `RigidBodyDesc.restrictRotations` and `RigidBodyDesc.restrictTranslations` to
+ `RigidBodyDesc.enabledRotations` and `RigidBodyDesc.enabledTranslations`.
+
+#### Added
+
+- Add `ImpulseJoint.setContactsEnabled`, and `MultibodyJoint.setContactsEnabled` to set whether
+ contacts are enabled between colliders attached to rigid-bodies linked by this joint.
+- Add `UnitImpulseJoint.setLimits` to set the limits of a unit joint.
+- Add `RigidBody.recomputeMassPropertiesFromColliders` to force the immediate computation
+ of a rigid-body’s mass properties (instead of waiting for them to be recomputed during the next
+ timestep). This is useful to be able to read immediately the result of a change of a rigid-body
+ additional mass-properties or a change of one of its collider’s mass-properties.
+- Add `RigidBody::setAdditionalMass` to set the additional mass for the rigid-body. The additional
+ angular inertia is automatically computed based on the attached colliders shapes. If this automatic
+ angular inertia computation isn’t desired, use `RigidBody::setAdditionalMassProperties` instead.
+- Add `Collider.setDensity`, `.setMass`, `.setMassProperties`, to alter a collider’s mass
+ properties. Note that `.setMass` will result in the collider’s angular inertia being automatically
+ computed based on this mass and on its shape.
+- Add `ColliderDesc.setMass` to set the mass of the collider instead of its density. Its angular
+ inertia tensor will be automatically computed based on this mass and its shape.
+- Add more filtering options for scene-queries. All the scene query methods now take additional optional
+ arguments to indicate if one particular collider, rigid-body, or type of colliders/rigid-bodies have to
+ be ignored by the query.
+- Add force reporting based on contact force events. The `EventHandler` trait has been modified to include
+ the method `EventQueue.drainContactForceEvents`. Contact force events are generated whenever the sum of the
+ magnitudes of all the forces between two colliders is greater than any of their
+ `Collider.contactForceEventThreshold` values (only the colliders with the `ActiveEvents.CONTACT_FORCE_EVENT`
+ flag set are taken into account for this threshold).
+
+### 0.8.1 (2022-04-06)
+
+Starting with this release, all the examples in `testbed2d` and `testbed3d` have been updated to `webpack 5`,
+and are written in Typescript. In addition, canary `0.0.0` releases will be generated automatically after each merge
+to the `master` branch.
+
+#### Fixed
+
+- Fix bug causing `World.intersectionPair` to always return `false`.
+
+### 0.8.0 (2022-03-31)
+
+#### Breaking changes
+
+- Most APIs that relied on rigid-body handles or collider handles have been modified to rely on `RigidBody`
+ or `Collider` objects instead. Handles are now only needed for events and hooks.
+- Rename STATIC to FIXED in the `ActiveCollisionTypes` flags.
+- The `RigidBody.applyForce` and `.applyTorque` methods have been replaced by `.addForce` and `.addTorque`. These
+ force/torques are no longer automatically zeroed after a timestep. To zero them manually, call `.resetForce` or
+ `.resetTorque`.
+- The `EventQueue.drainContactEvents` and `EventQueue.drainIntersectionEvents` have been merged into a single
+ method: `EventQueue:drainCollisionEvents`.
+
+#### Added
+
+- Add a lines-based debug-renderer. See [#119](https://github.com/dimforge/rapier.js/pull/119) for examples of
+ integration of the debug-renderer with `THREE.js` and `PIXI.js`.
+- Each rigid-body, collider, impulse joint, and multibody joint now have a unique object instance. This instance
+ in only valid as long as the corresponding objects is inserted to the `World`.
+- Add a `wakeUp: bool` argument to the `World.createImpulseJoint` and `MultibodyJointSet::createMultibodyJoint` to
+ automatically wake-up the rigid-bodies attached to the inserted joint.
+- Add a `filter` callback to all the scene queries. Use this for filtering more fine-grained than collision groups.
+- Add `collider.shape` that represents the shape of a collider. This is a more convenient way of reading the collider’s
+ shape properties. Modifying this shape will have no effect unless `collider.setShape` is called with the modified
+ shape.
+- Add `Collider.containsPoint`, `.projectPoint`, `.intersectsRay`, `.castShape`, `.castCollider`, `.intersectsShape`,
+ `.contactShape`, `.contactCollider`, `.castRay`, `.castRayAndGetNormal`.
+- Add `Shape.containsPoint`, `.projectPoint`, `.intersectsRay`, `.castShape`, `.intersectsShape`,
+ `.contactShape`, `.castRay`, `.castRayAndGetNormal`.
+- Add `World.projectPointAndGetFeature` which includes the feature Id the projected point lies on.
+- Add `RigidBodyDesc.sleeping` to initialize the rigid-body in a sleeping state.
+
+### 0.8.0-alpha.2 (2022-03-20)
+
+The changelog hasn’t been updated yet.
+For an overview of the changes, refer to the changelog for the unreleased Rust version:
+https://github.com/dimforge/rapier/blob/master/CHANGELOG.md#unreleased
+
+### 0.8.0-alpha.1 (2022-01-28)
+
+#### Fixed
+
+- Fix a crash when calling `collider.setShape`.
+- Fix a crash when calling `world.collidersWithAabbIntersectingAabb`.
+- Fix damping not being applied properly to some rigid-bodies.
+
+### 0.8.0-alpha.0 (2022-01-16)
+
+This release updates to Rapier 0.12.0-alpha.0 which contains:
+
+- A **complete rewrite** of the joint and contact constraint solver.
+- The support for locking individual translation axes.
+- The support for multibody joint.
+
+This is an **alpha** release because the new solver still needs some tuning,
+and the spherical joint motors/limits is currently not working.
+
+#### Breaking changes
+
+- In 3D: renamed `BallJoint` to `SphericalJoint`.
+- In 2D: renamed `BallJoint` to `RevoluteJoint`.
+- Remove the joint motors and limits for the spherical joint (this is a temporary removal until with leave alpha).
+- All the joint-related structures and methods (`RevoluteJoint`, `PrismaticJoint`, `createJoint`, etc.) have renamed to
+ include `impulse` in the names: `RevoluteImpulseJoint`, `PrismaticImpulseJoint`, `createImpulseJoint`, etc. This is
+ to differentiate them from the new multibody joints.
+- Remove from the integration parameters all the parameters that are no longer meaningful (`maxPositionIterations`,
+ `jointErp`, `warmstartCoeff`, `allowedAngularError`, `maxLinearCorrection`, `maxAngularCorrection`).
+
+#### Added
+
+- Add multibody joints. They are created the same way as impulse joints, except that they are created
+ by `world.createMultibodyJoint` instead of `world.createImpulseJoint`.
+- Add the ability to lock individual translation axes. Use `rigidBody.restrictTranslation`.
+
+#### Fixed
+
+- Fixed an issue with velocity-based kinematic bodies applying kinematic rotation improperly.
+- Fixed the second witness points and normals returned by shape-casts.
+- Fixed the second local contact normal and points returned by contact manifolds.
+
+### 0.7.6
+
+This release updates to Rapier 0.11.1 which contains several bug fixes.
+
+#### Fixed
+
+- Fix a bug causing large moving colliders to miss some collisions after some time.
+- Fix invalid forces generated by contacts with position-based kinematic bodies.
+- Fix a bug where two colliders without parent would not have their collision computed even if the
+ appropriate flags were set.
+
+### 0.7.5
+
+#### Fixed
+
+- Fixed an issue where a collider with no parent attached would not be created
+ under some conditions.
+
+### 0.7.4
+
+This release was broken and has been unpublished.
+
+### 0.7.3
+
+#### Fixed
+
+- The `collider.halfExtents()` methods now returns a valid value for round cuboids.
+
+### 0.7.2 (rapier-compat only)
+
+#### Modified
+
+- The `rapier-compat` packages don’t need the `Buffer` polyfill anymore.
+
+### 0.7.1
+
+#### Modified
+
+- Update to use Rapier 0.11.0.
+- The `rapier-compat` packages are now generated by rollup.
+
+### v0.7.0
+
+The typescripts bindings for Rapier have
+a [brand new user-guide](https://rapier.rs/docs/user_guides/javascript/getting_started_js)
+covering all the features of the physics engine!
+
+### Breaking change
+
+- The `World.castRay` and `World.castRayAndGetNormal` methods have a different signature now, making them
+ more flexible.
+- Rename `ActiveHooks::FILTER_CONTACT_PAIR` to `ActiveHooks.FILTER_CONTACT_PAIRS`.
+- Rename `ActiveHooks::FILTER_INTERSECTION_PAIR` to `ActiveHooks.FILTER_INTERSECTION_PAIRS`.
+- Rename `BodyStatus` to `RigidBodyType`.
+- Rename `RigidBody.bodyStatus()` to `RigidBody.bodyType()`.
+- Rename `RigidBodyDesc.setMassProperties` to `RigidBodyDesc.setAdditionalMassProperties`.
+- Rename `RigidBodyDesc.setPrincipalAngularInertia` to `RigidBodyDesc.setAdditionalPrincipalAngularInertia`.
+- Rename `ColliderDesc.setIsSensor` to `ColliderDesc.setSensor.
+
+#### Added
+
+- Add `Ray.pointAt(t)` that conveniently computes `ray.origin + ray.dir * t`.
+- Add access to joint motors by defining each joint with its own class deriving from `Joint`. Each joint now
+ have its relevant motor configuration
+ methods: `configurMotorModel, configureMotorVelocity, configureMotorPosition, configureMotor`.
+- Add `World.collidersWithAabbIntersectingAabb` for retrieving the handles of all the colliders intersecting the given
+ AABB.
+- Add many missing methods for reading/modifying a rigid-body state after its creation:
+ - `RigidBody.lockTranslations`
+ - `RigidBody.lockRotations`
+ - `RigidBody.restrictRotations`
+ - `RigidBody.dominanceGroup`
+ - `RigidBody.setDominanceGroup`
+ - `RigidBody.enableCcd`
+- Add `RigidBodyDesc.setDominanceGroup` for setting the dominance group of the rigid-body being built.
+- Add many missing methods for reading/modifying a collider state after its creation:
+ - `Collider.setSendor`
+ - `Collider.setShape`
+ - `Collider.setRestitution`
+ - `Collider.setFriction`
+ - `Collider.frictionCombineRule`
+ - `Collider.setFrictionCombineRule`
+ - `Collider.restitutionCombineRule`
+ - `Collider.setRestitutionCombineRule`
+ - `Collider.setCollisionGroups`
+ - `Collider.setSolverGroups`
+ - `Collider.activeHooks`
+ - `Collider.setActiveHooks`
+ - `Collider.activeEvents`
+ - `Collider.setActiveEvents`
+ - `Collider.activeCollisionTypes`
+ - `Collider.setTranslation`
+ - `Collider.setTranslationWrtParent`
+ - `Collider.setRotation`
+ - `Collider.setRotationWrtParent`
+- Add `ColliderDesc.setMassProperties` for setting explicitly the mass properties of the collider being built (instead
+ of relying on density).
+
+#### Modified
+
+- Colliders are no longer required to be attached to a rigid-body. Therefore, the second argument
+ of `World.createCollider`
+ is now optional.
+
+### v0.6.0
+
+#### Breaking changes
+
+- The `BodyStatus::Kinematic` variant has been replaced by `BodyStatus::KinematicPositionBased` and
+ `BodyStatus::KinematicVelocityBased`. Position-based kinematic bodies are controlled by setting (at each frame) the
+ next
+ kinematic position of the rigid-body (just like before), and the velocity-based kinematic bodies are controlled
+ by setting (at each frame) the velocity of the rigid-body.
+- The `RigidBodyDesc.newKinematic` has been replaced by `RigidBodyDesc.newKinematicPositionBased` and
+ `RigidBodyDesc.newKinematicVelocityBased` in order to build a position-based or velocity-based kinematic body.
+- All contact and intersection events are now disabled by default. The can be enabled for each collider individually
+ by setting
+ its `ActiveEvents`: `ColliderDesc.setActiveEvents(ActiveEvents.PROXIMITY_EVENTS | ActiveEvents.ContactEvents)`.
+
+#### Added
+
+- It is now possible to read contact information from the narrow-phase using:
+ - `world.narrowPhase.contactsWith(collider1, callback)`
+ - `world.narrowPhase.intersectionsWith(collider1, callback)`
+ - `world.narrowPhase.contactPair(collider1, collider2, callback)`
+ - `world.narrowPhase.intersectionPair(collider1, collider2, callback)`
+- It is now possible to apply custom collision-detection filtering rules (more flexible than collision groups)
+ based on a JS object by doing the following:
+ - Enable physics hooks for the colliders you want the custom rules to apply to:
+ `ColliderDesc.setActiveHooks(ActiveHooks.FILTER_CONTACT_PAIR | ActiveHooks.FILTER_CONTACT_PAIR)`
+ - Define an object that implements the `PhysicsHooks` interface.
+ - Pass and instance of your physics hooks object as the second argument of the `World.step` method.
+- It is now possible to enable collision-detection between two non-dynamic bodies (e.g. between a kinematic
+ body and a fixed body) by setting the active collision types of a collider:
+ `ColliderDesc.setActiveCollisionTypes(ActiveCollisionTypes.ALL)`
+
+### v0.5.3
+
+- Fix a crash when loading the WASM file on iOS safari.
+
+### v0.5.2
+
+- Fix a crash that could happen after adding and then removing a collider right away,
+ before stepping the simulation.
+
+### v0.5.1
+
+- Fix a determinism issue after snapshot restoration.
+
+### v0.5.0
+
+- Significantly improve the broad-phase performances when very large colliders are used.
+- Add `RigidBodyDesc::setCcdEnabled` to enable Continuous Collision Detection (CCD) for this rigid-body. CCD ensures
+ that a fast-moving rigid-body doesn't miss a collision (the tunneling problem).
+
+#### Breaking changes:
+
+- Removed multiple fields from `IntegrationParameters`. These were unused parameters.
+
+### v0.4.0
+
+- Fix a bug in ball/convex shape collision detection causing the ball to ignore penetrations with depths greater than
+ its radius.
+
+Breaking changes:
+
+- Removed `IntegrationParameters.restitutionVelocityThreshold`
+ and `IntegrationParameters.set_restitutionVelocityThreshold`.
+
+### v0.3.1
+
+- Fix crash happening when creating a collider with a `ColliderDesc.convexHull` shape.
+- Actually remove the second argument of `RigidBodyDesc.setMass` as mentioned in the 0.3.0 changelog.
+- Improve snapshotting performances.
+
+### v0.3.0
+
+#### Added
+
+- Added a `RAPIER.version()` function at the root of the package to retrieve the version of Rapier as a string.
+
+Several geometric queries have been added (the same methods have been added to the
+`QueryPipeline` too):
+
+- `World.intersectionsWithRay`: get all colliders intersecting a ray.
+- `World.intersectionWithShape`: get one collider intersecting a shape.
+- `World.projectPoint`: get the projection of a point on the closest collider.
+- `World.intersectionsWithPoint`: get all the colliders containing a point.
+- `World.castShape`: get the first collider intersecting a shape moving linearly
+ (aka. sweep test).
+- `World.intersectionsWithShape`: get all the colliders intersecting a shape.
+
+Several new shape types are now supported:
+
+- `RoundCuboid`, `Segment`, `Triangle`, `RoundTriangle`, `Polyline`, `ConvexPolygon` (2D only),
+ `RoundConvexPolygon` (2D only), `ConvexPolyhedron` (3D only), `RoundConvexPolyhedron` (3D only),
+ `RoundCone` (3D only).
+
+It is possible to build `ColliderDesc` using these new shapes:
+
+- `ColliderDesc.roundCuboid`, `ColliderDesc.segment`, `ColliderDesc.triangle`, `ColliderDesc.roundTriangle`,
+ `ColliderDesc.convexHull`, `ColliderDesc.roundConvexHull`, `ColliderDesc.Polyline`,
+ `ColliderDesc.convexPolyline` (2D only), `ColliderDesc.roundConvexPolyline` (2D only),
+ `ColliderDesc.convexMesh` (3D only),`ColliderDesc.roundConvexMesh` (3D only), `ColliderDesc.roundCone` (3D only).
+
+It is possible to specify different rules for combining friction and restitution coefficients of the two colliders
+involved in a contact with:
+
+- `ColliderDesc.frictionCombineRule`, and `ColliderDesc.restitutionCombineRule`.
+
+Various RigidBody-related getter and setters have been added:
+
+- `RigidBodyDesc.newStatic`, `RigidBodyDesc.newDynamic`, and `RigidBodyDesc.newKinematic` are new static method, short
+ equivalent to `new RigidBodyDesc(BodyStatus.Static)`, etc.
+- `RigidBodyDesc.setGravityScale`, `RigidBody.gravityScale`, `RigidBody.setGravityScale` to get/set the scale factor
+ applied to the gravity affecting a rigid-body. Setting this to 0.0 will make the rigid-body ignore gravity.
+- `RigidBody.setLinearDamping` and `RigidBody.setAngularDamping` to set the linear and angular damping of the
+ rigid-body.
+- `RigidBodyDesc.restrictRotations` to prevent rotations along specific coordinate axes. This replaces the three boolean
+ arguments previously passed to `.setPrincipalAngularInertia`.
+
+#### Breaking changes
+
+Breaking changes related to rigid-bodies:
+
+- The `RigidBodyDesc.setTranslation` and `RigidBodyDesc.setLinvel` methods now take the components of the translation
+ directly as arguments instead of a single `Vector`.
+- The `RigidBodyDesc.setMass` takes only one argument now. Use `RigidBodyDesc.lockTranslations` to lock the
+ translational motion of the rigid-body.
+- The `RigidBodyDesc.setPrincipalAngularInertia` no longer have boolean parameters to lock rotations.
+ Use `RigidBodyDesc.lockRotations` or `RigidBodyDesc.restrictRotations` to lock the rotational motion of the
+ rigid-body.
+
+Breaking changes related to colliders:
+
+- The `ColliderDesc.setTranslation` method now take the components of the translation directly as arguments instead of a
+ single `Vector`.
+- The `roundRadius` fields have been renamed `borderRadius`.
+- The `RawShapeType.Polygon` no longer exists. For a 2D convex polygon, use `RawShapeType.ConvexPolygon`
+ instead.
+- All occurrences of `Trimesh` have been replaced by `TriMesh` (note the change in case).
+
+Breaking changes related to events:
+
+- Rename all occurrences of `Proximity` to `Intersection`.
+- The `Proximity` enum has been removed.
+- The `drainIntersectionEvents` (previously called `drainProximityEvent`) will now call a callback with
+ arguments `(number, number, boolean)`: two collider handles, and a boolean indicating if they are intersecting.
+
+Breaking changes related to scene queries:
+
+- The `QueryPipeline.castRay` method now takes two additional parameters: a boolean indicating if the ray should not
+ propagate if it starts inside of a shape, and a bit mask indicating the group the ray is part of and these it
+ interacts with.
+- The `World.castRay` and `QueryPipeline.castRay` now return a struct of type `RayColliderHit`
+ which no longer contains the normal at the hit point. Use the new methods `World.castRayAndGetNormal`
+ or `QueryPipeline.castRayAndGetNormal` in order to retrieve the normal too.
+
+### v0.2.13
+
+- Fix a bug where `RigidBodyDesc.setMass(m)` with `m != 0.0` would cause the rotations of the created rigid-body to be
+ locked.
+
+### v0.2.12
+
+- Add a boolean argument to `RigidBodyDesc.setMass` to indicate if the mass contribution of colliders should be enabled
+ for this rigid-body or not.
+- Add a `RigidBody.lockRotations` method to lock all the rigid-body rotations resulting from forces.
+- Add a `RigidBody.lockTranslations` method to lock all the rigid-body translations resulting from forces.
+- Add a `RigidBody.setPrincipalAngularInertia` method to set the principal inertia of the rigid-body. This gives the
+ ability to lock individual rotation axes of one rigid-body.
+
+### v0.2.11
+
+- Fix a bug where removing when immediately adding a collider would cause collisions to fail with the new collider.
+- Fix a regression causing some colliders added after a few timesteps not to be properly taken into account.
+
+### v0.2.10
+
+- Fix a bug where removing a collider would result in a rigid-body being removed instead.
+- Fix a determinism issue when running simulation on the Apple M1 processor.
+- Add `JointData.prismatic` and `JointData.fixed` for creating prismatic joint or fixed joints.
+
+### v0.2.9
+
+- Add `RigidBody.setLinvel` to set the linear velocity of a rigid-body.
+- Add `RigidBody.setAngvel` to set the angular velocity of a rigid-body.
+
+### v0.2.8
+
+- Add `RigidBodySet.remove` to remove a rigid-body from the set.
+- Add `ColliderSet.remove` to remove a collider from the set.
+- Add `JointSet.remove` to remove a joint from the set.
+- Add `RigidBodyDesc.setLinearDamping` and `RigidBodyDesc.setAngularDamping` for setting the linear and angular damping
+ coefficient of the rigid-body to create.
+- Add `RigidBodyDesc.setMass`, and `RigidBodyDesc.setMassProperties` for setting the initial mass properties of a
+ rigid-body.
+- Add `ColliderDesc.setCollisionGroups` to use bit-masks for collision filtering between some pairs of colliders.
+- Add `ColliderDesc.setSolverGroups` to use bit-masks for making the constraints solver ignore contacts between some
+ pairs of colliders.
+- Add `ColliderDesc.heightfield` to build a collider with an heightfield shape.
+- Add `ColliderDesc.trimesh` to build a collider with a triangle mesh shape.
+
+### v0.2.7
+
+- Reduce snapshot size and computation times.
+
+### v0.2.6
+
+- Fix bug causing an unbounded memory usage when an objects falls indefinitely.
+
+### v0.2.5
+
+- Fix wrong result given by `RigidBody.isKinematic()` and `RigidBody.isDynamic()`.
+
+### v0.2.4
+
+- Add the support for round cylinder colliders (i.e. cylinders with round edges).
+
+### v0.2.3
+
+- Add the support for cone, cylinder, and capsule colliders.
+
+### v0.2.2
+
+- Fix regression causing the density and `isSensor` properties of `ColliderDesc` to not be taken into account.
+- Throw an exception when the parent handle passed to `world.createCollider` is not a number.
+
+### v0.2.1
+
+This is a significant rewrite of the JavaScript bindings for rapier. The objective of this rewrite is to make the API
+closer to Rapier's and remove most need for manual memory management.
+
+- Calling `.free()` is now required only for objects that live for the whole duration of the simulation. This means that
+ it is no longer necessary to `.free()` vectors, rays, ray intersections, colliders, rigid-bodies, etc. Object that
+ continue to require an explicit `.free()` are:
+ - `World` and `EventQueue`.
+ - Or, if you are not using the `World` directly:
+ `RigidBodySet`, `ColliderSet`, `JointSet`, `IntegrationParameters`, `PhysicsPipeline`, `QueryPipeline`
+ , `SerializationPipeline`, and `EventQueue`.
+- Collider.parent() now returns the `RigidBodyHandle` of its parent (instead of the `RigidBody` directly).
+- Colliders are now built with `world.createCollider`, i.e., `body.createCollider(colliderDesc)`
+ becomes `world.createCollider(colliderDesc, bodyHandle)`.
+- Shape types are not an enumeration instead of strings: `ShapeType.Ball` and `ShapeType.Cuboid` instead of `"ball"`
+ and `"cuboid"`.
+- `collider.handle` is now a field instead of a function.
+- `body.handle` is now a field instead of a function.
+- The world's gravity is now a `Vector` instead of individual components, i.e., `let world = new RAPIER.World(x, y, z);`
+ becomes `let world = new RAPIER.World(new RAPIER.Vector3(x, y, z))`.
+- Most methods that took individual components as argument (`setPosition`, `setKinematicPosition`, `setRotation`, etc.)
+ now take a `Vector` or `Rotation` structure instead. For example `rigid_body.setKinematicPosition(x, y, z)`
+ becomes `rigid_body.setKinematicPosition(new RAPIER.Vector3(x, y, z))`.
+- `world.stepWithEvents` becomes `world.step` (the event queue is the last optional argument).
+- `RigidBodyDesc` and `ColliderDesc` now use the builder pattern. For example
+ `let bodyDesc = new RAPIER.RigidBodyDesc("dynamic"); bodyDesc.setTranslation(x, y, z)` becomes
+ `new RigidBodyDesc(BodyStatus.Dynamic).setTranslation(new Vector(x, y, z))`.
+- `ray.dir` and `ray.origin` are now fields instead of methods.
+- 2D rotations are now just a `number` instead of a `Rotation` struct. So instead of doing `rotation.angle`, single use
+ the number as the rotation angle.
+- 3D rotations are now represented by the interface `Rotation` (with fields `{x,y,z,w}`) or the class `Quaternion`. Any
+ object with these `{x, y, z, w}` fields can be used wherever a `Rotation` is required.
+- 2D vectors are now represented by the interface `Vector` (with fields `{x,y}`) or the class `Vector2`). Any object
+ with these `{x,y}` fields can be used wherever a `Vector` is required.
+- 3D vectors are now represented by the interface `Vector` (with fields `{x,y,z}`) or the class `Vector3`). Any object
+ with these `{x,y,z}` fields can be used wherever a `Vector` is required.
+
+### v0.2.0
+
+See changelogs for v0.2.1 instead. The NPM package for v0.2.0 were missing some files.
+
+### v0.1.17
+
+- Fix bug when ghost forces and/or crashes could be observed when a kinematic body touches a fixed body.
+
+### v0.1.16
+
+- Fix kinematic rigid-body not waking up dynamic bodies it touches.
+- Added `new Ray(origin, direction)` constructor instead of `Ray.new(origin, direction)`.
+
+### v0.1.15
+
+- Fix crash when removing a kinematic rigid-body from the World.
+
+### v0.1.14
+
+- Fix issues where force application functions took ownership of the JS vector, preventing the user from freeing
+ with `Vector.free()` afterwards.
+
+### v0.1.13
+
+- Added `rigidBody.setNextKinematicTranslation` to set the translation of a kinematic rigid-body at the next timestep.
+- Added `rigidBody.setNextKinematicRotation` to set the rotation of a kinematic rigid-body at the next timestep.
+- Added `rigidBody.predictedTranslation` to get the translation of a kinematic rigid-body at the next timestep.
+- Added `rigidBody.predictedRotation` to set the rotation of a kinematic rigid-body at the next timestep.
+- Added `Ray` and `RayIntersection` structures for ray-casting.
+- Added `world.castRay` to compute the first hit of a ray with the physics scene.
+- Fix a bug causing a kinematic rigid-body not to teleport as expected after a `rigidBody.setPosition`.
+
+### v0.1.12
+
+- Added `world.removeCollider(collider)` to remove a collider from the physics world.
+- Added `colliderDesc.setTranslation(...)` to set the relative translation of the collider to build wrt. the rigid-body
+ it is attached to.
+- Added `colliderDesc.setRotation(...)` to set the relative rotation of the collider to build wrt. the rigid-body it is
+ attached to.
+
+### v0.1.11
+
+- Fix a bug causing a crash when the broad-phase proxy handles were recycled.
+
+### v0.1.10
+
+- Fix a determinism problem that could cause rigid-body handle allocation to be non-deterministic after a snapshot
+ restoration.
+
+### v0.1.9
+
+- Added `world.getCollider(handle)` that retrieves a collider from its integer handle.
+- Added `joint.handle()` that returns the integer handle of the joint.
+
+### v0.1.8
+
+- Added `world.forEachRigidBodyHandle(f)` to apply a closure on the integer handle of each rigid-body on the world.
+- Added `world.forEachActiveRigidBody(f)` to apply a closure on each rigid-body on the world.
+- Added `world.forEachActiveRigidBodyHandle(f)` to apply a closure on the integer handle of each rigid-body on the
+ world.
+- Added `rigidBody.applyForce`, `.applyTorque`, `.applyImpulse`, `.applyTorqueImpulse`, `.applyForceAtPoint`, and
+ `.applyImpulseAtPoint` to apply a manual force or torque to a rigid-body.
+- Added the `EventQueue` structure that can be used to collect and iterate through physics events.
+- Added the `Proximity` enum that represents the proximity state of a sensor collider and another collider.
+- Added the `world.stepWithEvents(eventQueue)` which executes a physics timestep and collects the physics events into
+ the given event queue.
+
+### v0.1.7
+
+- Added `world.getRigidBody(handle)` to retrieve a rigid-body from its handle.
+- Added `world.getJoint(handle)` to retrieve a joint from its handle.
+- Added `rigidBody.rotation()` to retrieve its world-space orientation as a quaternion.
+- Added `rigidBody.setTranslation(...)` to set the translation of a rigid-body.
+- Added `rigidBody.setRotation(...)` to set the orientation of a rigid-body.
+- Added `rigidBody.wakeUp()` to manually wake up a rigid-body.
+- Added `rigidBody_desc.setRotation(...)` to set tho orientation of the rigid-body to be created.
+
+### v0.1.6
+
+- Added `world.removeRigidBody(...)` to remove a rigid-body from the world.
diff --git a/thirdparty/rapier.js/Cargo.lock b/thirdparty/rapier.js/Cargo.lock
new file mode 100644
index 00000000..11154411
--- /dev/null
+++ b/thirdparty/rapier.js/Cargo.lock
@@ -0,0 +1,1711 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys",
+]
+
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bstr"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "by_address"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
+
+[[package]]
+name = "bytemuck"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
+
+[[package]]
+name = "cc"
+version = "1.2.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-link",
+]
+
+[[package]]
+name = "chrono-tz"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
+dependencies = [
+ "parse-zoneinfo",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "deunicode"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dimforge_rapier2d-deterministic"
+version = "0.19.3"
+dependencies = [
+ "bincode",
+ "js-sys",
+ "nalgebra",
+ "palette",
+ "parry2d",
+ "rapier2d",
+ "ref-cast",
+ "serde",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "ena"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "fast-srgb8"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "glam"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da"
+
+[[package]]
+name = "glam"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b"
+
+[[package]]
+name = "glam"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16"
+
+[[package]]
+name = "glam"
+version = "0.17.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776"
+
+[[package]]
+name = "glam"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34"
+
+[[package]]
+name = "glam"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca"
+
+[[package]]
+name = "glam"
+version = "0.20.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f"
+
+[[package]]
+name = "glam"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815"
+
+[[package]]
+name = "glam"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774"
+
+[[package]]
+name = "glam"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
+
+[[package]]
+name = "glam"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
+
+[[package]]
+name = "glam"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
+
+[[package]]
+name = "glam"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9"
+
+[[package]]
+name = "glam"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
+
+[[package]]
+name = "glam"
+version = "0.29.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
+
+[[package]]
+name = "glam"
+version = "0.30.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46"
+
+[[package]]
+name = "globset"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
+dependencies = [
+ "bitflags",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "foldhash 0.2.0",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "humansize"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "matrixmultiply"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
+dependencies = [
+ "autocfg",
+ "rawpointer",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "nalgebra"
+version = "0.34.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f"
+dependencies = [
+ "approx",
+ "glam 0.14.0",
+ "glam 0.15.2",
+ "glam 0.16.0",
+ "glam 0.17.3",
+ "glam 0.18.0",
+ "glam 0.19.0",
+ "glam 0.20.5",
+ "glam 0.21.3",
+ "glam 0.22.0",
+ "glam 0.23.0",
+ "glam 0.24.2",
+ "glam 0.25.0",
+ "glam 0.27.0",
+ "glam 0.28.0",
+ "glam 0.29.3",
+ "glam 0.30.9",
+ "matrixmultiply",
+ "nalgebra-macros",
+ "num-complex",
+ "num-rational",
+ "num-traits",
+ "serde",
+ "simba",
+ "typenum",
+]
+
+[[package]]
+name = "nalgebra-macros"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
+name = "ordered-float"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "palette"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6"
+dependencies = [
+ "approx",
+ "fast-srgb8",
+ "palette_derive",
+ "phf",
+]
+
+[[package]]
+name = "palette_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30"
+dependencies = [
+ "by_address",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "parry2d"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef681740349cec3ab9b5996b03b459b383b6998e1ffcb2804e8b57eb1e8491d9"
+dependencies = [
+ "approx",
+ "arrayvec",
+ "bitflags",
+ "downcast-rs",
+ "either",
+ "ena",
+ "foldhash 0.2.0",
+ "hashbrown 0.16.1",
+ "indexmap",
+ "log",
+ "nalgebra",
+ "num-derive",
+ "num-traits",
+ "ordered-float",
+ "serde",
+ "serde_arrays",
+ "simba",
+ "slab",
+ "smallvec",
+ "spade",
+ "thiserror",
+]
+
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pest"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
+dependencies = [
+ "memchr",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
+dependencies = [
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prepare_builds"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "clap_derive",
+ "tera",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "profiling"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
+dependencies = [
+ "profiling-procmacros",
+]
+
+[[package]]
+name = "profiling-procmacros"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rapier2d"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eab12a465faea032e025eb97a3b0763422391c3edb98200ed5efd516307df550"
+dependencies = [
+ "approx",
+ "arrayvec",
+ "bit-vec",
+ "bitflags",
+ "downcast-rs",
+ "log",
+ "nalgebra",
+ "num-derive",
+ "num-traits",
+ "ordered-float",
+ "parry2d",
+ "profiling",
+ "rustc-hash",
+ "serde",
+ "simba",
+ "static_assertions",
+ "thiserror",
+ "web-time",
+ "wide",
+]
+
+[[package]]
+name = "rawpointer"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
+
+[[package]]
+name = "ref-cast"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "robust"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "rustversion"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "safe_arch"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_arrays"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.141"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "simba"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95"
+dependencies = [
+ "approx",
+ "libm",
+ "num-complex",
+ "num-traits",
+ "paste",
+ "wide",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "slug"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
+dependencies = [
+ "deunicode",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "spade"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990"
+dependencies = [
+ "hashbrown 0.15.5",
+ "num-traits",
+ "robust",
+ "serde",
+ "smallvec",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tera"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee"
+dependencies = [
+ "chrono",
+ "chrono-tz",
+ "globwalk",
+ "humansize",
+ "lazy_static",
+ "percent-encoding",
+ "pest",
+ "pest_derive",
+ "rand",
+ "regex",
+ "serde",
+ "serde_json",
+ "slug",
+ "unic-segment",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
+dependencies = [
+ "unic-ucd-segment",
+]
+
+[[package]]
+name = "unic-ucd-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wide"
+version = "0.7.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03"
+dependencies = [
+ "bytemuck",
+ "safe_arch",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/thirdparty/rapier.js/Cargo.toml b/thirdparty/rapier.js/Cargo.toml
new file mode 100644
index 00000000..6691d264
--- /dev/null
+++ b/thirdparty/rapier.js/Cargo.toml
@@ -0,0 +1,23 @@
+[workspace]
+members = ["builds/*"]
+resolver = "2"
+
+[profile.release]
+debug = false
+codegen-units = 1
+#lto = true
+strip = true # Workaround for https://github.com/bevyengine/bevy/issues/16030 (the bug only happens in 2D)
+
+[patch.crates-io]
+#rapier2d = { path = "../rapier/crates/rapier2d" }
+#rapier3d = { path = "../rapier/crates/rapier3d" }
+#parry2d = { path = "../parry/crates/parry2d" }
+#parry3d = { path = "../parry/crates/parry3d" }
+#nalgebra = { path = "../nalgebra" }
+#simba = { path = "../simba" }
+
+#nalgebra = { git = "https://github.com/dimforge/nalgebra", branch = "dev" }
+#rapier2d = { git = "https://github.com/dimforge/rapier", rev = "82416e3ca66dcdc34c0f350cec570ef1019a199f" }
+#rapier3d = { git = "https://github.com/dimforge/rapier", rev = "82416e3ca66dcdc34c0f350cec570ef1019a199f" }
+#parry2d = { git = "https://github.com/dimforge/parry" }
+#parry3d = { git = "https://github.com/dimforge/parry" }
diff --git a/thirdparty/rapier.js/LICENSE b/thirdparty/rapier.js/LICENSE
new file mode 100644
index 00000000..e579674d
--- /dev/null
+++ b/thirdparty/rapier.js/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2020 Dimforge EURL
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/thirdparty/rapier.js/README.md b/thirdparty/rapier.js/README.md
new file mode 100644
index 00000000..d04cc4bf
--- /dev/null
+++ b/thirdparty/rapier.js/README.md
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Website | Documentation |
+ 2D examples (sources) |
+ 3D examples (sources)
+
+
+
+---
+
+
+2D and 3D physics engines
+for the JavaScript programming language (official bindings).
+
+
+---
+
+## Building packages manually
+
+From the root of the repository, run:
+
+```shell
+./builds/prepare_builds/prepare_all_projects.sh
+./builds/prepare_builds/build_all_projects.sh
+```
+
+Note that `prepare_all_projects.sh` only needs to be run once. It needs to be re-run if any file from the
+`builds/prepare_builds` directory (and subdirectories) are modified.
+
+The built packages will be in `builds/rapier2d/pkg`, `builds/rapier3d/pkg`, etc. To build the `-compat` variant of the
+packages, run `npm run build` in the `rapier-compat` directory. Note that this will only work if you already ran
+`prepare_all_projects.sh`. The compat packages are then generated in, e.g., `rapier-compat/builds/3d/pkg`.
+
+## Feature selection
+
+Multiple NPM packages exist for Rapier, depending on your needs:
+- [`@dimforge/rapier2d`](https://www.npmjs.com/package/@dimforge/rapier2d) or
+ [`@dimforge/rapier3d`](https://www.npmjs.com/package/@dimforge/rapier3d):
+ The main build of the Rapier physics engine for 2D or 3D physics simulation. This should have wide browser
+ support while offering great performances. This does **not** guarantee cross-platform determinism of the physics
+ simulation (but it is still locally deterministic, on the same machine).
+- [`@dimforge/rapier2d-simd`](https://www.npmjs.com/package/@dimforge/rapier2d-simd) or
+ [`@dimforge/rapier3d-simd`](https://www.npmjs.com/package/@dimforge/rapier3d-simd):
+ A build with internal SIMD optimizations enabled. More limited browser support (requires support for [simd128](https://caniuse.com/?search=simd)).
+- [`@dimforge/rapier2d-deterministic`](https://www.npmjs.com/package/@dimforge/rapier2d-deterministic) or
+ [`@dimforge/rapier3d-deterministic`](https://www.npmjs.com/package/@dimforge/rapier3d-deterministic):
+ A less optimized build but with a guarantee of a cross-platform deterministic execution of the physics simulation.
+
+## Bundler support
+
+Some bundlers will struggle with the `.wasm` file package into the builds above. Alternative `-compat` versions exist
+which embed the `.wasm` file into the `.js` sources encoded with base64. This results in a bigger package size, but
+much wider bundler support.
+
+Just append `-compat` to the build you are interested in:
+[`rapier2d-compat`](https://www.npmjs.com/package/@dimforge/rapier2d-compat),
+[`rapier2d-simd-compat`](https://www.npmjs.com/package/@dimforge/rapier2d-simd-compat),
+[`rapier2d-deterministic-compat`](https://www.npmjs.com/package/@dimforge/rapier2d-deterministic-compat),
+[`rapier3d-compat`](https://www.npmjs.com/package/@dimforge/rapier3d-compat),
+[`rapier3d-simd-compat`](https://www.npmjs.com/package/@dimforge/rapier3d-simd-compat),
+[`rapier3d-deterministic-compat`](https://www.npmjs.com/package/@dimforge/rapier3d-deterministic-compat).
+
+## Nightly builds
+
+Each time a new Pull Request is merged to the `main` branch of the [`rapier.js` repository](https://github.com/dimforge/rapier.js),
+an automatic _canary_ build is triggered. Builds published to npmjs under the _canary_ tag does not come with any
+stability guarantee and does not follow semver versioning. But it can be a useful solution to try out the latest
+features until a proper release is cut.
\ No newline at end of file
diff --git a/thirdparty/rapier.js/builds/prepare_builds/Cargo.toml b/thirdparty/rapier.js/builds/prepare_builds/Cargo.toml
new file mode 100644
index 00000000..905de28f
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "prepare_builds"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.5" }
+clap_derive = { version = "4.5" }
+tera = "1.20"
diff --git a/thirdparty/rapier.js/builds/prepare_builds/README.md b/thirdparty/rapier.js/builds/prepare_builds/README.md
new file mode 100644
index 00000000..4d05a2fa
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/README.md
@@ -0,0 +1,17 @@
+# Prepare builds
+
+This project helps with making specific package: it takes a few parameter to create a (1) folder ready to compile.
+
+It uses clap so you can pass `-h` to get more info about its parameters.
+
+## usage
+
+At workspace root: `cargo run -p prepare_builds -- -d dim2 -f simd`.
+
+Or use provided scripts: `./builds/prepare_builds/prepare_all_projects.sh && ./builds/prepare_builds/build_all_projects.sh`
+
+## Technical considerations
+
+Askama/rinja was not chosen because compiled templates make it difficult to iterate on a folder and parse all templates.
+
+This folder is in `builds/` only for the workspace member glob to not complain if `builds/` is inexistent or empty.
diff --git a/thirdparty/rapier.js/builds/prepare_builds/build_all_projects.sh b/thirdparty/rapier.js/builds/prepare_builds/build_all_projects.sh
new file mode 100644
index 00000000..a8ed7840
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/build_all_projects.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+for entry in builds/*
+do
+ if [[ "$(basename "$entry")" == $(basename $(dirname "${BASH_SOURCE[0]}")) ]]; then
+ echo "skipping directory: $entry"
+ continue;
+ fi
+ (
+ cd $entry
+ echo "building $entry"
+ # FIXME: ideally we'd use `npm ci`
+ # but we'd need to have generated the `package-lock` beforehand and committed them in the repository.
+ # I'm not sure yet how to store those `package-lock`s yet though.
+ # They should proably be similar to all packages, but I'm not sure.
+ npm i;
+ npm run build;
+ )
+done
diff --git a/thirdparty/rapier.js/builds/prepare_builds/prepare_all_projects.sh b/thirdparty/rapier.js/builds/prepare_builds/prepare_all_projects.sh
new file mode 100644
index 00000000..bed9c147
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/prepare_all_projects.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+features=(non-deterministic deterministic simd)
+dims=(dim2 dim3)
+
+for feature in ${features[@]}; do
+ for dim in ${dims[@]}; do
+ echo "preparing dimension $dim with feature $feature"
+ cargo run -p prepare_builds -- -d ${dim} -f ${feature}
+ done
+done
diff --git a/thirdparty/rapier.js/builds/prepare_builds/src/main.rs b/thirdparty/rapier.js/builds/prepare_builds/src/main.rs
new file mode 100644
index 00000000..56675e8e
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/src/main.rs
@@ -0,0 +1,182 @@
+use std::{
+ error::Error,
+ fs::{self, File},
+ io::Write,
+ path::{Path, PathBuf},
+};
+
+use clap::Parser;
+use clap_derive::{Parser, ValueEnum};
+use tera::{Context, Tera};
+
+/// Simple program to greet a person
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+pub struct Args {
+ /// Dimension to use
+ #[arg(short, long)]
+ dim: Dimension,
+
+ /// Features to enable
+ #[arg(short, long)]
+ feature_set: FeatureSet,
+}
+
+#[derive(ValueEnum, Debug, Clone, Copy)]
+pub enum Dimension {
+ Dim2,
+ Dim3,
+}
+
+#[derive(ValueEnum, Default, Debug, Clone, Copy)]
+pub enum FeatureSet {
+ #[default]
+ NonDeterministic,
+ Deterministic,
+ Simd,
+}
+
+/// Values to use when creating the new build folder.
+pub struct BuildValues {
+ /// Only the number of dimensions, as sometimes it will be prefixed by "dim" and sometimes post-fixed by "d".
+ pub dim: String,
+ /// real name of the additional features to enable in the project
+ pub feature_set: Vec,
+ pub target_dir: PathBuf,
+ pub template_dir: PathBuf,
+ pub additional_rust_flags: String,
+ pub additional_wasm_opt_flags: Vec,
+ pub js_package_name: String,
+}
+
+impl BuildValues {
+ pub fn new(args: Args) -> Self {
+ let dim = match args.dim {
+ Dimension::Dim2 => "2",
+ Dimension::Dim3 => "3",
+ };
+ let feature_set = match args.feature_set {
+ FeatureSet::NonDeterministic => vec![],
+ FeatureSet::Deterministic => vec!["enhanced-determinism"],
+ FeatureSet::Simd => vec!["simd-stable"],
+ };
+ let js_package_name = match args.feature_set {
+ FeatureSet::NonDeterministic => format!("rapier{dim}d"),
+ FeatureSet::Deterministic => format!("rapier{dim}d-deterministic"),
+ FeatureSet::Simd => format!("rapier{dim}d-simd"),
+ };
+
+ let root: PathBuf = env!("CARGO_MANIFEST_DIR").into();
+
+ Self {
+ dim: dim.to_string(),
+ feature_set: feature_set.iter().map(|f| f.to_string()).collect(),
+ template_dir: root.join("templates/").clone(),
+ target_dir: root.parent().unwrap().join(&js_package_name).into(),
+ additional_rust_flags: match args.feature_set {
+ FeatureSet::Simd => "RUSTFLAGS='-C target-feature=+simd128'".to_string(),
+ _ => "".to_string(),
+ },
+ additional_wasm_opt_flags: match args.feature_set {
+ FeatureSet::Simd => vec!["--enable-simd".to_string()],
+ _ => vec![],
+ },
+ js_package_name,
+ }
+ }
+}
+
+fn main() {
+ let args = Args::parse();
+ dbg!(&args);
+
+ let build_values = BuildValues::new(args);
+ copy_top_level_files_in_directory(&build_values.template_dir, &build_values.target_dir)
+ .expect("Failed to copy directory");
+ process_templates(&build_values).expect("Failed to process templates");
+}
+
+fn copy_top_level_files_in_directory(
+ src: impl AsRef,
+ dest: impl AsRef,
+) -> std::io::Result<()> {
+ let src = src.as_ref();
+ let dest = Path::new(&dest);
+ if dest.exists() {
+ fs::remove_dir_all(&dest)?;
+ }
+ fs::create_dir_all(&dest)?;
+
+ for entry in fs::read_dir(src)? {
+ let entry = entry?;
+ let path = entry.path();
+ let dest_path = dest.join(path.file_name().unwrap());
+
+ if !path.is_dir() {
+ fs::copy(&path, &dest_path)?;
+ }
+ }
+ Ok(())
+}
+
+/// Process all tera templates in the target directory:
+/// - Remove the extension
+/// - Render the templates to
+///
+fn process_templates(build_values: &BuildValues) -> std::io::Result<()> {
+ let target_dir = build_values.target_dir.clone();
+
+ let mut context = Context::new();
+ context.insert("dimension", &build_values.dim);
+ context.insert("additional_features", &build_values.feature_set);
+ context.insert("additional_rust_flags", &build_values.additional_rust_flags);
+ context.insert(
+ "additional_wasm_opt_flags",
+ &build_values.additional_wasm_opt_flags,
+ );
+ context.insert("js_package_name", &build_values.js_package_name);
+
+ let tera = match Tera::new(target_dir.join("**/*.tera").to_str().unwrap()) {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("Parsing error(s): {}", e);
+ ::std::process::exit(1);
+ }
+ };
+ dbg!(tera.templates.keys(), &context);
+
+ for entry in fs::read_dir(target_dir)? {
+ let entry = entry?;
+ let path = entry.path();
+ // For tera templates, remove extension.
+ if path.extension() == Some(std::ffi::OsStr::new("tera")) {
+ let mut i = path.iter();
+
+ // Get path from target directory
+ for _ in i
+ .by_ref()
+ .take_while(|c| *c != build_values.target_dir.file_name().unwrap())
+ {}
+ let path_template = i.as_path();
+ match tera.render(path_template.to_str().unwrap(), &context) {
+ Ok(s) => {
+ let old_path = path.clone();
+ let new_path = path.with_extension("");
+ let mut file = File::create(path.join(new_path))?;
+ file.write_all(s.as_bytes())?;
+ std::fs::remove_file(old_path)?;
+ }
+ Err(e) => {
+ eprintln!("Error: {}", e);
+ let mut cause = e.source();
+ while let Some(e) = cause {
+ eprintln!("Reason: {}", e);
+ cause = e.source();
+ }
+ }
+ };
+ }
+ }
+
+ Ok(())
+}
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/Cargo.toml.tera b/thirdparty/rapier.js/builds/prepare_builds/templates/Cargo.toml.tera
new file mode 100644
index 00000000..af9756d1
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/Cargo.toml.tera
@@ -0,0 +1,60 @@
+[package]
+name = "dimforge_{{ js_package_name }}" # Can't be named rapier{{ dimension }}d which conflicts with the dependency.
+version = "0.19.3"
+authors = ["Sébastien Crozet "]
+description = "{{ dimension }}-dimensional physics engine in Rust - official JS bindings."
+documentation = "https://rapier.rs/rustdoc/rapier{{ dimension }}d/index.html"
+homepage = "https://rapier.rs"
+repository = "https://github.com/dimforge/rapier.js"
+readme = "README.md"
+keywords = ["physics", "dynamics", "rigid", "real-time", "joints"]
+license = "Apache-2.0"
+edition = "2018"
+
+[features]
+default = ["dim{{ dimension }}"]
+dim{{ dimension }} = []
+
+[lib]
+name = "rapier_wasm{{ dimension }}d"
+path = "../../src/lib.rs"
+crate-type = ["cdylib", "rlib"]
+required-features = ["dim{{ dimension }}"]
+
+[lints]
+rust.unexpected_cfgs = { level = "warn", check-cfg = [
+ 'cfg(feature, values("dim{% if dimension == "2" %}3{% else %}2{% endif %}"))',
+] }
+
+[dependencies]
+rapier{{ dimension }}d = { version = "0.30.1", features = [
+ "serde-serialize",
+ "debug-render",
+ "profiler",
+ {%- for feature in additional_features %}
+ "{{ feature }}",
+ {%- endfor %}
+] }
+# The explicit dependency to parry only needed to pin the patch version.
+parry{{ dimension }}d = { version = "^0.25.3" }
+ref-cast = "1"
+wasm-bindgen = "0.2.100"
+js-sys = "0.3"
+nalgebra = "0.34"
+serde = { version = "1", features = ["derive", "rc"] }
+bincode = "1"
+palette = "0.7"
+
+[package.metadata.wasm-pack.profile.release]
+# add -g to keep debug symbols
+wasm-opt = [
+ '-O4',
+ '--dce',
+ # The two options below are needed because of: https://github.com/rustwasm/wasm-pack/issues/1501
+ '--enable-bulk-memory',
+ '--enable-nontrapping-float-to-int',
+ {%- for flag in additional_wasm_opt_flags %}
+ '{{ flag }}',
+ {%- endfor %}
+]
+#wasm-opt = ['-g']
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/LICENSE b/thirdparty/rapier.js/builds/prepare_builds/templates/LICENSE
new file mode 100644
index 00000000..e579674d
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2020 Dimforge EURL
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/README.md.tera b/thirdparty/rapier.js/builds/prepare_builds/templates/README.md.tera
new file mode 100644
index 00000000..3455eab2
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/README.md.tera
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Website | Documentation
+
+
+
+---
+
+
+{{ dimension }}D physics engine
+for the JavaScript programming language (official bindings).
+
+
+---
+
+## Feature selection
+
+Multiple NPM packages exist for Rapier, depending on your needs:
+- [`@dimforge/rapier2d`](https://www.npmjs.com/package/@dimforge/rapier2d) or
+ [`@dimforge/rapier3d`](https://www.npmjs.com/package/@dimforge/rapier3d):
+ The main build of the Rapier physics engine for 2D or 3D physics simulation. This should have wide browser
+ support while offering great performances. This does **not** guarantee cross-platform determinism of the physics
+ simulation (but it is still locally deterministic, on the same machine).
+- [`@dimforge/rapier2d-simd`](https://www.npmjs.com/package/@dimforge/rapier2d-simd) or
+ [`@dimforge/rapier3d-simd`](https://www.npmjs.com/package/@dimforge/rapier3d-simd):
+ A build with internal SIMD optimizations enabled. More limited browser support (requires support for [simd128](https://caniuse.com/?search=simd)).
+- [`@dimforge/rapier2d-deterministic`](https://www.npmjs.com/package/@dimforge/rapier2d-deterministic) or
+ [`@dimforge/rapier3d-deterministic`](https://www.npmjs.com/package/@dimforge/rapier3d-deterministic):
+ A less optimized build but with a guarantee of a cross-platform deterministic execution of the physics simulation.
+
+## Bundler support
+
+Some bundlers will struggle with the `.wasm` file package into the builds above. Alternative `-compat` versions exist
+which embed the `.wasm` file into the `.js` sources encoded with base64. This results in a bigger package size, but
+much wider bundler support.
+
+Just append `-compat` to the build you are interested in:
+[`rapier2d-compat`](https://www.npmjs.com/package/@dimforge/rapier2d-compat),
+[`rapier2d-simd-compat`](https://www.npmjs.com/package/@dimforge/rapier2d-simd-compat),
+[`rapier2d-deterministic-compat`](https://www.npmjs.com/package/@dimforge/rapier2d-deterministic-compat),
+[`rapier3d-compat`](https://www.npmjs.com/package/@dimforge/rapier3d-compat),
+[`rapier3d-simd-compat`](https://www.npmjs.com/package/@dimforge/rapier3d-simd-compat),
+[`rapier3d-deterministic-compat`](https://www.npmjs.com/package/@dimforge/rapier3d-deterministic-compat).
+
+## Nightly builds
+
+Each time a new Pull Request is merged to the `main` branch of the [`rapier.js` repository](https://github.com/dimforge/rapier.js),
+an automatic _canary_ build is triggered. Builds published to npmjs under the _canary_ tag does not come with any
+stability guarantee and does not follow semver versioning. But it can be a useful solution to try out the latest
+features until a proper release is cut.
\ No newline at end of file
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/build_rust.sh.tera b/thirdparty/rapier.js/builds/prepare_builds/templates/build_rust.sh.tera
new file mode 100644
index 00000000..6fd6ae82
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/build_rust.sh.tera
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Cleaning rust because changing rust flags may lead to different build results.
+cargo clean
+
+{{ additional_rust_flags }} npx wasm-pack build
+sed -i.bak 's#dimforge_rapier#@dimforge/rapier#g' pkg/package.json
+sed -i.bak 's/"rapier_wasm{{ dimension }}d_bg.wasm"/"*"/g' pkg/package.json
+(
+ cd pkg
+ npm pkg delete sideEffects
+ npm pkg set 'sideEffects[0]'="./*.js"
+)
+rm pkg/*.bak
+rm pkg/.gitignore
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/build_typescript.sh.tera b/thirdparty/rapier.js/builds/prepare_builds/templates/build_typescript.sh.tera
new file mode 100644
index 00000000..8fcce98b
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/build_typescript.sh.tera
@@ -0,0 +1,13 @@
+#! /bin/sh
+
+mkdir -p ./pkg/src
+cp -r ../../src.ts/* pkg/src/.
+rm -f ./pkg/raw.ts
+echo 'export * from "./rapier_wasm{{ dimension }}d"' > pkg/src/raw.ts
+# See https://serverfault.com/a/137848
+find pkg/ -type f -print0 | LC_ALL=C xargs -0 sed -i.bak '\:#if DIM{% if dimension == "2" %}3{% else %}2{% endif %}:,\:#endif:d'
+npx tsc
+# NOTE: we keep the typescripts files into the NPM package for source mapping: see #3
+sed -i.bak 's/"module": "rapier_wasm{{ dimension }}d.js"/"module": "rapier.js"/g' pkg/package.json
+sed -i.bak 's/"types": "rapier_wasm{{ dimension }}d.d.ts"/"types": "rapier.d.ts"/g' pkg/package.json
+find pkg/ -type f -name '*.bak' | xargs rm
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/package.json.tera b/thirdparty/rapier.js/builds/prepare_builds/templates/package.json.tera
new file mode 100644
index 00000000..95cc5f79
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/package.json.tera
@@ -0,0 +1,25 @@
+{
+ "name": "@dimforge/{{ js_package_name }}",
+ "description": "",
+ "private": true,
+ "exports": "./pkg",
+ "types": "./pkg/rapier.d.ts",
+ "scripts": {
+ "build": "npm run clean && npm run build:wasm && npm run build:ts && npm run build:doc",
+ "build:doc": "typedoc --tsconfig tsconfig_typedoc.json",
+ "build:wasm": "sh ./build_rust.sh",
+ "build:ts": "sh ./build_typescript.sh",
+ "clean": "rimraf pkg"
+ },
+ "//": "Better keep rimraf version in sync with wasm-pack, see https://github.com/rustwasm/wasm-pack/issues/1444",
+ "devDependencies": {
+ "rimraf": "^3.0.2",
+ "typedoc": "^0.25.13"
+ },
+ "dependencies": {
+ "wasm-pack": "^0.12.1"
+ },
+ "sideEffects": [
+ "./*.js"
+ ]
+}
\ No newline at end of file
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig.json b/thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig.json
new file mode 100644
index 00000000..ecdfc4ce
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "outDir": "./pkg",
+ "module": "ES6",
+ "target": "es6",
+ "lib": ["es6"],
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "declaration": true,
+ "rootDirs": ["./pkg", "./pkg/src"]
+ },
+ "files": ["./pkg/src/rapier.ts"]
+}
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig_typedoc.json b/thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig_typedoc.json
new file mode 100644
index 00000000..eea45699
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/tsconfig_typedoc.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "outDir": "./pkg",
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "declaration": true,
+ "lib": ["es6"]
+ },
+ "files": ["./pkg/rapier.d.ts"]
+}
diff --git a/thirdparty/rapier.js/builds/prepare_builds/templates/typedoc.json.tera b/thirdparty/rapier.js/builds/prepare_builds/templates/typedoc.json.tera
new file mode 100644
index 00000000..d96bb7a6
--- /dev/null
+++ b/thirdparty/rapier.js/builds/prepare_builds/templates/typedoc.json.tera
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://typedoc.org/schema.json",
+ "entryPoints": [
+ "./pkg/rapier.d.ts"
+ ],
+ "out": "./docs",
+ "readme": "none",
+ "excludeExternals": true,
+ "excludeNotDocumented": false,
+ "intentionallyNotExported": [
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawBroadPhase",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawCCDSolver",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawColliderSet",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawEventQueue",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawImpulseJointSet",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawMultibodyJointSet",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawIntegrationParameters",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawIslandManager",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawNarrowPhase",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawPhysicsPipeline",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawRigidBodySet",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawSerializationPipeline",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawContactManifold",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawShape",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawGenericJoint",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawJointAxis",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawRotation",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawVector",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawPointColliderProjection",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawPointProjection",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawRayColliderIntersection",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawRayColliderHit",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawRayIntersection",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawColliderShapeCastHit",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawShapeContact",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawShapeCastHit",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawQueryPipeline",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawDeserializedWorld",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawDebugRenderPipeline",
+ "pkg/rapier_wasm{{ dimension }}d.d.ts:RawContactForceEvent"
+ ]
+}
\ No newline at end of file
diff --git a/thirdparty/rapier.js/package.json b/thirdparty/rapier.js/package.json
new file mode 100644
index 00000000..ce0eec04
--- /dev/null
+++ b/thirdparty/rapier.js/package.json
@@ -0,0 +1,12 @@
+{
+ "scripts": {
+ "fmt": "prettier ."
+ },
+ "devDependencies": {
+ "prettier": "2.7.1",
+ "typedoc": "0.23.19",
+ "typescript": "4.8.4",
+ "wasm-opt": "1.4.0",
+ "wasm-pack": "0.12.1"
+ }
+}
diff --git a/thirdparty/rapier.js/publish_all_canary.sh b/thirdparty/rapier.js/publish_all_canary.sh
new file mode 100644
index 00000000..21155429
--- /dev/null
+++ b/thirdparty/rapier.js/publish_all_canary.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+for entry in builds/*/pkg
+do
+ (
+ echo "Publishing $entry"
+ cd $entry; npm version 0.0.0-$(git rev-parse --short HEAD)-$(date '+%Y%m%d') --git-tag-version false;
+ npm publish --tag canary --access public;
+ )
+done;
+
+for entry in rapier-compat/builds/*/pkg
+do
+ (
+ echo "Publishing $entry"
+ cd $entry; npm version 0.0.0-$(git rev-parse --short HEAD)-$(date '+%Y%m%d') --git-tag-version false;
+ npm publish --tag canary --access public;
+ )
+done;
\ No newline at end of file
diff --git a/thirdparty/rapier.js/publish_all_prod.sh b/thirdparty/rapier.js/publish_all_prod.sh
new file mode 100644
index 00000000..f04915cb
--- /dev/null
+++ b/thirdparty/rapier.js/publish_all_prod.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+for entry in builds/*/pkg
+do
+ (
+ echo "Publishing $entry"
+ cd $entry;
+ npm publish --access public;
+ )
+done;
+
+for entry in rapier-compat/builds/*/pkg
+do
+ (
+ echo "Publishing $entry"
+ cd $entry;
+ npm publish --access public;
+ )
+done;
\ No newline at end of file
diff --git a/thirdparty/rapier.js/rapier-compat/README.md b/thirdparty/rapier.js/rapier-compat/README.md
new file mode 100644
index 00000000..5d440ec2
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/README.md
@@ -0,0 +1,16 @@
+# tsconfig json files
+
+- tsconfig.common.json - shared TypeScript options
+- tsconfig.pkg2d.json - config for compiling rapier2d-compat
+- tsconfig.pkg3d.json - config for compiling rapier3d-compat
+- tconfig.json - for IDE (VSCode) and unit tests. Includes Jest types.
+
+## Generation steps
+
+Check `./package.json scripts`, which are used by CI.
+
+Summary:
+
+- build rust wasm projects into their dedicated folder in `./builds/`
+- copy common javascript and generate dimension specific javascript into a common `./pkg` folder
+- copy that `pkg` folder in each folder from `./builds`
diff --git a/thirdparty/rapier.js/rapier-compat/build-rust.sh b/thirdparty/rapier.js/rapier-compat/build-rust.sh
new file mode 100644
index 00000000..2acd18f8
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/build-rust.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+
+help()
+{
+ printf "Usage: %s: [-d 2|3] [-f deterministim|non-deterministic|simd]\n" $0
+}
+
+while getopts :d:f: name
+do
+ case $name in
+ d)
+ dimension="$OPTARG";;
+ f)
+ feature="$OPTARG";;
+ ?) help ; exit 1;;
+ esac
+done
+
+if [[ -z "$dimension" ]]; then
+ help; exit 2;
+fi
+if [[ -z "$feature" ]]; then
+ help; exit 3;
+fi
+
+if [[ $feature == "non-deterministic" ]]; then
+ feature_postfix=""
+else
+ feature_postfix="-${feature}"
+fi
+
+rust_source_directory="../builds/rapier${dimension}d${feature_postfix}"
+
+if [ ! -d "$rust_source_directory" ]; then
+ echo "Directory $rust_source_directory does not exist";
+ echo "You may want to generate rust projects first.";
+ help
+ exit 4;
+fi
+
+# Working dir in wasm-pack is the project root so we need that "../../"
+
+if [[ $feature == "simd" ]]; then
+ export additional_rustflags='-C target-feature=+simd128'
+else
+ export additional_rustflags=''
+fi
+
+RUSTFLAGS="${additional_rustflags}" wasm-pack --verbose build --target web --out-dir "../../rapier-compat/builds/${dimension}d${feature_postfix}/wasm-build" "$rust_source_directory"
diff --git a/thirdparty/rapier.js/rapier-compat/fix_raw_file.sh b/thirdparty/rapier.js/rapier-compat/fix_raw_file.sh
new file mode 100644
index 00000000..66f8bfaf
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/fix_raw_file.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+for feature in \
+2d 2d-deterministic 2d-simd \
+3d 3d-deterministic 3d-simd
+do
+
+echo 'export * from "'"./rapier_wasm$feature"'"' > builds/${feature}/pkg/raw.d.ts
+echo 'export * from "'"./rapier_wasm$feature"'"' > builds/${feature}/pkg/raw.d.ts
+
+done;
\ No newline at end of file
diff --git a/thirdparty/rapier.js/rapier-compat/gen_src.sh b/thirdparty/rapier.js/rapier-compat/gen_src.sh
new file mode 100644
index 00000000..24ccc441
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/gen_src.sh
@@ -0,0 +1,58 @@
+# Copy source and remove #if sections - similar to script in ../rapierXd
+set -e
+
+gen_js() {
+ DIM=$1
+ GENOUT="./gen${DIM}"
+
+ # Make output directories
+ mkdir -p ${GENOUT}
+
+ # Copy common sources
+ cp -r ../src.ts/* $GENOUT
+
+ # Copy compat mode override sources
+ rm -f "${GENOUT}/raw.ts" "${GENOUT}/init.ts"
+ cp -r ./src${DIM}/* $GENOUT
+}
+
+gen_js "2d"
+gen_js "3d"
+
+# See https://serverfault.com/a/137848
+find gen2d/ -type f -print0 | LC_ALL=C xargs -0 sed -i.bak '\:#if DIM3:,\:#endif:d'
+find gen3d/ -type f -print0 | LC_ALL=C xargs -0 sed -i.bak '\:#if DIM2:,\:#endif:d'
+
+# Clean up backup files.
+find gen2d/ -type f -name '*.bak' | xargs rm
+find gen3d/ -type f -name '*.bak' | xargs rm
+
+for features_set in \
+"2" "2 deterministic" "2 simd" \
+"3" "3 deterministic" "3 simd"
+do
+
+ set -- $features_set # Convert the "tuple" into the param args $1 $2...
+ dimension=$1
+ if [ -z "$2" ]; then
+ feature="${1}d";
+ else
+ feature="${1}d-${2}";
+ fi
+
+ mkdir -p ./builds/${feature}/pkg/
+
+ cp ./builds/${feature}/wasm-build/rapier_wasm* ./builds/${feature}/pkg/
+ cp -r ./gen${dimension}d ./builds/${feature}/
+
+ # copy tsconfig, as they contain paths
+ cp ./tsconfig.common.json ./tsconfig.json ./builds/${feature}/
+ cp ./tsconfig.pkg${dimension}d.json ./builds/${feature}/tsconfig.pkg.json
+
+ # "import.meta" causes Babel to choke, but the code path is never taken so just remove it.
+ sed -i.bak 's/import.meta.url/""/g' ./builds/${feature}/pkg/rapier_wasm${dimension}d.js
+
+ # Clean up backup files.
+ find ./builds/${feature}/pkg/ -type f -name '*.bak' | xargs rm
+
+done
diff --git a/thirdparty/rapier.js/rapier-compat/package.json b/thirdparty/rapier.js/rapier-compat/package.json
new file mode 100644
index 00000000..f97e9cf4
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "build-rapier",
+ "description": "Build scripts for compatibility package with inlined webassembly as base64.",
+ "private": true,
+ "scripts": {
+ "build-rust-2d-non-deterministic": "./build-rust.sh -f non-deterministic -d 2",
+ "build-rust-2d-deterministic": "./build-rust.sh -f deterministic -d 2",
+ "build-rust-2d-simd": "./build-rust.sh -f simd -d 2",
+ "build-rust-2d-all": "npm run build-rust-2d-non-deterministic && npm run build-rust-2d-deterministic && npm run build-rust-2d-simd",
+ "build-rust-3d-non-deterministic": "./build-rust.sh -f non-deterministic -d 3",
+ "build-rust-3d-deterministic": "./build-rust.sh -f deterministic -d 3",
+ "build-rust-3d-simd": "./build-rust.sh -f simd -d 3",
+ "build-rust-3d-all": "npm run build-rust-3d-non-deterministic && npm run build-rust-3d-deterministic && npm run build-rust-3d-simd",
+ "build-rust-all": "npm run build-rust-2d-all && npm run build-rust-3d-all",
+ "build-genjs": "sh ./gen_src.sh",
+ "build-js": "rollup --config rollup.config.js --bundleConfigAsCjs",
+ "fix-raw-file": "sh ./fix_raw_file.sh",
+ "build": "npm run clean && npm run build-rust-all && npm run build-genjs && npm run build-js && npm run fix-raw-file",
+ "clean": "rimraf gen2d gen3d builds",
+ "test": "jest --detectOpenHandles",
+ "all": "npm run build"
+ },
+ "dependencies": {
+ "base64-js": "^1.5.1"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^23.0.2",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-typescript": "^9.0.2",
+ "@rollup/plugin-terser": "0.1.0",
+ "@types/jest": "^29.2.1",
+ "jest": "^29.2.2",
+ "rimraf": "^3.0.2",
+ "rollup": "^3.2.5",
+ "rollup-plugin-base64": "^1.0.1",
+ "rollup-plugin-copy": "^3.4.0",
+ "rollup-plugin-filesize": "^9.1.2",
+ "ts-jest": "^29.0.3",
+ "tslib": "^2.4.1",
+ "typescript": "^4.8.4"
+ },
+ "jest": {
+ "roots": [
+ "/tests"
+ ],
+ "preset": "ts-jest",
+ "collectCoverageFrom": [
+ "builds/**/pkg/**/*.js"
+ ],
+ "transformIgnorePatterns": [
+ "[/\\\\]node_modules[/\\\\].+\\.(js|ts)$",
+ "builds/.*/[/\\\\]pkg[/\\\\].+\\.(js|ts)$"
+ ],
+ "moduleFileExtensions": [
+ "ts",
+ "js"
+ ]
+ }
+}
diff --git a/thirdparty/rapier.js/rapier-compat/rollup.config.js b/thirdparty/rapier.js/rapier-compat/rollup.config.js
new file mode 100644
index 00000000..d127c671
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/rollup.config.js
@@ -0,0 +1,85 @@
+import commonjs from "@rollup/plugin-commonjs";
+import {nodeResolve} from "@rollup/plugin-node-resolve";
+import typescript from "@rollup/plugin-typescript";
+import terser from "@rollup/plugin-terser";
+import path from "path";
+import {base64} from "rollup-plugin-base64";
+import copy from "rollup-plugin-copy";
+import filesize from "rollup-plugin-filesize";
+
+const config = (dim, features_postfix) => ({
+ input: `builds/${features_postfix}/gen${dim}/rapier.ts`,
+ output: [
+ {
+ file: `builds/${features_postfix}/pkg/rapier.mjs`,
+ format: "es",
+ sourcemap: true,
+ exports: "named",
+ },
+ {
+ file: `builds/${features_postfix}/pkg/rapier.cjs`,
+ format: "cjs",
+ sourcemap: true,
+ exports: "named",
+ },
+ ],
+ plugins: [
+ copy({
+ targets: [
+ {
+ src: `builds/${features_postfix}/wasm-build/package.json`,
+ dest: `builds/${features_postfix}/pkg/`,
+ transform(content) {
+ let config = JSON.parse(content.toString());
+ config.name = `@dimforge/rapier${features_postfix}-compat`;
+ config.description +=
+ " Compatibility package with inlined webassembly as base64.";
+ config.types = "rapier.d.ts";
+ config.main = "rapier.cjs";
+ config.module = "rapier.mjs";
+ config.exports = {
+ ".": {
+ types: "./rapier.d.ts",
+ require: "./rapier.cjs",
+ import: "./rapier.mjs",
+ },
+ };
+ // delete config.module;
+ config.files = ["*"];
+ return JSON.stringify(config, undefined, 2);
+ },
+ },
+ {
+ src: `../rapier${features_postfix}/LICENSE`,
+ dest: `builds/${features_postfix}/pkg`,
+ },
+ {
+ src: `../rapier${features_postfix}/README.md`,
+ dest: `builds/${features_postfix}/pkg`,
+ },
+ ],
+ }),
+ base64({include: "**/*.wasm"}),
+ terser(),
+ nodeResolve(),
+ commonjs(),
+ typescript({
+ tsconfig: path.resolve(
+ __dirname,
+ `builds/${features_postfix}/tsconfig.pkg.json`,
+ ),
+ sourceMap: true,
+ inlineSources: true,
+ }),
+ filesize(),
+ ],
+});
+
+export default [
+ config("2d", "2d"),
+ config("2d", "2d-deterministic"),
+ config("2d", "2d-simd"),
+ config("3d", "3d"),
+ config("3d", "3d-deterministic"),
+ config("3d", "3d-simd"),
+];
diff --git a/thirdparty/rapier.js/rapier-compat/src2d/init.ts b/thirdparty/rapier.js/rapier-compat/src2d/init.ts
new file mode 100644
index 00000000..c1f2b3f2
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/src2d/init.ts
@@ -0,0 +1,60 @@
+/**
+ * RAPIER initialization module with dynamic WASM loading support.
+ * RAPIER 初始化模块,支持动态 WASM 加载。
+ */
+
+import wasmInit from "../pkg/rapier_wasm2d";
+
+/**
+ * Input types for WASM initialization.
+ * WASM 初始化的输入类型。
+ */
+export type InitInput =
+ | RequestInfo // URL string or Request object
+ | URL // URL object
+ | Response // Fetch Response object
+ | BufferSource // ArrayBuffer or TypedArray
+ | WebAssembly.Module; // Pre-compiled module
+
+let initialized = false;
+
+/**
+ * Initializes RAPIER.
+ * Has to be called and awaited before using any library methods.
+ *
+ * 初始化 RAPIER。
+ * 必须在使用任何库方法之前调用并等待。
+ *
+ * @param input - WASM source (required). Can be URL, Response, ArrayBuffer, etc.
+ * WASM 源(必需)。可以是 URL、Response、ArrayBuffer 等。
+ *
+ * @example
+ * // Load from URL | 从 URL 加载
+ * await RAPIER.init('wasm/rapier_wasm2d_bg.wasm');
+ *
+ * @example
+ * // Load from fetch response | 从 fetch 响应加载
+ * const response = await fetch('wasm/rapier_wasm2d_bg.wasm');
+ * await RAPIER.init(response);
+ *
+ * @example
+ * // Load from ArrayBuffer | 从 ArrayBuffer 加载
+ * const buffer = await fetch('wasm/rapier_wasm2d_bg.wasm').then(r => r.arrayBuffer());
+ * await RAPIER.init(buffer);
+ */
+export async function init(input?: InitInput): Promise {
+ if (initialized) {
+ return;
+ }
+
+ await wasmInit(input);
+ initialized = true;
+}
+
+/**
+ * Check if RAPIER is already initialized.
+ * 检查 RAPIER 是否已初始化。
+ */
+export function isInitialized(): boolean {
+ return initialized;
+}
diff --git a/thirdparty/rapier.js/rapier-compat/src2d/raw.ts b/thirdparty/rapier.js/rapier-compat/src2d/raw.ts
new file mode 100644
index 00000000..4eadaffa
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/src2d/raw.ts
@@ -0,0 +1 @@
+export * from "../pkg/rapier_wasm2d";
diff --git a/thirdparty/rapier.js/rapier-compat/src3d/init.ts b/thirdparty/rapier.js/rapier-compat/src3d/init.ts
new file mode 100644
index 00000000..15b14ada
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/src3d/init.ts
@@ -0,0 +1,12 @@
+// @ts-ignore
+import wasmBase64 from "../pkg/rapier_wasm3d_bg.wasm";
+import wasmInit from "../pkg/rapier_wasm3d";
+import base64 from "base64-js";
+
+/**
+ * Initializes RAPIER.
+ * Has to be called and awaited before using any library methods.
+ */
+export async function init() {
+ await wasmInit(base64.toByteArray(wasmBase64 as unknown as string).buffer);
+}
diff --git a/thirdparty/rapier.js/rapier-compat/src3d/raw.ts b/thirdparty/rapier.js/rapier-compat/src3d/raw.ts
new file mode 100644
index 00000000..b123ae6b
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/src3d/raw.ts
@@ -0,0 +1 @@
+export * from "../pkg/rapier_wasm3d";
diff --git a/thirdparty/rapier.js/rapier-compat/tests/World2d.test.ts b/thirdparty/rapier.js/rapier-compat/tests/World2d.test.ts
new file mode 100644
index 00000000..45a3bb72
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tests/World2d.test.ts
@@ -0,0 +1,23 @@
+import {init, Vector2, World} from "../builds/2d-deterministic/pkg";
+
+describe("2d/World", () => {
+ let world: World;
+
+ beforeAll(init);
+
+ afterAll(async () => {
+ await Promise.resolve();
+ });
+
+ beforeEach(() => {
+ world = new World(new Vector2(0, 9.8));
+ });
+
+ afterEach(() => {
+ world.free();
+ });
+
+ test("constructor", () => {
+ expect(world.colliders.len()).toBe(0);
+ });
+});
diff --git a/thirdparty/rapier.js/rapier-compat/tests/World3d.test.ts b/thirdparty/rapier.js/rapier-compat/tests/World3d.test.ts
new file mode 100644
index 00000000..e0ddd546
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tests/World3d.test.ts
@@ -0,0 +1,23 @@
+import {init, Vector3, World} from "../builds/3d-deterministic/pkg";
+
+describe("3d/World", () => {
+ let world: World;
+
+ beforeAll(init);
+
+ afterAll(async () => {
+ await Promise.resolve();
+ });
+
+ beforeEach(() => {
+ world = new World(new Vector3(0, 9.8, 0));
+ });
+
+ afterEach(() => {
+ world.free();
+ });
+
+ test("constructor", () => {
+ expect(world.colliders.len()).toBe(0);
+ });
+});
diff --git a/thirdparty/rapier.js/rapier-compat/tests/math2d.test.ts b/thirdparty/rapier.js/rapier-compat/tests/math2d.test.ts
new file mode 100644
index 00000000..a7560565
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tests/math2d.test.ts
@@ -0,0 +1,15 @@
+import {Vector2, VectorOps} from "../builds/2d-deterministic/pkg";
+
+describe("2d/math", () => {
+ test("Vector2", () => {
+ const v = new Vector2(0, 1);
+ expect(v.x).toBe(0);
+ expect(v.y).toBe(1);
+ });
+
+ test("VectorOps", () => {
+ const v = VectorOps.new(0, 1);
+ expect(v.x).toBe(0);
+ expect(v.y).toBe(1);
+ });
+});
diff --git a/thirdparty/rapier.js/rapier-compat/tests/math3d.test.ts b/thirdparty/rapier.js/rapier-compat/tests/math3d.test.ts
new file mode 100644
index 00000000..2ff194cc
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tests/math3d.test.ts
@@ -0,0 +1,17 @@
+import {Vector3, VectorOps} from "../builds/3d-deterministic/pkg";
+
+describe("3d/math", () => {
+ test("Vector3", () => {
+ const v = new Vector3(0, 1, 2);
+ expect(v.x).toBe(0);
+ expect(v.y).toBe(1);
+ expect(v.z).toBe(2);
+ });
+
+ test("VectorOps", () => {
+ const v = VectorOps.new(0, 1, 2);
+ expect(v.x).toBe(0);
+ expect(v.y).toBe(1);
+ expect(v.z).toBe(2);
+ });
+});
diff --git a/thirdparty/rapier.js/rapier-compat/tsconfig.common.json b/thirdparty/rapier.js/rapier-compat/tsconfig.common.json
new file mode 100644
index 00000000..37bd7d7f
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tsconfig.common.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "module": "ES6",
+ "target": "es6",
+ "noEmitOnError": true,
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "strictFunctionTypes": true,
+ "lib": ["es6", "DOM"],
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "declaration": true
+ }
+}
diff --git a/thirdparty/rapier.js/rapier-compat/tsconfig.json b/thirdparty/rapier.js/rapier-compat/tsconfig.json
new file mode 100644
index 00000000..beeca944
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.common.json",
+ "compilerOptions": {
+ "lib": ["es6", "DOM"],
+ "esModuleInterop": true,
+ "types": ["jest"],
+ "outDir": "./dist"
+ },
+ "include": ["./tests/**/*.test.ts"]
+}
diff --git a/thirdparty/rapier.js/rapier-compat/tsconfig.pkg2d.json b/thirdparty/rapier.js/rapier-compat/tsconfig.pkg2d.json
new file mode 100644
index 00000000..77349ed2
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tsconfig.pkg2d.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.common.json",
+ "compilerOptions": {
+ "rootDir": "./gen2d",
+ "outDir": "./2d"
+ },
+ "files": ["./gen2d/rapier.ts"]
+}
diff --git a/thirdparty/rapier.js/rapier-compat/tsconfig.pkg3d.json b/thirdparty/rapier.js/rapier-compat/tsconfig.pkg3d.json
new file mode 100644
index 00000000..3d7323e8
--- /dev/null
+++ b/thirdparty/rapier.js/rapier-compat/tsconfig.pkg3d.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.common.json",
+ "compilerOptions": {
+ "rootDir": "./gen3d",
+ "outDir": "."
+ },
+ "files": ["./gen3d/rapier.ts"]
+}
diff --git a/thirdparty/rapier.js/src.ts/coarena.ts b/thirdparty/rapier.js/src.ts/coarena.ts
new file mode 100644
index 00000000..9bf978a5
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/coarena.ts
@@ -0,0 +1,70 @@
+export class Coarena {
+ fconv: Float64Array;
+ uconv: Uint32Array;
+ data: Array;
+ size: number;
+
+ public constructor() {
+ this.fconv = new Float64Array(1);
+ this.uconv = new Uint32Array(this.fconv.buffer);
+ this.data = new Array();
+ this.size = 0;
+ }
+
+ public set(handle: number, data: T) {
+ let i = this.index(handle);
+ while (this.data.length <= i) {
+ this.data.push(null);
+ }
+
+ if (this.data[i] == null) this.size += 1;
+ this.data[i] = data;
+ }
+
+ public len(): number {
+ return this.size;
+ }
+
+ public delete(handle: number) {
+ let i = this.index(handle);
+ if (i < this.data.length) {
+ if (this.data[i] != null) this.size -= 1;
+ this.data[i] = null;
+ }
+ }
+
+ public clear() {
+ this.data = new Array();
+ }
+
+ public get(handle: number): T | null {
+ let i = this.index(handle);
+ if (i < this.data.length) {
+ return this.data[i];
+ } else {
+ return null;
+ }
+ }
+
+ public forEach(f: (elt: T) => void) {
+ for (const elt of this.data) {
+ if (elt != null) f(elt);
+ }
+ }
+
+ public getAll(): Array {
+ return this.data.filter((elt) => elt != null);
+ }
+
+ private index(handle: number): number {
+ /// Extracts the index part of a handle (the lower 32 bits).
+ /// This is done by first injecting the handle into an Float64Array
+ /// which is itself injected into an Uint32Array (at construction time).
+ /// The 0-th value of the Uint32Array will become the `number` integer
+ /// representation of the lower 32 bits.
+ /// Also `this.uconv[1]` then contains the generation number as a `number`,
+ /// which we don’t really need.
+ this.fconv[0] = handle;
+ return this.uconv[0];
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/control/character_controller.ts b/thirdparty/rapier.js/src.ts/control/character_controller.ts
new file mode 100644
index 00000000..8e536392
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/control/character_controller.ts
@@ -0,0 +1,386 @@
+import {RawKinematicCharacterController, RawCharacterCollision} from "../raw";
+import {Rotation, Vector, VectorOps} from "../math";
+import {
+ BroadPhase,
+ Collider,
+ ColliderSet,
+ InteractionGroups,
+ NarrowPhase,
+ Shape,
+} from "../geometry";
+import {QueryFilterFlags, World} from "../pipeline";
+import {IntegrationParameters, RigidBody, RigidBodySet} from "../dynamics";
+
+/**
+ * A collision between the character and an obstacle hit on its path.
+ */
+export class CharacterCollision {
+ /** The collider involved in the collision. Null if the collider no longer exists in the physics world. */
+ public collider: Collider | null;
+ /** The translation delta applied to the character before this collision took place. */
+ public translationDeltaApplied: Vector;
+ /** The translation delta the character would move after this collision if there is no other obstacles. */
+ public translationDeltaRemaining: Vector;
+ /** The time-of-impact between the character and the obstacles. */
+ public toi: number;
+ /** The world-space contact point on the collider when the collision happens. */
+ public witness1: Vector;
+ /** The local-space contact point on the character when the collision happens. */
+ public witness2: Vector;
+ /** The world-space outward contact normal on the collider when the collision happens. */
+ public normal1: Vector;
+ /** The local-space outward contact normal on the character when the collision happens. */
+ public normal2: Vector;
+}
+
+/**
+ * A character controller for controlling kinematic bodies and parentless colliders by hitting
+ * and sliding against obstacles.
+ */
+export class KinematicCharacterController {
+ private raw: RawKinematicCharacterController;
+ private rawCharacterCollision: RawCharacterCollision;
+
+ private params: IntegrationParameters;
+ private broadPhase: BroadPhase;
+ private narrowPhase: NarrowPhase;
+ private bodies: RigidBodySet;
+ private colliders: ColliderSet;
+ private _applyImpulsesToDynamicBodies: boolean;
+ private _characterMass: number | null;
+
+ constructor(
+ offset: number,
+ params: IntegrationParameters,
+ broadPhase: BroadPhase,
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ ) {
+ this.params = params;
+ this.bodies = bodies;
+ this.colliders = colliders;
+ this.broadPhase = broadPhase;
+ this.narrowPhase = narrowPhase;
+ this.raw = new RawKinematicCharacterController(offset);
+ this.rawCharacterCollision = new RawCharacterCollision();
+ this._applyImpulsesToDynamicBodies = false;
+ this._characterMass = null;
+ }
+
+ /** @internal */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ this.rawCharacterCollision.free();
+ }
+
+ this.raw = undefined;
+ this.rawCharacterCollision = undefined;
+ }
+
+ /**
+ * The direction that goes "up". Used to determine where the floor is, and the floor’s angle.
+ */
+ public up(): Vector {
+ return this.raw.up();
+ }
+
+ /**
+ * Sets the direction that goes "up". Used to determine where the floor is, and the floor’s angle.
+ */
+ public setUp(vector: Vector) {
+ let rawVect = VectorOps.intoRaw(vector);
+ return this.raw.setUp(rawVect);
+ rawVect.free();
+ }
+
+ public applyImpulsesToDynamicBodies(): boolean {
+ return this._applyImpulsesToDynamicBodies;
+ }
+
+ public setApplyImpulsesToDynamicBodies(enabled: boolean) {
+ this._applyImpulsesToDynamicBodies = enabled;
+ }
+
+ /**
+ * Returns the custom value of the character mass, if it was set by `this.setCharacterMass`.
+ */
+ public characterMass(): number | null {
+ return this._characterMass;
+ }
+
+ /**
+ * Set the mass of the character to be used for impulse resolution if `self.applyImpulsesToDynamicBodies`
+ * is set to `true`.
+ *
+ * If no character mass is set explicitly (or if it is set to `null`) it is automatically assumed to be equal
+ * to the mass of the rigid-body the character collider is attached to; or equal to 0 if the character collider
+ * isn’t attached to any rigid-body.
+ *
+ * @param mass - The mass to set.
+ */
+ public setCharacterMass(mass: number | null) {
+ this._characterMass = mass;
+ }
+
+ /**
+ * A small gap to preserve between the character and its surroundings.
+ *
+ * This value should not be too large to avoid visual artifacts, but shouldn’t be too small
+ * (must not be zero) to improve numerical stability of the character controller.
+ */
+ public offset(): number {
+ return this.raw.offset();
+ }
+
+ /**
+ * Sets a small gap to preserve between the character and its surroundings.
+ *
+ * This value should not be too large to avoid visual artifacts, but shouldn’t be too small
+ * (must not be zero) to improve numerical stability of the character controller.
+ */
+ public setOffset(value: number) {
+ this.raw.setOffset(value);
+ }
+
+ /// Increase this number if your character appears to get stuck when sliding against surfaces.
+ ///
+ /// This is a small distance applied to the movement toward the contact normals of shapes hit
+ /// by the character controller. This helps shape-casting not getting stuck in an always-penetrating
+ /// state during the sliding calculation.
+ ///
+ /// This value should remain fairly small since it can introduce artificial "bumps" when sliding
+ /// along a flat surface.
+ public normalNudgeFactor(): number {
+ return this.raw.normalNudgeFactor();
+ }
+
+ /// Increase this number if your character appears to get stuck when sliding against surfaces.
+ ///
+ /// This is a small distance applied to the movement toward the contact normals of shapes hit
+ /// by the character controller. This helps shape-casting not getting stuck in an always-penetrating
+ /// state during the sliding calculation.
+ ///
+ /// This value should remain fairly small since it can introduce artificial "bumps" when sliding
+ /// along a flat surface.
+ public setNormalNudgeFactor(value: number) {
+ this.raw.setNormalNudgeFactor(value);
+ }
+
+ /**
+ * Is sliding against obstacles enabled?
+ */
+ public slideEnabled(): boolean {
+ return this.raw.slideEnabled();
+ }
+
+ /**
+ * Enable or disable sliding against obstacles.
+ */
+ public setSlideEnabled(enabled: boolean) {
+ this.raw.setSlideEnabled(enabled);
+ }
+
+ /**
+ * The maximum step height a character can automatically step over.
+ */
+ public autostepMaxHeight(): number | null {
+ return this.raw.autostepMaxHeight();
+ }
+
+ /**
+ * The minimum width of free space that must be available after stepping on a stair.
+ */
+ public autostepMinWidth(): number | null {
+ return this.raw.autostepMinWidth();
+ }
+
+ /**
+ * Can the character automatically step over dynamic bodies too?
+ */
+ public autostepIncludesDynamicBodies(): boolean | null {
+ return this.raw.autostepIncludesDynamicBodies();
+ }
+
+ /**
+ * Is automatically stepping over small objects enabled?
+ */
+ public autostepEnabled(): boolean {
+ return this.raw.autostepEnabled();
+ }
+
+ /**
+ * Enabled automatically stepping over small objects.
+ *
+ * @param maxHeight - The maximum step height a character can automatically step over.
+ * @param minWidth - The minimum width of free space that must be available after stepping on a stair.
+ * @param includeDynamicBodies - Can the character automatically step over dynamic bodies too?
+ */
+ public enableAutostep(
+ maxHeight: number,
+ minWidth: number,
+ includeDynamicBodies: boolean,
+ ) {
+ this.raw.enableAutostep(maxHeight, minWidth, includeDynamicBodies);
+ }
+
+ /**
+ * Disable automatically stepping over small objects.
+ */
+ public disableAutostep() {
+ return this.raw.disableAutostep();
+ }
+
+ /**
+ * The maximum angle (radians) between the floor’s normal and the `up` vector that the
+ * character is able to climb.
+ */
+ public maxSlopeClimbAngle(): number {
+ return this.raw.maxSlopeClimbAngle();
+ }
+
+ /**
+ * Sets the maximum angle (radians) between the floor’s normal and the `up` vector that the
+ * character is able to climb.
+ */
+ public setMaxSlopeClimbAngle(angle: number) {
+ this.raw.setMaxSlopeClimbAngle(angle);
+ }
+
+ /**
+ * The minimum angle (radians) between the floor’s normal and the `up` vector before the
+ * character starts to slide down automatically.
+ */
+ public minSlopeSlideAngle(): number {
+ return this.raw.minSlopeSlideAngle();
+ }
+
+ /**
+ * Sets the minimum angle (radians) between the floor’s normal and the `up` vector before the
+ * character starts to slide down automatically.
+ */
+ public setMinSlopeSlideAngle(angle: number) {
+ this.raw.setMinSlopeSlideAngle(angle);
+ }
+
+ /**
+ * If snap-to-ground is enabled, should the character be automatically snapped to the ground if
+ * the distance between the ground and its feet are smaller than the specified threshold?
+ */
+ public snapToGroundDistance(): number | null {
+ return this.raw.snapToGroundDistance();
+ }
+
+ /**
+ * Enables automatically snapping the character to the ground if the distance between
+ * the ground and its feet are smaller than the specified threshold.
+ */
+ public enableSnapToGround(distance: number) {
+ this.raw.enableSnapToGround(distance);
+ }
+
+ /**
+ * Disables automatically snapping the character to the ground.
+ */
+ public disableSnapToGround() {
+ this.raw.disableSnapToGround();
+ }
+
+ /**
+ * Is automatically snapping the character to the ground enabled?
+ */
+ public snapToGroundEnabled(): boolean {
+ return this.raw.snapToGroundEnabled();
+ }
+
+ /**
+ * Computes the movement the given collider is able to execute after hitting and sliding on obstacles.
+ *
+ * @param collider - The collider to move.
+ * @param desiredTranslationDelta - The desired collider movement.
+ * @param filterFlags - Flags for excluding whole subsets of colliders from the obstacles taken into account.
+ * @param filterGroups - Groups for excluding colliders with incompatible collision groups from the obstacles
+ * taken into account.
+ * @param filterPredicate - Any collider for which this closure returns `false` will be excluded from the
+ * obstacles taken into account.
+ */
+ public computeColliderMovement(
+ collider: Collider,
+ desiredTranslationDelta: Vector,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterPredicate?: (collider: Collider) => boolean,
+ ) {
+ let rawTranslationDelta = VectorOps.intoRaw(desiredTranslationDelta);
+ this.raw.computeColliderMovement(
+ this.params.dt,
+ this.broadPhase.raw,
+ this.narrowPhase.raw,
+ this.bodies.raw,
+ this.colliders.raw,
+ collider.handle,
+ rawTranslationDelta,
+ this._applyImpulsesToDynamicBodies,
+ this._characterMass,
+ filterFlags,
+ filterGroups,
+ this.colliders.castClosure(filterPredicate),
+ );
+ rawTranslationDelta.free();
+ }
+
+ /**
+ * The movement computed by the last call to `this.computeColliderMovement`.
+ */
+ public computedMovement(): Vector {
+ return VectorOps.fromRaw(this.raw.computedMovement());
+ }
+
+ /**
+ * The result of ground detection computed by the last call to `this.computeColliderMovement`.
+ */
+ public computedGrounded(): boolean {
+ return this.raw.computedGrounded();
+ }
+
+ /**
+ * The number of collisions against obstacles detected along the path of the last call
+ * to `this.computeColliderMovement`.
+ */
+ public numComputedCollisions(): number {
+ return this.raw.numComputedCollisions();
+ }
+
+ /**
+ * Returns the collision against one of the obstacles detected along the path of the last
+ * call to `this.computeColliderMovement`.
+ *
+ * @param i - The i-th collision will be returned.
+ * @param out - If this argument is set, it will be filled with the collision information.
+ */
+ public computedCollision(
+ i: number,
+ out?: CharacterCollision,
+ ): CharacterCollision | null {
+ if (!this.raw.computedCollision(i, this.rawCharacterCollision)) {
+ return null;
+ } else {
+ let c = this.rawCharacterCollision;
+ out = out ?? new CharacterCollision();
+ out.translationDeltaApplied = VectorOps.fromRaw(
+ c.translationDeltaApplied(),
+ );
+ out.translationDeltaRemaining = VectorOps.fromRaw(
+ c.translationDeltaRemaining(),
+ );
+ out.toi = c.toi();
+ out.witness1 = VectorOps.fromRaw(c.worldWitness1());
+ out.witness2 = VectorOps.fromRaw(c.worldWitness2());
+ out.normal1 = VectorOps.fromRaw(c.worldNormal1());
+ out.normal2 = VectorOps.fromRaw(c.worldNormal2());
+ out.collider = this.colliders.get(c.handle());
+ return out;
+ }
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/control/index.ts b/thirdparty/rapier.js/src.ts/control/index.ts
new file mode 100644
index 00000000..43e495a5
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/control/index.ts
@@ -0,0 +1,6 @@
+export * from "./character_controller";
+export * from "./pid_controller";
+
+// #if DIM3
+export * from "./ray_cast_vehicle_controller";
+// #endif
diff --git a/thirdparty/rapier.js/src.ts/control/pid_controller.ts b/thirdparty/rapier.js/src.ts/control/pid_controller.ts
new file mode 100644
index 00000000..52b2f006
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/control/pid_controller.ts
@@ -0,0 +1,207 @@
+import {RawPidController} from "../raw";
+import {Rotation, RotationOps, Vector, VectorOps} from "../math";
+import {Collider, ColliderSet, InteractionGroups, Shape} from "../geometry";
+import {QueryFilterFlags, World} from "../pipeline";
+import {IntegrationParameters, RigidBody, RigidBodySet} from "../dynamics";
+
+// TODO: unify with the JointAxesMask
+/**
+ * An enum representing the possible joint axes controlled by a PidController.
+ * They can be ORed together, like:
+ * PidAxesMask.LinX || PidAxesMask.LinY
+ * to get a pid controller that only constraints the translational X and Y axes.
+ *
+ * Possible axes are:
+ *
+ * - `X`: X translation axis
+ * - `Y`: Y translation axis
+ * - `Z`: Z translation axis
+ * - `AngX`: X angular rotation axis (3D only)
+ * - `AngY`: Y angular rotation axis (3D only)
+ * - `AngZ`: Z angular rotation axis
+ */
+export enum PidAxesMask {
+ None = 0,
+ LinX = 1 << 0,
+ LinY = 1 << 1,
+ LinZ = 1 << 2,
+ // #if DIM3
+ AngX = 1 << 3,
+ AngY = 1 << 4,
+ // #endif
+ AngZ = 1 << 5,
+ // #if DIM2
+ AllLin = PidAxesMask.LinX | PidAxesMask.LinY,
+ AllAng = PidAxesMask.AngZ,
+ // #endif
+ // #if DIM3
+ AllLin = PidAxesMask.LinX | PidAxesMask.LinY | PidAxesMask.LinZ,
+ AllAng = PidAxesMask.AngX | PidAxesMask.AngY | PidAxesMask.AngZ,
+ // #endif
+ All = PidAxesMask.AllLin | PidAxesMask.AllAng,
+}
+
+/**
+ * A controller for controlling dynamic bodies using the
+ * Proportional-Integral-Derivative correction model.
+ */
+export class PidController {
+ private raw: RawPidController;
+
+ private params: IntegrationParameters;
+ private bodies: RigidBodySet;
+
+ constructor(
+ params: IntegrationParameters,
+ bodies: RigidBodySet,
+ kp: number,
+ ki: number,
+ kd: number,
+ axes: PidAxesMask,
+ ) {
+ this.params = params;
+ this.bodies = bodies;
+ this.raw = new RawPidController(kp, ki, kd, axes);
+ }
+
+ /** @internal */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+
+ this.raw = undefined;
+ }
+
+ public setKp(kp: number, axes: PidAxesMask) {
+ this.raw.set_kp(kp, axes);
+ }
+
+ public setKi(ki: number, axes: PidAxesMask) {
+ this.raw.set_kp(ki, axes);
+ }
+
+ public setKd(kd: number, axes: PidAxesMask) {
+ this.raw.set_kp(kd, axes);
+ }
+
+ public setAxes(axes: PidAxesMask) {
+ this.raw.set_axes_mask(axes);
+ }
+
+ public resetIntegrals() {
+ this.raw.reset_integrals();
+ }
+
+ public applyLinearCorrection(
+ body: RigidBody,
+ targetPosition: Vector,
+ targetLinvel: Vector,
+ ) {
+ let rawPos = VectorOps.intoRaw(targetPosition);
+ let rawVel = VectorOps.intoRaw(targetLinvel);
+ this.raw.apply_linear_correction(
+ this.params.dt,
+ this.bodies.raw,
+ body.handle,
+ rawPos,
+ rawVel,
+ );
+ rawPos.free();
+ rawVel.free();
+ }
+
+ // #if DIM2
+ public applyAngularCorrection(
+ body: RigidBody,
+ targetRotation: number,
+ targetAngVel: number,
+ ) {
+ this.raw.apply_angular_correction(
+ this.params.dt,
+ this.bodies.raw,
+ body.handle,
+ targetRotation,
+ targetAngVel,
+ );
+ }
+ // #endif
+
+ // #if DIM3
+ public applyAngularCorrection(
+ body: RigidBody,
+ targetRotation: Rotation,
+ targetAngVel: Vector,
+ ) {
+ let rawPos = RotationOps.intoRaw(targetRotation);
+ let rawVel = VectorOps.intoRaw(targetAngVel);
+ this.raw.apply_angular_correction(
+ this.params.dt,
+ this.bodies.raw,
+ body.handle,
+ rawPos,
+ rawVel,
+ );
+ rawPos.free();
+ rawVel.free();
+ }
+ // #endif
+
+ public linearCorrection(
+ body: RigidBody,
+ targetPosition: Vector,
+ targetLinvel: Vector,
+ ): Vector {
+ let rawPos = VectorOps.intoRaw(targetPosition);
+ let rawVel = VectorOps.intoRaw(targetLinvel);
+ let correction = this.raw.linear_correction(
+ this.params.dt,
+ this.bodies.raw,
+ body.handle,
+ rawPos,
+ rawVel,
+ );
+ rawPos.free();
+ rawVel.free();
+
+ return VectorOps.fromRaw(correction);
+ }
+
+ // #if DIM2
+ public angularCorrection(
+ body: RigidBody,
+ targetRotation: number,
+ targetAngVel: number,
+ ): number {
+ return this.raw.angular_correction(
+ this.params.dt,
+ this.bodies.raw,
+ body.handle,
+ targetRotation,
+ targetAngVel,
+ );
+ }
+ // #endif
+
+ // #if DIM3
+ public angularCorrection(
+ body: RigidBody,
+ targetRotation: Rotation,
+ targetAngVel: Vector,
+ ): Vector {
+ let rawPos = RotationOps.intoRaw(targetRotation);
+ let rawVel = VectorOps.intoRaw(targetAngVel);
+ let correction = this.raw.angular_correction(
+ this.params.dt,
+ this.bodies.raw,
+ body.handle,
+ rawPos,
+ rawVel,
+ );
+ rawPos.free();
+ rawVel.free();
+
+ return VectorOps.fromRaw(correction);
+ }
+ // #endif
+}
diff --git a/thirdparty/rapier.js/src.ts/control/ray_cast_vehicle_controller.ts b/thirdparty/rapier.js/src.ts/control/ray_cast_vehicle_controller.ts
new file mode 100644
index 00000000..fa65b3e3
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/control/ray_cast_vehicle_controller.ts
@@ -0,0 +1,481 @@
+import {RawDynamicRayCastVehicleController} from "../raw";
+import {Vector, VectorOps} from "../math";
+import {
+ BroadPhase,
+ Collider,
+ ColliderSet,
+ InteractionGroups,
+ NarrowPhase,
+} from "../geometry";
+import {QueryFilterFlags} from "../pipeline";
+import {RigidBody, RigidBodyHandle, RigidBodySet} from "../dynamics";
+
+/**
+ * A character controller to simulate vehicles using ray-casting for the wheels.
+ */
+export class DynamicRayCastVehicleController {
+ private raw: RawDynamicRayCastVehicleController;
+ private broadPhase: BroadPhase;
+ private narrowPhase: NarrowPhase;
+ private bodies: RigidBodySet;
+ private colliders: ColliderSet;
+ private _chassis: RigidBody;
+
+ constructor(
+ chassis: RigidBody,
+ broadPhase: BroadPhase,
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ ) {
+ this.raw = new RawDynamicRayCastVehicleController(chassis.handle);
+ this.broadPhase = broadPhase;
+ this.narrowPhase = narrowPhase;
+ this.bodies = bodies;
+ this.colliders = colliders;
+ this._chassis = chassis;
+ }
+
+ /** @internal */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+
+ this.raw = undefined;
+ }
+
+ /**
+ * Updates the vehicle’s velocity based on its suspension, engine force, and brake.
+ *
+ * This directly updates the velocity of its chassis rigid-body.
+ *
+ * @param dt - Time increment used to integrate forces.
+ * @param filterFlags - Flag to exclude categories of objects from the wheels’ ray-cast.
+ * @param filterGroups - Only colliders compatible with these groups will be hit by the wheels’ ray-casts.
+ * @param filterPredicate - Callback to filter out which collider will be hit by the wheels’ ray-casts.
+ */
+ public updateVehicle(
+ dt: number,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterPredicate?: (collider: Collider) => boolean,
+ ) {
+ this.raw.update_vehicle(
+ dt,
+ this.broadPhase.raw,
+ this.narrowPhase.raw,
+ this.bodies.raw,
+ this.colliders.raw,
+ filterFlags,
+ filterGroups,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * The current forward speed of the vehicle.
+ */
+ public currentVehicleSpeed(): number {
+ return this.raw.current_vehicle_speed();
+ }
+
+ /**
+ * The rigid-body used as the chassis.
+ */
+ public chassis(): RigidBody {
+ return this._chassis;
+ }
+
+ /**
+ * The chassis’ local _up_ direction (`0 = x, 1 = y, 2 = z`).
+ */
+ get indexUpAxis(): number {
+ return this.raw.index_up_axis();
+ }
+
+ /**
+ * Sets the chassis’ local _up_ direction (`0 = x, 1 = y, 2 = z`).
+ */
+ set indexUpAxis(axis: number) {
+ this.raw.set_index_up_axis(axis);
+ }
+
+ /**
+ * The chassis’ local _forward_ direction (`0 = x, 1 = y, 2 = z`).
+ */
+ get indexForwardAxis(): number {
+ return this.raw.index_forward_axis();
+ }
+
+ /**
+ * Sets the chassis’ local _forward_ direction (`0 = x, 1 = y, 2 = z`).
+ */
+ set setIndexForwardAxis(axis: number) {
+ this.raw.set_index_forward_axis(axis);
+ }
+
+ /**
+ * Adds a new wheel attached to this vehicle.
+ * @param chassisConnectionCs - The position of the wheel relative to the chassis.
+ * @param directionCs - The direction of the wheel’s suspension, relative to the chassis. The ray-casting will
+ * happen following this direction to detect the ground.
+ * @param axleCs - The wheel’s axle axis, relative to the chassis.
+ * @param suspensionRestLength - The rest length of the wheel’s suspension spring.
+ * @param radius - The wheel’s radius.
+ */
+ public addWheel(
+ chassisConnectionCs: Vector,
+ directionCs: Vector,
+ axleCs: Vector,
+ suspensionRestLength: number,
+ radius: number,
+ ) {
+ let rawChassisConnectionCs = VectorOps.intoRaw(chassisConnectionCs);
+ let rawDirectionCs = VectorOps.intoRaw(directionCs);
+ let rawAxleCs = VectorOps.intoRaw(axleCs);
+
+ this.raw.add_wheel(
+ rawChassisConnectionCs,
+ rawDirectionCs,
+ rawAxleCs,
+ suspensionRestLength,
+ radius,
+ );
+
+ rawChassisConnectionCs.free();
+ rawDirectionCs.free();
+ rawAxleCs.free();
+ }
+
+ /**
+ * The number of wheels attached to this vehicle.
+ */
+ public numWheels(): number {
+ return this.raw.num_wheels();
+ }
+
+ /*
+ *
+ * Access to wheel properties.
+ *
+ */
+ /*
+ * Getters + setters
+ */
+ /**
+ * The position of the i-th wheel, relative to the chassis.
+ */
+ public wheelChassisConnectionPointCs(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.wheel_chassis_connection_point_cs(i));
+ }
+
+ /**
+ * Sets the position of the i-th wheel, relative to the chassis.
+ */
+ public setWheelChassisConnectionPointCs(i: number, value: Vector) {
+ let rawValue = VectorOps.intoRaw(value);
+ this.raw.set_wheel_chassis_connection_point_cs(i, rawValue);
+ rawValue.free();
+ }
+
+ /**
+ * The rest length of the i-th wheel’s suspension spring.
+ */
+ public wheelSuspensionRestLength(i: number): number | null {
+ return this.raw.wheel_suspension_rest_length(i);
+ }
+
+ /**
+ * Sets the rest length of the i-th wheel’s suspension spring.
+ */
+ public setWheelSuspensionRestLength(i: number, value: number) {
+ this.raw.set_wheel_suspension_rest_length(i, value);
+ }
+
+ /**
+ * The maximum distance the i-th wheel suspension can travel before and after its resting length.
+ */
+ public wheelMaxSuspensionTravel(i: number): number | null {
+ return this.raw.wheel_max_suspension_travel(i);
+ }
+
+ /**
+ * Sets the maximum distance the i-th wheel suspension can travel before and after its resting length.
+ */
+ public setWheelMaxSuspensionTravel(i: number, value: number) {
+ this.raw.set_wheel_max_suspension_travel(i, value);
+ }
+
+ /**
+ * The i-th wheel’s radius.
+ */
+ public wheelRadius(i: number): number | null {
+ return this.raw.wheel_radius(i);
+ }
+
+ /**
+ * Sets the i-th wheel’s radius.
+ */
+ public setWheelRadius(i: number, value: number) {
+ this.raw.set_wheel_radius(i, value);
+ }
+
+ /**
+ * The i-th wheel’s suspension stiffness.
+ *
+ * Increase this value if the suspension appears to not push the vehicle strong enough.
+ */
+ public wheelSuspensionStiffness(i: number): number | null {
+ return this.raw.wheel_suspension_stiffness(i);
+ }
+
+ /**
+ * Sets the i-th wheel’s suspension stiffness.
+ *
+ * Increase this value if the suspension appears to not push the vehicle strong enough.
+ */
+ public setWheelSuspensionStiffness(i: number, value: number) {
+ this.raw.set_wheel_suspension_stiffness(i, value);
+ }
+
+ /**
+ * The i-th wheel’s suspension’s damping when it is being compressed.
+ */
+ public wheelSuspensionCompression(i: number): number | null {
+ return this.raw.wheel_suspension_compression(i);
+ }
+
+ /**
+ * The i-th wheel’s suspension’s damping when it is being compressed.
+ */
+ public setWheelSuspensionCompression(i: number, value: number) {
+ this.raw.set_wheel_suspension_compression(i, value);
+ }
+
+ /**
+ * The i-th wheel’s suspension’s damping when it is being released.
+ *
+ * Increase this value if the suspension appears to overshoot.
+ */
+ public wheelSuspensionRelaxation(i: number): number | null {
+ return this.raw.wheel_suspension_relaxation(i);
+ }
+
+ /**
+ * Sets the i-th wheel’s suspension’s damping when it is being released.
+ *
+ * Increase this value if the suspension appears to overshoot.
+ */
+ public setWheelSuspensionRelaxation(i: number, value: number) {
+ this.raw.set_wheel_suspension_relaxation(i, value);
+ }
+
+ /**
+ * The maximum force applied by the i-th wheel’s suspension.
+ */
+ public wheelMaxSuspensionForce(i: number): number | null {
+ return this.raw.wheel_max_suspension_force(i);
+ }
+
+ /**
+ * Sets the maximum force applied by the i-th wheel’s suspension.
+ */
+ public setWheelMaxSuspensionForce(i: number, value: number) {
+ this.raw.set_wheel_max_suspension_force(i, value);
+ }
+
+ /**
+ * The maximum amount of braking impulse applied on the i-th wheel to slow down the vehicle.
+ */
+ public wheelBrake(i: number): number | null {
+ return this.raw.wheel_brake(i);
+ }
+
+ /**
+ * Set the maximum amount of braking impulse applied on the i-th wheel to slow down the vehicle.
+ */
+ public setWheelBrake(i: number, value: number) {
+ this.raw.set_wheel_brake(i, value);
+ }
+
+ /**
+ * The steering angle (radians) for the i-th wheel.
+ */
+ public wheelSteering(i: number): number | null {
+ return this.raw.wheel_steering(i);
+ }
+
+ /**
+ * Sets the steering angle (radians) for the i-th wheel.
+ */
+ public setWheelSteering(i: number, value: number) {
+ this.raw.set_wheel_steering(i, value);
+ }
+
+ /**
+ * The forward force applied by the i-th wheel on the chassis.
+ */
+ public wheelEngineForce(i: number): number | null {
+ return this.raw.wheel_engine_force(i);
+ }
+
+ /**
+ * Sets the forward force applied by the i-th wheel on the chassis.
+ */
+ public setWheelEngineForce(i: number, value: number) {
+ this.raw.set_wheel_engine_force(i, value);
+ }
+
+ /**
+ * The direction of the i-th wheel’s suspension, relative to the chassis.
+ *
+ * The ray-casting will happen following this direction to detect the ground.
+ */
+ public wheelDirectionCs(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.wheel_direction_cs(i));
+ }
+
+ /**
+ * Sets the direction of the i-th wheel’s suspension, relative to the chassis.
+ *
+ * The ray-casting will happen following this direction to detect the ground.
+ */
+ public setWheelDirectionCs(i: number, value: Vector) {
+ let rawValue = VectorOps.intoRaw(value);
+ this.raw.set_wheel_direction_cs(i, rawValue);
+ rawValue.free();
+ }
+
+ /**
+ * The i-th wheel’s axle axis, relative to the chassis.
+ *
+ * The axis index defined as 0 = X, 1 = Y, 2 = Z.
+ */
+ public wheelAxleCs(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.wheel_axle_cs(i));
+ }
+
+ /**
+ * Sets the i-th wheel’s axle axis, relative to the chassis.
+ *
+ * The axis index defined as 0 = X, 1 = Y, 2 = Z.
+ */
+ public setWheelAxleCs(i: number, value: Vector) {
+ let rawValue = VectorOps.intoRaw(value);
+ this.raw.set_wheel_axle_cs(i, rawValue);
+ rawValue.free();
+ }
+
+ /**
+ * Parameter controlling how much traction the tire has.
+ *
+ * The larger the value, the more instantaneous braking will happen (with the risk of
+ * causing the vehicle to flip if it’s too strong).
+ */
+ public wheelFrictionSlip(i: number): number | null {
+ return this.raw.wheel_friction_slip(i);
+ }
+
+ /**
+ * Sets the parameter controlling how much traction the tire has.
+ *
+ * The larger the value, the more instantaneous braking will happen (with the risk of
+ * causing the vehicle to flip if it’s too strong).
+ */
+ public setWheelFrictionSlip(i: number, value: number) {
+ this.raw.set_wheel_friction_slip(i, value);
+ }
+
+ /**
+ * The multiplier of friction between a tire and the collider it’s on top of.
+ *
+ * The larger the value, the stronger side friction will be.
+ */
+ public wheelSideFrictionStiffness(i: number): number | null {
+ return this.raw.wheel_side_friction_stiffness(i);
+ }
+
+ /**
+ * The multiplier of friction between a tire and the collider it’s on top of.
+ *
+ * The larger the value, the stronger side friction will be.
+ */
+ public setWheelSideFrictionStiffness(i: number, value: number) {
+ this.raw.set_wheel_side_friction_stiffness(i, value);
+ }
+
+ /*
+ * Getters only.
+ */
+
+ /**
+ * The i-th wheel’s current rotation angle (radians) on its axle.
+ */
+ public wheelRotation(i: number): number | null {
+ return this.raw.wheel_rotation(i);
+ }
+
+ /**
+ * The forward impulses applied by the i-th wheel on the chassis.
+ */
+ public wheelForwardImpulse(i: number): number | null {
+ return this.raw.wheel_forward_impulse(i);
+ }
+
+ /**
+ * The side impulses applied by the i-th wheel on the chassis.
+ */
+ public wheelSideImpulse(i: number): number | null {
+ return this.raw.wheel_side_impulse(i);
+ }
+
+ /**
+ * The force applied by the i-th wheel suspension.
+ */
+ public wheelSuspensionForce(i: number): number | null {
+ return this.raw.wheel_suspension_force(i);
+ }
+
+ /**
+ * The (world-space) contact normal between the i-th wheel and the floor.
+ */
+ public wheelContactNormal(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.wheel_contact_normal_ws(i));
+ }
+
+ /**
+ * The (world-space) point hit by the wheel’s ray-cast for the i-th wheel.
+ */
+ public wheelContactPoint(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.wheel_contact_point_ws(i));
+ }
+
+ /**
+ * The suspension length for the i-th wheel.
+ */
+ public wheelSuspensionLength(i: number): number | null {
+ return this.raw.wheel_suspension_length(i);
+ }
+
+ /**
+ * The (world-space) starting point of the ray-cast for the i-th wheel.
+ */
+ public wheelHardPoint(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.wheel_hard_point_ws(i));
+ }
+
+ /**
+ * Is the i-th wheel in contact with the ground?
+ */
+ public wheelIsInContact(i: number): boolean {
+ return this.raw.wheel_is_in_contact(i);
+ }
+
+ /**
+ * The collider hit by the ray-cast for the i-th wheel.
+ */
+ public wheelGroundObject(i: number): Collider | null {
+ return this.colliders.get(this.raw.wheel_ground_object(i));
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/ccd_solver.ts b/thirdparty/rapier.js/src.ts/dynamics/ccd_solver.ts
new file mode 100644
index 00000000..7f794222
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/ccd_solver.ts
@@ -0,0 +1,25 @@
+import {RawCCDSolver} from "../raw";
+
+/**
+ * The CCD solver responsible for resolving Continuous Collision Detection.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `ccdSolver.free()`
+ * once you are done using it.
+ */
+export class CCDSolver {
+ raw: RawCCDSolver;
+
+ /**
+ * Release the WASM memory occupied by this narrow-phase.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ constructor(raw?: RawCCDSolver) {
+ this.raw = raw || new RawCCDSolver();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/coefficient_combine_rule.ts b/thirdparty/rapier.js/src.ts/dynamics/coefficient_combine_rule.ts
new file mode 100644
index 00000000..680b402a
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/coefficient_combine_rule.ts
@@ -0,0 +1,13 @@
+/**
+ * A rule applied to combine coefficients.
+ *
+ * Use this when configuring the `ColliderDesc` to specify
+ * how friction and restitution coefficient should be combined
+ * in a contact.
+ */
+export enum CoefficientCombineRule {
+ Average = 0,
+ Min = 1,
+ Multiply = 2,
+ Max = 3,
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/impulse_joint.ts b/thirdparty/rapier.js/src.ts/dynamics/impulse_joint.ts
new file mode 100644
index 00000000..0c637ddf
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/impulse_joint.ts
@@ -0,0 +1,681 @@
+import {Rotation, Vector, VectorOps, RotationOps} from "../math";
+import {
+ RawGenericJoint,
+ RawImpulseJointSet,
+ RawRigidBodySet,
+ RawJointAxis,
+ RawJointType,
+ RawMotorModel,
+} from "../raw";
+import {RigidBody, RigidBodyHandle} from "./rigid_body";
+import {RigidBodySet} from "./rigid_body_set";
+// #if DIM3
+import {Quaternion} from "../math";
+// #endif
+
+/**
+ * The integer identifier of a collider added to a `ColliderSet`.
+ */
+export type ImpulseJointHandle = number;
+
+/**
+ * An enum grouping all possible types of joints:
+ *
+ * - `Revolute`: A revolute joint that removes all degrees of freedom between the affected
+ * bodies except for the rotation along one axis.
+ * - `Fixed`: A fixed joint that removes all relative degrees of freedom between the affected bodies.
+ * - `Prismatic`: A prismatic joint that removes all degrees of freedom between the affected
+ * bodies except for the translation along one axis.
+ * - `Spherical`: (3D only) A spherical joint that removes all relative linear degrees of freedom between the affected bodies.
+ * - `Generic`: (3D only) A joint with customizable degrees of freedom, allowing any of the 6 axes to be locked.
+ */
+export enum JointType {
+ Revolute,
+ Fixed,
+ Prismatic,
+ Rope,
+ Spring,
+ // #if DIM3
+ Spherical,
+ Generic,
+ // #endif
+}
+
+export enum MotorModel {
+ AccelerationBased,
+ ForceBased,
+}
+
+/**
+ * An enum representing the possible joint axes of a generic joint.
+ * They can be ORed together, like:
+ * JointAxesMask.LinX || JointAxesMask.LinY
+ * to get a joint that is only free in the X and Y translational (positional) axes.
+ *
+ * Possible free axes are:
+ *
+ * - `X`: X translation axis
+ * - `Y`: Y translation axis
+ * - `Z`: Z translation axis
+ * - `AngX`: X angular rotation axis
+ * - `AngY`: Y angular rotations axis
+ * - `AngZ`: Z angular rotation axis
+ */
+export enum JointAxesMask {
+ LinX = 1 << 0,
+ LinY = 1 << 1,
+ LinZ = 1 << 2,
+ AngX = 1 << 3,
+ AngY = 1 << 4,
+ AngZ = 1 << 5,
+}
+
+export class ImpulseJoint {
+ protected rawSet: RawImpulseJointSet; // The ImpulseJoint won't need to free this.
+ protected bodySet: RigidBodySet; // The ImpulseJoint won’t need to free this.
+ handle: ImpulseJointHandle;
+
+ constructor(
+ rawSet: RawImpulseJointSet,
+ bodySet: RigidBodySet,
+ handle: ImpulseJointHandle,
+ ) {
+ this.rawSet = rawSet;
+ this.bodySet = bodySet;
+ this.handle = handle;
+ }
+
+ public static newTyped(
+ rawSet: RawImpulseJointSet,
+ bodySet: RigidBodySet,
+ handle: ImpulseJointHandle,
+ ): ImpulseJoint {
+ switch (rawSet.jointType(handle)) {
+ case RawJointType.Revolute:
+ return new RevoluteImpulseJoint(rawSet, bodySet, handle);
+ case RawJointType.Prismatic:
+ return new PrismaticImpulseJoint(rawSet, bodySet, handle);
+ case RawJointType.Fixed:
+ return new FixedImpulseJoint(rawSet, bodySet, handle);
+ case RawJointType.Spring:
+ return new SpringImpulseJoint(rawSet, bodySet, handle);
+ case RawJointType.Rope:
+ return new RopeImpulseJoint(rawSet, bodySet, handle);
+ // #if DIM3
+ case RawJointType.Spherical:
+ return new SphericalImpulseJoint(rawSet, bodySet, handle);
+ case RawJointType.Generic:
+ return new GenericImpulseJoint(rawSet, bodySet, handle);
+ // #endif
+ default:
+ return new ImpulseJoint(rawSet, bodySet, handle);
+ }
+ }
+
+ /** @internal */
+ public finalizeDeserialization(bodySet: RigidBodySet) {
+ this.bodySet = bodySet;
+ }
+
+ /**
+ * Checks if this joint is still valid (i.e. that it has
+ * not been deleted from the joint set yet).
+ */
+ public isValid(): boolean {
+ return this.rawSet.contains(this.handle);
+ }
+
+ /**
+ * The first rigid-body this joint it attached to.
+ */
+ public body1(): RigidBody {
+ return this.bodySet.get(this.rawSet.jointBodyHandle1(this.handle));
+ }
+
+ /**
+ * The second rigid-body this joint is attached to.
+ */
+ public body2(): RigidBody {
+ return this.bodySet.get(this.rawSet.jointBodyHandle2(this.handle));
+ }
+
+ /**
+ * The type of this joint given as a string.
+ */
+ public type(): JointType {
+ return this.rawSet.jointType(this.handle) as number as JointType;
+ }
+
+ // #if DIM3
+ /**
+ * The rotation quaternion that aligns this joint's first local axis to the `x` axis.
+ */
+ public frameX1(): Rotation {
+ return RotationOps.fromRaw(this.rawSet.jointFrameX1(this.handle));
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * The rotation matrix that aligns this joint's second local axis to the `x` axis.
+ */
+ public frameX2(): Rotation {
+ return RotationOps.fromRaw(this.rawSet.jointFrameX2(this.handle));
+ }
+
+ // #endif
+
+ /**
+ * The position of the first anchor of this joint.
+ *
+ * The first anchor gives the position of the application point on the
+ * local frame of the first rigid-body it is attached to.
+ */
+ public anchor1(): Vector {
+ return VectorOps.fromRaw(this.rawSet.jointAnchor1(this.handle));
+ }
+
+ /**
+ * The position of the second anchor of this joint.
+ *
+ * The second anchor gives the position of the application point on the
+ * local frame of the second rigid-body it is attached to.
+ */
+ public anchor2(): Vector {
+ return VectorOps.fromRaw(this.rawSet.jointAnchor2(this.handle));
+ }
+
+ /**
+ * Sets the position of the first anchor of this joint.
+ *
+ * The first anchor gives the position of the application point on the
+ * local frame of the first rigid-body it is attached to.
+ */
+ public setAnchor1(newPos: Vector) {
+ const rawPoint = VectorOps.intoRaw(newPos);
+ this.rawSet.jointSetAnchor1(this.handle, rawPoint);
+ rawPoint.free();
+ }
+
+ /**
+ * Sets the position of the second anchor of this joint.
+ *
+ * The second anchor gives the position of the application point on the
+ * local frame of the second rigid-body it is attached to.
+ */
+ public setAnchor2(newPos: Vector) {
+ const rawPoint = VectorOps.intoRaw(newPos);
+ this.rawSet.jointSetAnchor2(this.handle, rawPoint);
+ rawPoint.free();
+ }
+
+ /**
+ * Controls whether contacts are computed between colliders attached
+ * to the rigid-bodies linked by this joint.
+ */
+ public setContactsEnabled(enabled: boolean) {
+ this.rawSet.jointSetContactsEnabled(this.handle, enabled);
+ }
+
+ /**
+ * Indicates if contacts are enabled between colliders attached
+ * to the rigid-bodies linked by this joint.
+ */
+ public contactsEnabled(): boolean {
+ return this.rawSet.jointContactsEnabled(this.handle);
+ }
+}
+
+export class UnitImpulseJoint extends ImpulseJoint {
+ /**
+ * The axis left free by this joint.
+ */
+ protected rawAxis?(): RawJointAxis;
+
+ /**
+ * Are the limits enabled for this joint?
+ */
+ public limitsEnabled(): boolean {
+ return this.rawSet.jointLimitsEnabled(this.handle, this.rawAxis());
+ }
+
+ /**
+ * The min limit of this joint.
+ */
+ public limitsMin(): number {
+ return this.rawSet.jointLimitsMin(this.handle, this.rawAxis());
+ }
+
+ /**
+ * The max limit of this joint.
+ */
+ public limitsMax(): number {
+ return this.rawSet.jointLimitsMax(this.handle, this.rawAxis());
+ }
+
+ /**
+ * Sets the limits of this joint.
+ *
+ * @param min - The minimum bound of this joint’s free coordinate.
+ * @param max - The maximum bound of this joint’s free coordinate.
+ */
+ public setLimits(min: number, max: number) {
+ this.rawSet.jointSetLimits(this.handle, this.rawAxis(), min, max);
+ }
+
+ public configureMotorModel(model: MotorModel) {
+ this.rawSet.jointConfigureMotorModel(
+ this.handle,
+ this.rawAxis(),
+ model as number as RawMotorModel,
+ );
+ }
+
+ public configureMotorVelocity(targetVel: number, factor: number) {
+ this.rawSet.jointConfigureMotorVelocity(
+ this.handle,
+ this.rawAxis(),
+ targetVel,
+ factor,
+ );
+ }
+
+ public configureMotorPosition(
+ targetPos: number,
+ stiffness: number,
+ damping: number,
+ ) {
+ this.rawSet.jointConfigureMotorPosition(
+ this.handle,
+ this.rawAxis(),
+ targetPos,
+ stiffness,
+ damping,
+ );
+ }
+
+ public configureMotor(
+ targetPos: number,
+ targetVel: number,
+ stiffness: number,
+ damping: number,
+ ) {
+ this.rawSet.jointConfigureMotor(
+ this.handle,
+ this.rawAxis(),
+ targetPos,
+ targetVel,
+ stiffness,
+ damping,
+ );
+ }
+}
+
+export class FixedImpulseJoint extends ImpulseJoint {}
+
+export class RopeImpulseJoint extends ImpulseJoint {}
+
+export class SpringImpulseJoint extends ImpulseJoint {}
+
+export class PrismaticImpulseJoint extends UnitImpulseJoint {
+ public rawAxis(): RawJointAxis {
+ return RawJointAxis.LinX;
+ }
+}
+
+export class RevoluteImpulseJoint extends UnitImpulseJoint {
+ public rawAxis(): RawJointAxis {
+ return RawJointAxis.AngX;
+ }
+}
+
+// #if DIM3
+export class GenericImpulseJoint extends ImpulseJoint {}
+
+export class SphericalImpulseJoint extends ImpulseJoint {
+ /* Unsupported by this alpha release.
+ public configureMotorModel(model: MotorModel) {
+ this.rawSet.jointConfigureMotorModel(this.handle, model);
+ }
+
+ public configureMotorVelocity(targetVel: Vector, factor: number) {
+ this.rawSet.jointConfigureBallMotorVelocity(this.handle, targetVel.x, targetVel.y, targetVel.z, factor);
+ }
+
+ public configureMotorPosition(targetPos: Quaternion, stiffness: number, damping: number) {
+ this.rawSet.jointConfigureBallMotorPosition(this.handle, targetPos.w, targetPos.x, targetPos.y, targetPos.z, stiffness, damping);
+ }
+
+ public configureMotor(targetPos: Quaternion, targetVel: Vector, stiffness: number, damping: number) {
+ this.rawSet.jointConfigureBallMotor(this.handle,
+ targetPos.w, targetPos.x, targetPos.y, targetPos.z,
+ targetVel.x, targetVel.y, targetVel.z,
+ stiffness, damping);
+ }
+ */
+}
+// #endif
+
+export class JointData {
+ anchor1: Vector;
+ anchor2: Vector;
+ axis: Vector;
+ frame1: Rotation;
+ frame2: Rotation;
+ jointType: JointType;
+ limitsEnabled: boolean;
+ limits: Array;
+ axesMask: JointAxesMask;
+ stiffness: number;
+ damping: number;
+ length: number;
+
+ private constructor() {}
+
+ /**
+ * Creates a new joint descriptor that builds a Fixed joint.
+ *
+ * A fixed joint removes all the degrees of freedom between the affected bodies, ensuring their
+ * anchor and local frames coincide in world-space.
+ *
+ * @param anchor1 - Point where the joint is attached on the first rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param frame1 - The reference orientation of the joint wrt. the first rigid-body.
+ * @param anchor2 - Point where the joint is attached on the second rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param frame2 - The reference orientation of the joint wrt. the second rigid-body.
+ */
+ public static fixed(
+ anchor1: Vector,
+ frame1: Rotation,
+ anchor2: Vector,
+ frame2: Rotation,
+ ): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.frame1 = frame1;
+ res.frame2 = frame2;
+ res.jointType = JointType.Fixed;
+ return res;
+ }
+
+ public static spring(
+ rest_length: number,
+ stiffness: number,
+ damping: number,
+ anchor1: Vector,
+ anchor2: Vector,
+ ): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.length = rest_length;
+ res.stiffness = stiffness;
+ res.damping = damping;
+ res.jointType = JointType.Spring;
+ return res;
+ }
+
+ public static rope(
+ length: number,
+ anchor1: Vector,
+ anchor2: Vector,
+ ): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.length = length;
+ res.jointType = JointType.Rope;
+ return res;
+ }
+
+ // #if DIM2
+
+ /**
+ * Create a new joint descriptor that builds revolute joints.
+ *
+ * A revolute joint allows three relative rotational degrees of freedom
+ * by preventing any relative translation between the anchors of the
+ * two attached rigid-bodies.
+ *
+ * @param anchor1 - Point where the joint is attached on the first rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param anchor2 - Point where the joint is attached on the second rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ */
+ public static revolute(anchor1: Vector, anchor2: Vector): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.jointType = JointType.Revolute;
+ return res;
+ }
+
+ /**
+ * Creates a new joint descriptor that builds a Prismatic joint.
+ *
+ * A prismatic joint removes all the degrees of freedom between the
+ * affected bodies, except for the translation along one axis.
+ *
+ * @param anchor1 - Point where the joint is attached on the first rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param anchor2 - Point where the joint is attached on the second rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param axis - Axis of the joint, expressed in the local-space of the rigid-bodies it is attached to.
+ */
+ public static prismatic(
+ anchor1: Vector,
+ anchor2: Vector,
+ axis: Vector,
+ ): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.axis = axis;
+ res.jointType = JointType.Prismatic;
+ return res;
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Create a new joint descriptor that builds generic joints.
+ *
+ * A generic joint allows customizing its degrees of freedom
+ * by supplying a mask of the joint axes that should remain locked.
+ *
+ * @param anchor1 - Point where the joint is attached on the first rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param anchor2 - Point where the joint is attached on the second rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param axis - The X axis of the joint, expressed in the local-space of the rigid-bodies it is attached to.
+ * @param axesMask - Mask representing the locked axes of the joint. You can use logical OR to select these from
+ * the JointAxesMask enum. For example, passing (JointAxesMask.AngX || JointAxesMask.AngY) will
+ * create a joint locked in the X and Y rotational axes.
+ */
+ public static generic(
+ anchor1: Vector,
+ anchor2: Vector,
+ axis: Vector,
+ axesMask: JointAxesMask,
+ ): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.axis = axis;
+ res.axesMask = axesMask;
+ res.jointType = JointType.Generic;
+ return res;
+ }
+
+ /**
+ * Create a new joint descriptor that builds spherical joints.
+ *
+ * A spherical joint allows three relative rotational degrees of freedom
+ * by preventing any relative translation between the anchors of the
+ * two attached rigid-bodies.
+ *
+ * @param anchor1 - Point where the joint is attached on the first rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param anchor2 - Point where the joint is attached on the second rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ */
+ public static spherical(anchor1: Vector, anchor2: Vector): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.jointType = JointType.Spherical;
+ return res;
+ }
+
+ /**
+ * Creates a new joint descriptor that builds a Prismatic joint.
+ *
+ * A prismatic joint removes all the degrees of freedom between the
+ * affected bodies, except for the translation along one axis.
+ *
+ * @param anchor1 - Point where the joint is attached on the first rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param anchor2 - Point where the joint is attached on the second rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param axis - Axis of the joint, expressed in the local-space of the rigid-bodies it is attached to.
+ */
+ public static prismatic(
+ anchor1: Vector,
+ anchor2: Vector,
+ axis: Vector,
+ ): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.axis = axis;
+ res.jointType = JointType.Prismatic;
+ return res;
+ }
+
+ /**
+ * Create a new joint descriptor that builds Revolute joints.
+ *
+ * A revolute joint removes all degrees of freedom between the affected
+ * bodies except for the rotation along one axis.
+ *
+ * @param anchor1 - Point where the joint is attached on the first rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param anchor2 - Point where the joint is attached on the second rigid-body affected by this joint. Expressed in the
+ * local-space of the rigid-body.
+ * @param axis - Axis of the joint, expressed in the local-space of the rigid-bodies it is attached to.
+ */
+ public static revolute(
+ anchor1: Vector,
+ anchor2: Vector,
+ axis: Vector,
+ ): JointData {
+ let res = new JointData();
+ res.anchor1 = anchor1;
+ res.anchor2 = anchor2;
+ res.axis = axis;
+ res.jointType = JointType.Revolute;
+ return res;
+ }
+ // #endif
+
+ public intoRaw(): RawGenericJoint {
+ let rawA1 = VectorOps.intoRaw(this.anchor1);
+ let rawA2 = VectorOps.intoRaw(this.anchor2);
+ let rawAx;
+ let result;
+ let limitsEnabled = false;
+ let limitsMin = 0.0;
+ let limitsMax = 0.0;
+
+ switch (this.jointType) {
+ case JointType.Fixed:
+ let rawFra1 = RotationOps.intoRaw(this.frame1);
+ let rawFra2 = RotationOps.intoRaw(this.frame2);
+ result = RawGenericJoint.fixed(rawA1, rawFra1, rawA2, rawFra2);
+ rawFra1.free();
+ rawFra2.free();
+ break;
+ case JointType.Spring:
+ result = RawGenericJoint.spring(
+ this.length,
+ this.stiffness,
+ this.damping,
+ rawA1,
+ rawA2,
+ );
+ break;
+ case JointType.Rope:
+ result = RawGenericJoint.rope(this.length, rawA1, rawA2);
+ break;
+ case JointType.Prismatic:
+ rawAx = VectorOps.intoRaw(this.axis);
+
+ if (!!this.limitsEnabled) {
+ limitsEnabled = true;
+ limitsMin = this.limits[0];
+ limitsMax = this.limits[1];
+ }
+
+ // #if DIM2
+ result = RawGenericJoint.prismatic(
+ rawA1,
+ rawA2,
+ rawAx,
+ limitsEnabled,
+ limitsMin,
+ limitsMax,
+ );
+ // #endif
+
+ // #if DIM3
+ result = RawGenericJoint.prismatic(
+ rawA1,
+ rawA2,
+ rawAx,
+ limitsEnabled,
+ limitsMin,
+ limitsMax,
+ );
+ // #endif
+
+ rawAx.free();
+ break;
+ // #if DIM2
+ case JointType.Revolute:
+ result = RawGenericJoint.revolute(rawA1, rawA2);
+ break;
+ // #endif
+ // #if DIM3
+ case JointType.Generic:
+ rawAx = VectorOps.intoRaw(this.axis);
+ // implicit type cast: axesMask is a JointAxesMask bitflag enum,
+ // we're treating it as a u8 on the Rust side
+ let rawAxesMask = this.axesMask;
+ result = RawGenericJoint.generic(
+ rawA1,
+ rawA2,
+ rawAx,
+ rawAxesMask,
+ );
+ break;
+ case JointType.Spherical:
+ result = RawGenericJoint.spherical(rawA1, rawA2);
+ break;
+ case JointType.Revolute:
+ rawAx = VectorOps.intoRaw(this.axis);
+ result = RawGenericJoint.revolute(rawA1, rawA2, rawAx);
+ rawAx.free();
+ break;
+ // #endif
+ }
+
+ rawA1.free();
+ rawA2.free();
+
+ return result;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/impulse_joint_set.ts b/thirdparty/rapier.js/src.ts/dynamics/impulse_joint_set.ts
new file mode 100644
index 00000000..2d382514
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/impulse_joint_set.ts
@@ -0,0 +1,165 @@
+import {RawImpulseJointSet} from "../raw";
+import {Coarena} from "../coarena";
+import {RigidBodySet} from "./rigid_body_set";
+import {
+ RevoluteImpulseJoint,
+ FixedImpulseJoint,
+ ImpulseJoint,
+ ImpulseJointHandle,
+ JointData,
+ JointType,
+ PrismaticImpulseJoint,
+ // #if DIM3
+ SphericalImpulseJoint,
+ // #endif
+} from "./impulse_joint";
+import {IslandManager} from "./island_manager";
+import {RigidBodyHandle} from "./rigid_body";
+import {Collider, ColliderHandle} from "../geometry";
+
+/**
+ * A set of joints.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `jointSet.free()`
+ * once you are done using it (and all the joints it created).
+ */
+export class ImpulseJointSet {
+ raw: RawImpulseJointSet;
+ private map: Coarena;
+
+ /**
+ * Release the WASM memory occupied by this joint set.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+
+ if (!!this.map) {
+ this.map.clear();
+ }
+ this.map = undefined;
+ }
+
+ constructor(raw?: RawImpulseJointSet) {
+ this.raw = raw || new RawImpulseJointSet();
+ this.map = new Coarena();
+ // Initialize the map with the existing elements, if any.
+ if (raw) {
+ raw.forEachJointHandle((handle: ImpulseJointHandle) => {
+ this.map.set(handle, ImpulseJoint.newTyped(raw, null, handle));
+ });
+ }
+ }
+
+ /** @internal */
+ public finalizeDeserialization(bodies: RigidBodySet) {
+ this.map.forEach((joint) => joint.finalizeDeserialization(bodies));
+ }
+
+ /**
+ * Creates a new joint and return its integer handle.
+ *
+ * @param bodies - The set of rigid-bodies containing the bodies the joint is attached to.
+ * @param desc - The joint's parameters.
+ * @param parent1 - The handle of the first rigid-body this joint is attached to.
+ * @param parent2 - The handle of the second rigid-body this joint is attached to.
+ * @param wakeUp - Should the attached rigid-bodies be awakened?
+ */
+ public createJoint(
+ bodies: RigidBodySet,
+ desc: JointData,
+ parent1: RigidBodyHandle,
+ parent2: RigidBodyHandle,
+ wakeUp: boolean,
+ ): ImpulseJoint {
+ const rawParams = desc.intoRaw();
+ const handle = this.raw.createJoint(
+ rawParams,
+ parent1,
+ parent2,
+ wakeUp,
+ );
+ rawParams.free();
+ let joint = ImpulseJoint.newTyped(this.raw, bodies, handle);
+ this.map.set(handle, joint);
+ return joint;
+ }
+
+ /**
+ * Remove a joint from this set.
+ *
+ * @param handle - The integer handle of the joint.
+ * @param wakeUp - If `true`, the rigid-bodies attached by the removed joint will be woken-up automatically.
+ */
+ public remove(handle: ImpulseJointHandle, wakeUp: boolean) {
+ this.raw.remove(handle, wakeUp);
+ this.unmap(handle);
+ }
+
+ /**
+ * Calls the given closure with the integer handle of each impulse joint attached to this rigid-body.
+ *
+ * @param f - The closure called with the integer handle of each impulse joint attached to the rigid-body.
+ */
+ public forEachJointHandleAttachedToRigidBody(
+ handle: RigidBodyHandle,
+ f: (handle: ImpulseJointHandle) => void,
+ ) {
+ this.raw.forEachJointAttachedToRigidBody(handle, f);
+ }
+
+ /**
+ * Internal function, do not call directly.
+ * @param handle
+ */
+ public unmap(handle: ImpulseJointHandle) {
+ this.map.delete(handle);
+ }
+
+ /**
+ * The number of joints on this set.
+ */
+ public len(): number {
+ return this.map.len();
+ }
+
+ /**
+ * Does this set contain a joint with the given handle?
+ *
+ * @param handle - The joint handle to check.
+ */
+ public contains(handle: ImpulseJointHandle): boolean {
+ return this.get(handle) != null;
+ }
+
+ /**
+ * Gets the joint with the given handle.
+ *
+ * Returns `null` if no joint with the specified handle exists.
+ *
+ * @param handle - The integer handle of the joint to retrieve.
+ */
+ public get(handle: ImpulseJointHandle): ImpulseJoint | null {
+ return this.map.get(handle);
+ }
+
+ /**
+ * Applies the given closure to each joint contained by this set.
+ *
+ * @param f - The closure to apply.
+ */
+ public forEach(f: (joint: ImpulseJoint) => void) {
+ this.map.forEach(f);
+ }
+
+ /**
+ * Gets all joints in the list.
+ *
+ * @returns joint list.
+ */
+ public getAll(): ImpulseJoint[] {
+ return this.map.getAll();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/index.ts b/thirdparty/rapier.js/src.ts/dynamics/index.ts
new file mode 100644
index 00000000..4141658e
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/index.ts
@@ -0,0 +1,10 @@
+export * from "./rigid_body";
+export * from "./rigid_body_set";
+export * from "./integration_parameters";
+export * from "./impulse_joint";
+export * from "./impulse_joint_set";
+export * from "./multibody_joint";
+export * from "./multibody_joint_set";
+export * from "./coefficient_combine_rule";
+export * from "./ccd_solver";
+export * from "./island_manager";
diff --git a/thirdparty/rapier.js/src.ts/dynamics/integration_parameters.ts b/thirdparty/rapier.js/src.ts/dynamics/integration_parameters.ts
new file mode 100644
index 00000000..b7253e21
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/integration_parameters.ts
@@ -0,0 +1,126 @@
+import {RawIntegrationParameters} from "../raw";
+
+export class IntegrationParameters {
+ raw: RawIntegrationParameters;
+
+ constructor(raw?: RawIntegrationParameters) {
+ this.raw = raw || new RawIntegrationParameters();
+ }
+
+ /**
+ * Free the WASM memory used by these integration parameters.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ /**
+ * The timestep length (default: `1.0 / 60.0`)
+ */
+ get dt(): number {
+ return this.raw.dt;
+ }
+
+ /**
+ * The Error Reduction Parameter in `[0, 1]` is the proportion of
+ * the positional error to be corrected at each time step (default: `0.2`).
+ */
+ get contact_erp(): number {
+ return this.raw.contact_erp;
+ }
+
+ get lengthUnit(): number {
+ return this.raw.lengthUnit;
+ }
+
+ /**
+ * Normalized amount of penetration the engine won’t attempt to correct (default: `0.001m`).
+ *
+ * This threshold considered by the physics engine is this value multiplied by the `lengthUnit`.
+ */
+ get normalizedAllowedLinearError(): number {
+ return this.raw.normalizedAllowedLinearError;
+ }
+
+ /**
+ * The maximal normalized distance separating two objects that will generate predictive contacts (default: `0.002`).
+ *
+ * This threshold considered by the physics engine is this value multiplied by the `lengthUnit`.
+ */
+ get normalizedPredictionDistance(): number {
+ return this.raw.normalizedPredictionDistance;
+ }
+
+ /**
+ * The number of solver iterations run by the constraints solver for calculating forces (default: `4`).
+ */
+ get numSolverIterations(): number {
+ return this.raw.numSolverIterations;
+ }
+
+ /**
+ * Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
+ */
+ get numInternalPgsIterations(): number {
+ return this.raw.numInternalPgsIterations;
+ }
+
+ /**
+ * Minimum number of dynamic bodies in each active island (default: `128`).
+ */
+ get minIslandSize(): number {
+ return this.raw.minIslandSize;
+ }
+
+ /**
+ * Maximum number of substeps performed by the solver (default: `1`).
+ */
+ get maxCcdSubsteps(): number {
+ return this.raw.maxCcdSubsteps;
+ }
+
+ set dt(value: number) {
+ this.raw.dt = value;
+ }
+
+ set contact_natural_frequency(value: number) {
+ this.raw.contact_natural_frequency = value;
+ }
+
+ set lengthUnit(value: number) {
+ this.raw.lengthUnit = value;
+ }
+
+ set normalizedAllowedLinearError(value: number) {
+ this.raw.normalizedAllowedLinearError = value;
+ }
+
+ set normalizedPredictionDistance(value: number) {
+ this.raw.normalizedPredictionDistance = value;
+ }
+
+ /**
+ * Sets the number of solver iterations run by the constraints solver for calculating forces (default: `4`).
+ */
+ set numSolverIterations(value: number) {
+ this.raw.numSolverIterations = value;
+ }
+
+ /**
+ * Sets the number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
+ */
+ set numInternalPgsIterations(value: number) {
+ this.raw.numInternalPgsIterations = value;
+ }
+
+ set minIslandSize(value: number) {
+ this.raw.minIslandSize = value;
+ }
+
+ set maxCcdSubsteps(value: number) {
+ this.raw.maxCcdSubsteps = value;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/island_manager.ts b/thirdparty/rapier.js/src.ts/dynamics/island_manager.ts
new file mode 100644
index 00000000..65ed21dc
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/island_manager.ts
@@ -0,0 +1,37 @@
+import {RawIslandManager} from "../raw";
+import {RigidBodyHandle} from "./rigid_body";
+
+/**
+ * The CCD solver responsible for resolving Continuous Collision Detection.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `ccdSolver.free()`
+ * once you are done using it.
+ */
+export class IslandManager {
+ raw: RawIslandManager;
+
+ /**
+ * Release the WASM memory occupied by this narrow-phase.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ constructor(raw?: RawIslandManager) {
+ this.raw = raw || new RawIslandManager();
+ }
+
+ /**
+ * Applies the given closure to the handle of each active rigid-bodies contained by this set.
+ *
+ * A rigid-body is active if it is not sleeping, i.e., if it moved recently.
+ *
+ * @param f - The closure to apply.
+ */
+ public forEachActiveRigidBodyHandle(f: (handle: RigidBodyHandle) => void) {
+ this.raw.forEachActiveRigidBodyHandle(f);
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/multibody_joint.ts b/thirdparty/rapier.js/src.ts/dynamics/multibody_joint.ts
new file mode 100644
index 00000000..5608aec7
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/multibody_joint.ts
@@ -0,0 +1,222 @@
+import {
+ RawImpulseJointSet,
+ RawJointAxis,
+ RawJointType,
+ RawMultibodyJointSet,
+} from "../raw";
+import {
+ FixedImpulseJoint,
+ ImpulseJointHandle,
+ JointType,
+ MotorModel,
+ PrismaticImpulseJoint,
+ RevoluteImpulseJoint,
+} from "./impulse_joint";
+
+// #if DIM3
+import {Quaternion} from "../math";
+import {SphericalImpulseJoint} from "./impulse_joint";
+// #endif
+
+/**
+ * The integer identifier of a collider added to a `ColliderSet`.
+ */
+export type MultibodyJointHandle = number;
+
+export class MultibodyJoint {
+ protected rawSet: RawMultibodyJointSet; // The MultibodyJoint won't need to free this.
+ handle: MultibodyJointHandle;
+
+ constructor(rawSet: RawMultibodyJointSet, handle: MultibodyJointHandle) {
+ this.rawSet = rawSet;
+ this.handle = handle;
+ }
+
+ public static newTyped(
+ rawSet: RawMultibodyJointSet,
+ handle: MultibodyJointHandle,
+ ): MultibodyJoint {
+ switch (rawSet.jointType(handle)) {
+ case RawJointType.Revolute:
+ return new RevoluteMultibodyJoint(rawSet, handle);
+ case RawJointType.Prismatic:
+ return new PrismaticMultibodyJoint(rawSet, handle);
+ case RawJointType.Fixed:
+ return new FixedMultibodyJoint(rawSet, handle);
+ // #if DIM3
+ case RawJointType.Spherical:
+ return new SphericalMultibodyJoint(rawSet, handle);
+ // #endif
+ default:
+ return new MultibodyJoint(rawSet, handle);
+ }
+ }
+
+ /**
+ * Checks if this joint is still valid (i.e. that it has
+ * not been deleted from the joint set yet).
+ */
+ public isValid(): boolean {
+ return this.rawSet.contains(this.handle);
+ }
+
+ // /**
+ // * The unique integer identifier of the first rigid-body this joint it attached to.
+ // */
+ // public bodyHandle1(): RigidBodyHandle {
+ // return this.rawSet.jointBodyHandle1(this.handle);
+ // }
+ //
+ // /**
+ // * The unique integer identifier of the second rigid-body this joint is attached to.
+ // */
+ // public bodyHandle2(): RigidBodyHandle {
+ // return this.rawSet.jointBodyHandle2(this.handle);
+ // }
+ //
+ // /**
+ // * The type of this joint given as a string.
+ // */
+ // public type(): JointType {
+ // return this.rawSet.jointType(this.handle);
+ // }
+ //
+ // // #if DIM3
+ // /**
+ // * The rotation quaternion that aligns this joint's first local axis to the `x` axis.
+ // */
+ // public frameX1(): Rotation {
+ // return RotationOps.fromRaw(this.rawSet.jointFrameX1(this.handle));
+ // }
+ //
+ // // #endif
+ //
+ // // #if DIM3
+ // /**
+ // * The rotation matrix that aligns this joint's second local axis to the `x` axis.
+ // */
+ // public frameX2(): Rotation {
+ // return RotationOps.fromRaw(this.rawSet.jointFrameX2(this.handle));
+ // }
+ //
+ // // #endif
+ //
+ // /**
+ // * The position of the first anchor of this joint.
+ // *
+ // * The first anchor gives the position of the points application point on the
+ // * local frame of the first rigid-body it is attached to.
+ // */
+ // public anchor1(): Vector {
+ // return VectorOps.fromRaw(this.rawSet.jointAnchor1(this.handle));
+ // }
+ //
+ // /**
+ // * The position of the second anchor of this joint.
+ // *
+ // * The second anchor gives the position of the points application point on the
+ // * local frame of the second rigid-body it is attached to.
+ // */
+ // public anchor2(): Vector {
+ // return VectorOps.fromRaw(this.rawSet.jointAnchor2(this.handle));
+ // }
+
+ /**
+ * Controls whether contacts are computed between colliders attached
+ * to the rigid-bodies linked by this joint.
+ */
+ public setContactsEnabled(enabled: boolean) {
+ this.rawSet.jointSetContactsEnabled(this.handle, enabled);
+ }
+
+ /**
+ * Indicates if contacts are enabled between colliders attached
+ * to the rigid-bodies linked by this joint.
+ */
+ public contactsEnabled(): boolean {
+ return this.rawSet.jointContactsEnabled(this.handle);
+ }
+}
+
+export class UnitMultibodyJoint extends MultibodyJoint {
+ /**
+ * The axis left free by this joint.
+ */
+ protected rawAxis?(): RawJointAxis;
+
+ // /**
+ // * Are the limits enabled for this joint?
+ // */
+ // public limitsEnabled(): boolean {
+ // return this.rawSet.jointLimitsEnabled(this.handle, this.rawAxis());
+ // }
+ //
+ // /**
+ // * The min limit of this joint.
+ // */
+ // public limitsMin(): number {
+ // return this.rawSet.jointLimitsMin(this.handle, this.rawAxis());
+ // }
+ //
+ // /**
+ // * The max limit of this joint.
+ // */
+ // public limitsMax(): number {
+ // return this.rawSet.jointLimitsMax(this.handle, this.rawAxis());
+ // }
+ //
+ // public configureMotorModel(model: MotorModel) {
+ // this.rawSet.jointConfigureMotorModel(this.handle, this.rawAxis(), model);
+ // }
+ //
+ // public configureMotorVelocity(targetVel: number, factor: number) {
+ // this.rawSet.jointConfigureMotorVelocity(this.handle, this.rawAxis(), targetVel, factor);
+ // }
+ //
+ // public configureMotorPosition(targetPos: number, stiffness: number, damping: number) {
+ // this.rawSet.jointConfigureMotorPosition(this.handle, this.rawAxis(), targetPos, stiffness, damping);
+ // }
+ //
+ // public configureMotor(targetPos: number, targetVel: number, stiffness: number, damping: number) {
+ // this.rawSet.jointConfigureMotor(this.handle, this.rawAxis(), targetPos, targetVel, stiffness, damping);
+ // }
+}
+
+export class FixedMultibodyJoint extends MultibodyJoint {}
+
+export class PrismaticMultibodyJoint extends UnitMultibodyJoint {
+ public rawAxis(): RawJointAxis {
+ return RawJointAxis.LinX;
+ }
+}
+
+export class RevoluteMultibodyJoint extends UnitMultibodyJoint {
+ public rawAxis(): RawJointAxis {
+ return RawJointAxis.AngX;
+ }
+}
+
+// #if DIM3
+export class SphericalMultibodyJoint extends MultibodyJoint {
+ /* Unsupported by this alpha release.
+ public configureMotorModel(model: MotorModel) {
+ this.rawSet.jointConfigureMotorModel(this.handle, model);
+ }
+
+ public configureMotorVelocity(targetVel: Vector, factor: number) {
+ this.rawSet.jointConfigureBallMotorVelocity(this.handle, targetVel.x, targetVel.y, targetVel.z, factor);
+ }
+
+ public configureMotorPosition(targetPos: Quaternion, stiffness: number, damping: number) {
+ this.rawSet.jointConfigureBallMotorPosition(this.handle, targetPos.w, targetPos.x, targetPos.y, targetPos.z, stiffness, damping);
+ }
+
+ public configureMotor(targetPos: Quaternion, targetVel: Vector, stiffness: number, damping: number) {
+ this.rawSet.jointConfigureBallMotor(this.handle,
+ targetPos.w, targetPos.x, targetPos.y, targetPos.z,
+ targetVel.x, targetVel.y, targetVel.z,
+ stiffness, damping);
+ }
+ */
+}
+// #endif
diff --git a/thirdparty/rapier.js/src.ts/dynamics/multibody_joint_set.ts b/thirdparty/rapier.js/src.ts/dynamics/multibody_joint_set.ts
new file mode 100644
index 00000000..2fbb3403
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/multibody_joint_set.ts
@@ -0,0 +1,157 @@
+import {RawMultibodyJointSet} from "../raw";
+import {Coarena} from "../coarena";
+import {RigidBodySet} from "./rigid_body_set";
+import {
+ MultibodyJoint,
+ MultibodyJointHandle,
+ RevoluteMultibodyJoint,
+ FixedMultibodyJoint,
+ PrismaticMultibodyJoint,
+ // #if DIM3
+ SphericalMultibodyJoint,
+ // #endif
+} from "./multibody_joint";
+import {ImpulseJointHandle, JointData, JointType} from "./impulse_joint";
+import {IslandManager} from "./island_manager";
+import {ColliderHandle} from "../geometry";
+import {RigidBodyHandle} from "./rigid_body";
+
+/**
+ * A set of joints.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `jointSet.free()`
+ * once you are done using it (and all the joints it created).
+ */
+export class MultibodyJointSet {
+ raw: RawMultibodyJointSet;
+ private map: Coarena;
+
+ /**
+ * Release the WASM memory occupied by this joint set.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+
+ if (!!this.map) {
+ this.map.clear();
+ }
+ this.map = undefined;
+ }
+
+ constructor(raw?: RawMultibodyJointSet) {
+ this.raw = raw || new RawMultibodyJointSet();
+ this.map = new Coarena();
+ // Initialize the map with the existing elements, if any.
+ if (raw) {
+ raw.forEachJointHandle((handle: MultibodyJointHandle) => {
+ this.map.set(handle, MultibodyJoint.newTyped(this.raw, handle));
+ });
+ }
+ }
+
+ /**
+ * Creates a new joint and return its integer handle.
+ *
+ * @param desc - The joint's parameters.
+ * @param parent1 - The handle of the first rigid-body this joint is attached to.
+ * @param parent2 - The handle of the second rigid-body this joint is attached to.
+ * @param wakeUp - Should the attached rigid-bodies be awakened?
+ */
+ public createJoint(
+ desc: JointData,
+ parent1: RigidBodyHandle,
+ parent2: RigidBodyHandle,
+ wakeUp: boolean,
+ ): MultibodyJoint {
+ const rawParams = desc.intoRaw();
+ const handle = this.raw.createJoint(
+ rawParams,
+ parent1,
+ parent2,
+ wakeUp,
+ );
+ rawParams.free();
+ let joint = MultibodyJoint.newTyped(this.raw, handle);
+ this.map.set(handle, joint);
+ return joint;
+ }
+
+ /**
+ * Remove a joint from this set.
+ *
+ * @param handle - The integer handle of the joint.
+ * @param wake_up - If `true`, the rigid-bodies attached by the removed joint will be woken-up automatically.
+ */
+ public remove(handle: MultibodyJointHandle, wake_up: boolean) {
+ this.raw.remove(handle, wake_up);
+ this.map.delete(handle);
+ }
+
+ /**
+ * Internal function, do not call directly.
+ * @param handle
+ */
+ public unmap(handle: MultibodyJointHandle) {
+ this.map.delete(handle);
+ }
+
+ /**
+ * The number of joints on this set.
+ */
+ public len(): number {
+ return this.map.len();
+ }
+
+ /**
+ * Does this set contain a joint with the given handle?
+ *
+ * @param handle - The joint handle to check.
+ */
+ public contains(handle: MultibodyJointHandle): boolean {
+ return this.get(handle) != null;
+ }
+
+ /**
+ * Gets the joint with the given handle.
+ *
+ * Returns `null` if no joint with the specified handle exists.
+ *
+ * @param handle - The integer handle of the joint to retrieve.
+ */
+ public get(handle: MultibodyJointHandle): MultibodyJoint | null {
+ return this.map.get(handle);
+ }
+
+ /**
+ * Applies the given closure to each joint contained by this set.
+ *
+ * @param f - The closure to apply.
+ */
+ public forEach(f: (joint: MultibodyJoint) => void) {
+ this.map.forEach(f);
+ }
+
+ /**
+ * Calls the given closure with the integer handle of each multibody joint attached to this rigid-body.
+ *
+ * @param f - The closure called with the integer handle of each multibody joint attached to the rigid-body.
+ */
+ public forEachJointHandleAttachedToRigidBody(
+ handle: RigidBodyHandle,
+ f: (handle: MultibodyJointHandle) => void,
+ ) {
+ this.raw.forEachJointAttachedToRigidBody(handle, f);
+ }
+
+ /**
+ * Gets all joints in the list.
+ *
+ * @returns joint list.
+ */
+ public getAll(): MultibodyJoint[] {
+ return this.map.getAll();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/rigid_body.ts b/thirdparty/rapier.js/src.ts/dynamics/rigid_body.ts
new file mode 100644
index 00000000..8f52dbe5
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/rigid_body.ts
@@ -0,0 +1,1691 @@
+import {RawRigidBodySet, RawRigidBodyType} from "../raw";
+import {Rotation, RotationOps, Vector, VectorOps} from "../math";
+// #if DIM3
+import {SdpMatrix3, SdpMatrix3Ops} from "../math";
+// #endif
+import {Collider, ColliderSet} from "../geometry";
+
+/**
+ * The integer identifier of a collider added to a `ColliderSet`.
+ */
+export type RigidBodyHandle = number;
+
+/**
+ * The simulation status of a rigid-body.
+ */
+// TODO: rename this to RigidBodyType
+export enum RigidBodyType {
+ /**
+ * A `RigidBodyType::Dynamic` body can be affected by all external forces.
+ */
+ Dynamic = 0,
+ /**
+ * A `RigidBodyType::Fixed` body cannot be affected by external forces.
+ */
+ Fixed,
+ /**
+ * A `RigidBodyType::KinematicPositionBased` body cannot be affected by any external forces but can be controlled
+ * by the user at the position level while keeping realistic one-way interaction with dynamic bodies.
+ *
+ * One-way interaction means that a kinematic body can push a dynamic body, but a kinematic body
+ * cannot be pushed by anything. In other words, the trajectory of a kinematic body can only be
+ * modified by the user and is independent from any contact or joint it is involved in.
+ */
+ KinematicPositionBased,
+ /**
+ * A `RigidBodyType::KinematicVelocityBased` body cannot be affected by any external forces but can be controlled
+ * by the user at the velocity level while keeping realistic one-way interaction with dynamic bodies.
+ *
+ * One-way interaction means that a kinematic body can push a dynamic body, but a kinematic body
+ * cannot be pushed by anything. In other words, the trajectory of a kinematic body can only be
+ * modified by the user and is independent from any contact or joint it is involved in.
+ */
+ KinematicVelocityBased,
+}
+
+/**
+ * A rigid-body.
+ */
+export class RigidBody {
+ private rawSet: RawRigidBodySet; // The RigidBody won't need to free this.
+ private colliderSet: ColliderSet;
+ readonly handle: RigidBodyHandle;
+
+ /**
+ * An arbitrary user-defined object associated with this rigid-body.
+ */
+ public userData?: unknown;
+
+ constructor(
+ rawSet: RawRigidBodySet,
+ colliderSet: ColliderSet,
+ handle: RigidBodyHandle,
+ ) {
+ this.rawSet = rawSet;
+ this.colliderSet = colliderSet;
+ this.handle = handle;
+ }
+
+ /** @internal */
+ public finalizeDeserialization(colliderSet: ColliderSet) {
+ this.colliderSet = colliderSet;
+ }
+
+ /**
+ * Checks if this rigid-body is still valid (i.e. that it has
+ * not been deleted from the rigid-body set yet.
+ */
+ public isValid(): boolean {
+ return this.rawSet.contains(this.handle);
+ }
+
+ /**
+ * Locks or unlocks the ability of this rigid-body to translate.
+ *
+ * @param locked - If `true`, this rigid-body will no longer translate due to forces and impulses.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ */
+ public lockTranslations(locked: boolean, wakeUp: boolean) {
+ return this.rawSet.rbLockTranslations(this.handle, locked, wakeUp);
+ }
+
+ /**
+ * Locks or unlocks the ability of this rigid-body to rotate.
+ *
+ * @param locked - If `true`, this rigid-body will no longer rotate due to torques and impulses.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ */
+ public lockRotations(locked: boolean, wakeUp: boolean) {
+ return this.rawSet.rbLockRotations(this.handle, locked, wakeUp);
+ }
+
+ // #if DIM2
+ /**
+ * Locks or unlocks the ability of this rigid-body to translation along individual coordinate axes.
+ *
+ * @param enableX - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the X coordinate axis.
+ * @param enableY - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the Y coordinate axis.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ */
+ public setEnabledTranslations(
+ enableX: boolean,
+ enableY: boolean,
+ wakeUp: boolean,
+ ) {
+ return this.rawSet.rbSetEnabledTranslations(
+ this.handle,
+ enableX,
+ enableY,
+ wakeUp,
+ );
+ }
+
+ /**
+ * Locks or unlocks the ability of this rigid-body to translation along individual coordinate axes.
+ *
+ * @param enableX - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the X coordinate axis.
+ * @param enableY - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the Y coordinate axis.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ * @deprecated use `this.setEnabledTranslations` with the same arguments instead.
+ */
+ public restrictTranslations(
+ enableX: boolean,
+ enableY: boolean,
+ wakeUp: boolean,
+ ) {
+ this.setEnabledTranslations(enableX, enableX, wakeUp);
+ }
+
+ // #endif
+ // #if DIM3
+ /**
+ * Locks or unlocks the ability of this rigid-body to translate along individual coordinate axes.
+ *
+ * @param enableX - If `false`, this rigid-body will no longer translate due to torques and impulses, along the X coordinate axis.
+ * @param enableY - If `false`, this rigid-body will no longer translate due to torques and impulses, along the Y coordinate axis.
+ * @param enableZ - If `false`, this rigid-body will no longer translate due to torques and impulses, along the Z coordinate axis.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ */
+ public setEnabledTranslations(
+ enableX: boolean,
+ enableY: boolean,
+ enableZ: boolean,
+ wakeUp: boolean,
+ ) {
+ return this.rawSet.rbSetEnabledTranslations(
+ this.handle,
+ enableX,
+ enableY,
+ enableZ,
+ wakeUp,
+ );
+ }
+
+ /**
+ * Locks or unlocks the ability of this rigid-body to translate along individual coordinate axes.
+ *
+ * @param enableX - If `false`, this rigid-body will no longer translate due to torques and impulses, along the X coordinate axis.
+ * @param enableY - If `false`, this rigid-body will no longer translate due to torques and impulses, along the Y coordinate axis.
+ * @param enableZ - If `false`, this rigid-body will no longer translate due to torques and impulses, along the Z coordinate axis.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ * @deprecated use `this.setEnabledTranslations` with the same arguments instead.
+ */
+ public restrictTranslations(
+ enableX: boolean,
+ enableY: boolean,
+ enableZ: boolean,
+ wakeUp: boolean,
+ ) {
+ this.setEnabledTranslations(enableX, enableY, enableZ, wakeUp);
+ }
+
+ /**
+ * Locks or unlocks the ability of this rigid-body to rotate along individual coordinate axes.
+ *
+ * @param enableX - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the X coordinate axis.
+ * @param enableY - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the Y coordinate axis.
+ * @param enableZ - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the Z coordinate axis.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ */
+ public setEnabledRotations(
+ enableX: boolean,
+ enableY: boolean,
+ enableZ: boolean,
+ wakeUp: boolean,
+ ) {
+ return this.rawSet.rbSetEnabledRotations(
+ this.handle,
+ enableX,
+ enableY,
+ enableZ,
+ wakeUp,
+ );
+ }
+
+ /**
+ * Locks or unlocks the ability of this rigid-body to rotate along individual coordinate axes.
+ *
+ * @param enableX - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the X coordinate axis.
+ * @param enableY - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the Y coordinate axis.
+ * @param enableZ - If `false`, this rigid-body will no longer rotate due to torques and impulses, along the Z coordinate axis.
+ * @param wakeUp - If `true`, this rigid-body will be automatically awaken if it is currently asleep.
+ * @deprecated use `this.setEnabledRotations` with the same arguments instead.
+ */
+ public restrictRotations(
+ enableX: boolean,
+ enableY: boolean,
+ enableZ: boolean,
+ wakeUp: boolean,
+ ) {
+ this.setEnabledRotations(enableX, enableY, enableZ, wakeUp);
+ }
+
+ // #endif
+
+ /**
+ * The dominance group, in [-127, +127] this rigid-body is part of.
+ */
+ public dominanceGroup(): number {
+ return this.rawSet.rbDominanceGroup(this.handle);
+ }
+
+ /**
+ * Sets the dominance group of this rigid-body.
+ *
+ * @param group - The dominance group of this rigid-body. Must be a signed integer in the range [-127, +127].
+ */
+ public setDominanceGroup(group: number) {
+ this.rawSet.rbSetDominanceGroup(this.handle, group);
+ }
+
+ /**
+ * The number of additional solver iterations that will be run for this
+ * rigid-body and everything that interacts with it directly or indirectly
+ * through contacts or joints.
+ */
+ public additionalSolverIterations(): number {
+ return this.rawSet.rbAdditionalSolverIterations(this.handle);
+ }
+
+ /**
+ * Sets the number of additional solver iterations that will be run for this
+ * rigid-body and everything that interacts with it directly or indirectly
+ * through contacts or joints.
+ *
+ * Compared to increasing the global `World.numSolverIteration`, setting this
+ * value lets you increase accuracy on only a subset of the scene, resulting in reduced
+ * performance loss.
+ *
+ * @param iters - The new number of additional solver iterations (default: 0).
+ */
+ public setAdditionalSolverIterations(iters: number) {
+ this.rawSet.rbSetAdditionalSolverIterations(this.handle, iters);
+ }
+
+ /**
+ * Enable or disable CCD (Continuous Collision Detection) for this rigid-body.
+ *
+ * @param enabled - If `true`, CCD will be enabled for this rigid-body.
+ */
+ public enableCcd(enabled: boolean) {
+ this.rawSet.rbEnableCcd(this.handle, enabled);
+ }
+
+ /**
+ * Sets the soft-CCD prediction distance for this rigid-body.
+ *
+ * See the documentation of `RigidBodyDesc.setSoftCcdPrediction` for
+ * additional details.
+ */
+ public setSoftCcdPrediction(distance: number) {
+ this.rawSet.rbSetSoftCcdPrediction(this.handle, distance);
+ }
+
+ /**
+ * Gets the soft-CCD prediction distance for this rigid-body.
+ *
+ * See the documentation of `RigidBodyDesc.setSoftCcdPrediction` for
+ * additional details.
+ */
+ public softCcdPrediction(): number {
+ return this.rawSet.rbSoftCcdPrediction(this.handle);
+ }
+
+ /**
+ * The world-space translation of this rigid-body.
+ */
+ public translation(): Vector {
+ let res = this.rawSet.rbTranslation(this.handle);
+ return VectorOps.fromRaw(res);
+ }
+
+ /**
+ * The world-space orientation of this rigid-body.
+ */
+ public rotation(): Rotation {
+ let res = this.rawSet.rbRotation(this.handle);
+ return RotationOps.fromRaw(res);
+ }
+
+ /**
+ * The world-space next translation of this rigid-body.
+ *
+ * If this rigid-body is kinematic this value is set by the `setNextKinematicTranslation`
+ * method and is used for estimating the kinematic body velocity at the next timestep.
+ * For non-kinematic bodies, this value is currently unspecified.
+ */
+ public nextTranslation(): Vector {
+ let res = this.rawSet.rbNextTranslation(this.handle);
+ return VectorOps.fromRaw(res);
+ }
+
+ /**
+ * The world-space next orientation of this rigid-body.
+ *
+ * If this rigid-body is kinematic this value is set by the `setNextKinematicRotation`
+ * method and is used for estimating the kinematic body velocity at the next timestep.
+ * For non-kinematic bodies, this value is currently unspecified.
+ */
+ public nextRotation(): Rotation {
+ let res = this.rawSet.rbNextRotation(this.handle);
+ return RotationOps.fromRaw(res);
+ }
+
+ /**
+ * Sets the translation of this rigid-body.
+ *
+ * @param tra - The world-space position of the rigid-body.
+ * @param wakeUp - Forces the rigid-body to wake-up so it is properly affected by forces if it
+ * wasn't moving before modifying its position.
+ */
+ public setTranslation(tra: Vector, wakeUp: boolean) {
+ // #if DIM2
+ this.rawSet.rbSetTranslation(this.handle, tra.x, tra.y, wakeUp);
+ // #endif
+ // #if DIM3
+ this.rawSet.rbSetTranslation(this.handle, tra.x, tra.y, tra.z, wakeUp);
+ // #endif
+ }
+
+ /**
+ * Sets the linear velocity of this rigid-body.
+ *
+ * @param vel - The linear velocity to set.
+ * @param wakeUp - Forces the rigid-body to wake-up if it was asleep.
+ */
+ public setLinvel(vel: Vector, wakeUp: boolean) {
+ let rawVel = VectorOps.intoRaw(vel);
+ this.rawSet.rbSetLinvel(this.handle, rawVel, wakeUp);
+ rawVel.free();
+ }
+
+ /**
+ * The scale factor applied to the gravity affecting
+ * this rigid-body.
+ */
+ public gravityScale(): number {
+ return this.rawSet.rbGravityScale(this.handle);
+ }
+
+ /**
+ * Sets the scale factor applied to the gravity affecting
+ * this rigid-body.
+ *
+ * @param factor - The scale factor to set. A value of 0.0 means
+ * that this rigid-body will on longer be affected by gravity.
+ * @param wakeUp - Forces the rigid-body to wake-up if it was asleep.
+ */
+ public setGravityScale(factor: number, wakeUp: boolean) {
+ this.rawSet.rbSetGravityScale(this.handle, factor, wakeUp);
+ }
+
+ // #if DIM3
+ /**
+ * Sets the rotation quaternion of this rigid-body.
+ *
+ * This does nothing if a zero quaternion is provided.
+ *
+ * @param rotation - The rotation to set.
+ * @param wakeUp - Forces the rigid-body to wake-up so it is properly affected by forces if it
+ * wasn't moving before modifying its position.
+ */
+ public setRotation(rot: Rotation, wakeUp: boolean) {
+ this.rawSet.rbSetRotation(
+ this.handle,
+ rot.x,
+ rot.y,
+ rot.z,
+ rot.w,
+ wakeUp,
+ );
+ }
+
+ /**
+ * Sets the angular velocity fo this rigid-body.
+ *
+ * @param vel - The angular velocity to set.
+ * @param wakeUp - Forces the rigid-body to wake-up if it was asleep.
+ */
+ public setAngvel(vel: Vector, wakeUp: boolean) {
+ let rawVel = VectorOps.intoRaw(vel);
+ this.rawSet.rbSetAngvel(this.handle, rawVel, wakeUp);
+ rawVel.free();
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * Sets the rotation angle of this rigid-body.
+ *
+ * @param angle - The rotation angle, in radians.
+ * @param wakeUp - Forces the rigid-body to wake-up so it is properly affected by forces if it
+ * wasn't moving before modifying its position.
+ */
+ public setRotation(angle: number, wakeUp: boolean) {
+ this.rawSet.rbSetRotation(this.handle, angle, wakeUp);
+ }
+
+ /**
+ * Sets the angular velocity fo this rigid-body.
+ *
+ * @param vel - The angular velocity to set.
+ * @param wakeUp - Forces the rigid-body to wake-up if it was asleep.
+ */
+ public setAngvel(vel: number, wakeUp: boolean) {
+ this.rawSet.rbSetAngvel(this.handle, vel, wakeUp);
+ }
+
+ // #endif
+
+ /**
+ * If this rigid body is kinematic, sets its future translation after the next timestep integration.
+ *
+ * This should be used instead of `rigidBody.setTranslation` to make the dynamic object
+ * interacting with this kinematic body behave as expected. Internally, Rapier will compute
+ * an artificial velocity for this rigid-body from its current position and its next kinematic
+ * position. This velocity will be used to compute forces on dynamic bodies interacting with
+ * this body.
+ *
+ * @param t - The kinematic translation to set.
+ */
+ public setNextKinematicTranslation(t: Vector) {
+ // #if DIM2
+ this.rawSet.rbSetNextKinematicTranslation(this.handle, t.x, t.y);
+ // #endif
+ // #if DIM3
+ this.rawSet.rbSetNextKinematicTranslation(this.handle, t.x, t.y, t.z);
+ // #endif
+ }
+
+ // #if DIM3
+ /**
+ * If this rigid body is kinematic, sets its future rotation after the next timestep integration.
+ *
+ * This should be used instead of `rigidBody.setRotation` to make the dynamic object
+ * interacting with this kinematic body behave as expected. Internally, Rapier will compute
+ * an artificial velocity for this rigid-body from its current position and its next kinematic
+ * position. This velocity will be used to compute forces on dynamic bodies interacting with
+ * this body.
+ *
+ * @param rot - The kinematic rotation to set.
+ */
+ public setNextKinematicRotation(rot: Rotation) {
+ this.rawSet.rbSetNextKinematicRotation(
+ this.handle,
+ rot.x,
+ rot.y,
+ rot.z,
+ rot.w,
+ );
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * If this rigid body is kinematic, sets its future rotation after the next timestep integration.
+ *
+ * This should be used instead of `rigidBody.setRotation` to make the dynamic object
+ * interacting with this kinematic body behave as expected. Internally, Rapier will compute
+ * an artificial velocity for this rigid-body from its current position and its next kinematic
+ * position. This velocity will be used to compute forces on dynamic bodies interacting with
+ * this body.
+ *
+ * @param angle - The kinematic rotation angle, in radians.
+ */
+ public setNextKinematicRotation(angle: number) {
+ this.rawSet.rbSetNextKinematicRotation(this.handle, angle);
+ }
+
+ // #endif
+
+ /**
+ * The linear velocity of this rigid-body.
+ */
+ public linvel(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbLinvel(this.handle));
+ }
+
+ /**
+ * The velocity of the given world-space point on this rigid-body.
+ */
+ public velocityAtPoint(point: Vector): Vector {
+ const rawPoint = VectorOps.intoRaw(point);
+ let result = VectorOps.fromRaw(
+ this.rawSet.rbVelocityAtPoint(this.handle, rawPoint),
+ );
+ rawPoint.free();
+ return result;
+ }
+
+ // #if DIM3
+ /**
+ * The angular velocity of this rigid-body.
+ */
+ public angvel(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbAngvel(this.handle));
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * The angular velocity of this rigid-body.
+ */
+ public angvel(): number {
+ return this.rawSet.rbAngvel(this.handle);
+ }
+
+ // #endif
+
+ /**
+ * The mass of this rigid-body.
+ */
+ public mass(): number {
+ return this.rawSet.rbMass(this.handle);
+ }
+
+ /**
+ * The inverse mass taking into account translation locking.
+ */
+ public effectiveInvMass(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbEffectiveInvMass(this.handle));
+ }
+
+ /**
+ * The inverse of the mass of a rigid-body.
+ *
+ * If this is zero, the rigid-body is assumed to have infinite mass.
+ */
+ public invMass(): number {
+ return this.rawSet.rbInvMass(this.handle);
+ }
+
+ /**
+ * The center of mass of a rigid-body expressed in its local-space.
+ */
+ public localCom(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbLocalCom(this.handle));
+ }
+
+ /**
+ * The world-space center of mass of the rigid-body.
+ */
+ public worldCom(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbWorldCom(this.handle));
+ }
+
+ // #if DIM2
+ /**
+ * The inverse of the principal angular inertia of the rigid-body.
+ *
+ * Components set to zero are assumed to be infinite along the corresponding principal axis.
+ */
+ public invPrincipalInertia(): number {
+ return this.rawSet.rbInvPrincipalInertia(this.handle);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * The inverse of the principal angular inertia of the rigid-body.
+ *
+ * Components set to zero are assumed to be infinite along the corresponding principal axis.
+ */
+ public invPrincipalInertia(): Vector {
+ return VectorOps.fromRaw(
+ this.rawSet.rbInvPrincipalInertia(this.handle),
+ );
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * The angular inertia along the principal inertia axes of the rigid-body.
+ */
+ public principalInertia(): number {
+ return this.rawSet.rbPrincipalInertia(this.handle);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * The angular inertia along the principal inertia axes of the rigid-body.
+ */
+ public principalInertia(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbPrincipalInertia(this.handle));
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * The principal vectors of the local angular inertia tensor of the rigid-body.
+ */
+ public principalInertiaLocalFrame(): Rotation {
+ return RotationOps.fromRaw(
+ this.rawSet.rbPrincipalInertiaLocalFrame(this.handle),
+ );
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * The world-space inverse angular inertia tensor of the rigid-body,
+ * taking into account rotation locking.
+ */
+ public effectiveWorldInvInertia(): number {
+ return this.rawSet.rbEffectiveWorldInvInertia(this.handle);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * The world-space inverse angular inertia tensor of the rigid-body,
+ * taking into account rotation locking.
+ */
+ public effectiveWorldInvInertia(): SdpMatrix3 {
+ return SdpMatrix3Ops.fromRaw(
+ this.rawSet.rbEffectiveWorldInvInertia(this.handle),
+ );
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * The effective world-space angular inertia (that takes the potential rotation locking into account) of
+ * this rigid-body.
+ */
+ public effectiveAngularInertia(): number {
+ return this.rawSet.rbEffectiveAngularInertia(this.handle);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * The effective world-space angular inertia (that takes the potential rotation locking into account) of
+ * this rigid-body.
+ */
+ public effectiveAngularInertia(): SdpMatrix3 {
+ return SdpMatrix3Ops.fromRaw(
+ this.rawSet.rbEffectiveAngularInertia(this.handle),
+ );
+ }
+
+ // #endif
+
+ /**
+ * Put this rigid body to sleep.
+ *
+ * A sleeping body no longer moves and is no longer simulated by the physics engine unless
+ * it is waken up. It can be woken manually with `this.wakeUp()` or automatically due to
+ * external forces like contacts.
+ */
+ public sleep() {
+ this.rawSet.rbSleep(this.handle);
+ }
+
+ /**
+ * Wakes this rigid-body up.
+ *
+ * A dynamic rigid-body that does not move during several consecutive frames will
+ * be put to sleep by the physics engine, i.e., it will stop being simulated in order
+ * to avoid useless computations.
+ * This methods forces a sleeping rigid-body to wake-up. This is useful, e.g., before modifying
+ * the position of a dynamic body so that it is properly simulated afterwards.
+ */
+ public wakeUp() {
+ this.rawSet.rbWakeUp(this.handle);
+ }
+
+ /**
+ * Is CCD enabled for this rigid-body?
+ */
+ public isCcdEnabled(): boolean {
+ return this.rawSet.rbIsCcdEnabled(this.handle);
+ }
+
+ /**
+ * The number of colliders attached to this rigid-body.
+ */
+ public numColliders(): number {
+ return this.rawSet.rbNumColliders(this.handle);
+ }
+
+ /**
+ * Retrieves the `i-th` collider attached to this rigid-body.
+ *
+ * @param i - The index of the collider to retrieve. Must be a number in `[0, this.numColliders()[`.
+ * This index is **not** the same as the unique identifier of the collider.
+ */
+ public collider(i: number): Collider {
+ return this.colliderSet.get(this.rawSet.rbCollider(this.handle, i));
+ }
+
+ /**
+ * Sets whether this rigid-body is enabled or not.
+ *
+ * @param enabled - Set to `false` to disable this rigid-body and all its attached colliders.
+ */
+ public setEnabled(enabled: boolean) {
+ this.rawSet.rbSetEnabled(this.handle, enabled);
+ }
+
+ /**
+ * Is this rigid-body enabled?
+ */
+ public isEnabled(): boolean {
+ return this.rawSet.rbIsEnabled(this.handle);
+ }
+
+ /**
+ * The status of this rigid-body: static, dynamic, or kinematic.
+ */
+ public bodyType(): RigidBodyType {
+ return this.rawSet.rbBodyType(this.handle) as number as RigidBodyType;
+ }
+
+ /**
+ * Set a new status for this rigid-body: static, dynamic, or kinematic.
+ */
+ public setBodyType(type: RigidBodyType, wakeUp: boolean) {
+ return this.rawSet.rbSetBodyType(
+ this.handle,
+ type as number as RawRigidBodyType,
+ wakeUp,
+ );
+ }
+
+ /**
+ * Is this rigid-body sleeping?
+ */
+ public isSleeping(): boolean {
+ return this.rawSet.rbIsSleeping(this.handle);
+ }
+
+ /**
+ * Is the velocity of this rigid-body not zero?
+ */
+ public isMoving(): boolean {
+ return this.rawSet.rbIsMoving(this.handle);
+ }
+
+ /**
+ * Is this rigid-body static?
+ */
+ public isFixed(): boolean {
+ return this.rawSet.rbIsFixed(this.handle);
+ }
+
+ /**
+ * Is this rigid-body kinematic?
+ */
+ public isKinematic(): boolean {
+ return this.rawSet.rbIsKinematic(this.handle);
+ }
+
+ /**
+ * Is this rigid-body dynamic?
+ */
+ public isDynamic(): boolean {
+ return this.rawSet.rbIsDynamic(this.handle);
+ }
+
+ /**
+ * The linear damping coefficient of this rigid-body.
+ */
+ public linearDamping(): number {
+ return this.rawSet.rbLinearDamping(this.handle);
+ }
+
+ /**
+ * The angular damping coefficient of this rigid-body.
+ */
+ public angularDamping(): number {
+ return this.rawSet.rbAngularDamping(this.handle);
+ }
+
+ /**
+ * Sets the linear damping factor applied to this rigid-body.
+ *
+ * @param factor - The damping factor to set.
+ */
+ public setLinearDamping(factor: number) {
+ this.rawSet.rbSetLinearDamping(this.handle, factor);
+ }
+
+ /**
+ * Recompute the mass-properties of this rigid-bodies based on its currently attached colliders.
+ */
+ public recomputeMassPropertiesFromColliders() {
+ this.rawSet.rbRecomputeMassPropertiesFromColliders(
+ this.handle,
+ this.colliderSet.raw,
+ );
+ }
+
+ /**
+ * Sets the rigid-body's additional mass.
+ *
+ * The total angular inertia of the rigid-body will be scaled automatically based on this additional mass. If this
+ * scaling effect isn’t desired, use Self::additional_mass_properties instead of this method.
+ *
+ * This is only the "additional" mass because the total mass of the rigid-body is equal to the sum of this
+ * additional mass and the mass computed from the colliders (with non-zero densities) attached to this rigid-body.
+ *
+ * That total mass (which includes the attached colliders’ contributions) will be updated at the name physics step,
+ * or can be updated manually with `this.recomputeMassPropertiesFromColliders`.
+ *
+ * This will override any previous additional mass-properties set by `this.setAdditionalMass`,
+ * `this.setAdditionalMassProperties`, `RigidBodyDesc::setAdditionalMass`, or
+ * `RigidBodyDesc.setAdditionalMassfProperties` for this rigid-body.
+ *
+ * @param mass - The additional mass to set.
+ * @param wakeUp - If `true` then the rigid-body will be woken up if it was put to sleep because it did not move for a while.
+ */
+ public setAdditionalMass(mass: number, wakeUp: boolean) {
+ this.rawSet.rbSetAdditionalMass(this.handle, mass, wakeUp);
+ }
+
+ // #if DIM3
+ /**
+ * Sets the rigid-body's additional mass-properties.
+ *
+ * This is only the "additional" mass-properties because the total mass-properties of the rigid-body is equal to the
+ * sum of this additional mass-properties and the mass computed from the colliders (with non-zero densities) attached
+ * to this rigid-body.
+ *
+ * That total mass-properties (which include the attached colliders’ contributions) will be updated at the name
+ * physics step, or can be updated manually with `this.recomputeMassPropertiesFromColliders`.
+ *
+ * This will override any previous mass-properties set by `this.setAdditionalMass`,
+ * `this.setAdditionalMassProperties`, `RigidBodyDesc.setAdditionalMass`, or `RigidBodyDesc.setAdditionalMassProperties`
+ * for this rigid-body.
+ *
+ * If `wake_up` is true then the rigid-body will be woken up if it was put to sleep because it did not move for a while.
+ */
+ public setAdditionalMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: Vector,
+ angularInertiaLocalFrame: Rotation,
+ wakeUp: boolean,
+ ) {
+ let rawCom = VectorOps.intoRaw(centerOfMass);
+ let rawPrincipalInertia = VectorOps.intoRaw(principalAngularInertia);
+ let rawInertiaFrame = RotationOps.intoRaw(angularInertiaLocalFrame);
+
+ this.rawSet.rbSetAdditionalMassProperties(
+ this.handle,
+ mass,
+ rawCom,
+ rawPrincipalInertia,
+ rawInertiaFrame,
+ wakeUp,
+ );
+
+ rawCom.free();
+ rawPrincipalInertia.free();
+ rawInertiaFrame.free();
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * Sets the rigid-body's additional mass-properties.
+ *
+ * This is only the "additional" mass-properties because the total mass-properties of the rigid-body is equal to the
+ * sum of this additional mass-properties and the mass computed from the colliders (with non-zero densities) attached
+ * to this rigid-body.
+ *
+ * That total mass-properties (which include the attached colliders’ contributions) will be updated at the name
+ * physics step, or can be updated manually with `this.recomputeMassPropertiesFromColliders`.
+ *
+ * This will override any previous mass-properties set by `this.setAdditionalMass`,
+ * `this.setAdditionalMassProperties`, `RigidBodyDesc.setAdditionalMass`, or `RigidBodyDesc.setAdditionalMassProperties`
+ * for this rigid-body.
+ *
+ * If `wake_up` is true then the rigid-body will be woken up if it was put to sleep because it did not move for a while.
+ */
+ public setAdditionalMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: number,
+ wakeUp: boolean,
+ ) {
+ let rawCom = VectorOps.intoRaw(centerOfMass);
+ this.rawSet.rbSetAdditionalMassProperties(
+ this.handle,
+ mass,
+ rawCom,
+ principalAngularInertia,
+ wakeUp,
+ );
+ rawCom.free();
+ }
+
+ // #endif
+
+ /**
+ * Sets the linear damping factor applied to this rigid-body.
+ *
+ * @param factor - The damping factor to set.
+ */
+ public setAngularDamping(factor: number) {
+ this.rawSet.rbSetAngularDamping(this.handle, factor);
+ }
+
+ /**
+ * Resets to zero the user forces (but not torques) applied to this rigid-body.
+ *
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public resetForces(wakeUp: boolean) {
+ this.rawSet.rbResetForces(this.handle, wakeUp);
+ }
+
+ /**
+ * Resets to zero the user torques applied to this rigid-body.
+ *
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public resetTorques(wakeUp: boolean) {
+ this.rawSet.rbResetTorques(this.handle, wakeUp);
+ }
+
+ /**
+ * Adds a force at the center-of-mass of this rigid-body.
+ *
+ * @param force - the world-space force to add to the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public addForce(force: Vector, wakeUp: boolean) {
+ const rawForce = VectorOps.intoRaw(force);
+ this.rawSet.rbAddForce(this.handle, rawForce, wakeUp);
+ rawForce.free();
+ }
+
+ /**
+ * Applies an impulse at the center-of-mass of this rigid-body.
+ *
+ * @param impulse - the world-space impulse to apply on the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public applyImpulse(impulse: Vector, wakeUp: boolean) {
+ const rawImpulse = VectorOps.intoRaw(impulse);
+ this.rawSet.rbApplyImpulse(this.handle, rawImpulse, wakeUp);
+ rawImpulse.free();
+ }
+
+ // #if DIM2
+ /**
+ * Adds a torque at the center-of-mass of this rigid-body.
+ *
+ * @param torque - the torque to add to the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public addTorque(torque: number, wakeUp: boolean) {
+ this.rawSet.rbAddTorque(this.handle, torque, wakeUp);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Adds a torque at the center-of-mass of this rigid-body.
+ *
+ * @param torque - the world-space torque to add to the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public addTorque(torque: Vector, wakeUp: boolean) {
+ const rawTorque = VectorOps.intoRaw(torque);
+ this.rawSet.rbAddTorque(this.handle, rawTorque, wakeUp);
+ rawTorque.free();
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * Applies an impulsive torque at the center-of-mass of this rigid-body.
+ *
+ * @param torqueImpulse - the torque impulse to apply on the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public applyTorqueImpulse(torqueImpulse: number, wakeUp: boolean) {
+ this.rawSet.rbApplyTorqueImpulse(this.handle, torqueImpulse, wakeUp);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Applies an impulsive torque at the center-of-mass of this rigid-body.
+ *
+ * @param torqueImpulse - the world-space torque impulse to apply on the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public applyTorqueImpulse(torqueImpulse: Vector, wakeUp: boolean) {
+ const rawTorqueImpulse = VectorOps.intoRaw(torqueImpulse);
+ this.rawSet.rbApplyTorqueImpulse(this.handle, rawTorqueImpulse, wakeUp);
+ rawTorqueImpulse.free();
+ }
+
+ // #endif
+
+ /**
+ * Adds a force at the given world-space point of this rigid-body.
+ *
+ * @param force - the world-space force to add to the rigid-body.
+ * @param point - the world-space point where the impulse is to be applied on the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public addForceAtPoint(force: Vector, point: Vector, wakeUp: boolean) {
+ const rawForce = VectorOps.intoRaw(force);
+ const rawPoint = VectorOps.intoRaw(point);
+ this.rawSet.rbAddForceAtPoint(this.handle, rawForce, rawPoint, wakeUp);
+ rawForce.free();
+ rawPoint.free();
+ }
+
+ /**
+ * Applies an impulse at the given world-space point of this rigid-body.
+ *
+ * @param impulse - the world-space impulse to apply on the rigid-body.
+ * @param point - the world-space point where the impulse is to be applied on the rigid-body.
+ * @param wakeUp - should the rigid-body be automatically woken-up?
+ */
+ public applyImpulseAtPoint(
+ impulse: Vector,
+ point: Vector,
+ wakeUp: boolean,
+ ) {
+ const rawImpulse = VectorOps.intoRaw(impulse);
+ const rawPoint = VectorOps.intoRaw(point);
+ this.rawSet.rbApplyImpulseAtPoint(
+ this.handle,
+ rawImpulse,
+ rawPoint,
+ wakeUp,
+ );
+ rawImpulse.free();
+ rawPoint.free();
+ }
+
+ /**
+ * Retrieves the constant force(s) the user added to this rigid-body
+ * Returns zero if the rigid-body is not dynamic.
+ */
+ public userForce(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbUserForce(this.handle));
+ }
+
+ // #if DIM2
+ /**
+ * Retrieves the constant torque(s) the user added to this rigid-body
+ * Returns zero if the rigid-body is not dynamic.
+ */
+ public userTorque(): number {
+ return this.rawSet.rbUserTorque(this.handle);
+ }
+ // #endif
+
+ // #if DIM3
+ /**
+ * Retrieves the constant torque(s) the user added to this rigid-body
+ * Returns zero if the rigid-body is not dynamic.
+ */
+ public userTorque(): Vector {
+ return VectorOps.fromRaw(this.rawSet.rbUserTorque(this.handle));
+ }
+ // #endif
+}
+
+export class RigidBodyDesc {
+ enabled: boolean;
+ translation: Vector;
+ rotation: Rotation;
+ gravityScale: number;
+ mass: number;
+ massOnly: boolean;
+ centerOfMass: Vector;
+ translationsEnabledX: boolean;
+ translationsEnabledY: boolean;
+ linvel: Vector;
+ // #if DIM2
+ angvel: number;
+ principalAngularInertia: number;
+ rotationsEnabled: boolean;
+ // #endif
+ // #if DIM3
+ angvel: Vector;
+ principalAngularInertia: Vector;
+ angularInertiaLocalFrame: Rotation;
+ translationsEnabledZ: boolean;
+ rotationsEnabledX: boolean;
+ rotationsEnabledY: boolean;
+ rotationsEnabledZ: boolean;
+ // #endif
+ linearDamping: number;
+ angularDamping: number;
+ status: RigidBodyType;
+ canSleep: boolean;
+ sleeping: boolean;
+ ccdEnabled: boolean;
+ softCcdPrediction: number;
+ dominanceGroup: number;
+ additionalSolverIterations: number;
+ userData?: unknown;
+
+ constructor(status: RigidBodyType) {
+ this.enabled = true;
+ this.status = status;
+ this.translation = VectorOps.zeros();
+ this.rotation = RotationOps.identity();
+ this.gravityScale = 1.0;
+ this.linvel = VectorOps.zeros();
+ this.mass = 0.0;
+ this.massOnly = false;
+ this.centerOfMass = VectorOps.zeros();
+ this.translationsEnabledX = true;
+ this.translationsEnabledY = true;
+ // #if DIM2
+ this.angvel = 0.0;
+ this.principalAngularInertia = 0.0;
+ this.rotationsEnabled = true;
+ // #endif
+ // #if DIM3
+ this.angvel = VectorOps.zeros();
+ this.principalAngularInertia = VectorOps.zeros();
+ this.angularInertiaLocalFrame = RotationOps.identity();
+ this.translationsEnabledZ = true;
+ this.rotationsEnabledX = true;
+ this.rotationsEnabledY = true;
+ this.rotationsEnabledZ = true;
+ // #endif
+ this.linearDamping = 0.0;
+ this.angularDamping = 0.0;
+ this.canSleep = true;
+ this.sleeping = false;
+ this.ccdEnabled = false;
+ this.softCcdPrediction = 0.0;
+ this.dominanceGroup = 0;
+ this.additionalSolverIterations = 0;
+ }
+
+ /**
+ * A rigid-body descriptor used to build a dynamic rigid-body.
+ */
+ public static dynamic(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.Dynamic);
+ }
+
+ /**
+ * A rigid-body descriptor used to build a position-based kinematic rigid-body.
+ */
+ public static kinematicPositionBased(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.KinematicPositionBased);
+ }
+
+ /**
+ * A rigid-body descriptor used to build a velocity-based kinematic rigid-body.
+ */
+ public static kinematicVelocityBased(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.KinematicVelocityBased);
+ }
+
+ /**
+ * A rigid-body descriptor used to build a fixed rigid-body.
+ */
+ public static fixed(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.Fixed);
+ }
+
+ /**
+ * A rigid-body descriptor used to build a dynamic rigid-body.
+ *
+ * @deprecated The method has been renamed to `.dynamic()`.
+ */
+ public static newDynamic(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.Dynamic);
+ }
+
+ /**
+ * A rigid-body descriptor used to build a position-based kinematic rigid-body.
+ *
+ * @deprecated The method has been renamed to `.kinematicPositionBased()`.
+ */
+ public static newKinematicPositionBased(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.KinematicPositionBased);
+ }
+
+ /**
+ * A rigid-body descriptor used to build a velocity-based kinematic rigid-body.
+ *
+ * @deprecated The method has been renamed to `.kinematicVelocityBased()`.
+ */
+ public static newKinematicVelocityBased(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.KinematicVelocityBased);
+ }
+
+ /**
+ * A rigid-body descriptor used to build a fixed rigid-body.
+ *
+ * @deprecated The method has been renamed to `.fixed()`.
+ */
+ public static newStatic(): RigidBodyDesc {
+ return new RigidBodyDesc(RigidBodyType.Fixed);
+ }
+
+ public setDominanceGroup(group: number): RigidBodyDesc {
+ this.dominanceGroup = group;
+ return this;
+ }
+
+ /**
+ * Sets the number of additional solver iterations that will be run for this
+ * rigid-body and everything that interacts with it directly or indirectly
+ * through contacts or joints.
+ *
+ * Compared to increasing the global `World.numSolverIteration`, setting this
+ * value lets you increase accuracy on only a subset of the scene, resulting in reduced
+ * performance loss.
+ *
+ * @param iters - The new number of additional solver iterations (default: 0).
+ */
+ public setAdditionalSolverIterations(iters: number): RigidBodyDesc {
+ this.additionalSolverIterations = iters;
+ return this;
+ }
+
+ /**
+ * Sets whether the created rigid-body will be enabled or disabled.
+ * @param enabled − If set to `false` the rigid-body will be disabled at creation.
+ */
+ public setEnabled(enabled: boolean): RigidBodyDesc {
+ this.enabled = enabled;
+ return this;
+ }
+
+ // #if DIM2
+ /**
+ * Sets the initial translation of the rigid-body to create.
+ */
+ public setTranslation(x: number, y: number): RigidBodyDesc {
+ if (typeof x != "number" || typeof y != "number")
+ throw TypeError("The translation components must be numbers.");
+
+ this.translation = {x: x, y: y};
+ return this;
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Sets the initial translation of the rigid-body to create.
+ *
+ * @param tra - The translation to set.
+ */
+ public setTranslation(x: number, y: number, z: number): RigidBodyDesc {
+ if (
+ typeof x != "number" ||
+ typeof y != "number" ||
+ typeof z != "number"
+ )
+ throw TypeError("The translation components must be numbers.");
+
+ this.translation = {x: x, y: y, z: z};
+ return this;
+ }
+
+ // #endif
+
+ /**
+ * Sets the initial rotation of the rigid-body to create.
+ *
+ * @param rot - The rotation to set.
+ */
+ public setRotation(rot: Rotation): RigidBodyDesc {
+ // #if DIM2
+ this.rotation = rot;
+ // #endif
+ // #if DIM3
+ RotationOps.copy(this.rotation, rot);
+ // #endif
+ return this;
+ }
+
+ /**
+ * Sets the scale factor applied to the gravity affecting
+ * the rigid-body being built.
+ *
+ * @param scale - The scale factor. Set this to `0.0` if the rigid-body
+ * needs to ignore gravity.
+ */
+ public setGravityScale(scale: number): RigidBodyDesc {
+ this.gravityScale = scale;
+ return this;
+ }
+
+ /**
+ * Sets the initial mass of the rigid-body being built, before adding colliders' contributions.
+ *
+ * @param mass − The initial mass of the rigid-body to create.
+ */
+ public setAdditionalMass(mass: number): RigidBodyDesc {
+ this.mass = mass;
+ this.massOnly = true;
+ return this;
+ }
+
+ // #if DIM2
+ /**
+ * Sets the initial linear velocity of the rigid-body to create.
+ *
+ * @param x - The linear velocity to set along the `x` axis.
+ * @param y - The linear velocity to set along the `y` axis.
+ */
+ public setLinvel(x: number, y: number): RigidBodyDesc {
+ if (typeof x != "number" || typeof y != "number")
+ throw TypeError("The linvel components must be numbers.");
+
+ this.linvel = {x: x, y: y};
+ return this;
+ }
+
+ /**
+ * Sets the initial angular velocity of the rigid-body to create.
+ *
+ * @param vel - The angular velocity to set.
+ */
+ public setAngvel(vel: number): RigidBodyDesc {
+ this.angvel = vel;
+ return this;
+ }
+
+ /**
+ * Sets the mass properties of the rigid-body being built.
+ *
+ * Note that the final mass properties of the rigid-bodies depends
+ * on the initial mass-properties of the rigid-body (set by this method)
+ * to which is added the contributions of all the colliders with non-zero density
+ * attached to this rigid-body.
+ *
+ * Therefore, if you want your provided mass properties to be the final
+ * mass properties of your rigid-body, don't attach colliders to it, or
+ * only attach colliders with densities equal to zero.
+ *
+ * @param mass − The initial mass of the rigid-body to create.
+ * @param centerOfMass − The initial center-of-mass of the rigid-body to create.
+ * @param principalAngularInertia − The initial principal angular inertia of the rigid-body to create.
+ */
+ public setAdditionalMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: number,
+ ): RigidBodyDesc {
+ this.mass = mass;
+ VectorOps.copy(this.centerOfMass, centerOfMass);
+ this.principalAngularInertia = principalAngularInertia;
+ this.massOnly = false;
+ return this;
+ }
+
+ /**
+ * Allow translation of this rigid-body only along specific axes.
+ * @param translationsEnabledX - Are translations along the X axis enabled?
+ * @param translationsEnabledY - Are translations along the y axis enabled?
+ */
+ public enabledTranslations(
+ translationsEnabledX: boolean,
+ translationsEnabledY: boolean,
+ ): RigidBodyDesc {
+ this.translationsEnabledX = translationsEnabledX;
+ this.translationsEnabledY = translationsEnabledY;
+ return this;
+ }
+
+ /**
+ * Allow translation of this rigid-body only along specific axes.
+ * @param translationsEnabledX - Are translations along the X axis enabled?
+ * @param translationsEnabledY - Are translations along the y axis enabled?
+ * @deprecated use `this.enabledTranslations` with the same arguments instead.
+ */
+ public restrictTranslations(
+ translationsEnabledX: boolean,
+ translationsEnabledY: boolean,
+ ): RigidBodyDesc {
+ return this.enabledTranslations(
+ translationsEnabledX,
+ translationsEnabledY,
+ );
+ }
+
+ /**
+ * Locks all translations that would have resulted from forces on
+ * the created rigid-body.
+ */
+ public lockTranslations(): RigidBodyDesc {
+ return this.restrictTranslations(false, false);
+ }
+
+ /**
+ * Locks all rotations that would have resulted from forces on
+ * the created rigid-body.
+ */
+ public lockRotations(): RigidBodyDesc {
+ this.rotationsEnabled = false;
+ return this;
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Sets the initial linear velocity of the rigid-body to create.
+ *
+ * @param x - The linear velocity to set along the `x` axis.
+ * @param y - The linear velocity to set along the `y` axis.
+ * @param z - The linear velocity to set along the `z` axis.
+ */
+ public setLinvel(x: number, y: number, z: number): RigidBodyDesc {
+ if (
+ typeof x != "number" ||
+ typeof y != "number" ||
+ typeof z != "number"
+ )
+ throw TypeError("The linvel components must be numbers.");
+
+ this.linvel = {x: x, y: y, z: z};
+ return this;
+ }
+
+ /**
+ * Sets the initial angular velocity of the rigid-body to create.
+ *
+ * @param vel - The angular velocity to set.
+ */
+ public setAngvel(vel: Vector): RigidBodyDesc {
+ VectorOps.copy(this.angvel, vel);
+ return this;
+ }
+
+ /**
+ * Sets the mass properties of the rigid-body being built.
+ *
+ * Note that the final mass properties of the rigid-bodies depends
+ * on the initial mass-properties of the rigid-body (set by this method)
+ * to which is added the contributions of all the colliders with non-zero density
+ * attached to this rigid-body.
+ *
+ * Therefore, if you want your provided mass properties to be the final
+ * mass properties of your rigid-body, don't attach colliders to it, or
+ * only attach colliders with densities equal to zero.
+ *
+ * @param mass − The initial mass of the rigid-body to create.
+ * @param centerOfMass − The initial center-of-mass of the rigid-body to create.
+ * @param principalAngularInertia − The initial principal angular inertia of the rigid-body to create.
+ * These are the eigenvalues of the angular inertia matrix.
+ * @param angularInertiaLocalFrame − The initial local angular inertia frame of the rigid-body to create.
+ * These are the eigenvectors of the angular inertia matrix.
+ */
+ public setAdditionalMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: Vector,
+ angularInertiaLocalFrame: Rotation,
+ ): RigidBodyDesc {
+ this.mass = mass;
+ VectorOps.copy(this.centerOfMass, centerOfMass);
+ VectorOps.copy(this.principalAngularInertia, principalAngularInertia);
+ RotationOps.copy(
+ this.angularInertiaLocalFrame,
+ angularInertiaLocalFrame,
+ );
+ this.massOnly = false;
+ return this;
+ }
+
+ /**
+ * Allow translation of this rigid-body only along specific axes.
+ * @param translationsEnabledX - Are translations along the X axis enabled?
+ * @param translationsEnabledY - Are translations along the y axis enabled?
+ * @param translationsEnabledZ - Are translations along the Z axis enabled?
+ */
+ public enabledTranslations(
+ translationsEnabledX: boolean,
+ translationsEnabledY: boolean,
+ translationsEnabledZ: boolean,
+ ): RigidBodyDesc {
+ this.translationsEnabledX = translationsEnabledX;
+ this.translationsEnabledY = translationsEnabledY;
+ this.translationsEnabledZ = translationsEnabledZ;
+ return this;
+ }
+
+ /**
+ * Allow translation of this rigid-body only along specific axes.
+ * @param translationsEnabledX - Are translations along the X axis enabled?
+ * @param translationsEnabledY - Are translations along the y axis enabled?
+ * @param translationsEnabledZ - Are translations along the Z axis enabled?
+ * @deprecated use `this.enabledTranslations` with the same arguments instead.
+ */
+ public restrictTranslations(
+ translationsEnabledX: boolean,
+ translationsEnabledY: boolean,
+ translationsEnabledZ: boolean,
+ ): RigidBodyDesc {
+ return this.enabledTranslations(
+ translationsEnabledX,
+ translationsEnabledY,
+ translationsEnabledZ,
+ );
+ }
+
+ /**
+ * Locks all translations that would have resulted from forces on
+ * the created rigid-body.
+ */
+ public lockTranslations(): RigidBodyDesc {
+ return this.enabledTranslations(false, false, false);
+ }
+
+ /**
+ * Allow rotation of this rigid-body only along specific axes.
+ * @param rotationsEnabledX - Are rotations along the X axis enabled?
+ * @param rotationsEnabledY - Are rotations along the y axis enabled?
+ * @param rotationsEnabledZ - Are rotations along the Z axis enabled?
+ */
+ public enabledRotations(
+ rotationsEnabledX: boolean,
+ rotationsEnabledY: boolean,
+ rotationsEnabledZ: boolean,
+ ): RigidBodyDesc {
+ this.rotationsEnabledX = rotationsEnabledX;
+ this.rotationsEnabledY = rotationsEnabledY;
+ this.rotationsEnabledZ = rotationsEnabledZ;
+ return this;
+ }
+
+ /**
+ * Allow rotation of this rigid-body only along specific axes.
+ * @param rotationsEnabledX - Are rotations along the X axis enabled?
+ * @param rotationsEnabledY - Are rotations along the y axis enabled?
+ * @param rotationsEnabledZ - Are rotations along the Z axis enabled?
+ * @deprecated use `this.enabledRotations` with the same arguments instead.
+ */
+ public restrictRotations(
+ rotationsEnabledX: boolean,
+ rotationsEnabledY: boolean,
+ rotationsEnabledZ: boolean,
+ ): RigidBodyDesc {
+ return this.enabledRotations(
+ rotationsEnabledX,
+ rotationsEnabledY,
+ rotationsEnabledZ,
+ );
+ }
+
+ /**
+ * Locks all rotations that would have resulted from forces on
+ * the created rigid-body.
+ */
+ public lockRotations(): RigidBodyDesc {
+ return this.restrictRotations(false, false, false);
+ }
+
+ // #endif
+
+ /**
+ * Sets the linear damping of the rigid-body to create.
+ *
+ * This will progressively slowdown the translational movement of the rigid-body.
+ *
+ * @param damping - The angular damping coefficient. Should be >= 0. The higher this
+ * value is, the stronger the translational slowdown will be.
+ */
+ public setLinearDamping(damping: number): RigidBodyDesc {
+ this.linearDamping = damping;
+ return this;
+ }
+
+ /**
+ * Sets the angular damping of the rigid-body to create.
+ *
+ * This will progressively slowdown the rotational movement of the rigid-body.
+ *
+ * @param damping - The angular damping coefficient. Should be >= 0. The higher this
+ * value is, the stronger the rotational slowdown will be.
+ */
+ public setAngularDamping(damping: number): RigidBodyDesc {
+ this.angularDamping = damping;
+ return this;
+ }
+
+ /**
+ * Sets whether or not the rigid-body to create can sleep.
+ *
+ * @param can - true if the rigid-body can sleep, false if it can't.
+ */
+ public setCanSleep(can: boolean): RigidBodyDesc {
+ this.canSleep = can;
+ return this;
+ }
+
+ /**
+ * Sets whether or not the rigid-body is to be created asleep.
+ *
+ * @param can - true if the rigid-body should be in sleep, default false.
+ */
+ setSleeping(sleeping: boolean): RigidBodyDesc {
+ this.sleeping = sleeping;
+ return this;
+ }
+
+ /**
+ * Sets whether Continuous Collision Detection (CCD) is enabled for this rigid-body.
+ *
+ * @param enabled - true if the rigid-body has CCD enabled.
+ */
+ public setCcdEnabled(enabled: boolean): RigidBodyDesc {
+ this.ccdEnabled = enabled;
+ return this;
+ }
+
+ /**
+ * Sets the maximum prediction distance Soft Continuous Collision-Detection.
+ *
+ * When set to 0, soft-CCD is disabled. Soft-CCD helps prevent tunneling especially of
+ * slow-but-thin to moderately fast objects. The soft CCD prediction distance indicates how
+ * far in the object’s path the CCD algorithm is allowed to inspect. Large values can impact
+ * performance badly by increasing the work needed from the broad-phase.
+ *
+ * It is a generally cheaper variant of regular CCD (that can be enabled with
+ * `RigidBodyDesc::setCcdEnabled` since it relies on predictive constraints instead of
+ * shape-cast and substeps.
+ */
+ public setSoftCcdPrediction(distance: number): RigidBodyDesc {
+ this.softCcdPrediction = distance;
+ return this;
+ }
+
+ /**
+ * Sets the user-defined object of this rigid-body.
+ *
+ * @param userData - The user-defined object to set.
+ */
+ public setUserData(data?: unknown): RigidBodyDesc {
+ this.userData = data;
+ return this;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/dynamics/rigid_body_set.ts b/thirdparty/rapier.js/src.ts/dynamics/rigid_body_set.ts
new file mode 100644
index 00000000..4c2e47bd
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/dynamics/rigid_body_set.ts
@@ -0,0 +1,238 @@
+import {RawRigidBodySet, RawRigidBodyType} from "../raw";
+import {Coarena} from "../coarena";
+import {VectorOps, RotationOps} from "../math";
+import {
+ RigidBody,
+ RigidBodyDesc,
+ RigidBodyHandle,
+ RigidBodyType,
+} from "./rigid_body";
+import {ColliderSet} from "../geometry";
+import {ImpulseJointSet} from "./impulse_joint_set";
+import {MultibodyJointSet} from "./multibody_joint_set";
+import {IslandManager} from "./island_manager";
+
+/**
+ * A set of rigid bodies that can be handled by a physics pipeline.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `rigidBodySet.free()`
+ * once you are done using it (and all the rigid-bodies it created).
+ */
+export class RigidBodySet {
+ raw: RawRigidBodySet;
+ private map: Coarena;
+
+ /**
+ * Release the WASM memory occupied by this rigid-body set.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+
+ if (!!this.map) {
+ this.map.clear();
+ }
+ this.map = undefined;
+ }
+
+ constructor(raw?: RawRigidBodySet) {
+ this.raw = raw || new RawRigidBodySet();
+ this.map = new Coarena();
+ // deserialize
+ if (raw) {
+ raw.forEachRigidBodyHandle((handle: RigidBodyHandle) => {
+ this.map.set(handle, new RigidBody(raw, null, handle));
+ });
+ }
+ }
+
+ /**
+ * Internal method, do not call this explicitly.
+ */
+ public finalizeDeserialization(colliderSet: ColliderSet) {
+ this.map.forEach((rb) => rb.finalizeDeserialization(colliderSet));
+ }
+
+ /**
+ * Creates a new rigid-body and return its integer handle.
+ *
+ * @param desc - The description of the rigid-body to create.
+ */
+ public createRigidBody(
+ colliderSet: ColliderSet,
+ desc: RigidBodyDesc,
+ ): RigidBody {
+ let rawTra = VectorOps.intoRaw(desc.translation);
+ let rawRot = RotationOps.intoRaw(desc.rotation);
+ let rawLv = VectorOps.intoRaw(desc.linvel);
+ let rawCom = VectorOps.intoRaw(desc.centerOfMass);
+
+ // #if DIM3
+ let rawAv = VectorOps.intoRaw(desc.angvel);
+ let rawPrincipalInertia = VectorOps.intoRaw(
+ desc.principalAngularInertia,
+ );
+ let rawInertiaFrame = RotationOps.intoRaw(
+ desc.angularInertiaLocalFrame,
+ );
+ // #endif
+
+ let handle = this.raw.createRigidBody(
+ desc.enabled,
+ rawTra,
+ rawRot,
+ desc.gravityScale,
+ desc.mass,
+ desc.massOnly,
+ rawCom,
+ rawLv,
+ // #if DIM2
+ desc.angvel,
+ desc.principalAngularInertia,
+ desc.translationsEnabledX,
+ desc.translationsEnabledY,
+ desc.rotationsEnabled,
+ // #endif
+ // #if DIM3
+ rawAv,
+ rawPrincipalInertia,
+ rawInertiaFrame,
+ desc.translationsEnabledX,
+ desc.translationsEnabledY,
+ desc.translationsEnabledZ,
+ desc.rotationsEnabledX,
+ desc.rotationsEnabledY,
+ desc.rotationsEnabledZ,
+ // #endif
+ desc.linearDamping,
+ desc.angularDamping,
+ desc.status as number as RawRigidBodyType,
+ desc.canSleep,
+ desc.sleeping,
+ desc.softCcdPrediction,
+ desc.ccdEnabled,
+ desc.dominanceGroup,
+ desc.additionalSolverIterations,
+ );
+
+ rawTra.free();
+ rawRot.free();
+ rawLv.free();
+ rawCom.free();
+
+ // #if DIM3
+ rawAv.free();
+ rawPrincipalInertia.free();
+ rawInertiaFrame.free();
+ // #endif
+
+ const body = new RigidBody(this.raw, colliderSet, handle);
+ body.userData = desc.userData;
+
+ this.map.set(handle, body);
+
+ return body;
+ }
+
+ /**
+ * Removes a rigid-body from this set.
+ *
+ * This will also remove all the colliders and joints attached to the rigid-body.
+ *
+ * @param handle - The integer handle of the rigid-body to remove.
+ * @param colliders - The set of colliders that may contain colliders attached to the removed rigid-body.
+ * @param impulseJoints - The set of impulse joints that may contain joints attached to the removed rigid-body.
+ * @param multibodyJoints - The set of multibody joints that may contain joints attached to the removed rigid-body.
+ */
+ public remove(
+ handle: RigidBodyHandle,
+ islands: IslandManager,
+ colliders: ColliderSet,
+ impulseJoints: ImpulseJointSet,
+ multibodyJoints: MultibodyJointSet,
+ ) {
+ // Unmap the entities that will be removed automatically because of the rigid-body removals.
+ for (let i = 0; i < this.raw.rbNumColliders(handle); i += 1) {
+ colliders.unmap(this.raw.rbCollider(handle, i));
+ }
+
+ impulseJoints.forEachJointHandleAttachedToRigidBody(handle, (handle) =>
+ impulseJoints.unmap(handle),
+ );
+ multibodyJoints.forEachJointHandleAttachedToRigidBody(
+ handle,
+ (handle) => multibodyJoints.unmap(handle),
+ );
+
+ // Remove the rigid-body.
+ this.raw.remove(
+ handle,
+ islands.raw,
+ colliders.raw,
+ impulseJoints.raw,
+ multibodyJoints.raw,
+ );
+ this.map.delete(handle);
+ }
+
+ /**
+ * The number of rigid-bodies on this set.
+ */
+ public len(): number {
+ return this.map.len();
+ }
+
+ /**
+ * Does this set contain a rigid-body with the given handle?
+ *
+ * @param handle - The rigid-body handle to check.
+ */
+ public contains(handle: RigidBodyHandle): boolean {
+ return this.get(handle) != null;
+ }
+
+ /**
+ * Gets the rigid-body with the given handle.
+ *
+ * @param handle - The handle of the rigid-body to retrieve.
+ */
+ public get(handle: RigidBodyHandle): RigidBody | null {
+ return this.map.get(handle);
+ }
+
+ /**
+ * Applies the given closure to each rigid-body contained by this set.
+ *
+ * @param f - The closure to apply.
+ */
+ public forEach(f: (body: RigidBody) => void) {
+ this.map.forEach(f);
+ }
+
+ /**
+ * Applies the given closure to each active rigid-bodies contained by this set.
+ *
+ * A rigid-body is active if it is not sleeping, i.e., if it moved recently.
+ *
+ * @param f - The closure to apply.
+ */
+ public forEachActiveRigidBody(
+ islands: IslandManager,
+ f: (body: RigidBody) => void,
+ ) {
+ islands.forEachActiveRigidBodyHandle((handle) => {
+ f(this.get(handle));
+ });
+ }
+
+ /**
+ * Gets all rigid-bodies in the list.
+ *
+ * @returns rigid-bodies list.
+ */
+ public getAll(): RigidBody[] {
+ return this.map.getAll();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/exports.ts b/thirdparty/rapier.js/src.ts/exports.ts
new file mode 100644
index 00000000..237bd545
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/exports.ts
@@ -0,0 +1,27 @@
+import {version as vers, reserve_memory as reserve} from "./raw";
+
+export function version(): string {
+ return vers();
+}
+
+/// Reserves additional memory in WASM land.
+///
+/// This will grow the internal WASM memory buffer so that it can fit at least
+/// the specified amount of extra bytes. This can help reduce future runtime
+/// overhead due to dynamic internal memory growth once the limit of the
+/// pre-allocated memory is reached.
+///
+/// This feature is still experimental. Due to the nature of the internal
+/// allocator, there can be situations where the allocator decides to perform
+/// additional internal memory growth even though not all `extraBytesCount`
+/// are occupied yet.
+export function reserveMemory(extraBytesCount: number) {
+ reserve(extraBytesCount);
+}
+
+export * from "./math";
+export * from "./dynamics";
+export * from "./geometry";
+export * from "./pipeline";
+export * from "./init";
+export * from "./control";
diff --git a/thirdparty/rapier.js/src.ts/geometry/broad_phase.ts b/thirdparty/rapier.js/src.ts/geometry/broad_phase.ts
new file mode 100644
index 00000000..addaee92
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/broad_phase.ts
@@ -0,0 +1,520 @@
+import {RawBroadPhase, RawRayColliderIntersection} from "../raw";
+import {RigidBodyHandle, RigidBodySet} from "../dynamics";
+import {ColliderSet} from "./collider_set";
+import {Ray, RayColliderHit, RayColliderIntersection} from "./ray";
+import {InteractionGroups} from "./interaction_groups";
+import {ColliderHandle} from "./collider";
+import {Rotation, RotationOps, Vector, VectorOps} from "../math";
+import {Shape} from "./shape";
+import {PointColliderProjection} from "./point";
+import {ColliderShapeCastHit} from "./toi";
+import {QueryFilterFlags} from "../pipeline";
+import {NarrowPhase} from "./narrow_phase";
+
+/**
+ * The broad-phase used for coarse collision-detection.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `broadPhase.free()`
+ * once you are done using it.
+ */
+export class BroadPhase {
+ raw: RawBroadPhase;
+
+ /**
+ * Release the WASM memory occupied by this broad-phase.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ constructor(raw?: RawBroadPhase) {
+ this.raw = raw || new RawBroadPhase();
+ }
+
+ /**
+ * Find the closest intersection between a ray and a set of collider.
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ * @param groups - Used to filter the colliders that can or cannot be hit by the ray.
+ * @param filter - The callback to filter out which collider will be hit.
+ */
+ public castRay(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ ray: Ray,
+ maxToi: number,
+ solid: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ): RayColliderHit | null {
+ let rawOrig = VectorOps.intoRaw(ray.origin);
+ let rawDir = VectorOps.intoRaw(ray.dir);
+ let result = RayColliderHit.fromRaw(
+ colliders,
+ this.raw.castRay(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawOrig,
+ rawDir,
+ maxToi,
+ solid,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ ),
+ );
+
+ rawOrig.free();
+ rawDir.free();
+
+ return result;
+ }
+
+ /**
+ * Find the closest intersection between a ray and a set of collider.
+ *
+ * This also computes the normal at the hit point.
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ * @param groups - Used to filter the colliders that can or cannot be hit by the ray.
+ */
+ public castRayAndGetNormal(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ ray: Ray,
+ maxToi: number,
+ solid: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ): RayColliderIntersection | null {
+ let rawOrig = VectorOps.intoRaw(ray.origin);
+ let rawDir = VectorOps.intoRaw(ray.dir);
+ let result = RayColliderIntersection.fromRaw(
+ colliders,
+ this.raw.castRayAndGetNormal(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawOrig,
+ rawDir,
+ maxToi,
+ solid,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ ),
+ );
+
+ rawOrig.free();
+ rawDir.free();
+
+ return result;
+ }
+
+ /**
+ * Cast a ray and collects all the intersections between a ray and the scene.
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ * @param groups - Used to filter the colliders that can or cannot be hit by the ray.
+ * @param callback - The callback called once per hit (in no particular order) between a ray and a collider.
+ * If this callback returns `false`, then the cast will stop and no further hits will be detected/reported.
+ */
+ public intersectionsWithRay(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ ray: Ray,
+ maxToi: number,
+ solid: boolean,
+ callback: (intersect: RayColliderIntersection) => boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ) {
+ let rawOrig = VectorOps.intoRaw(ray.origin);
+ let rawDir = VectorOps.intoRaw(ray.dir);
+ let rawCallback = (rawInter: RawRayColliderIntersection) => {
+ return callback(
+ RayColliderIntersection.fromRaw(colliders, rawInter),
+ );
+ };
+
+ this.raw.intersectionsWithRay(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawOrig,
+ rawDir,
+ maxToi,
+ solid,
+ rawCallback,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ );
+
+ rawOrig.free();
+ rawDir.free();
+ }
+
+ /**
+ * Gets the handle of up to one collider intersecting the given shape.
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param shapePos - The position of the shape used for the intersection test.
+ * @param shapeRot - The orientation of the shape used for the intersection test.
+ * @param shape - The shape used for the intersection test.
+ * @param groups - The bit groups and filter associated to the ray, in order to only
+ * hit the colliders with collision groups compatible with the ray's group.
+ */
+ public intersectionWithShape(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ shapePos: Vector,
+ shapeRot: Rotation,
+ shape: Shape,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ): ColliderHandle | null {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawShape = shape.intoRaw();
+ let result = this.raw.intersectionWithShape(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawPos,
+ rawRot,
+ rawShape,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ );
+
+ rawPos.free();
+ rawRot.free();
+ rawShape.free();
+
+ return result;
+ }
+
+ /**
+ * Find the projection of a point on the closest collider.
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param point - The point to project.
+ * @param solid - If this is set to `true` then the collider shapes are considered to
+ * be plain (if the point is located inside of a plain shape, its projection is the point
+ * itself). If it is set to `false` the collider shapes are considered to be hollow
+ * (if the point is located inside of an hollow shape, it is projected on the shape's
+ * boundary).
+ * @param groups - The bit groups and filter associated to the point to project, in order to only
+ * project on colliders with collision groups compatible with the ray's group.
+ */
+ public projectPoint(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ point: Vector,
+ solid: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ): PointColliderProjection | null {
+ let rawPoint = VectorOps.intoRaw(point);
+ let result = PointColliderProjection.fromRaw(
+ colliders,
+ this.raw.projectPoint(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawPoint,
+ solid,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ ),
+ );
+
+ rawPoint.free();
+
+ return result;
+ }
+
+ /**
+ * Find the projection of a point on the closest collider.
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param point - The point to project.
+ * @param groups - The bit groups and filter associated to the point to project, in order to only
+ * project on colliders with collision groups compatible with the ray's group.
+ */
+ public projectPointAndGetFeature(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ point: Vector,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ): PointColliderProjection | null {
+ let rawPoint = VectorOps.intoRaw(point);
+ let result = PointColliderProjection.fromRaw(
+ colliders,
+ this.raw.projectPointAndGetFeature(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawPoint,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ ),
+ );
+
+ rawPoint.free();
+
+ return result;
+ }
+
+ /**
+ * Find all the colliders containing the given point.
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param point - The point used for the containment test.
+ * @param groups - The bit groups and filter associated to the point to test, in order to only
+ * test on colliders with collision groups compatible with the ray's group.
+ * @param callback - A function called with the handles of each collider with a shape
+ * containing the `point`.
+ */
+ public intersectionsWithPoint(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ point: Vector,
+ callback: (handle: ColliderHandle) => boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ) {
+ let rawPoint = VectorOps.intoRaw(point);
+
+ this.raw.intersectionsWithPoint(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawPoint,
+ callback,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ );
+
+ rawPoint.free();
+ }
+
+ /**
+ * Casts a shape at a constant linear velocity and retrieve the first collider it hits.
+ * This is similar to ray-casting except that we are casting a whole shape instead of
+ * just a point (the ray origin).
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param shapePos - The initial position of the shape to cast.
+ * @param shapeRot - The initial rotation of the shape to cast.
+ * @param shapeVel - The constant velocity of the shape to cast (i.e. the cast direction).
+ * @param shape - The shape to cast.
+ * @param targetDistance − If the shape moves closer to this distance from a collider, a hit
+ * will be returned.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the distance traveled by the shape to `shapeVel.norm() * maxToi`.
+ * @param stopAtPenetration - If set to `false`, the linear shape-cast won’t immediately stop if
+ * the shape is penetrating another shape at its starting point **and** its trajectory is such
+ * that it’s on a path to exit that penetration state.
+ * @param groups - The bit groups and filter associated to the shape to cast, in order to only
+ * test on colliders with collision groups compatible with this group.
+ */
+ public castShape(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ shapePos: Vector,
+ shapeRot: Rotation,
+ shapeVel: Vector,
+ shape: Shape,
+ targetDistance: number,
+ maxToi: number,
+ stopAtPenetration: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ): ColliderShapeCastHit | null {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawVel = VectorOps.intoRaw(shapeVel);
+ let rawShape = shape.intoRaw();
+
+ let result = ColliderShapeCastHit.fromRaw(
+ colliders,
+ this.raw.castShape(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawPos,
+ rawRot,
+ rawVel,
+ rawShape,
+ targetDistance,
+ maxToi,
+ stopAtPenetration,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ ),
+ );
+
+ rawPos.free();
+ rawRot.free();
+ rawVel.free();
+ rawShape.free();
+
+ return result;
+ }
+
+ /**
+ * Retrieve all the colliders intersecting the given shape.
+ *
+ * @param colliders - The set of colliders taking part in this pipeline.
+ * @param shapePos - The position of the shape to test.
+ * @param shapeRot - The orientation of the shape to test.
+ * @param shape - The shape to test.
+ * @param groups - The bit groups and filter associated to the shape to test, in order to only
+ * test on colliders with collision groups compatible with this group.
+ * @param callback - A function called with the handles of each collider intersecting the `shape`.
+ */
+ public intersectionsWithShape(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ shapePos: Vector,
+ shapeRot: Rotation,
+ shape: Shape,
+ callback: (handle: ColliderHandle) => boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: ColliderHandle,
+ filterExcludeRigidBody?: RigidBodyHandle,
+ filterPredicate?: (collider: ColliderHandle) => boolean,
+ ) {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawShape = shape.intoRaw();
+
+ this.raw.intersectionsWithShape(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawPos,
+ rawRot,
+ rawShape,
+ callback,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider,
+ filterExcludeRigidBody,
+ filterPredicate,
+ );
+
+ rawPos.free();
+ rawRot.free();
+ rawShape.free();
+ }
+
+ /**
+ * Finds the handles of all the colliders with an AABB intersecting the given AABB.
+ *
+ * @param aabbCenter - The center of the AABB to test.
+ * @param aabbHalfExtents - The half-extents of the AABB to test.
+ * @param callback - The callback that will be called with the handles of all the colliders
+ * currently intersecting the given AABB.
+ */
+ public collidersWithAabbIntersectingAabb(
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ aabbCenter: Vector,
+ aabbHalfExtents: Vector,
+ callback: (handle: ColliderHandle) => boolean,
+ ) {
+ let rawCenter = VectorOps.intoRaw(aabbCenter);
+ let rawHalfExtents = VectorOps.intoRaw(aabbHalfExtents);
+ this.raw.collidersWithAabbIntersectingAabb(
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ rawCenter,
+ rawHalfExtents,
+ callback,
+ );
+ rawCenter.free();
+ rawHalfExtents.free();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/geometry/collider.ts b/thirdparty/rapier.js/src.ts/geometry/collider.ts
new file mode 100644
index 00000000..25817362
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/collider.ts
@@ -0,0 +1,1983 @@
+import {RawColliderSet} from "../raw";
+import {Rotation, RotationOps, Vector, VectorOps} from "../math";
+import {
+ CoefficientCombineRule,
+ RigidBody,
+ RigidBodyHandle,
+ RigidBodySet,
+} from "../dynamics";
+import {ActiveHooks, ActiveEvents} from "../pipeline";
+import {InteractionGroups} from "./interaction_groups";
+import {
+ Shape,
+ Cuboid,
+ Ball,
+ ShapeType,
+ Capsule,
+ Voxels,
+ TriMesh,
+ Polyline,
+ Heightfield,
+ Segment,
+ Triangle,
+ RoundTriangle,
+ RoundCuboid,
+ HalfSpace,
+ TriMeshFlags,
+ // #if DIM2
+ ConvexPolygon,
+ RoundConvexPolygon,
+ // #endif
+ // #if DIM3
+ Cylinder,
+ RoundCylinder,
+ Cone,
+ RoundCone,
+ ConvexPolyhedron,
+ RoundConvexPolyhedron,
+ HeightFieldFlags,
+ // #endif
+} from "./shape";
+import {Ray, RayIntersection} from "./ray";
+import {PointProjection} from "./point";
+import {ColliderShapeCastHit, ShapeCastHit} from "./toi";
+import {ShapeContact} from "./contact";
+import {ColliderSet} from "./collider_set";
+
+/**
+ * Flags affecting whether collision-detection happens between two colliders
+ * depending on the type of rigid-bodies they are attached to.
+ */
+export enum ActiveCollisionTypes {
+ /**
+ * Enable collision-detection between a collider attached to a dynamic body
+ * and another collider attached to a dynamic body.
+ */
+ DYNAMIC_DYNAMIC = 0b0000_0000_0000_0001,
+ /**
+ * Enable collision-detection between a collider attached to a dynamic body
+ * and another collider attached to a kinematic body.
+ */
+ DYNAMIC_KINEMATIC = 0b0000_0000_0000_1100,
+ /**
+ * Enable collision-detection between a collider attached to a dynamic body
+ * and another collider attached to a fixed body (or not attached to any body).
+ */
+ DYNAMIC_FIXED = 0b0000_0000_0000_0010,
+ /**
+ * Enable collision-detection between a collider attached to a kinematic body
+ * and another collider attached to a kinematic body.
+ */
+ KINEMATIC_KINEMATIC = 0b1100_1100_0000_0000,
+
+ /**
+ * Enable collision-detection between a collider attached to a kinematic body
+ * and another collider attached to a fixed body (or not attached to any body).
+ */
+ KINEMATIC_FIXED = 0b0010_0010_0000_0000,
+
+ /**
+ * Enable collision-detection between a collider attached to a fixed body (or
+ * not attached to any body) and another collider attached to a fixed body (or
+ * not attached to any body).
+ */
+ FIXED_FIXED = 0b0000_0000_0010_0000,
+ /**
+ * The default active collision types, enabling collisions between a dynamic body
+ * and another body of any type, but not enabling collisions between two non-dynamic bodies.
+ */
+ DEFAULT = DYNAMIC_KINEMATIC | DYNAMIC_DYNAMIC | DYNAMIC_FIXED,
+ /**
+ * Enable collisions between any kind of rigid-bodies (including between two non-dynamic bodies).
+ */
+ ALL = DYNAMIC_KINEMATIC |
+ DYNAMIC_DYNAMIC |
+ DYNAMIC_FIXED |
+ KINEMATIC_KINEMATIC |
+ KINEMATIC_FIXED |
+ KINEMATIC_KINEMATIC,
+}
+
+/**
+ * The integer identifier of a collider added to a `ColliderSet`.
+ */
+export type ColliderHandle = number;
+
+/**
+ * A geometric entity that can be attached to a body so it can be affected
+ * by contacts and proximity queries.
+ */
+export class Collider {
+ private colliderSet: ColliderSet; // The Collider won't need to free this.
+ readonly handle: ColliderHandle;
+ private _shape: Shape; // TODO: deprecate/remove this since it isn’t a reliable way of getting the latest shape properties.
+ private _parent: RigidBody | null;
+
+ constructor(
+ colliderSet: ColliderSet,
+ handle: ColliderHandle,
+ parent: RigidBody | null,
+ shape?: Shape,
+ ) {
+ this.colliderSet = colliderSet;
+ this.handle = handle;
+ this._parent = parent;
+ this._shape = shape;
+ }
+
+ /** @internal */
+ public finalizeDeserialization(bodies: RigidBodySet) {
+ if (this.handle != null) {
+ this._parent = bodies.get(
+ this.colliderSet.raw.coParent(this.handle),
+ );
+ }
+ }
+
+ private ensureShapeIsCached() {
+ if (!this._shape)
+ this._shape = Shape.fromRaw(this.colliderSet.raw, this.handle);
+ }
+
+ /**
+ * The shape of this collider.
+ */
+ public get shape(): Shape {
+ this.ensureShapeIsCached();
+ return this._shape;
+ }
+
+ /**
+ * Set the internal cached JS shape to null.
+ *
+ * This can be useful if you want to free some memory (assuming you are not
+ * holding any other references to the shape object), or in order to force
+ * the recalculation of the JS shape (the next time the `shape` getter is
+ * accessed) from the WASM source of truth.
+ */
+ public clearShapeCache() {
+ this._shape = null;
+ }
+
+ /**
+ * Checks if this collider is still valid (i.e. that it has
+ * not been deleted from the collider set yet).
+ */
+ public isValid(): boolean {
+ return this.colliderSet.raw.contains(this.handle);
+ }
+
+ /**
+ * The world-space translation of this collider.
+ */
+ public translation(): Vector {
+ return VectorOps.fromRaw(
+ this.colliderSet.raw.coTranslation(this.handle),
+ );
+ }
+
+ /**
+ * The translation of this collider relative to its parent rigid-body.
+ *
+ * Returns `null` if the collider doesn’t have a parent rigid-body.
+ */
+ public translationWrtParent(): Vector | null {
+ return VectorOps.fromRaw(
+ this.colliderSet.raw.coTranslationWrtParent(this.handle),
+ );
+ }
+
+ /**
+ * The world-space orientation of this collider.
+ */
+ public rotation(): Rotation {
+ return RotationOps.fromRaw(
+ this.colliderSet.raw.coRotation(this.handle),
+ );
+ }
+
+ /**
+ * The orientation of this collider relative to its parent rigid-body.
+ *
+ * Returns `null` if the collider doesn’t have a parent rigid-body.
+ */
+ public rotationWrtParent(): Rotation | null {
+ return RotationOps.fromRaw(
+ this.colliderSet.raw.coRotationWrtParent(this.handle),
+ );
+ }
+
+ /**
+ * Is this collider a sensor?
+ */
+ public isSensor(): boolean {
+ return this.colliderSet.raw.coIsSensor(this.handle);
+ }
+
+ /**
+ * Sets whether this collider is a sensor.
+ * @param isSensor - If `true`, the collider will be a sensor.
+ */
+ public setSensor(isSensor: boolean) {
+ this.colliderSet.raw.coSetSensor(this.handle, isSensor);
+ }
+
+ /**
+ * Sets the new shape of the collider.
+ * @param shape - The collider’s new shape.
+ */
+ public setShape(shape: Shape) {
+ let rawShape = shape.intoRaw();
+ this.colliderSet.raw.coSetShape(this.handle, rawShape);
+ rawShape.free();
+ this._shape = shape;
+ }
+
+ /**
+ * Sets whether this collider is enabled or not.
+ *
+ * @param enabled - Set to `false` to disable this collider (its parent rigid-body won’t be disabled automatically by this).
+ */
+ public setEnabled(enabled: boolean) {
+ this.colliderSet.raw.coSetEnabled(this.handle, enabled);
+ }
+
+ /**
+ * Is this collider enabled?
+ */
+ public isEnabled(): boolean {
+ return this.colliderSet.raw.coIsEnabled(this.handle);
+ }
+
+ /**
+ * Sets the restitution coefficient of the collider to be created.
+ *
+ * @param restitution - The restitution coefficient in `[0, 1]`. A value of 0 (the default) means no bouncing behavior
+ * while 1 means perfect bouncing (though energy may still be lost due to numerical errors of the
+ * constraints solver).
+ */
+ public setRestitution(restitution: number) {
+ this.colliderSet.raw.coSetRestitution(this.handle, restitution);
+ }
+
+ /**
+ * Sets the friction coefficient of the collider to be created.
+ *
+ * @param friction - The friction coefficient. Must be greater or equal to 0. This is generally smaller than 1. The
+ * higher the coefficient, the stronger friction forces will be for contacts with the collider
+ * being built.
+ */
+ public setFriction(friction: number) {
+ this.colliderSet.raw.coSetFriction(this.handle, friction);
+ }
+
+ /**
+ * Gets the rule used to combine the friction coefficients of two colliders
+ * colliders involved in a contact.
+ */
+ public frictionCombineRule(): CoefficientCombineRule {
+ return this.colliderSet.raw.coFrictionCombineRule(this.handle);
+ }
+
+ /**
+ * Sets the rule used to combine the friction coefficients of two colliders
+ * colliders involved in a contact.
+ *
+ * @param rule − The combine rule to apply.
+ */
+ public setFrictionCombineRule(rule: CoefficientCombineRule) {
+ this.colliderSet.raw.coSetFrictionCombineRule(this.handle, rule);
+ }
+
+ /**
+ * Gets the rule used to combine the restitution coefficients of two colliders
+ * colliders involved in a contact.
+ */
+ public restitutionCombineRule(): CoefficientCombineRule {
+ return this.colliderSet.raw.coRestitutionCombineRule(this.handle);
+ }
+
+ /**
+ * Sets the rule used to combine the restitution coefficients of two colliders
+ * colliders involved in a contact.
+ *
+ * @param rule − The combine rule to apply.
+ */
+ public setRestitutionCombineRule(rule: CoefficientCombineRule) {
+ this.colliderSet.raw.coSetRestitutionCombineRule(this.handle, rule);
+ }
+
+ /**
+ * Sets the collision groups used by this collider.
+ *
+ * Two colliders will interact iff. their collision groups are compatible.
+ * See the documentation of `InteractionGroups` for details on teh used bit pattern.
+ *
+ * @param groups - The collision groups used for the collider being built.
+ */
+ public setCollisionGroups(groups: InteractionGroups) {
+ this.colliderSet.raw.coSetCollisionGroups(this.handle, groups);
+ }
+
+ /**
+ * Sets the solver groups used by this collider.
+ *
+ * Forces between two colliders in contact will be computed iff their solver
+ * groups are compatible.
+ * See the documentation of `InteractionGroups` for details on the used bit pattern.
+ *
+ * @param groups - The solver groups used for the collider being built.
+ */
+ public setSolverGroups(groups: InteractionGroups) {
+ this.colliderSet.raw.coSetSolverGroups(this.handle, groups);
+ }
+
+ /**
+ * Sets the contact skin for this collider.
+ *
+ * See the documentation of `ColliderDesc.setContactSkin` for additional details.
+ */
+ public contactSkin(): number {
+ return this.colliderSet.raw.coContactSkin(this.handle);
+ }
+
+ /**
+ * Sets the contact skin for this collider.
+ *
+ * See the documentation of `ColliderDesc.setContactSkin` for additional details.
+ *
+ * @param thickness - The contact skin thickness.
+ */
+ public setContactSkin(thickness: number) {
+ return this.colliderSet.raw.coSetContactSkin(this.handle, thickness);
+ }
+
+ /**
+ * Get the physics hooks active for this collider.
+ */
+ public activeHooks(): ActiveHooks {
+ return this.colliderSet.raw.coActiveHooks(this.handle);
+ }
+
+ /**
+ * Set the physics hooks active for this collider.
+ *
+ * Use this to enable custom filtering rules for contact/intersecstion pairs involving this collider.
+ *
+ * @param activeHooks - The hooks active for contact/intersection pairs involving this collider.
+ */
+ public setActiveHooks(activeHooks: ActiveHooks) {
+ this.colliderSet.raw.coSetActiveHooks(this.handle, activeHooks);
+ }
+
+ /**
+ * The events active for this collider.
+ */
+ public activeEvents(): ActiveEvents {
+ return this.colliderSet.raw.coActiveEvents(this.handle);
+ }
+
+ /**
+ * Set the events active for this collider.
+ *
+ * Use this to enable contact and/or intersection event reporting for this collider.
+ *
+ * @param activeEvents - The events active for contact/intersection pairs involving this collider.
+ */
+ public setActiveEvents(activeEvents: ActiveEvents) {
+ this.colliderSet.raw.coSetActiveEvents(this.handle, activeEvents);
+ }
+
+ /**
+ * Gets the collision types active for this collider.
+ */
+ public activeCollisionTypes(): ActiveCollisionTypes {
+ return this.colliderSet.raw.coActiveCollisionTypes(this.handle);
+ }
+
+ /**
+ * Sets the total force magnitude beyond which a contact force event can be emitted.
+ *
+ * @param threshold - The new force threshold.
+ */
+ public setContactForceEventThreshold(threshold: number) {
+ return this.colliderSet.raw.coSetContactForceEventThreshold(
+ this.handle,
+ threshold,
+ );
+ }
+
+ /**
+ * The total force magnitude beyond which a contact force event can be emitted.
+ */
+ public contactForceEventThreshold(): number {
+ return this.colliderSet.raw.coContactForceEventThreshold(this.handle);
+ }
+
+ /**
+ * Set the collision types active for this collider.
+ *
+ * @param activeCollisionTypes - The hooks active for contact/intersection pairs involving this collider.
+ */
+ public setActiveCollisionTypes(activeCollisionTypes: ActiveCollisionTypes) {
+ this.colliderSet.raw.coSetActiveCollisionTypes(
+ this.handle,
+ activeCollisionTypes,
+ );
+ }
+
+ /**
+ * Sets the uniform density of this collider.
+ *
+ * This will override any previous mass-properties set by `this.setDensity`,
+ * `this.setMass`, `this.setMassProperties`, `ColliderDesc.density`,
+ * `ColliderDesc.mass`, or `ColliderDesc.massProperties` for this collider.
+ *
+ * The mass and angular inertia of this collider will be computed automatically based on its
+ * shape.
+ */
+ public setDensity(density: number) {
+ this.colliderSet.raw.coSetDensity(this.handle, density);
+ }
+
+ /**
+ * Sets the mass of this collider.
+ *
+ * This will override any previous mass-properties set by `this.setDensity`,
+ * `this.setMass`, `this.setMassProperties`, `ColliderDesc.density`,
+ * `ColliderDesc.mass`, or `ColliderDesc.massProperties` for this collider.
+ *
+ * The angular inertia of this collider will be computed automatically based on its shape
+ * and this mass value.
+ */
+ public setMass(mass: number) {
+ this.colliderSet.raw.coSetMass(this.handle, mass);
+ }
+
+ // #if DIM3
+ /**
+ * Sets the mass of this collider.
+ *
+ * This will override any previous mass-properties set by `this.setDensity`,
+ * `this.setMass`, `this.setMassProperties`, `ColliderDesc.density`,
+ * `ColliderDesc.mass`, or `ColliderDesc.massProperties` for this collider.
+ */
+ public setMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: Vector,
+ angularInertiaLocalFrame: Rotation,
+ ) {
+ let rawCom = VectorOps.intoRaw(centerOfMass);
+ let rawPrincipalInertia = VectorOps.intoRaw(principalAngularInertia);
+ let rawInertiaFrame = RotationOps.intoRaw(angularInertiaLocalFrame);
+
+ this.colliderSet.raw.coSetMassProperties(
+ this.handle,
+ mass,
+ rawCom,
+ rawPrincipalInertia,
+ rawInertiaFrame,
+ );
+
+ rawCom.free();
+ rawPrincipalInertia.free();
+ rawInertiaFrame.free();
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * Sets the mass of this collider.
+ *
+ * This will override any previous mass-properties set by `this.setDensity`,
+ * `this.setMass`, `this.setMassProperties`, `ColliderDesc.density`,
+ * `ColliderDesc.mass`, or `ColliderDesc.massProperties` for this collider.
+ */
+ public setMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: number,
+ ) {
+ let rawCom = VectorOps.intoRaw(centerOfMass);
+ this.colliderSet.raw.coSetMassProperties(
+ this.handle,
+ mass,
+ rawCom,
+ principalAngularInertia,
+ );
+ rawCom.free();
+ }
+
+ // #endif
+
+ /**
+ * Sets the translation of this collider.
+ *
+ * @param tra - The world-space position of the collider.
+ */
+ public setTranslation(tra: Vector) {
+ // #if DIM2
+ this.colliderSet.raw.coSetTranslation(this.handle, tra.x, tra.y);
+ // #endif
+ // #if DIM3
+ this.colliderSet.raw.coSetTranslation(this.handle, tra.x, tra.y, tra.z);
+ // #endif
+ }
+
+ /**
+ * Sets the translation of this collider relative to its parent rigid-body.
+ *
+ * Does nothing if this collider isn't attached to a rigid-body.
+ *
+ * @param tra - The new translation of the collider relative to its parent.
+ */
+ public setTranslationWrtParent(tra: Vector) {
+ // #if DIM2
+ this.colliderSet.raw.coSetTranslationWrtParent(
+ this.handle,
+ tra.x,
+ tra.y,
+ );
+ // #endif
+ // #if DIM3
+ this.colliderSet.raw.coSetTranslationWrtParent(
+ this.handle,
+ tra.x,
+ tra.y,
+ tra.z,
+ );
+ // #endif
+ }
+
+ // #if DIM3
+ /**
+ * Sets the rotation quaternion of this collider.
+ *
+ * This does nothing if a zero quaternion is provided.
+ *
+ * @param rotation - The rotation to set.
+ */
+ public setRotation(rot: Rotation) {
+ this.colliderSet.raw.coSetRotation(
+ this.handle,
+ rot.x,
+ rot.y,
+ rot.z,
+ rot.w,
+ );
+ }
+
+ /**
+ * Sets the rotation quaternion of this collider relative to its parent rigid-body.
+ *
+ * This does nothing if a zero quaternion is provided or if this collider isn't
+ * attached to a rigid-body.
+ *
+ * @param rotation - The rotation to set.
+ */
+ public setRotationWrtParent(rot: Rotation) {
+ this.colliderSet.raw.coSetRotationWrtParent(
+ this.handle,
+ rot.x,
+ rot.y,
+ rot.z,
+ rot.w,
+ );
+ }
+
+ // #endif
+ // #if DIM2
+ /**
+ * Sets the rotation angle of this collider.
+ *
+ * @param angle - The rotation angle, in radians.
+ */
+ public setRotation(angle: number) {
+ this.colliderSet.raw.coSetRotation(this.handle, angle);
+ }
+
+ /**
+ * Sets the rotation angle of this collider relative to its parent rigid-body.
+ *
+ * Does nothing if this collider isn't attached to a rigid-body.
+ *
+ * @param angle - The rotation angle, in radians.
+ */
+ public setRotationWrtParent(angle: number) {
+ this.colliderSet.raw.coSetRotationWrtParent(this.handle, angle);
+ }
+
+ // #endif
+
+ /**
+ * The type of the shape of this collider.
+ */
+ public shapeType(): ShapeType {
+ return this.colliderSet.raw.coShapeType(
+ this.handle,
+ ) as number as ShapeType;
+ }
+
+ /**
+ * The half-extents of this collider if it is a cuboid shape.
+ */
+ public halfExtents(): Vector {
+ return VectorOps.fromRaw(
+ this.colliderSet.raw.coHalfExtents(this.handle),
+ );
+ }
+
+ /**
+ * Sets the half-extents of this collider if it is a cuboid shape.
+ *
+ * @param newHalfExtents - desired half extents.
+ */
+ public setHalfExtents(newHalfExtents: Vector) {
+ const rawPoint = VectorOps.intoRaw(newHalfExtents);
+ this.colliderSet.raw.coSetHalfExtents(this.handle, rawPoint);
+ }
+
+ /**
+ * The radius of this collider if it is a ball, cylinder, capsule, or cone shape.
+ */
+ public radius(): number {
+ return this.colliderSet.raw.coRadius(this.handle);
+ }
+
+ /**
+ * Sets the radius of this collider if it is a ball, cylinder, capsule, or cone shape.
+ *
+ * @param newRadius - desired radius.
+ */
+ public setRadius(newRadius: number): void {
+ this.colliderSet.raw.coSetRadius(this.handle, newRadius);
+ }
+
+ /**
+ * The radius of the round edges of this collider if it is a round cylinder.
+ */
+ public roundRadius(): number {
+ return this.colliderSet.raw.coRoundRadius(this.handle);
+ }
+
+ /**
+ * Sets the radius of the round edges of this collider if it has round edges.
+ *
+ * @param newBorderRadius - desired round edge radius.
+ */
+ public setRoundRadius(newBorderRadius: number) {
+ this.colliderSet.raw.coSetRoundRadius(this.handle, newBorderRadius);
+ }
+
+ /**
+ * The half height of this collider if it is a cylinder, capsule, or cone shape.
+ */
+ public halfHeight(): number {
+ return this.colliderSet.raw.coHalfHeight(this.handle);
+ }
+
+ /**
+ * Sets the half height of this collider if it is a cylinder, capsule, or cone shape.
+ *
+ * @param newHalfheight - desired half height.
+ */
+ public setHalfHeight(newHalfheight: number) {
+ this.colliderSet.raw.coSetHalfHeight(this.handle, newHalfheight);
+ }
+
+ /**
+ * If this collider has a Voxels shape, this will mark the voxel at the
+ * given grid coordinates as filled or empty (depending on the `filled`
+ * argument).
+ *
+ * Each input value is assumed to be an integer.
+ *
+ * The operation is O(1), unless the provided coordinates are out of the
+ * bounds of the currently allocated internal grid in which case the grid
+ * will be grown automatically.
+ */
+ public setVoxel(
+ ix: number,
+ iy: number,
+ // #if DIM3
+ iz: number,
+ // #endif
+ filled: boolean,
+ ) {
+ this.colliderSet.raw.coSetVoxel(
+ this.handle,
+ ix,
+ iy,
+ // #if DIM3
+ iz,
+ // #endif
+ filled,
+ );
+ // We modified the shape, invalidate it to keep our cache
+ // up-to-date the next time the user requests the shape data.
+ // PERF: this isn’t ideal for performances as this adds a
+ // hidden, non-constant, cost.
+ this._shape = null;
+ }
+
+ /**
+ * If this and `voxels2` are voxel colliders, and a voxel from `this` was
+ * modified with `setVoxel`, this will ensure that a
+ * moving object transitioning across the boundaries of these colliders
+ * won’t suffer from the "internal edges" artifact.
+ *
+ * The indices `ix, iy, iz` indicate the integer coordinates of the voxel in
+ * the local coordinate frame of `this`.
+ *
+ * If the voxels in `voxels2` live in a different coordinate space from `this`,
+ * then the `shift_*` argument indicate the distance, in voxel units, between
+ * the origin of `this` to the origin of `voxels2`.
+ *
+ * This method is intended to be called between `this` and all the other
+ * voxels colliders with a domain intersecting `this` or sharing a domain
+ * boundary. This is an incremental maintenance of the effect of
+ * `combineVoxelStates`.
+ */
+ public propagateVoxelChange(
+ voxels2: Collider,
+ ix: number,
+ iy: number,
+ // #if DIM3
+ iz: number,
+ // #endif
+ shift_x: number,
+ shift_y: number,
+ // #if DIM3
+ shift_z: number,
+ // #endif
+ ) {
+ this.colliderSet.raw.coPropagateVoxelChange(
+ this.handle,
+ voxels2.handle,
+ ix,
+ iy,
+ // #if DIM3
+ iz,
+ // #endif
+ shift_x,
+ shift_y,
+ // #if DIM3
+ shift_z,
+ // #endif
+ );
+ // We modified the shape, invalidate it to keep our cache
+ // up-to-date the next time the user requests the shape data.
+ // PERF: this isn’t ideal for performances as this adds a
+ // hidden, non-constant, cost.
+ this._shape = null;
+ }
+
+ /**
+ * If this and `voxels2` are voxel colliders, this will ensure that a
+ * moving object transitioning across the boundaries of these colliders
+ * won’t suffer from the "internal edges" artifact.
+ *
+ * If the voxels in `voxels2` live in a different coordinate space from `this`,
+ * then the `shift_*` argument indicate the distance, in voxel units, between
+ * the origin of `this` to the origin of `voxels2`.
+ *
+ * This method is intended to be called once between all pairs of voxels
+ * colliders with intersecting domains or shared boundaries.
+ *
+ * If either voxels collider is then modified with `setVoxel`, the
+ * `propagateVoxelChange` method must be called to maintain the coupling
+ * between the voxels shapes after the modification.
+ */
+ public combineVoxelStates(
+ voxels2: Collider,
+ shift_x: number,
+ shift_y: number,
+ // #if DIM3
+ shift_z: number,
+ // #endif
+ ) {
+ this.colliderSet.raw.coCombineVoxelStates(
+ this.handle,
+ voxels2.handle,
+ shift_x,
+ shift_y,
+ // #if DIM3
+ shift_z,
+ // #endif
+ );
+ // We modified the shape, invalidate it to keep our cache
+ // up-to-date the next time the user requests the shape data.
+ // PERF: this isn’t ideal for performances as this adds a
+ // hidden, non-constant, cost.
+ this._shape = null;
+ }
+
+ /**
+ * If this collider has a triangle mesh, polyline, convex polygon, or convex polyhedron shape,
+ * this returns the vertex buffer of said shape.
+ */
+ public vertices(): Float32Array {
+ return this.colliderSet.raw.coVertices(this.handle);
+ }
+
+ /**
+ * If this collider has a triangle mesh, polyline, or convex polyhedron shape,
+ * this returns the index buffer of said shape.
+ */
+ public indices(): Uint32Array | undefined {
+ return this.colliderSet.raw.coIndices(this.handle);
+ }
+
+ /**
+ * If this collider has a heightfield shape, this returns the heights buffer of
+ * the heightfield.
+ * In 3D, the returned height matrix is provided in column-major order.
+ */
+ public heightfieldHeights(): Float32Array {
+ return this.colliderSet.raw.coHeightfieldHeights(this.handle);
+ }
+
+ /**
+ * If this collider has a heightfield shape, this returns the scale
+ * applied to it.
+ */
+ public heightfieldScale(): Vector {
+ let scale = this.colliderSet.raw.coHeightfieldScale(this.handle);
+ return VectorOps.fromRaw(scale);
+ }
+
+ // #if DIM3
+ /**
+ * If this collider has a heightfield shape, this returns the number of
+ * rows of its height matrix.
+ */
+ public heightfieldNRows(): number {
+ return this.colliderSet.raw.coHeightfieldNRows(this.handle);
+ }
+
+ /**
+ * If this collider has a heightfield shape, this returns the number of
+ * columns of its height matrix.
+ */
+ public heightfieldNCols(): number {
+ return this.colliderSet.raw.coHeightfieldNCols(this.handle);
+ }
+
+ // #endif
+
+ /**
+ * The rigid-body this collider is attached to.
+ */
+ public parent(): RigidBody | null {
+ return this._parent;
+ }
+
+ /**
+ * The friction coefficient of this collider.
+ */
+ public friction(): number {
+ return this.colliderSet.raw.coFriction(this.handle);
+ }
+
+ /**
+ * The restitution coefficient of this collider.
+ */
+ public restitution(): number {
+ return this.colliderSet.raw.coRestitution(this.handle);
+ }
+
+ /**
+ * The density of this collider.
+ */
+ public density(): number {
+ return this.colliderSet.raw.coDensity(this.handle);
+ }
+
+ /**
+ * The mass of this collider.
+ */
+ public mass(): number {
+ return this.colliderSet.raw.coMass(this.handle);
+ }
+
+ /**
+ * The volume of this collider.
+ */
+ public volume(): number {
+ return this.colliderSet.raw.coVolume(this.handle);
+ }
+
+ /**
+ * The collision groups of this collider.
+ */
+ public collisionGroups(): InteractionGroups {
+ return this.colliderSet.raw.coCollisionGroups(this.handle);
+ }
+
+ /**
+ * The solver groups of this collider.
+ */
+ public solverGroups(): InteractionGroups {
+ return this.colliderSet.raw.coSolverGroups(this.handle);
+ }
+
+ /**
+ * Tests if this collider contains a point.
+ *
+ * @param point - The point to test.
+ */
+ public containsPoint(point: Vector): boolean {
+ let rawPoint = VectorOps.intoRaw(point);
+ let result = this.colliderSet.raw.coContainsPoint(
+ this.handle,
+ rawPoint,
+ );
+
+ rawPoint.free();
+
+ return result;
+ }
+
+ /**
+ * Find the projection of a point on this collider.
+ *
+ * @param point - The point to project.
+ * @param solid - If this is set to `true` then the collider shapes are considered to
+ * be plain (if the point is located inside of a plain shape, its projection is the point
+ * itself). If it is set to `false` the collider shapes are considered to be hollow
+ * (if the point is located inside of an hollow shape, it is projected on the shape's
+ * boundary).
+ */
+ public projectPoint(point: Vector, solid: boolean): PointProjection | null {
+ let rawPoint = VectorOps.intoRaw(point);
+ let result = PointProjection.fromRaw(
+ this.colliderSet.raw.coProjectPoint(this.handle, rawPoint, solid),
+ );
+
+ rawPoint.free();
+
+ return result;
+ }
+
+ /**
+ * Tests if this collider intersects the given ray.
+ *
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ */
+ public intersectsRay(ray: Ray, maxToi: number): boolean {
+ let rawOrig = VectorOps.intoRaw(ray.origin);
+ let rawDir = VectorOps.intoRaw(ray.dir);
+ let result = this.colliderSet.raw.coIntersectsRay(
+ this.handle,
+ rawOrig,
+ rawDir,
+ maxToi,
+ );
+
+ rawOrig.free();
+ rawDir.free();
+
+ return result;
+ }
+
+ /*
+ * Computes the smallest time between this and the given shape under translational movement are separated by a distance smaller or equal to distance.
+ *
+ * @param collider1Vel - The constant velocity of the current shape to cast (i.e. the cast direction).
+ * @param shape2 - The shape to cast against.
+ * @param shape2Pos - The position of the second shape.
+ * @param shape2Rot - The rotation of the second shape.
+ * @param shape2Vel - The constant velocity of the second shape.
+ * @param targetDistance − If the shape moves closer to this distance from a collider, a hit
+ * will be returned.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the distance traveled by the shape to `collider1Vel.norm() * maxToi`.
+ * @param stopAtPenetration - If set to `false`, the linear shape-cast won’t immediately stop if
+ * the shape is penetrating another shape at its starting point **and** its trajectory is such
+ * that it’s on a path to exit that penetration state.
+ */
+ public castShape(
+ collider1Vel: Vector,
+ shape2: Shape,
+ shape2Pos: Vector,
+ shape2Rot: Rotation,
+ shape2Vel: Vector,
+ targetDistance: number,
+ maxToi: number,
+ stopAtPenetration: boolean,
+ ): ShapeCastHit | null {
+ let rawCollider1Vel = VectorOps.intoRaw(collider1Vel);
+ let rawShape2Pos = VectorOps.intoRaw(shape2Pos);
+ let rawShape2Rot = RotationOps.intoRaw(shape2Rot);
+ let rawShape2Vel = VectorOps.intoRaw(shape2Vel);
+ let rawShape2 = shape2.intoRaw();
+
+ let result = ShapeCastHit.fromRaw(
+ this.colliderSet,
+ this.colliderSet.raw.coCastShape(
+ this.handle,
+ rawCollider1Vel,
+ rawShape2,
+ rawShape2Pos,
+ rawShape2Rot,
+ rawShape2Vel,
+ targetDistance,
+ maxToi,
+ stopAtPenetration,
+ ),
+ );
+
+ rawCollider1Vel.free();
+ rawShape2Pos.free();
+ rawShape2Rot.free();
+ rawShape2Vel.free();
+ rawShape2.free();
+
+ return result;
+ }
+
+ /*
+ * Computes the smallest time between this and the given collider under translational movement are separated by a distance smaller or equal to distance.
+ *
+ * @param collider1Vel - The constant velocity of the current collider to cast (i.e. the cast direction).
+ * @param collider2 - The collider to cast against.
+ * @param collider2Vel - The constant velocity of the second collider.
+ * @param targetDistance − If the shape moves closer to this distance from a collider, a hit
+ * will be returned.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the distance traveled by the shape to `shapeVel.norm() * maxToi`.
+ * @param stopAtPenetration - If set to `false`, the linear shape-cast won’t immediately stop if
+ * the shape is penetrating another shape at its starting point **and** its trajectory is such
+ * that it’s on a path to exit that penetration state.
+ */
+ public castCollider(
+ collider1Vel: Vector,
+ collider2: Collider,
+ collider2Vel: Vector,
+ targetDistance: number,
+ maxToi: number,
+ stopAtPenetration: boolean,
+ ): ColliderShapeCastHit | null {
+ let rawCollider1Vel = VectorOps.intoRaw(collider1Vel);
+ let rawCollider2Vel = VectorOps.intoRaw(collider2Vel);
+
+ let result = ColliderShapeCastHit.fromRaw(
+ this.colliderSet,
+ this.colliderSet.raw.coCastCollider(
+ this.handle,
+ rawCollider1Vel,
+ collider2.handle,
+ rawCollider2Vel,
+ targetDistance,
+ maxToi,
+ stopAtPenetration,
+ ),
+ );
+
+ rawCollider1Vel.free();
+ rawCollider2Vel.free();
+
+ return result;
+ }
+
+ public intersectsShape(
+ shape2: Shape,
+ shapePos2: Vector,
+ shapeRot2: Rotation,
+ ): boolean {
+ let rawPos2 = VectorOps.intoRaw(shapePos2);
+ let rawRot2 = RotationOps.intoRaw(shapeRot2);
+ let rawShape2 = shape2.intoRaw();
+
+ let result = this.colliderSet.raw.coIntersectsShape(
+ this.handle,
+ rawShape2,
+ rawPos2,
+ rawRot2,
+ );
+
+ rawPos2.free();
+ rawRot2.free();
+ rawShape2.free();
+
+ return result;
+ }
+
+ /**
+ * Computes one pair of contact points between the shape owned by this collider and the given shape.
+ *
+ * @param shape2 - The second shape.
+ * @param shape2Pos - The initial position of the second shape.
+ * @param shape2Rot - The rotation of the second shape.
+ * @param prediction - The prediction value, if the shapes are separated by a distance greater than this value, test will fail.
+ * @returns `null` if the shapes are separated by a distance greater than prediction, otherwise contact details. The result is given in world-space.
+ */
+ contactShape(
+ shape2: Shape,
+ shape2Pos: Vector,
+ shape2Rot: Rotation,
+ prediction: number,
+ ): ShapeContact | null {
+ let rawPos2 = VectorOps.intoRaw(shape2Pos);
+ let rawRot2 = RotationOps.intoRaw(shape2Rot);
+ let rawShape2 = shape2.intoRaw();
+
+ let result = ShapeContact.fromRaw(
+ this.colliderSet.raw.coContactShape(
+ this.handle,
+ rawShape2,
+ rawPos2,
+ rawRot2,
+ prediction,
+ ),
+ );
+
+ rawPos2.free();
+ rawRot2.free();
+ rawShape2.free();
+
+ return result;
+ }
+
+ /**
+ * Computes one pair of contact points between the collider and the given collider.
+ *
+ * @param collider2 - The second collider.
+ * @param prediction - The prediction value, if the shapes are separated by a distance greater than this value, test will fail.
+ * @returns `null` if the shapes are separated by a distance greater than prediction, otherwise contact details. The result is given in world-space.
+ */
+ contactCollider(
+ collider2: Collider,
+ prediction: number,
+ ): ShapeContact | null {
+ let result = ShapeContact.fromRaw(
+ this.colliderSet.raw.coContactCollider(
+ this.handle,
+ collider2.handle,
+ prediction,
+ ),
+ );
+
+ return result;
+ }
+
+ /**
+ * Find the closest intersection between a ray and this collider.
+ *
+ * This also computes the normal at the hit point.
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ * @returns The time-of-impact between this collider and the ray, or `-1` if there is no intersection.
+ */
+ public castRay(ray: Ray, maxToi: number, solid: boolean): number {
+ let rawOrig = VectorOps.intoRaw(ray.origin);
+ let rawDir = VectorOps.intoRaw(ray.dir);
+ let result = this.colliderSet.raw.coCastRay(
+ this.handle,
+ rawOrig,
+ rawDir,
+ maxToi,
+ solid,
+ );
+
+ rawOrig.free();
+ rawDir.free();
+
+ return result;
+ }
+
+ /**
+ * Find the closest intersection between a ray and this collider.
+ *
+ * This also computes the normal at the hit point.
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ */
+ public castRayAndGetNormal(
+ ray: Ray,
+ maxToi: number,
+ solid: boolean,
+ ): RayIntersection | null {
+ let rawOrig = VectorOps.intoRaw(ray.origin);
+ let rawDir = VectorOps.intoRaw(ray.dir);
+ let result = RayIntersection.fromRaw(
+ this.colliderSet.raw.coCastRayAndGetNormal(
+ this.handle,
+ rawOrig,
+ rawDir,
+ maxToi,
+ solid,
+ ),
+ );
+
+ rawOrig.free();
+ rawDir.free();
+
+ return result;
+ }
+}
+
+export enum MassPropsMode {
+ Density,
+ Mass,
+ MassProps,
+}
+
+export class ColliderDesc {
+ enabled: boolean;
+ shape: Shape;
+ massPropsMode: MassPropsMode;
+ mass: number;
+ centerOfMass: Vector;
+ // #if DIM2
+ principalAngularInertia: number;
+ rotationsEnabled: boolean;
+ // #endif
+ // #if DIM3
+ principalAngularInertia: Vector;
+ angularInertiaLocalFrame: Rotation;
+ // #endif
+ density: number;
+ friction: number;
+ restitution: number;
+ rotation: Rotation;
+ translation: Vector;
+ isSensor: boolean;
+ collisionGroups: InteractionGroups;
+ solverGroups: InteractionGroups;
+ frictionCombineRule: CoefficientCombineRule;
+ restitutionCombineRule: CoefficientCombineRule;
+ activeEvents: ActiveEvents;
+ activeHooks: ActiveHooks;
+ activeCollisionTypes: ActiveCollisionTypes;
+ contactForceEventThreshold: number;
+ contactSkin: number;
+
+ /**
+ * Initializes a collider descriptor from the collision shape.
+ *
+ * @param shape - The shape of the collider being built.
+ */
+ constructor(shape: Shape) {
+ this.enabled = true;
+ this.shape = shape;
+ this.massPropsMode = MassPropsMode.Density;
+ this.density = 1.0;
+ this.friction = 0.5;
+ this.restitution = 0.0;
+ this.rotation = RotationOps.identity();
+ this.translation = VectorOps.zeros();
+ this.isSensor = false;
+ this.collisionGroups = 0xffff_ffff;
+ this.solverGroups = 0xffff_ffff;
+ this.frictionCombineRule = CoefficientCombineRule.Average;
+ this.restitutionCombineRule = CoefficientCombineRule.Average;
+ this.activeCollisionTypes = ActiveCollisionTypes.DEFAULT;
+ this.activeEvents = ActiveEvents.NONE;
+ this.activeHooks = ActiveHooks.NONE;
+ this.mass = 0.0;
+ this.centerOfMass = VectorOps.zeros();
+ this.contactForceEventThreshold = 0.0;
+ this.contactSkin = 0.0;
+
+ // #if DIM2
+ this.principalAngularInertia = 0.0;
+ this.rotationsEnabled = true;
+ // #endif
+ // #if DIM3
+ this.principalAngularInertia = VectorOps.zeros();
+ this.angularInertiaLocalFrame = RotationOps.identity();
+ // #endif
+ }
+
+ /**
+ * Create a new collider descriptor with a ball shape.
+ *
+ * @param radius - The radius of the ball.
+ */
+ public static ball(radius: number): ColliderDesc {
+ const shape = new Ball(radius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Create a new collider descriptor with a capsule shape.
+ *
+ * @param halfHeight - The half-height of the capsule, along the `y` axis.
+ * @param radius - The radius of the capsule basis.
+ */
+ public static capsule(halfHeight: number, radius: number): ColliderDesc {
+ const shape = new Capsule(halfHeight, radius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new segment shape.
+ *
+ * @param a - The first point of the segment.
+ * @param b - The second point of the segment.
+ */
+ public static segment(a: Vector, b: Vector): ColliderDesc {
+ const shape = new Segment(a, b);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new triangle shape.
+ *
+ * @param a - The first point of the triangle.
+ * @param b - The second point of the triangle.
+ * @param c - The third point of the triangle.
+ */
+ public static triangle(a: Vector, b: Vector, c: Vector): ColliderDesc {
+ const shape = new Triangle(a, b, c);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new triangle shape with round corners.
+ *
+ * @param a - The first point of the triangle.
+ * @param b - The second point of the triangle.
+ * @param c - The third point of the triangle.
+ * @param borderRadius - The radius of the borders of this triangle. In 3D,
+ * this is also equal to half the thickness of the triangle.
+ */
+ public static roundTriangle(
+ a: Vector,
+ b: Vector,
+ c: Vector,
+ borderRadius: number,
+ ): ColliderDesc {
+ const shape = new RoundTriangle(a, b, c, borderRadius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor with a polyline shape.
+ *
+ * @param vertices - The coordinates of the polyline's vertices.
+ * @param indices - The indices of the polyline's segments. If this is `undefined` or `null`,
+ * the vertices are assumed to describe a line strip.
+ */
+ public static polyline(
+ vertices: Float32Array,
+ indices?: Uint32Array | null,
+ ): ColliderDesc {
+ const shape = new Polyline(vertices, indices);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor with a shape made of voxels.
+ *
+ * @param data - Defines the set of voxels. If this is a `Int32Array` then
+ * each voxel is defined from its (signed) grid coordinates,
+ * with 3 (resp 2) contiguous integers per voxel in 3D (resp 2D).
+ * If this is a `Float32Array`, each voxel will be such that
+ * they contain at least one point from this array (where each
+ * point is defined from 3 (resp 2) contiguous numbers per point
+ * in 3D (resp 2D).
+ * @param voxelSize - The size of each voxel.
+ */
+ public static voxels(
+ voxels: Float32Array | Int32Array,
+ voxelSize: Vector,
+ ): ColliderDesc {
+ const shape = new Voxels(voxels, voxelSize);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor with a triangle mesh shape.
+ *
+ * @param vertices - The coordinates of the triangle mesh's vertices.
+ * @param indices - The indices of the triangle mesh's triangles.
+ */
+ public static trimesh(
+ vertices: Float32Array,
+ indices: Uint32Array,
+ flags?: TriMeshFlags,
+ ): ColliderDesc {
+ const shape = new TriMesh(vertices, indices, flags);
+ return new ColliderDesc(shape);
+ }
+
+ // #if DIM2
+ /**
+ * Creates a new collider descriptor with a rectangular shape.
+ *
+ * @param hx - The half-width of the rectangle along its local `x` axis.
+ * @param hy - The half-width of the rectangle along its local `y` axis.
+ */
+ public static cuboid(hx: number, hy: number): ColliderDesc {
+ const shape = new Cuboid(hx, hy);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor with a rectangular shape with round borders.
+ *
+ * @param hx - The half-width of the rectangle along its local `x` axis.
+ * @param hy - The half-width of the rectangle along its local `y` axis.
+ * @param borderRadius - The radius of the cuboid's borders.
+ */
+ public static roundCuboid(
+ hx: number,
+ hy: number,
+ borderRadius: number,
+ ): ColliderDesc {
+ const shape = new RoundCuboid(hx, hy, borderRadius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider description with a halfspace (infinite plane) shape.
+ *
+ * @param normal - The outward normal of the plane.
+ */
+ public static halfspace(normal: Vector): ColliderDesc {
+ const shape = new HalfSpace(normal);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor with a heightfield shape.
+ *
+ * @param heights - The heights of the heightfield, along its local `y` axis.
+ * @param scale - The scale factor applied to the heightfield.
+ */
+ public static heightfield(
+ heights: Float32Array,
+ scale: Vector,
+ ): ColliderDesc {
+ const shape = new Heightfield(heights, scale);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Computes the convex-hull of the given points and use the resulting
+ * convex polygon as the shape for this new collider descriptor.
+ *
+ * @param points - The point that will be used to compute the convex-hull.
+ */
+ public static convexHull(points: Float32Array): ColliderDesc | null {
+ const shape = new ConvexPolygon(points, false);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor that uses the given set of points assumed
+ * to form a convex polyline (no convex-hull computation will be done).
+ *
+ * @param vertices - The vertices of the convex polyline.
+ */
+ public static convexPolyline(vertices: Float32Array): ColliderDesc | null {
+ const shape = new ConvexPolygon(vertices, true);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Computes the convex-hull of the given points and use the resulting
+ * convex polygon as the shape for this new collider descriptor. A
+ * border is added to that convex polygon to give it round corners.
+ *
+ * @param points - The point that will be used to compute the convex-hull.
+ * @param borderRadius - The radius of the round border added to the convex polygon.
+ */
+ public static roundConvexHull(
+ points: Float32Array,
+ borderRadius: number,
+ ): ColliderDesc | null {
+ const shape = new RoundConvexPolygon(points, borderRadius, false);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor that uses the given set of points assumed
+ * to form a round convex polyline (no convex-hull computation will be done).
+ *
+ * @param vertices - The vertices of the convex polyline.
+ * @param borderRadius - The radius of the round border added to the convex polyline.
+ */
+ public static roundConvexPolyline(
+ vertices: Float32Array,
+ borderRadius: number,
+ ): ColliderDesc | null {
+ const shape = new RoundConvexPolygon(vertices, borderRadius, true);
+ return new ColliderDesc(shape);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Creates a new collider descriptor with a cuboid shape.
+ *
+ * @param hx - The half-width of the rectangle along its local `x` axis.
+ * @param hy - The half-width of the rectangle along its local `y` axis.
+ * @param hz - The half-width of the rectangle along its local `z` axis.
+ */
+ public static cuboid(hx: number, hy: number, hz: number): ColliderDesc {
+ const shape = new Cuboid(hx, hy, hz);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor with a rectangular shape with round borders.
+ *
+ * @param hx - The half-width of the rectangle along its local `x` axis.
+ * @param hy - The half-width of the rectangle along its local `y` axis.
+ * @param hz - The half-width of the rectangle along its local `z` axis.
+ * @param borderRadius - The radius of the cuboid's borders.
+ */
+ public static roundCuboid(
+ hx: number,
+ hy: number,
+ hz: number,
+ borderRadius: number,
+ ): ColliderDesc {
+ const shape = new RoundCuboid(hx, hy, hz, borderRadius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor with a heightfield shape.
+ *
+ * @param nrows − The number of rows in the heights matrix.
+ * @param ncols - The number of columns in the heights matrix.
+ * @param heights - The heights of the heightfield along its local `y` axis,
+ * provided as a matrix stored in column-major order.
+ * @param scale - The scale factor applied to the heightfield.
+ */
+ public static heightfield(
+ nrows: number,
+ ncols: number,
+ heights: Float32Array,
+ scale: Vector,
+ flags?: HeightFieldFlags,
+ ): ColliderDesc {
+ const shape = new Heightfield(nrows, ncols, heights, scale, flags);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Create a new collider descriptor with a cylinder shape.
+ *
+ * @param halfHeight - The half-height of the cylinder, along the `y` axis.
+ * @param radius - The radius of the cylinder basis.
+ */
+ public static cylinder(halfHeight: number, radius: number): ColliderDesc {
+ const shape = new Cylinder(halfHeight, radius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Create a new collider descriptor with a cylinder shape with rounded corners.
+ *
+ * @param halfHeight - The half-height of the cylinder, along the `y` axis.
+ * @param radius - The radius of the cylinder basis.
+ * @param borderRadius - The radius of the cylinder's rounded edges and vertices.
+ */
+ public static roundCylinder(
+ halfHeight: number,
+ radius: number,
+ borderRadius: number,
+ ): ColliderDesc {
+ const shape = new RoundCylinder(halfHeight, radius, borderRadius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Create a new collider descriptor with a cone shape.
+ *
+ * @param halfHeight - The half-height of the cone, along the `y` axis.
+ * @param radius - The radius of the cone basis.
+ */
+ public static cone(halfHeight: number, radius: number): ColliderDesc {
+ const shape = new Cone(halfHeight, radius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Create a new collider descriptor with a cone shape with rounded corners.
+ *
+ * @param halfHeight - The half-height of the cone, along the `y` axis.
+ * @param radius - The radius of the cone basis.
+ * @param borderRadius - The radius of the cone's rounded edges and vertices.
+ */
+ public static roundCone(
+ halfHeight: number,
+ radius: number,
+ borderRadius: number,
+ ): ColliderDesc {
+ const shape = new RoundCone(halfHeight, radius, borderRadius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Computes the convex-hull of the given points and use the resulting
+ * convex polyhedron as the shape for this new collider descriptor.
+ *
+ * @param points - The point that will be used to compute the convex-hull.
+ */
+ public static convexHull(points: Float32Array): ColliderDesc | null {
+ const shape = new ConvexPolyhedron(points, null);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor that uses the given set of points assumed
+ * to form a convex polyline (no convex-hull computation will be done).
+ *
+ * @param vertices - The vertices of the convex polyline.
+ */
+ public static convexMesh(
+ vertices: Float32Array,
+ indices?: Uint32Array | null,
+ ): ColliderDesc | null {
+ const shape = new ConvexPolyhedron(vertices, indices);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Computes the convex-hull of the given points and use the resulting
+ * convex polyhedron as the shape for this new collider descriptor. A
+ * border is added to that convex polyhedron to give it round corners.
+ *
+ * @param points - The point that will be used to compute the convex-hull.
+ * @param borderRadius - The radius of the round border added to the convex polyhedron.
+ */
+ public static roundConvexHull(
+ points: Float32Array,
+ borderRadius: number,
+ ): ColliderDesc | null {
+ const shape = new RoundConvexPolyhedron(points, null, borderRadius);
+ return new ColliderDesc(shape);
+ }
+
+ /**
+ * Creates a new collider descriptor that uses the given set of points assumed
+ * to form a round convex polyline (no convex-hull computation will be done).
+ *
+ * @param vertices - The vertices of the convex polyline.
+ * @param borderRadius - The radius of the round border added to the convex polyline.
+ */
+ public static roundConvexMesh(
+ vertices: Float32Array,
+ indices: Uint32Array | null,
+ borderRadius: number,
+ ): ColliderDesc | null {
+ const shape = new RoundConvexPolyhedron(
+ vertices,
+ indices,
+ borderRadius,
+ );
+ return new ColliderDesc(shape);
+ }
+
+ // #endif
+
+ // #if DIM2
+ /**
+ * Sets the position of the collider to be created relative to the rigid-body it is attached to.
+ */
+ public setTranslation(x: number, y: number): ColliderDesc {
+ if (typeof x != "number" || typeof y != "number")
+ throw TypeError("The translation components must be numbers.");
+
+ this.translation = {x: x, y: y};
+ return this;
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Sets the position of the collider to be created relative to the rigid-body it is attached to.
+ */
+ public setTranslation(x: number, y: number, z: number): ColliderDesc {
+ if (
+ typeof x != "number" ||
+ typeof y != "number" ||
+ typeof z != "number"
+ )
+ throw TypeError("The translation components must be numbers.");
+
+ this.translation = {x: x, y: y, z: z};
+ return this;
+ }
+
+ // #endif
+
+ /**
+ * Sets the rotation of the collider to be created relative to the rigid-body it is attached to.
+ *
+ * @param rot - The rotation of the collider to be created relative to the rigid-body it is attached to.
+ */
+ public setRotation(rot: Rotation): ColliderDesc {
+ // #if DIM2
+ this.rotation = rot;
+ // #endif
+ // #if DIM3
+ RotationOps.copy(this.rotation, rot);
+ // #endif
+ return this;
+ }
+
+ /**
+ * Sets whether or not the collider being created is a sensor.
+ *
+ * A sensor collider does not take part of the physics simulation, but generates
+ * proximity events.
+ *
+ * @param sensor - Set to `true` of the collider built is to be a sensor.
+ */
+ public setSensor(sensor: boolean): ColliderDesc {
+ this.isSensor = sensor;
+ return this;
+ }
+
+ /**
+ * Sets whether the created collider will be enabled or disabled.
+ * @param enabled − If set to `false` the collider will be disabled at creation.
+ */
+ public setEnabled(enabled: boolean): ColliderDesc {
+ this.enabled = enabled;
+ return this;
+ }
+
+ /**
+ * Sets the contact skin of the collider.
+ *
+ * The contact skin acts as if the collider was enlarged with a skin of width `skin_thickness`
+ * around it, keeping objects further apart when colliding.
+ *
+ * A non-zero contact skin can increase performance, and in some cases, stability. However
+ * it creates a small gap between colliding object (equal to the sum of their skin). If the
+ * skin is sufficiently small, this might not be visually significant or can be hidden by the
+ * rendering assets.
+ */
+ public setContactSkin(thickness: number): ColliderDesc {
+ this.contactSkin = thickness;
+ return this;
+ }
+
+ /**
+ * Sets the density of the collider being built.
+ *
+ * The mass and angular inertia tensor will be computed automatically based on this density and the collider’s shape.
+ *
+ * @param density - The density to set, must be greater or equal to 0. A density of 0 means that this collider
+ * will not affect the mass or angular inertia of the rigid-body it is attached to.
+ */
+ public setDensity(density: number): ColliderDesc {
+ this.massPropsMode = MassPropsMode.Density;
+ this.density = density;
+ return this;
+ }
+
+ /**
+ * Sets the mass of the collider being built.
+ *
+ * The angular inertia tensor will be computed automatically based on this mass and the collider’s shape.
+ *
+ * @param mass - The mass to set, must be greater or equal to 0.
+ */
+ public setMass(mass: number): ColliderDesc {
+ this.massPropsMode = MassPropsMode.Mass;
+ this.mass = mass;
+ return this;
+ }
+
+ // #if DIM2
+ /**
+ * Sets the mass properties of the collider being built.
+ *
+ * This replaces the mass-properties automatically computed from the collider's density and shape.
+ * These mass-properties will be added to the mass-properties of the rigid-body this collider will be attached to.
+ *
+ * @param mass − The mass of the collider to create.
+ * @param centerOfMass − The center-of-mass of the collider to create.
+ * @param principalAngularInertia − The principal angular inertia of the collider to create.
+ */
+ public setMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: number,
+ ): ColliderDesc {
+ this.massPropsMode = MassPropsMode.MassProps;
+ this.mass = mass;
+ VectorOps.copy(this.centerOfMass, centerOfMass);
+ this.principalAngularInertia = principalAngularInertia;
+ return this;
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Sets the mass properties of the collider being built.
+ *
+ * This replaces the mass-properties automatically computed from the collider's density and shape.
+ * These mass-properties will be added to the mass-properties of the rigid-body this collider will be attached to.
+ *
+ * @param mass − The mass of the collider to create.
+ * @param centerOfMass − The center-of-mass of the collider to create.
+ * @param principalAngularInertia − The initial principal angular inertia of the collider to create.
+ * These are the eigenvalues of the angular inertia matrix.
+ * @param angularInertiaLocalFrame − The initial local angular inertia frame of the collider to create.
+ * These are the eigenvectors of the angular inertia matrix.
+ */
+ public setMassProperties(
+ mass: number,
+ centerOfMass: Vector,
+ principalAngularInertia: Vector,
+ angularInertiaLocalFrame: Rotation,
+ ): ColliderDesc {
+ this.massPropsMode = MassPropsMode.MassProps;
+ this.mass = mass;
+ VectorOps.copy(this.centerOfMass, centerOfMass);
+ VectorOps.copy(this.principalAngularInertia, principalAngularInertia);
+ RotationOps.copy(
+ this.angularInertiaLocalFrame,
+ angularInertiaLocalFrame,
+ );
+ return this;
+ }
+
+ // #endif
+
+ /**
+ * Sets the restitution coefficient of the collider to be created.
+ *
+ * @param restitution - The restitution coefficient in `[0, 1]`. A value of 0 (the default) means no bouncing behavior
+ * while 1 means perfect bouncing (though energy may still be lost due to numerical errors of the
+ * constraints solver).
+ */
+ public setRestitution(restitution: number): ColliderDesc {
+ this.restitution = restitution;
+ return this;
+ }
+
+ /**
+ * Sets the friction coefficient of the collider to be created.
+ *
+ * @param friction - The friction coefficient. Must be greater or equal to 0. This is generally smaller than 1. The
+ * higher the coefficient, the stronger friction forces will be for contacts with the collider
+ * being built.
+ */
+ public setFriction(friction: number): ColliderDesc {
+ this.friction = friction;
+ return this;
+ }
+
+ /**
+ * Sets the rule used to combine the friction coefficients of two colliders
+ * colliders involved in a contact.
+ *
+ * @param rule − The combine rule to apply.
+ */
+ public setFrictionCombineRule(rule: CoefficientCombineRule): ColliderDesc {
+ this.frictionCombineRule = rule;
+ return this;
+ }
+
+ /**
+ * Sets the rule used to combine the restitution coefficients of two colliders
+ * colliders involved in a contact.
+ *
+ * @param rule − The combine rule to apply.
+ */
+ public setRestitutionCombineRule(
+ rule: CoefficientCombineRule,
+ ): ColliderDesc {
+ this.restitutionCombineRule = rule;
+ return this;
+ }
+
+ /**
+ * Sets the collision groups used by this collider.
+ *
+ * Two colliders will interact iff. their collision groups are compatible.
+ * See the documentation of `InteractionGroups` for details on teh used bit pattern.
+ *
+ * @param groups - The collision groups used for the collider being built.
+ */
+ public setCollisionGroups(groups: InteractionGroups): ColliderDesc {
+ this.collisionGroups = groups;
+ return this;
+ }
+
+ /**
+ * Sets the solver groups used by this collider.
+ *
+ * Forces between two colliders in contact will be computed iff their solver
+ * groups are compatible.
+ * See the documentation of `InteractionGroups` for details on the used bit pattern.
+ *
+ * @param groups - The solver groups used for the collider being built.
+ */
+ public setSolverGroups(groups: InteractionGroups): ColliderDesc {
+ this.solverGroups = groups;
+ return this;
+ }
+
+ /**
+ * Set the physics hooks active for this collider.
+ *
+ * Use this to enable custom filtering rules for contact/intersecstion pairs involving this collider.
+ *
+ * @param activeHooks - The hooks active for contact/intersection pairs involving this collider.
+ */
+ public setActiveHooks(activeHooks: ActiveHooks): ColliderDesc {
+ this.activeHooks = activeHooks;
+ return this;
+ }
+
+ /**
+ * Set the events active for this collider.
+ *
+ * Use this to enable contact and/or intersection event reporting for this collider.
+ *
+ * @param activeEvents - The events active for contact/intersection pairs involving this collider.
+ */
+ public setActiveEvents(activeEvents: ActiveEvents): ColliderDesc {
+ this.activeEvents = activeEvents;
+ return this;
+ }
+
+ /**
+ * Set the collision types active for this collider.
+ *
+ * @param activeCollisionTypes - The hooks active for contact/intersection pairs involving this collider.
+ */
+ public setActiveCollisionTypes(
+ activeCollisionTypes: ActiveCollisionTypes,
+ ): ColliderDesc {
+ this.activeCollisionTypes = activeCollisionTypes;
+ return this;
+ }
+
+ /**
+ * Sets the total force magnitude beyond which a contact force event can be emitted.
+ *
+ * @param threshold - The force threshold to set.
+ */
+ public setContactForceEventThreshold(threshold: number): ColliderDesc {
+ this.contactForceEventThreshold = threshold;
+ return this;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/geometry/collider_set.ts b/thirdparty/rapier.js/src.ts/geometry/collider_set.ts
new file mode 100644
index 00000000..832b837a
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/collider_set.ts
@@ -0,0 +1,213 @@
+import {RawColliderSet} from "../raw";
+import {Coarena} from "../coarena";
+import {RotationOps, VectorOps} from "../math";
+import {Collider, ColliderDesc, ColliderHandle} from "./collider";
+import {ImpulseJointHandle, IslandManager, RigidBodyHandle} from "../dynamics";
+import {RigidBodySet} from "../dynamics";
+
+/**
+ * A set of rigid bodies that can be handled by a physics pipeline.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `colliderSet.free()`
+ * once you are done using it (and all the rigid-bodies it created).
+ */
+export class ColliderSet {
+ raw: RawColliderSet;
+ private map: Coarena;
+
+ /**
+ * Release the WASM memory occupied by this collider set.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+
+ if (!!this.map) {
+ this.map.clear();
+ }
+ this.map = undefined;
+ }
+
+ constructor(raw?: RawColliderSet) {
+ this.raw = raw || new RawColliderSet();
+ this.map = new Coarena();
+ // Initialize the map with the existing elements, if any.
+ if (raw) {
+ raw.forEachColliderHandle((handle: ColliderHandle) => {
+ this.map.set(handle, new Collider(this, handle, null));
+ });
+ }
+ }
+
+ /** @internal */
+ public castClosure(
+ f?: (collider: Collider) => Res,
+ ): (handle: ColliderHandle) => Res | undefined {
+ return (handle) => {
+ if (!!f) {
+ return f(this.get(handle));
+ } else {
+ return undefined;
+ }
+ };
+ }
+
+ /** @internal */
+ public finalizeDeserialization(bodies: RigidBodySet) {
+ this.map.forEach((collider) =>
+ collider.finalizeDeserialization(bodies),
+ );
+ }
+
+ /**
+ * Creates a new collider and return its integer handle.
+ *
+ * @param bodies - The set of bodies where the collider's parent can be found.
+ * @param desc - The collider's description.
+ * @param parentHandle - The integer handle of the rigid-body this collider is attached to.
+ */
+ public createCollider(
+ bodies: RigidBodySet,
+ desc: ColliderDesc,
+ parentHandle: RigidBodyHandle,
+ ): Collider {
+ let hasParent = parentHandle != undefined && parentHandle != null;
+
+ if (hasParent && isNaN(parentHandle))
+ throw Error(
+ "Cannot create a collider with a parent rigid-body handle that is not a number.",
+ );
+
+ let rawShape = desc.shape.intoRaw();
+ let rawTra = VectorOps.intoRaw(desc.translation);
+ let rawRot = RotationOps.intoRaw(desc.rotation);
+ let rawCom = VectorOps.intoRaw(desc.centerOfMass);
+
+ // #if DIM3
+ let rawPrincipalInertia = VectorOps.intoRaw(
+ desc.principalAngularInertia,
+ );
+ let rawInertiaFrame = RotationOps.intoRaw(
+ desc.angularInertiaLocalFrame,
+ );
+ // #endif
+
+ let handle = this.raw.createCollider(
+ desc.enabled,
+ rawShape,
+ rawTra,
+ rawRot,
+ desc.massPropsMode,
+ desc.mass,
+ rawCom,
+ // #if DIM2
+ desc.principalAngularInertia,
+ // #endif
+ // #if DIM3
+ rawPrincipalInertia,
+ rawInertiaFrame,
+ // #endif
+ desc.density,
+ desc.friction,
+ desc.restitution,
+ desc.frictionCombineRule,
+ desc.restitutionCombineRule,
+ desc.isSensor,
+ desc.collisionGroups,
+ desc.solverGroups,
+ desc.activeCollisionTypes,
+ desc.activeHooks,
+ desc.activeEvents,
+ desc.contactForceEventThreshold,
+ desc.contactSkin,
+ hasParent,
+ hasParent ? parentHandle : 0,
+ bodies.raw,
+ );
+
+ rawShape.free();
+ rawTra.free();
+ rawRot.free();
+ rawCom.free();
+
+ // #if DIM3
+ rawPrincipalInertia.free();
+ rawInertiaFrame.free();
+ // #endif
+
+ let parent = hasParent ? bodies.get(parentHandle) : null;
+ let collider = new Collider(this, handle, parent, desc.shape);
+ this.map.set(handle, collider);
+ return collider;
+ }
+
+ /**
+ * Remove a collider from this set.
+ *
+ * @param handle - The integer handle of the collider to remove.
+ * @param bodies - The set of rigid-body containing the rigid-body the collider is attached to.
+ * @param wakeUp - If `true`, the rigid-body the removed collider is attached to will be woken-up automatically.
+ */
+ public remove(
+ handle: ColliderHandle,
+ islands: IslandManager,
+ bodies: RigidBodySet,
+ wakeUp: boolean,
+ ) {
+ this.raw.remove(handle, islands.raw, bodies.raw, wakeUp);
+ this.unmap(handle);
+ }
+
+ /**
+ * Internal function, do not call directly.
+ * @param handle
+ */
+ public unmap(handle: ImpulseJointHandle) {
+ this.map.delete(handle);
+ }
+
+ /**
+ * Gets the rigid-body with the given handle.
+ *
+ * @param handle - The handle of the rigid-body to retrieve.
+ */
+ public get(handle: ColliderHandle): Collider | null {
+ return this.map.get(handle);
+ }
+
+ /**
+ * The number of colliders on this set.
+ */
+ public len(): number {
+ return this.map.len();
+ }
+
+ /**
+ * Does this set contain a collider with the given handle?
+ *
+ * @param handle - The collider handle to check.
+ */
+ public contains(handle: ColliderHandle): boolean {
+ return this.get(handle) != null;
+ }
+
+ /**
+ * Applies the given closure to each collider contained by this set.
+ *
+ * @param f - The closure to apply.
+ */
+ public forEach(f: (collider: Collider) => void) {
+ this.map.forEach(f);
+ }
+
+ /**
+ * Gets all colliders in the list.
+ *
+ * @returns collider list.
+ */
+ public getAll(): Collider[] {
+ return this.map.getAll();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/geometry/contact.ts b/thirdparty/rapier.js/src.ts/geometry/contact.ts
new file mode 100644
index 00000000..802f57f4
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/contact.ts
@@ -0,0 +1,62 @@
+import {Vector, VectorOps} from "../math";
+import {RawShapeContact} from "../raw";
+
+/**
+ * The contact info between two shapes.
+ */
+export class ShapeContact {
+ /**
+ * Distance between the two contact points.
+ * If this is negative, this contact represents a penetration.
+ */
+ distance: number;
+
+ /**
+ * Position of the contact on the first shape.
+ */
+ point1: Vector;
+
+ /**
+ * Position of the contact on the second shape.
+ */
+ point2: Vector;
+
+ /**
+ * Contact normal, pointing towards the exterior of the first shape.
+ */
+ normal1: Vector;
+
+ /**
+ * Contact normal, pointing towards the exterior of the second shape.
+ * If these contact data are expressed in world-space, this normal is equal to -normal1.
+ */
+ normal2: Vector;
+
+ constructor(
+ dist: number,
+ point1: Vector,
+ point2: Vector,
+ normal1: Vector,
+ normal2: Vector,
+ ) {
+ this.distance = dist;
+ this.point1 = point1;
+ this.point2 = point2;
+ this.normal1 = normal1;
+ this.normal2 = normal2;
+ }
+
+ public static fromRaw(raw: RawShapeContact): ShapeContact {
+ if (!raw) return null;
+
+ const result = new ShapeContact(
+ raw.distance(),
+ VectorOps.fromRaw(raw.point1()),
+ VectorOps.fromRaw(raw.point2()),
+ VectorOps.fromRaw(raw.normal1()),
+ VectorOps.fromRaw(raw.normal2()),
+ );
+ raw.free();
+ return result;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/geometry/feature.ts b/thirdparty/rapier.js/src.ts/geometry/feature.ts
new file mode 100644
index 00000000..bec9560d
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/feature.ts
@@ -0,0 +1,16 @@
+// #if DIM2
+export enum FeatureType {
+ Vertex,
+ Face,
+ Unknown,
+}
+// #endif
+
+// #if DIM3
+export enum FeatureType {
+ Vertex,
+ Edge,
+ Face,
+ Unknown,
+}
+// #endif
diff --git a/thirdparty/rapier.js/src.ts/geometry/index.ts b/thirdparty/rapier.js/src.ts/geometry/index.ts
new file mode 100644
index 00000000..1bd19e50
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/index.ts
@@ -0,0 +1,11 @@
+export * from "./broad_phase";
+export * from "./narrow_phase";
+export * from "./shape";
+export * from "./collider";
+export * from "./collider_set";
+export * from "./feature";
+export * from "./ray";
+export * from "./point";
+export * from "./toi";
+export * from "./interaction_groups";
+export * from "./contact";
diff --git a/thirdparty/rapier.js/src.ts/geometry/interaction_groups.ts b/thirdparty/rapier.js/src.ts/geometry/interaction_groups.ts
new file mode 100644
index 00000000..4d73fd31
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/interaction_groups.ts
@@ -0,0 +1,18 @@
+/**
+ * Pairwise filtering using bit masks.
+ *
+ * This filtering method is based on two 16-bit values:
+ * - The interaction groups (the 16 left-most bits of `self.0`).
+ * - The interaction mask (the 16 right-most bits of `self.0`).
+ *
+ * An interaction is allowed between two filters `a` and `b` two conditions
+ * are met simultaneously:
+ * - The interaction groups of `a` has at least one bit set to `1` in common with the interaction mask of `b`.
+ * - The interaction groups of `b` has at least one bit set to `1` in common with the interaction mask of `a`.
+ * In other words, interactions are allowed between two filter iff. the following condition is met:
+ *
+ * ```
+ * ((a >> 16) & b) != 0 && ((b >> 16) & a) != 0
+ * ```
+ */
+export type InteractionGroups = number;
diff --git a/thirdparty/rapier.js/src.ts/geometry/narrow_phase.ts b/thirdparty/rapier.js/src.ts/geometry/narrow_phase.ts
new file mode 100644
index 00000000..af04a227
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/narrow_phase.ts
@@ -0,0 +1,203 @@
+import {RawNarrowPhase, RawContactManifold} from "../raw";
+import {ColliderHandle} from "./collider";
+import {Vector, VectorOps} from "../math";
+
+/**
+ * The narrow-phase used for precise collision-detection.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `narrowPhase.free()`
+ * once you are done using it.
+ */
+export class NarrowPhase {
+ raw: RawNarrowPhase;
+ tempManifold: TempContactManifold;
+
+ /**
+ * Release the WASM memory occupied by this narrow-phase.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ constructor(raw?: RawNarrowPhase) {
+ this.raw = raw || new RawNarrowPhase();
+ this.tempManifold = new TempContactManifold(null);
+ }
+
+ /**
+ * Enumerates all the colliders potentially in contact with the given collider.
+ *
+ * @param collider1 - The second collider involved in the contact.
+ * @param f - Closure that will be called on each collider that is in contact with `collider1`.
+ */
+ public contactPairsWith(
+ collider1: ColliderHandle,
+ f: (collider2: ColliderHandle) => void,
+ ) {
+ this.raw.contact_pairs_with(collider1, f);
+ }
+
+ /**
+ * Enumerates all the colliders intersecting the given colliders, assuming one of them
+ * is a sensor.
+ */
+ public intersectionPairsWith(
+ collider1: ColliderHandle,
+ f: (collider2: ColliderHandle) => void,
+ ) {
+ this.raw.intersection_pairs_with(collider1, f);
+ }
+
+ /**
+ * Iterates through all the contact manifolds between the given pair of colliders.
+ *
+ * @param collider1 - The first collider involved in the contact.
+ * @param collider2 - The second collider involved in the contact.
+ * @param f - Closure that will be called on each contact manifold between the two colliders. If the second argument
+ * passed to this closure is `true`, then the contact manifold data is flipped, i.e., methods like `localNormal1`
+ * actually apply to the `collider2` and fields like `localNormal2` apply to the `collider1`.
+ */
+ public contactPair(
+ collider1: ColliderHandle,
+ collider2: ColliderHandle,
+ f: (manifold: TempContactManifold, flipped: boolean) => void,
+ ) {
+ const rawPair = this.raw.contact_pair(collider1, collider2);
+
+ if (!!rawPair) {
+ const flipped = rawPair.collider1() != collider1;
+
+ let i;
+ for (i = 0; i < rawPair.numContactManifolds(); ++i) {
+ this.tempManifold.raw = rawPair.contactManifold(i);
+ if (!!this.tempManifold.raw) {
+ f(this.tempManifold, flipped);
+ }
+
+ // SAFETY: The RawContactManifold stores a raw pointer that will be invalidated
+ // at the next timestep. So we must be sure to free the pair here
+ // to avoid unsoundness in the Rust code.
+ this.tempManifold.free();
+ }
+ rawPair.free();
+ }
+ }
+
+ /**
+ * Returns `true` if `collider1` and `collider2` intersect and at least one of them is a sensor.
+ * @param collider1 − The first collider involved in the intersection.
+ * @param collider2 − The second collider involved in the intersection.
+ */
+ public intersectionPair(
+ collider1: ColliderHandle,
+ collider2: ColliderHandle,
+ ): boolean {
+ return this.raw.intersection_pair(collider1, collider2);
+ }
+}
+
+export class TempContactManifold {
+ raw: RawContactManifold;
+
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ constructor(raw: RawContactManifold) {
+ this.raw = raw;
+ }
+
+ public normal(): Vector {
+ return VectorOps.fromRaw(this.raw.normal());
+ }
+
+ public localNormal1(): Vector {
+ return VectorOps.fromRaw(this.raw.local_n1());
+ }
+
+ public localNormal2(): Vector {
+ return VectorOps.fromRaw(this.raw.local_n2());
+ }
+
+ public subshape1(): number {
+ return this.raw.subshape1();
+ }
+
+ public subshape2(): number {
+ return this.raw.subshape2();
+ }
+
+ public numContacts(): number {
+ return this.raw.num_contacts();
+ }
+
+ public localContactPoint1(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.contact_local_p1(i));
+ }
+
+ public localContactPoint2(i: number): Vector | null {
+ return VectorOps.fromRaw(this.raw.contact_local_p2(i));
+ }
+
+ public contactDist(i: number): number {
+ return this.raw.contact_dist(i);
+ }
+
+ public contactFid1(i: number): number {
+ return this.raw.contact_fid1(i);
+ }
+
+ public contactFid2(i: number): number {
+ return this.raw.contact_fid2(i);
+ }
+
+ public contactImpulse(i: number): number {
+ return this.raw.contact_impulse(i);
+ }
+
+ // #if DIM2
+ public contactTangentImpulse(i: number): number {
+ return this.raw.contact_tangent_impulse(i);
+ }
+ // #endif
+
+ // #if DIM3
+ public contactTangentImpulseX(i: number): number {
+ return this.raw.contact_tangent_impulse_x(i);
+ }
+
+ public contactTangentImpulseY(i: number): number {
+ return this.raw.contact_tangent_impulse_y(i);
+ }
+ // #endif
+
+ public numSolverContacts(): number {
+ return this.raw.num_solver_contacts();
+ }
+
+ public solverContactPoint(i: number): Vector {
+ return VectorOps.fromRaw(this.raw.solver_contact_point(i));
+ }
+
+ public solverContactDist(i: number): number {
+ return this.raw.solver_contact_dist(i);
+ }
+
+ public solverContactFriction(i: number): number {
+ return this.raw.solver_contact_friction(i);
+ }
+
+ public solverContactRestitution(i: number): number {
+ return this.raw.solver_contact_restitution(i);
+ }
+
+ public solverContactTangentVelocity(i: number): Vector {
+ return VectorOps.fromRaw(this.raw.solver_contact_tangent_velocity(i));
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/geometry/point.ts b/thirdparty/rapier.js/src.ts/geometry/point.ts
new file mode 100644
index 00000000..d272b7e8
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/point.ts
@@ -0,0 +1,98 @@
+import {Collider, ColliderHandle} from "./collider";
+import {Vector, VectorOps} from "../math";
+import {
+ RawFeatureType,
+ RawPointColliderProjection,
+ RawPointProjection,
+} from "../raw";
+import {FeatureType} from "./feature";
+import {ColliderSet} from "./collider_set";
+
+/**
+ * The projection of a point on a collider.
+ */
+export class PointProjection {
+ /**
+ * The projection of the point on the collider.
+ */
+ point: Vector;
+ /**
+ * Is the point inside of the collider?
+ */
+ isInside: boolean;
+
+ constructor(point: Vector, isInside: boolean) {
+ this.point = point;
+ this.isInside = isInside;
+ }
+
+ public static fromRaw(raw: RawPointProjection): PointProjection {
+ if (!raw) return null;
+
+ const result = new PointProjection(
+ VectorOps.fromRaw(raw.point()),
+ raw.isInside(),
+ );
+ raw.free();
+ return result;
+ }
+}
+
+/**
+ * The projection of a point on a collider (includes the collider handle).
+ */
+export class PointColliderProjection {
+ /**
+ * The collider hit by the ray.
+ */
+ collider: Collider;
+ /**
+ * The projection of the point on the collider.
+ */
+ point: Vector;
+ /**
+ * Is the point inside of the collider?
+ */
+ isInside: boolean;
+
+ /**
+ * The type of the geometric feature the point was projected on.
+ */
+ featureType = FeatureType.Unknown;
+
+ /**
+ * The id of the geometric feature the point was projected on.
+ */
+ featureId: number | undefined = undefined;
+
+ constructor(
+ collider: Collider,
+ point: Vector,
+ isInside: boolean,
+ featureType?: FeatureType,
+ featureId?: number,
+ ) {
+ this.collider = collider;
+ this.point = point;
+ this.isInside = isInside;
+ if (featureId !== undefined) this.featureId = featureId;
+ if (featureType !== undefined) this.featureType = featureType;
+ }
+
+ public static fromRaw(
+ colliderSet: ColliderSet,
+ raw: RawPointColliderProjection,
+ ): PointColliderProjection {
+ if (!raw) return null;
+
+ const result = new PointColliderProjection(
+ colliderSet.get(raw.colliderHandle()),
+ VectorOps.fromRaw(raw.point()),
+ raw.isInside(),
+ raw.featureType() as number as FeatureType,
+ raw.featureId(),
+ );
+ raw.free();
+ return result;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/geometry/ray.ts b/thirdparty/rapier.js/src.ts/geometry/ray.ts
new file mode 100644
index 00000000..36df5ee3
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/ray.ts
@@ -0,0 +1,192 @@
+import {Vector, VectorOps} from "../math";
+import {
+ RawFeatureType,
+ RawRayColliderIntersection,
+ RawRayColliderHit,
+ RawRayIntersection,
+} from "../raw";
+import {Collider} from "./collider";
+import {FeatureType} from "./feature";
+import {ColliderSet} from "./collider_set";
+
+/**
+ * A ray. This is a directed half-line.
+ */
+export class Ray {
+ /**
+ * The starting point of the ray.
+ */
+ public origin: Vector;
+ /**
+ * The direction of propagation of the ray.
+ */
+ public dir: Vector;
+
+ /**
+ * Builds a ray from its origin and direction.
+ *
+ * @param origin - The ray's starting point.
+ * @param dir - The ray's direction of propagation.
+ */
+ constructor(origin: Vector, dir: Vector) {
+ this.origin = origin;
+ this.dir = dir;
+ }
+
+ public pointAt(t: number): Vector {
+ return {
+ x: this.origin.x + this.dir.x * t,
+ y: this.origin.y + this.dir.y * t,
+ // #if DIM3
+ z: this.origin.z + this.dir.z * t,
+ // #endif
+ };
+ }
+}
+
+/**
+ * The intersection between a ray and a collider.
+ */
+export class RayIntersection {
+ /**
+ * The time-of-impact of the ray with the collider.
+ *
+ * The hit point is obtained from the ray's origin and direction: `origin + dir * timeOfImpact`.
+ */
+ timeOfImpact: number;
+ /**
+ * The normal of the collider at the hit point.
+ */
+ normal: Vector;
+
+ /**
+ * The type of the geometric feature the point was projected on.
+ */
+ featureType = FeatureType.Unknown;
+
+ /**
+ * The id of the geometric feature the point was projected on.
+ */
+ featureId: number | undefined = undefined;
+
+ constructor(
+ timeOfImpact: number,
+ normal: Vector,
+ featureType?: FeatureType,
+ featureId?: number,
+ ) {
+ this.timeOfImpact = timeOfImpact;
+ this.normal = normal;
+ if (featureId !== undefined) this.featureId = featureId;
+ if (featureType !== undefined) this.featureType = featureType;
+ }
+
+ public static fromRaw(raw: RawRayIntersection): RayIntersection {
+ if (!raw) return null;
+
+ const result = new RayIntersection(
+ raw.time_of_impact(),
+ VectorOps.fromRaw(raw.normal()),
+ raw.featureType() as number as FeatureType,
+ raw.featureId(),
+ );
+ raw.free();
+ return result;
+ }
+}
+
+/**
+ * The intersection between a ray and a collider (includes the collider handle).
+ */
+export class RayColliderIntersection {
+ /**
+ * The collider hit by the ray.
+ */
+ collider: Collider;
+ /**
+ * The time-of-impact of the ray with the collider.
+ *
+ * The hit point is obtained from the ray's origin and direction: `origin + dir * timeOfImpact`.
+ */
+ timeOfImpact: number;
+ /**
+ * The normal of the collider at the hit point.
+ */
+ normal: Vector;
+
+ /**
+ * The type of the geometric feature the point was projected on.
+ */
+ featureType = FeatureType.Unknown;
+
+ /**
+ * The id of the geometric feature the point was projected on.
+ */
+ featureId: number | undefined = undefined;
+
+ constructor(
+ collider: Collider,
+ timeOfImpact: number,
+ normal: Vector,
+ featureType?: FeatureType,
+ featureId?: number,
+ ) {
+ this.collider = collider;
+ this.timeOfImpact = timeOfImpact;
+ this.normal = normal;
+ if (featureId !== undefined) this.featureId = featureId;
+ if (featureType !== undefined) this.featureType = featureType;
+ }
+
+ public static fromRaw(
+ colliderSet: ColliderSet,
+ raw: RawRayColliderIntersection,
+ ): RayColliderIntersection {
+ if (!raw) return null;
+
+ const result = new RayColliderIntersection(
+ colliderSet.get(raw.colliderHandle()),
+ raw.time_of_impact(),
+ VectorOps.fromRaw(raw.normal()),
+ raw.featureType() as number as FeatureType,
+ raw.featureId(),
+ );
+ raw.free();
+ return result;
+ }
+}
+
+/**
+ * The time of impact between a ray and a collider.
+ */
+export class RayColliderHit {
+ /**
+ * The handle of the collider hit by the ray.
+ */
+ collider: Collider;
+ /**
+ * The time-of-impact of the ray with the collider.
+ *
+ * The hit point is obtained from the ray's origin and direction: `origin + dir * timeOfImpact`.
+ */
+ timeOfImpact: number;
+
+ constructor(collider: Collider, timeOfImpact: number) {
+ this.collider = collider;
+ this.timeOfImpact = timeOfImpact;
+ }
+
+ public static fromRaw(
+ colliderSet: ColliderSet,
+ raw: RawRayColliderHit,
+ ): RayColliderHit {
+ if (!raw) return null;
+
+ const result = new RayColliderHit(
+ colliderSet.get(raw.colliderHandle()),
+ raw.timeOfImpact(),
+ );
+ raw.free();
+ return result;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/geometry/shape.ts b/thirdparty/rapier.js/src.ts/geometry/shape.ts
new file mode 100644
index 00000000..f3546982
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/shape.ts
@@ -0,0 +1,1558 @@
+import {Vector, VectorOps, Rotation, RotationOps} from "../math";
+import {RawColliderSet, RawShape, RawShapeType} from "../raw";
+import {ShapeContact} from "./contact";
+import {PointProjection} from "./point";
+import {Ray, RayIntersection} from "./ray";
+import {ShapeCastHit} from "./toi";
+import {ColliderHandle} from "./collider";
+
+export abstract class Shape {
+ public abstract intoRaw(): RawShape;
+
+ /**
+ * The concrete type of this shape.
+ */
+ public abstract get type(): ShapeType;
+
+ /**
+ * instant mode without cache
+ */
+ public static fromRaw(
+ rawSet: RawColliderSet,
+ handle: ColliderHandle,
+ ): Shape {
+ const rawType = rawSet.coShapeType(handle);
+
+ let extents: Vector;
+ let borderRadius: number;
+ let vs: Float32Array;
+ let indices: Uint32Array;
+ let halfHeight: number;
+ let radius: number;
+ let normal: Vector;
+
+ switch (rawType) {
+ case RawShapeType.Ball:
+ return new Ball(rawSet.coRadius(handle));
+ case RawShapeType.Cuboid:
+ extents = rawSet.coHalfExtents(handle);
+ // #if DIM2
+ return new Cuboid(extents.x, extents.y);
+ // #endif
+
+ // #if DIM3
+ return new Cuboid(extents.x, extents.y, extents.z);
+ // #endif
+
+ case RawShapeType.RoundCuboid:
+ extents = rawSet.coHalfExtents(handle);
+ borderRadius = rawSet.coRoundRadius(handle);
+
+ // #if DIM2
+ return new RoundCuboid(extents.x, extents.y, borderRadius);
+ // #endif
+
+ // #if DIM3
+ return new RoundCuboid(
+ extents.x,
+ extents.y,
+ extents.z,
+ borderRadius,
+ );
+ // #endif
+
+ case RawShapeType.Capsule:
+ halfHeight = rawSet.coHalfHeight(handle);
+ radius = rawSet.coRadius(handle);
+ return new Capsule(halfHeight, radius);
+ case RawShapeType.Segment:
+ vs = rawSet.coVertices(handle);
+
+ // #if DIM2
+ return new Segment(
+ VectorOps.new(vs[0], vs[1]),
+ VectorOps.new(vs[2], vs[3]),
+ );
+ // #endif
+
+ // #if DIM3
+ return new Segment(
+ VectorOps.new(vs[0], vs[1], vs[2]),
+ VectorOps.new(vs[3], vs[4], vs[5]),
+ );
+ // #endif
+
+ case RawShapeType.Polyline:
+ vs = rawSet.coVertices(handle);
+ indices = rawSet.coIndices(handle);
+ return new Polyline(vs, indices);
+ case RawShapeType.Triangle:
+ vs = rawSet.coVertices(handle);
+
+ // #if DIM2
+ return new Triangle(
+ VectorOps.new(vs[0], vs[1]),
+ VectorOps.new(vs[2], vs[3]),
+ VectorOps.new(vs[4], vs[5]),
+ );
+ // #endif
+
+ // #if DIM3
+ return new Triangle(
+ VectorOps.new(vs[0], vs[1], vs[2]),
+ VectorOps.new(vs[3], vs[4], vs[5]),
+ VectorOps.new(vs[6], vs[7], vs[8]),
+ );
+ // #endif
+
+ case RawShapeType.RoundTriangle:
+ vs = rawSet.coVertices(handle);
+ borderRadius = rawSet.coRoundRadius(handle);
+
+ // #if DIM2
+ return new RoundTriangle(
+ VectorOps.new(vs[0], vs[1]),
+ VectorOps.new(vs[2], vs[3]),
+ VectorOps.new(vs[4], vs[5]),
+ borderRadius,
+ );
+ // #endif
+
+ // #if DIM3
+ return new RoundTriangle(
+ VectorOps.new(vs[0], vs[1], vs[2]),
+ VectorOps.new(vs[3], vs[4], vs[5]),
+ VectorOps.new(vs[6], vs[7], vs[8]),
+ borderRadius,
+ );
+ // #endif
+
+ case RawShapeType.HalfSpace:
+ normal = VectorOps.fromRaw(rawSet.coHalfspaceNormal(handle));
+ return new HalfSpace(normal);
+
+ case RawShapeType.Voxels:
+ const vox_data = rawSet.coVoxelData(handle);
+ const vox_size = rawSet.coVoxelSize(handle);
+ return new Voxels(vox_data, vox_size);
+
+ case RawShapeType.TriMesh:
+ vs = rawSet.coVertices(handle);
+ indices = rawSet.coIndices(handle);
+ const tri_flags = rawSet.coTriMeshFlags(handle);
+ return new TriMesh(vs, indices, tri_flags);
+
+ case RawShapeType.HeightField:
+ const scale = rawSet.coHeightfieldScale(handle);
+ const heights = rawSet.coHeightfieldHeights(handle);
+
+ // #if DIM2
+ return new Heightfield(heights, scale);
+ // #endif
+
+ // #if DIM3
+ const nrows = rawSet.coHeightfieldNRows(handle);
+ const ncols = rawSet.coHeightfieldNCols(handle);
+ const hf_flags = rawSet.coHeightFieldFlags(handle);
+ return new Heightfield(nrows, ncols, heights, scale, hf_flags);
+ // #endif
+
+ // #if DIM2
+ case RawShapeType.ConvexPolygon:
+ vs = rawSet.coVertices(handle);
+ return new ConvexPolygon(vs, false);
+ case RawShapeType.RoundConvexPolygon:
+ vs = rawSet.coVertices(handle);
+ borderRadius = rawSet.coRoundRadius(handle);
+ return new RoundConvexPolygon(vs, borderRadius, false);
+ // #endif
+
+ // #if DIM3
+ case RawShapeType.ConvexPolyhedron:
+ vs = rawSet.coVertices(handle);
+ indices = rawSet.coIndices(handle);
+ return new ConvexPolyhedron(vs, indices);
+ case RawShapeType.RoundConvexPolyhedron:
+ vs = rawSet.coVertices(handle);
+ indices = rawSet.coIndices(handle);
+ borderRadius = rawSet.coRoundRadius(handle);
+ return new RoundConvexPolyhedron(vs, indices, borderRadius);
+ case RawShapeType.Cylinder:
+ halfHeight = rawSet.coHalfHeight(handle);
+ radius = rawSet.coRadius(handle);
+ return new Cylinder(halfHeight, radius);
+ case RawShapeType.RoundCylinder:
+ halfHeight = rawSet.coHalfHeight(handle);
+ radius = rawSet.coRadius(handle);
+ borderRadius = rawSet.coRoundRadius(handle);
+ return new RoundCylinder(halfHeight, radius, borderRadius);
+ case RawShapeType.Cone:
+ halfHeight = rawSet.coHalfHeight(handle);
+ radius = rawSet.coRadius(handle);
+ return new Cone(halfHeight, radius);
+ case RawShapeType.RoundCone:
+ halfHeight = rawSet.coHalfHeight(handle);
+ radius = rawSet.coRadius(handle);
+ borderRadius = rawSet.coRoundRadius(handle);
+ return new RoundCone(halfHeight, radius, borderRadius);
+ // #endif
+
+ default:
+ throw new Error("unknown shape type: " + rawType);
+ }
+ }
+
+ /**
+ * Computes the time of impact between two moving shapes.
+ * @param shapePos1 - The initial position of this sahpe.
+ * @param shapeRot1 - The rotation of this shape.
+ * @param shapeVel1 - The velocity of this shape.
+ * @param shape2 - The second moving shape.
+ * @param shapePos2 - The initial position of the second shape.
+ * @param shapeRot2 - The rotation of the second shape.
+ * @param shapeVel2 - The velocity of the second shape.
+ * @param targetDistance − If the shape moves closer to this distance from a collider, a hit
+ * will be returned.
+ * @param maxToi - The maximum time when the impact can happen.
+ * @param stopAtPenetration - If set to `false`, the linear shape-cast won’t immediately stop if
+ * the shape is penetrating another shape at its starting point **and** its trajectory is such
+ * that it’s on a path to exit that penetration state.
+ * @returns If the two moving shapes collider at some point along their trajectories, this returns the
+ * time at which the two shape collider as well as the contact information during the impact. Returns
+ * `null`if the two shapes never collide along their paths.
+ */
+ public castShape(
+ shapePos1: Vector,
+ shapeRot1: Rotation,
+ shapeVel1: Vector,
+ shape2: Shape,
+ shapePos2: Vector,
+ shapeRot2: Rotation,
+ shapeVel2: Vector,
+ targetDistance: number,
+ maxToi: number,
+ stopAtPenetration: boolean,
+ ): ShapeCastHit | null {
+ let rawPos1 = VectorOps.intoRaw(shapePos1);
+ let rawRot1 = RotationOps.intoRaw(shapeRot1);
+ let rawVel1 = VectorOps.intoRaw(shapeVel1);
+ let rawPos2 = VectorOps.intoRaw(shapePos2);
+ let rawRot2 = RotationOps.intoRaw(shapeRot2);
+ let rawVel2 = VectorOps.intoRaw(shapeVel2);
+
+ let rawShape1 = this.intoRaw();
+ let rawShape2 = shape2.intoRaw();
+
+ let result = ShapeCastHit.fromRaw(
+ null,
+ rawShape1.castShape(
+ rawPos1,
+ rawRot1,
+ rawVel1,
+ rawShape2,
+ rawPos2,
+ rawRot2,
+ rawVel2,
+ targetDistance,
+ maxToi,
+ stopAtPenetration,
+ ),
+ );
+
+ rawPos1.free();
+ rawRot1.free();
+ rawVel1.free();
+ rawPos2.free();
+ rawRot2.free();
+ rawVel2.free();
+
+ rawShape1.free();
+ rawShape2.free();
+
+ return result;
+ }
+
+ /**
+ * Tests if this shape intersects another shape.
+ *
+ * @param shapePos1 - The position of this shape.
+ * @param shapeRot1 - The rotation of this shape.
+ * @param shape2 - The second shape to test.
+ * @param shapePos2 - The position of the second shape.
+ * @param shapeRot2 - The rotation of the second shape.
+ * @returns `true` if the two shapes intersect, `false` if they don’t.
+ */
+ public intersectsShape(
+ shapePos1: Vector,
+ shapeRot1: Rotation,
+ shape2: Shape,
+ shapePos2: Vector,
+ shapeRot2: Rotation,
+ ): boolean {
+ let rawPos1 = VectorOps.intoRaw(shapePos1);
+ let rawRot1 = RotationOps.intoRaw(shapeRot1);
+ let rawPos2 = VectorOps.intoRaw(shapePos2);
+ let rawRot2 = RotationOps.intoRaw(shapeRot2);
+
+ let rawShape1 = this.intoRaw();
+ let rawShape2 = shape2.intoRaw();
+
+ let result = rawShape1.intersectsShape(
+ rawPos1,
+ rawRot1,
+ rawShape2,
+ rawPos2,
+ rawRot2,
+ );
+
+ rawPos1.free();
+ rawRot1.free();
+ rawPos2.free();
+ rawRot2.free();
+
+ rawShape1.free();
+ rawShape2.free();
+
+ return result;
+ }
+
+ /**
+ * Computes one pair of contact points between two shapes.
+ *
+ * @param shapePos1 - The initial position of this sahpe.
+ * @param shapeRot1 - The rotation of this shape.
+ * @param shape2 - The second shape.
+ * @param shapePos2 - The initial position of the second shape.
+ * @param shapeRot2 - The rotation of the second shape.
+ * @param prediction - The prediction value, if the shapes are separated by a distance greater than this value, test will fail.
+ * @returns `null` if the shapes are separated by a distance greater than prediction, otherwise contact details. The result is given in world-space.
+ */
+ contactShape(
+ shapePos1: Vector,
+ shapeRot1: Rotation,
+ shape2: Shape,
+ shapePos2: Vector,
+ shapeRot2: Rotation,
+ prediction: number,
+ ): ShapeContact | null {
+ let rawPos1 = VectorOps.intoRaw(shapePos1);
+ let rawRot1 = RotationOps.intoRaw(shapeRot1);
+ let rawPos2 = VectorOps.intoRaw(shapePos2);
+ let rawRot2 = RotationOps.intoRaw(shapeRot2);
+
+ let rawShape1 = this.intoRaw();
+ let rawShape2 = shape2.intoRaw();
+
+ let result = ShapeContact.fromRaw(
+ rawShape1.contactShape(
+ rawPos1,
+ rawRot1,
+ rawShape2,
+ rawPos2,
+ rawRot2,
+ prediction,
+ ),
+ );
+
+ rawPos1.free();
+ rawRot1.free();
+ rawPos2.free();
+ rawRot2.free();
+
+ rawShape1.free();
+ rawShape2.free();
+
+ return result;
+ }
+
+ containsPoint(
+ shapePos: Vector,
+ shapeRot: Rotation,
+ point: Vector,
+ ): boolean {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawPoint = VectorOps.intoRaw(point);
+ let rawShape = this.intoRaw();
+
+ let result = rawShape.containsPoint(rawPos, rawRot, rawPoint);
+
+ rawPos.free();
+ rawRot.free();
+ rawPoint.free();
+ rawShape.free();
+
+ return result;
+ }
+
+ projectPoint(
+ shapePos: Vector,
+ shapeRot: Rotation,
+ point: Vector,
+ solid: boolean,
+ ): PointProjection {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawPoint = VectorOps.intoRaw(point);
+ let rawShape = this.intoRaw();
+
+ let result = PointProjection.fromRaw(
+ rawShape.projectPoint(rawPos, rawRot, rawPoint, solid),
+ );
+
+ rawPos.free();
+ rawRot.free();
+ rawPoint.free();
+ rawShape.free();
+
+ return result;
+ }
+
+ intersectsRay(
+ ray: Ray,
+ shapePos: Vector,
+ shapeRot: Rotation,
+ maxToi: number,
+ ): boolean {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawRayOrig = VectorOps.intoRaw(ray.origin);
+ let rawRayDir = VectorOps.intoRaw(ray.dir);
+ let rawShape = this.intoRaw();
+
+ let result = rawShape.intersectsRay(
+ rawPos,
+ rawRot,
+ rawRayOrig,
+ rawRayDir,
+ maxToi,
+ );
+
+ rawPos.free();
+ rawRot.free();
+ rawRayOrig.free();
+ rawRayDir.free();
+ rawShape.free();
+
+ return result;
+ }
+
+ castRay(
+ ray: Ray,
+ shapePos: Vector,
+ shapeRot: Rotation,
+ maxToi: number,
+ solid: boolean,
+ ): number {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawRayOrig = VectorOps.intoRaw(ray.origin);
+ let rawRayDir = VectorOps.intoRaw(ray.dir);
+ let rawShape = this.intoRaw();
+
+ let result = rawShape.castRay(
+ rawPos,
+ rawRot,
+ rawRayOrig,
+ rawRayDir,
+ maxToi,
+ solid,
+ );
+
+ rawPos.free();
+ rawRot.free();
+ rawRayOrig.free();
+ rawRayDir.free();
+ rawShape.free();
+
+ return result;
+ }
+
+ castRayAndGetNormal(
+ ray: Ray,
+ shapePos: Vector,
+ shapeRot: Rotation,
+ maxToi: number,
+ solid: boolean,
+ ): RayIntersection {
+ let rawPos = VectorOps.intoRaw(shapePos);
+ let rawRot = RotationOps.intoRaw(shapeRot);
+ let rawRayOrig = VectorOps.intoRaw(ray.origin);
+ let rawRayDir = VectorOps.intoRaw(ray.dir);
+ let rawShape = this.intoRaw();
+
+ let result = RayIntersection.fromRaw(
+ rawShape.castRayAndGetNormal(
+ rawPos,
+ rawRot,
+ rawRayOrig,
+ rawRayDir,
+ maxToi,
+ solid,
+ ),
+ );
+
+ rawPos.free();
+ rawRot.free();
+ rawRayOrig.free();
+ rawRayDir.free();
+ rawShape.free();
+
+ return result;
+ }
+}
+
+// #if DIM2
+/**
+ * An enumeration representing the type of a shape.
+ */
+export enum ShapeType {
+ Ball = 0,
+ Cuboid = 1,
+ Capsule = 2,
+ Segment = 3,
+ Polyline = 4,
+ Triangle = 5,
+ TriMesh = 6,
+ HeightField = 7,
+ // Compound = 8,
+ ConvexPolygon = 9,
+ RoundCuboid = 10,
+ RoundTriangle = 11,
+ RoundConvexPolygon = 12,
+ HalfSpace = 13,
+ Voxels = 14,
+}
+
+// #endif
+
+// #if DIM3
+
+/**
+ * An enumeration representing the type of a shape.
+ */
+export enum ShapeType {
+ Ball = 0,
+ Cuboid = 1,
+ Capsule = 2,
+ Segment = 3,
+ Polyline = 4,
+ Triangle = 5,
+ TriMesh = 6,
+ HeightField = 7,
+ // Compound = 8,
+ ConvexPolyhedron = 9,
+ Cylinder = 10,
+ Cone = 11,
+ RoundCuboid = 12,
+ RoundTriangle = 13,
+ RoundCylinder = 14,
+ RoundCone = 15,
+ RoundConvexPolyhedron = 16,
+ HalfSpace = 17,
+ Voxels = 18,
+}
+
+// NOTE: this **must** match the bits in the HeightFieldFlags on the rust side.
+/**
+ * Flags controlling the behavior of some operations involving heightfields.
+ */
+export enum HeightFieldFlags {
+ /**
+ * If set, a special treatment will be applied to contact manifold calculation to eliminate
+ * or fix contacts normals that could lead to incorrect bumps in physics simulation (especially
+ * on flat surfaces).
+ *
+ * This is achieved by taking into account adjacent triangle normals when computing contact
+ * points for a given triangle.
+ */
+ FIX_INTERNAL_EDGES = 0b0000_0001,
+}
+
+// #endif
+
+// NOTE: this **must** match the TriMeshFlags on the rust side.
+/**
+ * Flags controlling the behavior of the triangle mesh creation and of some
+ * operations involving triangle meshes.
+ */
+export enum TriMeshFlags {
+ // NOTE: these two flags are not really useful in JS.
+ //
+ // /**
+ // * If set, the half-edge topology of the trimesh will be computed if possible.
+ // */
+ // HALF_EDGE_TOPOLOGY = 0b0000_0001,
+ // /** If set, the half-edge topology and connected components of the trimesh will be computed if possible.
+ // *
+ // * Because of the way it is currently implemented, connected components can only be computed on
+ // * a mesh where the half-edge topology computation succeeds. It will no longer be the case in the
+ // * future once we decouple the computations.
+ // */
+ // CONNECTED_COMPONENTS = 0b0000_0010,
+ /**
+ * If set, any triangle that results in a failing half-hedge topology computation will be deleted.
+ */
+ DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100,
+ /**
+ * If set, the trimesh will be assumed to be oriented (with outward normals).
+ *
+ * The pseudo-normals of its vertices and edges will be computed.
+ */
+ ORIENTED = 0b0000_1000,
+ /**
+ * If set, the duplicate vertices of the trimesh will be merged.
+ *
+ * Two vertices with the exact same coordinates will share the same entry on the
+ * vertex buffer and the index buffer is adjusted accordingly.
+ */
+ MERGE_DUPLICATE_VERTICES = 0b0001_0000,
+ /**
+ * If set, the triangles sharing two vertices with identical index values will be removed.
+ *
+ * Because of the way it is currently implemented, this methods implies that duplicate
+ * vertices will be merged. It will no longer be the case in the future once we decouple
+ * the computations.
+ */
+ DELETE_DEGENERATE_TRIANGLES = 0b0010_0000,
+ /**
+ * If set, two triangles sharing three vertices with identical index values (in any order)
+ * will be removed.
+ *
+ * Because of the way it is currently implemented, this methods implies that duplicate
+ * vertices will be merged. It will no longer be the case in the future once we decouple
+ * the computations.
+ */
+ DELETE_DUPLICATE_TRIANGLES = 0b0100_0000,
+ /**
+ * If set, a special treatment will be applied to contact manifold calculation to eliminate
+ * or fix contacts normals that could lead to incorrect bumps in physics simulation
+ * (especially on flat surfaces).
+ *
+ * This is achieved by taking into account adjacent triangle normals when computing contact
+ * points for a given triangle.
+ *
+ * /!\ NOT SUPPORTED IN THE 2D VERSION OF RAPIER.
+ */
+ FIX_INTERNAL_EDGES = 0b1000_0000 | TriMeshFlags.MERGE_DUPLICATE_VERTICES,
+}
+
+/**
+ * A shape that is a sphere in 3D and a circle in 2D.
+ */
+export class Ball extends Shape {
+ readonly type = ShapeType.Ball;
+
+ /**
+ * The balls radius.
+ */
+ radius: number;
+
+ /**
+ * Creates a new ball with the given radius.
+ * @param radius - The balls radius.
+ */
+ constructor(radius: number) {
+ super();
+ this.radius = radius;
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.ball(this.radius);
+ }
+}
+
+export class HalfSpace extends Shape {
+ readonly type = ShapeType.HalfSpace;
+
+ /**
+ * The outward normal of the half-space.
+ */
+ normal: Vector;
+
+ /**
+ * Creates a new halfspace delimited by an infinite plane.
+ *
+ * @param normal - The outward normal of the plane.
+ */
+ constructor(normal: Vector) {
+ super();
+ this.normal = normal;
+ }
+
+ public intoRaw(): RawShape {
+ let n = VectorOps.intoRaw(this.normal);
+ let result = RawShape.halfspace(n);
+ n.free();
+ return result;
+ }
+}
+
+/**
+ * A shape that is a box in 3D and a rectangle in 2D.
+ */
+export class Cuboid extends Shape {
+ readonly type = ShapeType.Cuboid;
+
+ /**
+ * The half extent of the cuboid along each coordinate axis.
+ */
+ halfExtents: Vector;
+
+ // #if DIM2
+ /**
+ * Creates a new 2D rectangle.
+ * @param hx - The half width of the rectangle.
+ * @param hy - The helf height of the rectangle.
+ */
+ constructor(hx: number, hy: number) {
+ super();
+ this.halfExtents = VectorOps.new(hx, hy);
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Creates a new 3D cuboid.
+ * @param hx - The half width of the cuboid.
+ * @param hy - The half height of the cuboid.
+ * @param hz - The half depth of the cuboid.
+ */
+ constructor(hx: number, hy: number, hz: number) {
+ super();
+ this.halfExtents = VectorOps.new(hx, hy, hz);
+ }
+
+ // #endif
+
+ public intoRaw(): RawShape {
+ // #if DIM2
+ return RawShape.cuboid(this.halfExtents.x, this.halfExtents.y);
+ // #endif
+
+ // #if DIM3
+ return RawShape.cuboid(
+ this.halfExtents.x,
+ this.halfExtents.y,
+ this.halfExtents.z,
+ );
+ // #endif
+ }
+}
+
+/**
+ * A shape that is a box in 3D and a rectangle in 2D, with round corners.
+ */
+export class RoundCuboid extends Shape {
+ readonly type = ShapeType.RoundCuboid;
+
+ /**
+ * The half extent of the cuboid along each coordinate axis.
+ */
+ halfExtents: Vector;
+
+ /**
+ * The radius of the cuboid's round border.
+ */
+ borderRadius: number;
+
+ // #if DIM2
+ /**
+ * Creates a new 2D rectangle.
+ * @param hx - The half width of the rectangle.
+ * @param hy - The helf height of the rectangle.
+ * @param borderRadius - The radius of the borders of this cuboid. This will
+ * effectively increase the half-extents of the cuboid by this radius.
+ */
+ constructor(hx: number, hy: number, borderRadius: number) {
+ super();
+ this.halfExtents = VectorOps.new(hx, hy);
+ this.borderRadius = borderRadius;
+ }
+
+ // #endif
+
+ // #if DIM3
+ /**
+ * Creates a new 3D cuboid.
+ * @param hx - The half width of the cuboid.
+ * @param hy - The half height of the cuboid.
+ * @param hz - The half depth of the cuboid.
+ * @param borderRadius - The radius of the borders of this cuboid. This will
+ * effectively increase the half-extents of the cuboid by this radius.
+ */
+ constructor(hx: number, hy: number, hz: number, borderRadius: number) {
+ super();
+ this.halfExtents = VectorOps.new(hx, hy, hz);
+ this.borderRadius = borderRadius;
+ }
+
+ // #endif
+
+ public intoRaw(): RawShape {
+ // #if DIM2
+ return RawShape.roundCuboid(
+ this.halfExtents.x,
+ this.halfExtents.y,
+ this.borderRadius,
+ );
+ // #endif
+
+ // #if DIM3
+ return RawShape.roundCuboid(
+ this.halfExtents.x,
+ this.halfExtents.y,
+ this.halfExtents.z,
+ this.borderRadius,
+ );
+ // #endif
+ }
+}
+
+/**
+ * A shape that is a capsule.
+ */
+export class Capsule extends Shape {
+ readonly type = ShapeType.Capsule;
+
+ /**
+ * The radius of the capsule's basis.
+ */
+ radius: number;
+
+ /**
+ * The capsule's half height, along the `y` axis.
+ */
+ halfHeight: number;
+
+ /**
+ * Creates a new capsule with the given radius and half-height.
+ * @param halfHeight - The balls half-height along the `y` axis.
+ * @param radius - The balls radius.
+ */
+ constructor(halfHeight: number, radius: number) {
+ super();
+ this.halfHeight = halfHeight;
+ this.radius = radius;
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.capsule(this.halfHeight, this.radius);
+ }
+}
+
+/**
+ * A shape that is a segment.
+ */
+export class Segment extends Shape {
+ readonly type = ShapeType.Segment;
+
+ /**
+ * The first point of the segment.
+ */
+ a: Vector;
+
+ /**
+ * The second point of the segment.
+ */
+ b: Vector;
+
+ /**
+ * Creates a new segment shape.
+ * @param a - The first point of the segment.
+ * @param b - The second point of the segment.
+ */
+ constructor(a: Vector, b: Vector) {
+ super();
+ this.a = a;
+ this.b = b;
+ }
+
+ public intoRaw(): RawShape {
+ let ra = VectorOps.intoRaw(this.a);
+ let rb = VectorOps.intoRaw(this.b);
+ let result = RawShape.segment(ra, rb);
+ ra.free();
+ rb.free();
+ return result;
+ }
+}
+
+/**
+ * A shape that is a segment.
+ */
+export class Triangle extends Shape {
+ readonly type = ShapeType.Triangle;
+
+ /**
+ * The first point of the triangle.
+ */
+ a: Vector;
+
+ /**
+ * The second point of the triangle.
+ */
+ b: Vector;
+
+ /**
+ * The second point of the triangle.
+ */
+ c: Vector;
+
+ /**
+ * Creates a new triangle shape.
+ *
+ * @param a - The first point of the triangle.
+ * @param b - The second point of the triangle.
+ * @param c - The third point of the triangle.
+ */
+ constructor(a: Vector, b: Vector, c: Vector) {
+ super();
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+
+ public intoRaw(): RawShape {
+ let ra = VectorOps.intoRaw(this.a);
+ let rb = VectorOps.intoRaw(this.b);
+ let rc = VectorOps.intoRaw(this.c);
+ let result = RawShape.triangle(ra, rb, rc);
+ ra.free();
+ rb.free();
+ rc.free();
+ return result;
+ }
+}
+
+/**
+ * A shape that is a triangle with round borders and a non-zero thickness.
+ */
+export class RoundTriangle extends Shape {
+ readonly type = ShapeType.RoundTriangle;
+
+ /**
+ * The first point of the triangle.
+ */
+ a: Vector;
+
+ /**
+ * The second point of the triangle.
+ */
+ b: Vector;
+
+ /**
+ * The second point of the triangle.
+ */
+ c: Vector;
+
+ /**
+ * The radius of the triangles's rounded edges and vertices.
+ * In 3D, this is also equal to half the thickness of the round triangle.
+ */
+ borderRadius: number;
+
+ /**
+ * Creates a new triangle shape with round corners.
+ *
+ * @param a - The first point of the triangle.
+ * @param b - The second point of the triangle.
+ * @param c - The third point of the triangle.
+ * @param borderRadius - The radius of the borders of this triangle. In 3D,
+ * this is also equal to half the thickness of the triangle.
+ */
+ constructor(a: Vector, b: Vector, c: Vector, borderRadius: number) {
+ super();
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.borderRadius = borderRadius;
+ }
+
+ public intoRaw(): RawShape {
+ let ra = VectorOps.intoRaw(this.a);
+ let rb = VectorOps.intoRaw(this.b);
+ let rc = VectorOps.intoRaw(this.c);
+ let result = RawShape.roundTriangle(ra, rb, rc, this.borderRadius);
+ ra.free();
+ rb.free();
+ rc.free();
+ return result;
+ }
+}
+
+/**
+ * A shape that is a triangle mesh.
+ */
+export class Polyline extends Shape {
+ readonly type = ShapeType.Polyline;
+
+ /**
+ * The vertices of the polyline.
+ */
+ vertices: Float32Array;
+
+ /**
+ * The indices of the segments.
+ */
+ indices: Uint32Array;
+
+ /**
+ * Creates a new polyline shape.
+ *
+ * @param vertices - The coordinates of the polyline's vertices.
+ * @param indices - The indices of the polyline's segments. If this is `null` or not provided, then
+ * the vertices are assumed to form a line strip.
+ */
+ constructor(vertices: Float32Array, indices?: Uint32Array) {
+ super();
+ this.vertices = vertices;
+ this.indices = indices ?? new Uint32Array(0);
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.polyline(this.vertices, this.indices);
+ }
+}
+
+/**
+ * A shape made of voxels.
+ */
+export class Voxels extends Shape {
+ readonly type = ShapeType.Voxels;
+
+ /**
+ * The points or grid coordinates used to initialize the voxels.
+ */
+ data: Float32Array | Int32Array;
+
+ /**
+ * The dimensions of each voxel.
+ */
+ voxelSize: Vector;
+
+ /**
+ * Creates a new shape made of voxels.
+ *
+ * @param data - Defines the set of voxels. If this is a `Int32Array` then
+ * each voxel is defined from its (signed) grid coordinates,
+ * with 3 (resp 2) contiguous integers per voxel in 3D (resp 2D).
+ * If this is a `Float32Array`, each voxel will be such that
+ * they contain at least one point from this array (where each
+ * point is defined from 3 (resp 2) contiguous numbers per point
+ * in 3D (resp 2D).
+ * @param voxelSize - The size of each voxel.
+ */
+ constructor(data: Float32Array | Int32Array, voxelSize: Vector) {
+ super();
+ this.data = data;
+ this.voxelSize = voxelSize;
+ }
+
+ public intoRaw(): RawShape {
+ let voxelSize = VectorOps.intoRaw(this.voxelSize);
+
+ let result;
+ if (this.data instanceof Int32Array) {
+ result = RawShape.voxels(voxelSize, this.data);
+ } else {
+ result = RawShape.voxelsFromPoints(voxelSize, this.data);
+ }
+
+ voxelSize.free();
+ return result;
+ }
+}
+
+/**
+ * A shape that is a triangle mesh.
+ */
+export class TriMesh extends Shape {
+ readonly type = ShapeType.TriMesh;
+
+ /**
+ * The vertices of the triangle mesh.
+ */
+ vertices: Float32Array;
+
+ /**
+ * The indices of the triangles.
+ */
+ indices: Uint32Array;
+
+ /**
+ * The triangle mesh flags.
+ */
+ flags: TriMeshFlags;
+
+ /**
+ * Creates a new triangle mesh shape.
+ *
+ * @param vertices - The coordinates of the triangle mesh's vertices.
+ * @param indices - The indices of the triangle mesh's triangles.
+ */
+ constructor(
+ vertices: Float32Array,
+ indices: Uint32Array,
+ flags?: TriMeshFlags,
+ ) {
+ super();
+ this.vertices = vertices;
+ this.indices = indices;
+ this.flags = flags;
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.trimesh(this.vertices, this.indices, this.flags);
+ }
+}
+
+// #if DIM2
+/**
+ * A shape that is a convex polygon.
+ */
+export class ConvexPolygon extends Shape {
+ readonly type = ShapeType.ConvexPolygon;
+
+ /**
+ * The vertices of the convex polygon.
+ */
+ vertices: Float32Array;
+
+ /**
+ * Do we want to assume the vertices already form a convex hull?
+ */
+ skipConvexHullComputation: boolean;
+
+ /**
+ * Creates a new convex polygon shape.
+ *
+ * @param vertices - The coordinates of the convex polygon's vertices.
+ * @param skipConvexHullComputation - If set to `true`, the input points will
+ * be assumed to form a convex polyline and no convex-hull computation will
+ * be done automatically.
+ */
+ constructor(vertices: Float32Array, skipConvexHullComputation: boolean) {
+ super();
+ this.vertices = vertices;
+ this.skipConvexHullComputation = !!skipConvexHullComputation;
+ }
+
+ public intoRaw(): RawShape {
+ if (this.skipConvexHullComputation) {
+ return RawShape.convexPolyline(this.vertices);
+ } else {
+ return RawShape.convexHull(this.vertices);
+ }
+ }
+}
+
+/**
+ * A shape that is a convex polygon.
+ */
+export class RoundConvexPolygon extends Shape {
+ readonly type = ShapeType.RoundConvexPolygon;
+
+ /**
+ * The vertices of the convex polygon.
+ */
+ vertices: Float32Array;
+
+ /**
+ * Do we want to assume the vertices already form a convex hull?
+ */
+ skipConvexHullComputation: boolean;
+
+ /**
+ * The radius of the convex polygon's rounded edges and vertices.
+ */
+ borderRadius: number;
+
+ /**
+ * Creates a new convex polygon shape.
+ *
+ * @param vertices - The coordinates of the convex polygon's vertices.
+ * @param borderRadius - The radius of the borders of this convex polygon.
+ * @param skipConvexHullComputation - If set to `true`, the input points will
+ * be assumed to form a convex polyline and no convex-hull computation will
+ * be done automatically.
+ */
+ constructor(
+ vertices: Float32Array,
+ borderRadius: number,
+ skipConvexHullComputation: boolean,
+ ) {
+ super();
+ this.vertices = vertices;
+ this.borderRadius = borderRadius;
+ this.skipConvexHullComputation = !!skipConvexHullComputation;
+ }
+
+ public intoRaw(): RawShape {
+ if (this.skipConvexHullComputation) {
+ return RawShape.roundConvexPolyline(
+ this.vertices,
+ this.borderRadius,
+ );
+ } else {
+ return RawShape.roundConvexHull(this.vertices, this.borderRadius);
+ }
+ }
+}
+
+/**
+ * A shape that is a heightfield.
+ */
+export class Heightfield extends Shape {
+ readonly type = ShapeType.HeightField;
+
+ /**
+ * The heights of the heightfield, along its local `y` axis.
+ */
+ heights: Float32Array;
+
+ /**
+ * The heightfield's length along its local `x` axis.
+ */
+ scale: Vector;
+
+ /**
+ * Creates a new heightfield shape.
+ *
+ * @param heights - The heights of the heightfield, along its local `y` axis.
+ * @param scale - The scale factor applied to the heightfield.
+ */
+ constructor(heights: Float32Array, scale: Vector) {
+ super();
+ this.heights = heights;
+ this.scale = scale;
+ }
+
+ public intoRaw(): RawShape {
+ let rawScale = VectorOps.intoRaw(this.scale);
+ let rawShape = RawShape.heightfield(this.heights, rawScale);
+ rawScale.free();
+ return rawShape;
+ }
+}
+
+// #endif
+
+// #if DIM3
+/**
+ * A shape that is a convex polygon.
+ */
+export class ConvexPolyhedron extends Shape {
+ readonly type = ShapeType.ConvexPolyhedron;
+
+ /**
+ * The vertices of the convex polygon.
+ */
+ vertices: Float32Array;
+
+ /**
+ * The indices of the convex polygon.
+ */
+ indices?: Uint32Array | null;
+
+ /**
+ * Creates a new convex polygon shape.
+ *
+ * @param vertices - The coordinates of the convex polygon's vertices.
+ * @param indices - The index buffer of this convex mesh. If this is `null`
+ * or `undefined`, the convex-hull of the input vertices will be computed
+ * automatically. Otherwise, it will be assumed that the mesh you provide
+ * is already convex.
+ */
+ constructor(vertices: Float32Array, indices?: Uint32Array | null) {
+ super();
+ this.vertices = vertices;
+ this.indices = indices;
+ }
+
+ public intoRaw(): RawShape {
+ if (!!this.indices) {
+ return RawShape.convexMesh(this.vertices, this.indices);
+ } else {
+ return RawShape.convexHull(this.vertices);
+ }
+ }
+}
+
+/**
+ * A shape that is a convex polygon.
+ */
+export class RoundConvexPolyhedron extends Shape {
+ readonly type = ShapeType.RoundConvexPolyhedron;
+
+ /**
+ * The vertices of the convex polygon.
+ */
+ vertices: Float32Array;
+
+ /**
+ * The indices of the convex polygon.
+ */
+ indices?: Uint32Array;
+
+ /**
+ * The radius of the convex polyhedron's rounded edges and vertices.
+ */
+ borderRadius: number;
+
+ /**
+ * Creates a new convex polygon shape.
+ *
+ * @param vertices - The coordinates of the convex polygon's vertices.
+ * @param indices - The index buffer of this convex mesh. If this is `null`
+ * or `undefined`, the convex-hull of the input vertices will be computed
+ * automatically. Otherwise, it will be assumed that the mesh you provide
+ * is already convex.
+ * @param borderRadius - The radius of the borders of this convex polyhedron.
+ */
+ constructor(
+ vertices: Float32Array,
+ indices: Uint32Array | null | undefined,
+ borderRadius: number,
+ ) {
+ super();
+ this.vertices = vertices;
+ this.indices = indices;
+ this.borderRadius = borderRadius;
+ }
+
+ public intoRaw(): RawShape {
+ if (!!this.indices) {
+ return RawShape.roundConvexMesh(
+ this.vertices,
+ this.indices,
+ this.borderRadius,
+ );
+ } else {
+ return RawShape.roundConvexHull(this.vertices, this.borderRadius);
+ }
+ }
+}
+
+/**
+ * A shape that is a heightfield.
+ */
+export class Heightfield extends Shape {
+ readonly type = ShapeType.HeightField;
+
+ /**
+ * The number of rows in the heights matrix.
+ */
+ nrows: number;
+
+ /**
+ * The number of columns in the heights matrix.
+ */
+ ncols: number;
+
+ /**
+ * The heights of the heightfield along its local `y` axis,
+ * provided as a matrix stored in column-major order.
+ */
+ heights: Float32Array;
+
+ /**
+ * The dimensions of the heightfield's local `x,z` plane.
+ */
+ scale: Vector;
+
+ /**
+ * Flags applied to the heightfield.
+ */
+ flags: HeightFieldFlags;
+
+ /**
+ * Creates a new heightfield shape.
+ *
+ * @param nrows − The number of rows in the heights matrix.
+ * @param ncols - The number of columns in the heights matrix.
+ * @param heights - The heights of the heightfield along its local `y` axis,
+ * provided as a matrix stored in column-major order.
+ * @param scale - The dimensions of the heightfield's local `x,z` plane.
+ */
+ constructor(
+ nrows: number,
+ ncols: number,
+ heights: Float32Array,
+ scale: Vector,
+ flags?: HeightFieldFlags,
+ ) {
+ super();
+ this.nrows = nrows;
+ this.ncols = ncols;
+ this.heights = heights;
+ this.scale = scale;
+ this.flags = flags;
+ }
+
+ public intoRaw(): RawShape {
+ let rawScale = VectorOps.intoRaw(this.scale);
+ let rawShape = RawShape.heightfield(
+ this.nrows,
+ this.ncols,
+ this.heights,
+ rawScale,
+ this.flags,
+ );
+ rawScale.free();
+ return rawShape;
+ }
+}
+
+/**
+ * A shape that is a 3D cylinder.
+ */
+export class Cylinder extends Shape {
+ readonly type = ShapeType.Cylinder;
+
+ /**
+ * The radius of the cylinder's basis.
+ */
+ radius: number;
+
+ /**
+ * The cylinder's half height, along the `y` axis.
+ */
+ halfHeight: number;
+
+ /**
+ * Creates a new cylinder with the given radius and half-height.
+ * @param halfHeight - The balls half-height along the `y` axis.
+ * @param radius - The balls radius.
+ */
+ constructor(halfHeight: number, radius: number) {
+ super();
+ this.halfHeight = halfHeight;
+ this.radius = radius;
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.cylinder(this.halfHeight, this.radius);
+ }
+}
+
+/**
+ * A shape that is a 3D cylinder with round corners.
+ */
+export class RoundCylinder extends Shape {
+ readonly type = ShapeType.RoundCylinder;
+
+ /**
+ * The radius of the cylinder's basis.
+ */
+ radius: number;
+
+ /**
+ * The cylinder's half height, along the `y` axis.
+ */
+ halfHeight: number;
+
+ /**
+ * The radius of the cylinder's rounded edges and vertices.
+ */
+ borderRadius: number;
+
+ /**
+ * Creates a new cylinder with the given radius and half-height.
+ * @param halfHeight - The balls half-height along the `y` axis.
+ * @param radius - The balls radius.
+ * @param borderRadius - The radius of the borders of this cylinder.
+ */
+ constructor(halfHeight: number, radius: number, borderRadius: number) {
+ super();
+ this.borderRadius = borderRadius;
+ this.halfHeight = halfHeight;
+ this.radius = radius;
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.roundCylinder(
+ this.halfHeight,
+ this.radius,
+ this.borderRadius,
+ );
+ }
+}
+
+/**
+ * A shape that is a 3D cone.
+ */
+export class Cone extends Shape {
+ readonly type = ShapeType.Cone;
+
+ /**
+ * The radius of the cone's basis.
+ */
+ radius: number;
+
+ /**
+ * The cone's half height, along the `y` axis.
+ */
+ halfHeight: number;
+
+ /**
+ * Creates a new cone with the given radius and half-height.
+ * @param halfHeight - The balls half-height along the `y` axis.
+ * @param radius - The balls radius.
+ */
+ constructor(halfHeight: number, radius: number) {
+ super();
+ this.halfHeight = halfHeight;
+ this.radius = radius;
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.cone(this.halfHeight, this.radius);
+ }
+}
+
+/**
+ * A shape that is a 3D cone with round corners.
+ */
+export class RoundCone extends Shape {
+ readonly type = ShapeType.RoundCone;
+
+ /**
+ * The radius of the cone's basis.
+ */
+ radius: number;
+
+ /**
+ * The cone's half height, along the `y` axis.
+ */
+ halfHeight: number;
+
+ /**
+ * The radius of the cylinder's rounded edges and vertices.
+ */
+ borderRadius: number;
+
+ /**
+ * Creates a new cone with the given radius and half-height.
+ * @param halfHeight - The balls half-height along the `y` axis.
+ * @param radius - The balls radius.
+ * @param borderRadius - The radius of the borders of this cone.
+ */
+ constructor(halfHeight: number, radius: number, borderRadius: number) {
+ super();
+ this.halfHeight = halfHeight;
+ this.radius = radius;
+ this.borderRadius = borderRadius;
+ }
+
+ public intoRaw(): RawShape {
+ return RawShape.roundCone(
+ this.halfHeight,
+ this.radius,
+ this.borderRadius,
+ );
+ }
+}
+
+// #endif
diff --git a/thirdparty/rapier.js/src.ts/geometry/toi.ts b/thirdparty/rapier.js/src.ts/geometry/toi.ts
new file mode 100644
index 00000000..ade13f17
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/geometry/toi.ts
@@ -0,0 +1,105 @@
+import {Collider} from "./collider";
+import {Vector, VectorOps} from "../math";
+import {RawShapeCastHit, RawColliderShapeCastHit} from "../raw";
+import {ColliderSet} from "./collider_set";
+
+/**
+ * The intersection between a ray and a collider.
+ */
+export class ShapeCastHit {
+ /**
+ * The time of impact of the two shapes.
+ */
+ time_of_impact: number;
+ /**
+ * The local-space contact point on the first shape, at
+ * the time of impact.
+ */
+ witness1: Vector;
+ /**
+ * The local-space contact point on the second shape, at
+ * the time of impact.
+ */
+ witness2: Vector;
+ /**
+ * The local-space normal on the first shape, at
+ * the time of impact.
+ */
+ normal1: Vector;
+ /**
+ * The local-space normal on the second shape, at
+ * the time of impact.
+ */
+ normal2: Vector;
+
+ constructor(
+ time_of_impact: number,
+ witness1: Vector,
+ witness2: Vector,
+ normal1: Vector,
+ normal2: Vector,
+ ) {
+ this.time_of_impact = time_of_impact;
+ this.witness1 = witness1;
+ this.witness2 = witness2;
+ this.normal1 = normal1;
+ this.normal2 = normal2;
+ }
+
+ public static fromRaw(
+ colliderSet: ColliderSet,
+ raw: RawShapeCastHit,
+ ): ShapeCastHit {
+ if (!raw) return null;
+
+ const result = new ShapeCastHit(
+ raw.time_of_impact(),
+ VectorOps.fromRaw(raw.witness1()),
+ VectorOps.fromRaw(raw.witness2()),
+ VectorOps.fromRaw(raw.normal1()),
+ VectorOps.fromRaw(raw.normal2()),
+ );
+ raw.free();
+ return result;
+ }
+}
+
+/**
+ * The intersection between a ray and a collider.
+ */
+export class ColliderShapeCastHit extends ShapeCastHit {
+ /**
+ * The handle of the collider hit by the ray.
+ */
+ collider: Collider;
+
+ constructor(
+ collider: Collider,
+ time_of_impact: number,
+ witness1: Vector,
+ witness2: Vector,
+ normal1: Vector,
+ normal2: Vector,
+ ) {
+ super(time_of_impact, witness1, witness2, normal1, normal2);
+ this.collider = collider;
+ }
+
+ public static fromRaw(
+ colliderSet: ColliderSet,
+ raw: RawColliderShapeCastHit,
+ ): ColliderShapeCastHit {
+ if (!raw) return null;
+
+ const result = new ColliderShapeCastHit(
+ colliderSet.get(raw.colliderHandle()),
+ raw.time_of_impact(),
+ VectorOps.fromRaw(raw.witness1()),
+ VectorOps.fromRaw(raw.witness2()),
+ VectorOps.fromRaw(raw.normal1()),
+ VectorOps.fromRaw(raw.normal2()),
+ );
+ raw.free();
+ return result;
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/init.ts b/thirdparty/rapier.js/src.ts/init.ts
new file mode 100644
index 00000000..e9e66aad
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/init.ts
@@ -0,0 +1,2 @@
+// Placeholder for the `init` function used in rapier-compat.
+export {};
diff --git a/thirdparty/rapier.js/src.ts/math.ts b/thirdparty/rapier.js/src.ts/math.ts
new file mode 100644
index 00000000..77669258
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/math.ts
@@ -0,0 +1,263 @@
+import {RawVector, RawRotation} from "./raw";
+// #if DIM3
+import {RawSdpMatrix3} from "./raw";
+// #endif
+
+// #if DIM2
+export interface Vector {
+ x: number;
+ y: number;
+}
+
+/**
+ * A 2D vector.
+ */
+export class Vector2 implements Vector {
+ x: number;
+ y: number;
+
+ constructor(x: number, y: number) {
+ this.x = x;
+ this.y = y;
+ }
+}
+
+export class VectorOps {
+ public static new(x: number, y: number): Vector {
+ return new Vector2(x, y);
+ }
+
+ public static zeros(): Vector {
+ return VectorOps.new(0.0, 0.0);
+ }
+
+ // FIXME: type ram: RawVector?
+ public static fromRaw(raw: RawVector): Vector | null {
+ if (!raw) return null;
+
+ let res = VectorOps.new(raw.x, raw.y);
+ raw.free();
+ return res;
+ }
+
+ public static intoRaw(v: Vector): RawVector {
+ return new RawVector(v.x, v.y);
+ }
+
+ public static copy(out: Vector, input: Vector) {
+ out.x = input.x;
+ out.y = input.y;
+ }
+}
+
+/**
+ * A rotation angle in radians.
+ */
+export type Rotation = number;
+
+export class RotationOps {
+ public static identity(): number {
+ return 0.0;
+ }
+
+ public static fromRaw(raw: RawRotation): Rotation | null {
+ if (!raw) return null;
+
+ let res = raw.angle;
+ raw.free();
+ return res;
+ }
+
+ public static intoRaw(angle: Rotation): RawRotation {
+ return RawRotation.fromAngle(angle);
+ }
+}
+
+// #endif
+
+// #if DIM3
+export interface Vector {
+ x: number;
+ y: number;
+ z: number;
+}
+
+/**
+ * A 3D vector.
+ */
+export class Vector3 implements Vector {
+ x: number;
+ y: number;
+ z: number;
+
+ constructor(x: number, y: number, z: number) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+}
+
+export class VectorOps {
+ public static new(x: number, y: number, z: number): Vector {
+ return new Vector3(x, y, z);
+ }
+
+ public static intoRaw(v: Vector): RawVector {
+ return new RawVector(v.x, v.y, v.z);
+ }
+
+ public static zeros(): Vector {
+ return VectorOps.new(0.0, 0.0, 0.0);
+ }
+
+ // FIXME: type ram: RawVector?
+ public static fromRaw(raw: RawVector): Vector | null {
+ if (!raw) return null;
+
+ let res = VectorOps.new(raw.x, raw.y, raw.z);
+ raw.free();
+ return res;
+ }
+
+ public static copy(out: Vector, input: Vector) {
+ out.x = input.x;
+ out.y = input.y;
+ out.z = input.z;
+ }
+}
+
+export interface Rotation {
+ x: number;
+ y: number;
+ z: number;
+ w: number;
+}
+
+/**
+ * A quaternion.
+ */
+export class Quaternion implements Rotation {
+ x: number;
+ y: number;
+ z: number;
+ w: number;
+
+ constructor(x: number, y: number, z: number, w: number) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+}
+
+export class RotationOps {
+ public static identity(): Rotation {
+ return new Quaternion(0.0, 0.0, 0.0, 1.0);
+ }
+
+ public static fromRaw(raw: RawRotation): Rotation | null {
+ if (!raw) return null;
+
+ let res = new Quaternion(raw.x, raw.y, raw.z, raw.w);
+ raw.free();
+ return res;
+ }
+
+ public static intoRaw(rot: Rotation): RawRotation {
+ return new RawRotation(rot.x, rot.y, rot.z, rot.w);
+ }
+
+ public static copy(out: Rotation, input: Rotation) {
+ out.x = input.x;
+ out.y = input.y;
+ out.z = input.z;
+ out.w = input.w;
+ }
+}
+
+/**
+ * A 3D symmetric-positive-definite matrix.
+ */
+export class SdpMatrix3 {
+ /**
+ * Row major list of the upper-triangular part of the symmetric matrix.
+ */
+ elements: Float32Array;
+
+ /**
+ * Matrix element at row 1, column 1.
+ */
+ public get m11(): number {
+ return this.elements[0];
+ }
+
+ /**
+ * Matrix element at row 1, column 2.
+ */
+ public get m12(): number {
+ return this.elements[1];
+ }
+
+ /**
+ * Matrix element at row 2, column 1.
+ */
+ public get m21(): number {
+ return this.m12;
+ }
+
+ /**
+ * Matrix element at row 1, column 3.
+ */
+ public get m13(): number {
+ return this.elements[2];
+ }
+
+ /**
+ * Matrix element at row 3, column 1.
+ */
+ public get m31(): number {
+ return this.m13;
+ }
+
+ /**
+ * Matrix element at row 2, column 2.
+ */
+ public get m22(): number {
+ return this.elements[3];
+ }
+
+ /**
+ * Matrix element at row 2, column 3.
+ */
+ public get m23(): number {
+ return this.elements[4];
+ }
+
+ /**
+ * Matrix element at row 3, column 2.
+ */
+ public get m32(): number {
+ return this.m23;
+ }
+
+ /**
+ * Matrix element at row 3, column 3.
+ */
+ public get m33(): number {
+ return this.elements[5];
+ }
+
+ constructor(elements: Float32Array) {
+ this.elements = elements;
+ }
+}
+
+export class SdpMatrix3Ops {
+ public static fromRaw(raw: RawSdpMatrix3): SdpMatrix3 {
+ const sdpMatrix3 = new SdpMatrix3(raw.elements());
+ raw.free();
+ return sdpMatrix3;
+ }
+}
+
+// #endif
diff --git a/thirdparty/rapier.js/src.ts/pipeline/debug_render_pipeline.ts b/thirdparty/rapier.js/src.ts/pipeline/debug_render_pipeline.ts
new file mode 100644
index 00000000..bf0d7057
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/debug_render_pipeline.ts
@@ -0,0 +1,85 @@
+import {RawDebugRenderPipeline} from "../raw";
+import {Vector, VectorOps} from "../math";
+import {
+ IntegrationParameters,
+ IslandManager,
+ ImpulseJointSet,
+ MultibodyJointSet,
+ RigidBodySet,
+} from "../dynamics";
+import {BroadPhase, Collider, ColliderSet, NarrowPhase} from "../geometry";
+import {QueryFilterFlags} from "./query_pipeline";
+
+/**
+ * The vertex and color buffers for debug-redering the physics scene.
+ */
+export class DebugRenderBuffers {
+ /**
+ * The lines to render. This is a flat array containing all the lines
+ * to render. Each line is described as two consecutive point. Each
+ * point is described as two (in 2D) or three (in 3D) consecutive
+ * floats. For example, in 2D, the array: `[1, 2, 3, 4, 5, 6, 7, 8]`
+ * describes the two segments `[[1, 2], [3, 4]]` and `[[5, 6], [7, 8]]`.
+ */
+ public vertices: Float32Array;
+ /**
+ * The color buffer. There is one color per vertex, and each color
+ * has four consecutive components (in RGBA format).
+ */
+ public colors: Float32Array;
+
+ constructor(vertices: Float32Array, colors: Float32Array) {
+ this.vertices = vertices;
+ this.colors = colors;
+ }
+}
+
+/**
+ * A pipeline for rendering the physics scene.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `debugRenderPipeline.free()`
+ * once you are done using it (and all the rigid-bodies it created).
+ */
+export class DebugRenderPipeline {
+ raw: RawDebugRenderPipeline;
+ public vertices: Float32Array;
+ public colors: Float32Array;
+
+ /**
+ * Release the WASM memory occupied by this serialization pipeline.
+ */
+ free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ this.vertices = undefined;
+ this.colors = undefined;
+ }
+
+ constructor(raw?: RawDebugRenderPipeline) {
+ this.raw = raw || new RawDebugRenderPipeline();
+ }
+
+ public render(
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ impulse_joints: ImpulseJointSet,
+ multibody_joints: MultibodyJointSet,
+ narrow_phase: NarrowPhase,
+ filterFlags?: QueryFilterFlags,
+ filterPredicate?: (collider: Collider) => boolean,
+ ) {
+ this.raw.render(
+ bodies.raw,
+ colliders.raw,
+ impulse_joints.raw,
+ multibody_joints.raw,
+ narrow_phase.raw,
+ filterFlags,
+ colliders.castClosure(filterPredicate),
+ );
+ this.vertices = this.raw.vertices();
+ this.colors = this.raw.colors();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/pipeline/event_queue.ts b/thirdparty/rapier.js/src.ts/pipeline/event_queue.ts
new file mode 100644
index 00000000..e39c1582
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/event_queue.ts
@@ -0,0 +1,158 @@
+import {RawContactForceEvent, RawEventQueue} from "../raw";
+import {RigidBodyHandle} from "../dynamics";
+import {Collider, ColliderHandle} from "../geometry";
+import {Vector, VectorOps} from "../math";
+
+/**
+ * Flags indicating what events are enabled for colliders.
+ */
+export enum ActiveEvents {
+ NONE = 0,
+ /**
+ * Enable collision events.
+ */
+ COLLISION_EVENTS = 0b0001,
+ /**
+ * Enable contact force events.
+ */
+ CONTACT_FORCE_EVENTS = 0b0010,
+}
+
+/**
+ * Event occurring when the sum of the magnitudes of the
+ * contact forces between two colliders exceed a threshold.
+ *
+ * This object should **not** be stored anywhere. Its properties can only be
+ * read from within the closure given to `EventHandler.drainContactForceEvents`.
+ */
+export class TempContactForceEvent {
+ raw: RawContactForceEvent;
+
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ /**
+ * The first collider involved in the contact.
+ */
+ public collider1(): ColliderHandle {
+ return this.raw.collider1();
+ }
+
+ /**
+ * The second collider involved in the contact.
+ */
+ public collider2(): ColliderHandle {
+ return this.raw.collider2();
+ }
+
+ /**
+ * The sum of all the forces between the two colliders.
+ */
+ public totalForce(): Vector {
+ return VectorOps.fromRaw(this.raw.total_force());
+ }
+
+ /**
+ * The sum of the magnitudes of each force between the two colliders.
+ *
+ * Note that this is **not** the same as the magnitude of `self.total_force`.
+ * Here we are summing the magnitude of all the forces, instead of taking
+ * the magnitude of their sum.
+ */
+ public totalForceMagnitude(): number {
+ return this.raw.total_force_magnitude();
+ }
+
+ /**
+ * The world-space (unit) direction of the force with strongest magnitude.
+ */
+ public maxForceDirection(): Vector {
+ return VectorOps.fromRaw(this.raw.max_force_direction());
+ }
+
+ /**
+ * The magnitude of the largest force at a contact point of this contact pair.
+ */
+ public maxForceMagnitude(): number {
+ return this.raw.max_force_magnitude();
+ }
+}
+
+/**
+ * A structure responsible for collecting events generated
+ * by the physics engine.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `eventQueue.free()`
+ * once you are done using it.
+ */
+export class EventQueue {
+ raw: RawEventQueue;
+
+ /**
+ * Creates a new event collector.
+ *
+ * @param autoDrain -setting this to `true` is strongly recommended. If true, the collector will
+ * be automatically drained before each `world.step(collector)`. If false, the collector will
+ * keep all events in memory unless it is manually drained/cleared; this may lead to unbounded use of
+ * RAM if no drain is performed.
+ */
+ constructor(autoDrain: boolean, raw?: RawEventQueue) {
+ this.raw = raw || new RawEventQueue(autoDrain);
+ }
+
+ /**
+ * Release the WASM memory occupied by this event-queue.
+ */
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ /**
+ * Applies the given javascript closure on each collision event of this collector, then clear
+ * the internal collision event buffer.
+ *
+ * @param f - JavaScript closure applied to each collision event. The
+ * closure must take three arguments: two integers representing the handles of the colliders
+ * involved in the collision, and a boolean indicating if the collision started (true) or stopped
+ * (false).
+ */
+ public drainCollisionEvents(
+ f: (
+ handle1: ColliderHandle,
+ handle2: ColliderHandle,
+ started: boolean,
+ ) => void,
+ ) {
+ this.raw.drainCollisionEvents(f);
+ }
+
+ /**
+ * Applies the given javascript closure on each contact force event of this collector, then clear
+ * the internal collision event buffer.
+ *
+ * @param f - JavaScript closure applied to each collision event. The
+ * closure must take one `TempContactForceEvent` argument.
+ */
+ public drainContactForceEvents(f: (event: TempContactForceEvent) => void) {
+ let event = new TempContactForceEvent();
+ this.raw.drainContactForceEvents((raw: RawContactForceEvent) => {
+ event.raw = raw;
+ f(event);
+ event.free();
+ });
+ }
+
+ /**
+ * Removes all events contained by this collector
+ */
+ public clear() {
+ this.raw.clear();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/pipeline/index.ts b/thirdparty/rapier.js/src.ts/pipeline/index.ts
new file mode 100644
index 00000000..092c1a59
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/index.ts
@@ -0,0 +1,7 @@
+export * from "./world";
+export * from "./physics_pipeline";
+export * from "./serialization_pipeline";
+export * from "./event_queue";
+export * from "./physics_hooks";
+export * from "./debug_render_pipeline";
+export * from "./query_pipeline";
diff --git a/thirdparty/rapier.js/src.ts/pipeline/physics_hooks.ts b/thirdparty/rapier.js/src.ts/pipeline/physics_hooks.ts
new file mode 100644
index 00000000..20e12ad9
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/physics_hooks.ts
@@ -0,0 +1,54 @@
+import {RigidBodyHandle} from "../dynamics";
+import {ColliderHandle} from "../geometry";
+
+export enum ActiveHooks {
+ NONE = 0,
+ FILTER_CONTACT_PAIRS = 0b0001,
+ FILTER_INTERSECTION_PAIRS = 0b0010,
+ // MODIFY_SOLVER_CONTACTS = 0b0100, /* Not supported yet in JS. */
+}
+
+export enum SolverFlags {
+ EMPTY = 0b000,
+ COMPUTE_IMPULSE = 0b001,
+}
+
+export interface PhysicsHooks {
+ /**
+ * Function that determines if contacts computation should happen between two colliders, and how the
+ * constraints solver should behave for these contacts.
+ *
+ * This will only be executed and taken into account if at least one of the involved colliders contains the
+ * `ActiveHooks.FILTER_CONTACT_PAIR` flag in its active hooks.
+ *
+ * @param collider1 − Handle of the first collider involved in the potential contact.
+ * @param collider2 − Handle of the second collider involved in the potential contact.
+ * @param body1 − Handle of the first body involved in the potential contact.
+ * @param body2 − Handle of the second body involved in the potential contact.
+ */
+ filterContactPair(
+ collider1: ColliderHandle,
+ collider2: ColliderHandle,
+ body1: RigidBodyHandle,
+ body2: RigidBodyHandle,
+ ): SolverFlags | null;
+
+ /**
+ * Function that determines if intersection computation should happen between two colliders (where at least
+ * one is a sensor).
+ *
+ * This will only be executed and taken into account if `one of the involved colliders contains the
+ * `ActiveHooks.FILTER_INTERSECTION_PAIR` flag in its active hooks.
+ *
+ * @param collider1 − Handle of the first collider involved in the potential contact.
+ * @param collider2 − Handle of the second collider involved in the potential contact.
+ * @param body1 − Handle of the first body involved in the potential contact.
+ * @param body2 − Handle of the second body involved in the potential contact.
+ */
+ filterIntersectionPair(
+ collider1: ColliderHandle,
+ collider2: ColliderHandle,
+ body1: RigidBodyHandle,
+ body2: RigidBodyHandle,
+ ): boolean;
+}
diff --git a/thirdparty/rapier.js/src.ts/pipeline/physics_pipeline.ts b/thirdparty/rapier.js/src.ts/pipeline/physics_pipeline.ts
new file mode 100644
index 00000000..a3319205
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/physics_pipeline.ts
@@ -0,0 +1,85 @@
+import {RawPhysicsPipeline} from "../raw";
+import {Vector, VectorOps} from "../math";
+import {
+ IntegrationParameters,
+ ImpulseJointSet,
+ MultibodyJointSet,
+ RigidBodyHandle,
+ RigidBodySet,
+ CCDSolver,
+ IslandManager,
+} from "../dynamics";
+import {
+ BroadPhase,
+ ColliderHandle,
+ ColliderSet,
+ NarrowPhase,
+} from "../geometry";
+import {EventQueue} from "./event_queue";
+import {PhysicsHooks} from "./physics_hooks";
+
+export class PhysicsPipeline {
+ raw: RawPhysicsPipeline;
+
+ public free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ constructor(raw?: RawPhysicsPipeline) {
+ this.raw = raw || new RawPhysicsPipeline();
+ }
+
+ public step(
+ gravity: Vector,
+ integrationParameters: IntegrationParameters,
+ islands: IslandManager,
+ broadPhase: BroadPhase,
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ impulseJoints: ImpulseJointSet,
+ multibodyJoints: MultibodyJointSet,
+ ccdSolver: CCDSolver,
+ eventQueue?: EventQueue,
+ hooks?: PhysicsHooks,
+ ) {
+ let rawG = VectorOps.intoRaw(gravity);
+
+ if (!!eventQueue) {
+ this.raw.stepWithEvents(
+ rawG,
+ integrationParameters.raw,
+ islands.raw,
+ broadPhase.raw,
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ impulseJoints.raw,
+ multibodyJoints.raw,
+ ccdSolver.raw,
+ eventQueue.raw,
+ hooks,
+ !!hooks ? hooks.filterContactPair : null,
+ !!hooks ? hooks.filterIntersectionPair : null,
+ );
+ } else {
+ this.raw.step(
+ rawG,
+ integrationParameters.raw,
+ islands.raw,
+ broadPhase.raw,
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ impulseJoints.raw,
+ multibodyJoints.raw,
+ ccdSolver.raw,
+ );
+ }
+
+ rawG.free();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/pipeline/query_pipeline.ts b/thirdparty/rapier.js/src.ts/pipeline/query_pipeline.ts
new file mode 100644
index 00000000..5610ff31
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/query_pipeline.ts
@@ -0,0 +1,57 @@
+import {RawRayColliderIntersection} from "../raw";
+import {
+ ColliderHandle,
+ ColliderSet,
+ InteractionGroups,
+ PointColliderProjection,
+ Ray,
+ RayColliderIntersection,
+ RayColliderHit,
+ Shape,
+ ColliderShapeCastHit,
+} from "../geometry";
+import {IslandManager, RigidBodyHandle, RigidBodySet} from "../dynamics";
+import {Rotation, RotationOps, Vector, VectorOps} from "../math";
+
+// NOTE: must match the bits in the QueryFilterFlags on the Rust side.
+/**
+ * Flags for excluding whole sets of colliders from a scene query.
+ */
+export enum QueryFilterFlags {
+ /**
+ * Exclude from the query any collider attached to a fixed rigid-body and colliders with no rigid-body attached.
+ */
+ EXCLUDE_FIXED = 0b0000_0001,
+ /**
+ * Exclude from the query any collider attached to a dynamic rigid-body.
+ */
+ EXCLUDE_KINEMATIC = 0b0000_0010,
+ /**
+ * Exclude from the query any collider attached to a kinematic rigid-body.
+ */
+ EXCLUDE_DYNAMIC = 0b0000_0100,
+ /**
+ * Exclude from the query any collider that is a sensor.
+ */
+ EXCLUDE_SENSORS = 0b0000_1000,
+ /**
+ * Exclude from the query any collider that is not a sensor.
+ */
+ EXCLUDE_SOLIDS = 0b0001_0000,
+ /**
+ * Excludes all colliders not attached to a dynamic rigid-body.
+ */
+ ONLY_DYNAMIC = QueryFilterFlags.EXCLUDE_FIXED |
+ QueryFilterFlags.EXCLUDE_KINEMATIC,
+ /**
+ * Excludes all colliders not attached to a kinematic rigid-body.
+ */
+ ONLY_KINEMATIC = QueryFilterFlags.EXCLUDE_DYNAMIC |
+ QueryFilterFlags.EXCLUDE_FIXED,
+ /**
+ * Exclude all colliders attached to a non-fixed rigid-body
+ * (this will not exclude colliders not attached to any rigid-body).
+ */
+ ONLY_FIXED = QueryFilterFlags.EXCLUDE_DYNAMIC |
+ QueryFilterFlags.EXCLUDE_KINEMATIC,
+}
diff --git a/thirdparty/rapier.js/src.ts/pipeline/serialization_pipeline.ts b/thirdparty/rapier.js/src.ts/pipeline/serialization_pipeline.ts
new file mode 100644
index 00000000..87fde398
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/serialization_pipeline.ts
@@ -0,0 +1,84 @@
+import {RawSerializationPipeline} from "../raw";
+import {Vector, VectorOps} from "../math";
+import {
+ IntegrationParameters,
+ IslandManager,
+ ImpulseJointSet,
+ MultibodyJointSet,
+ RigidBodySet,
+} from "../dynamics";
+import {BroadPhase, ColliderSet, NarrowPhase} from "../geometry";
+import {World} from "./world";
+
+/**
+ * A pipeline for serializing the physics scene.
+ *
+ * To avoid leaking WASM resources, this MUST be freed manually with `serializationPipeline.free()`
+ * once you are done using it (and all the rigid-bodies it created).
+ */
+export class SerializationPipeline {
+ raw: RawSerializationPipeline;
+
+ /**
+ * Release the WASM memory occupied by this serialization pipeline.
+ */
+ free() {
+ if (!!this.raw) {
+ this.raw.free();
+ }
+ this.raw = undefined;
+ }
+
+ constructor(raw?: RawSerializationPipeline) {
+ this.raw = raw || new RawSerializationPipeline();
+ }
+
+ /**
+ * Serialize a complete physics state into a single byte array.
+ * @param gravity - The current gravity affecting the simulation.
+ * @param integrationParameters - The integration parameters of the simulation.
+ * @param broadPhase - The broad-phase of the simulation.
+ * @param narrowPhase - The narrow-phase of the simulation.
+ * @param bodies - The rigid-bodies taking part into the simulation.
+ * @param colliders - The colliders taking part into the simulation.
+ * @param impulseJoints - The impulse joints taking part into the simulation.
+ * @param multibodyJoints - The multibody joints taking part into the simulation.
+ */
+ public serializeAll(
+ gravity: Vector,
+ integrationParameters: IntegrationParameters,
+ islands: IslandManager,
+ broadPhase: BroadPhase,
+ narrowPhase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ impulseJoints: ImpulseJointSet,
+ multibodyJoints: MultibodyJointSet,
+ ): Uint8Array {
+ let rawGra = VectorOps.intoRaw(gravity);
+
+ const res = this.raw.serializeAll(
+ rawGra,
+ integrationParameters.raw,
+ islands.raw,
+ broadPhase.raw,
+ narrowPhase.raw,
+ bodies.raw,
+ colliders.raw,
+ impulseJoints.raw,
+ multibodyJoints.raw,
+ );
+ rawGra.free();
+
+ return res;
+ }
+
+ /**
+ * Deserialize the complete physics state from a single byte array.
+ *
+ * @param data - The byte array to deserialize.
+ */
+ public deserializeAll(data: Uint8Array): World {
+ return World.fromRaw(this.raw.deserializeAll(data));
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/pipeline/world.ts b/thirdparty/rapier.js/src.ts/pipeline/world.ts
new file mode 100644
index 00000000..9ee85616
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/pipeline/world.ts
@@ -0,0 +1,1312 @@
+import {
+ RawBroadPhase,
+ RawCCDSolver,
+ RawColliderSet,
+ RawDeserializedWorld,
+ RawIntegrationParameters,
+ RawIslandManager,
+ RawImpulseJointSet,
+ RawMultibodyJointSet,
+ RawNarrowPhase,
+ RawPhysicsPipeline,
+ RawRigidBodySet,
+ RawSerializationPipeline,
+ RawDebugRenderPipeline,
+} from "../raw";
+
+import {
+ BroadPhase,
+ Collider,
+ ColliderDesc,
+ ColliderHandle,
+ ColliderSet,
+ InteractionGroups,
+ NarrowPhase,
+ PointColliderProjection,
+ Ray,
+ RayColliderIntersection,
+ RayColliderHit,
+ Shape,
+ ColliderShapeCastHit,
+ TempContactManifold,
+} from "../geometry";
+import {
+ CCDSolver,
+ IntegrationParameters,
+ IslandManager,
+ ImpulseJoint,
+ ImpulseJointHandle,
+ MultibodyJoint,
+ MultibodyJointHandle,
+ JointData,
+ ImpulseJointSet,
+ MultibodyJointSet,
+ RigidBody,
+ RigidBodyDesc,
+ RigidBodyHandle,
+ RigidBodySet,
+} from "../dynamics";
+import {Rotation, Vector, VectorOps} from "../math";
+import {PhysicsPipeline} from "./physics_pipeline";
+import {QueryFilterFlags} from "./query_pipeline";
+import {SerializationPipeline} from "./serialization_pipeline";
+import {EventQueue} from "./event_queue";
+import {PhysicsHooks} from "./physics_hooks";
+import {DebugRenderBuffers, DebugRenderPipeline} from "./debug_render_pipeline";
+import {
+ KinematicCharacterController,
+ PidAxesMask,
+ PidController,
+} from "../control";
+import {Coarena} from "../coarena";
+
+// #if DIM3
+import {DynamicRayCastVehicleController} from "../control";
+
+// #endif
+
+/**
+ * The physics world.
+ *
+ * This contains all the data-structures necessary for creating and simulating
+ * bodies with contacts, joints, and external forces.
+ */
+export class World {
+ public gravity: Vector;
+ integrationParameters: IntegrationParameters;
+ islands: IslandManager;
+ broadPhase: BroadPhase;
+ narrowPhase: NarrowPhase;
+ bodies: RigidBodySet;
+ colliders: ColliderSet;
+ impulseJoints: ImpulseJointSet;
+ multibodyJoints: MultibodyJointSet;
+ ccdSolver: CCDSolver;
+ physicsPipeline: PhysicsPipeline;
+ serializationPipeline: SerializationPipeline;
+ debugRenderPipeline: DebugRenderPipeline;
+ characterControllers: Set;
+ pidControllers: Set;
+
+ // #if DIM3
+ vehicleControllers: Set;
+
+ // #endif
+
+ /**
+ * Release the WASM memory occupied by this physics world.
+ *
+ * All the fields of this physics world will be freed as well,
+ * so there is no need to call their `.free()` methods individually.
+ */
+ public free() {
+ this.integrationParameters.free();
+ this.islands.free();
+ this.broadPhase.free();
+ this.narrowPhase.free();
+ this.bodies.free();
+ this.colliders.free();
+ this.impulseJoints.free();
+ this.multibodyJoints.free();
+ this.ccdSolver.free();
+ this.physicsPipeline.free();
+ this.serializationPipeline.free();
+ this.debugRenderPipeline.free();
+ this.characterControllers.forEach((controller) => controller.free());
+ this.pidControllers.forEach((controller) => controller.free());
+
+ // #if DIM3
+ this.vehicleControllers.forEach((controller) => controller.free());
+ // #endif
+
+ this.integrationParameters = undefined;
+ this.islands = undefined;
+ this.broadPhase = undefined;
+ this.narrowPhase = undefined;
+ this.bodies = undefined;
+ this.colliders = undefined;
+ this.ccdSolver = undefined;
+ this.impulseJoints = undefined;
+ this.multibodyJoints = undefined;
+ this.physicsPipeline = undefined;
+ this.serializationPipeline = undefined;
+ this.debugRenderPipeline = undefined;
+ this.characterControllers = undefined;
+ this.pidControllers = undefined;
+
+ // #if DIM3
+ this.vehicleControllers = undefined;
+ // #endif
+ }
+
+ constructor(
+ gravity: Vector,
+ rawIntegrationParameters?: RawIntegrationParameters,
+ rawIslands?: RawIslandManager,
+ rawBroadPhase?: RawBroadPhase,
+ rawNarrowPhase?: RawNarrowPhase,
+ rawBodies?: RawRigidBodySet,
+ rawColliders?: RawColliderSet,
+ rawImpulseJoints?: RawImpulseJointSet,
+ rawMultibodyJoints?: RawMultibodyJointSet,
+ rawCCDSolver?: RawCCDSolver,
+ rawPhysicsPipeline?: RawPhysicsPipeline,
+ rawSerializationPipeline?: RawSerializationPipeline,
+ rawDebugRenderPipeline?: RawDebugRenderPipeline,
+ ) {
+ this.gravity = gravity;
+ this.integrationParameters = new IntegrationParameters(
+ rawIntegrationParameters,
+ );
+ this.islands = new IslandManager(rawIslands);
+ this.broadPhase = new BroadPhase(rawBroadPhase);
+ this.narrowPhase = new NarrowPhase(rawNarrowPhase);
+ this.bodies = new RigidBodySet(rawBodies);
+ this.colliders = new ColliderSet(rawColliders);
+ this.impulseJoints = new ImpulseJointSet(rawImpulseJoints);
+ this.multibodyJoints = new MultibodyJointSet(rawMultibodyJoints);
+ this.ccdSolver = new CCDSolver(rawCCDSolver);
+ this.physicsPipeline = new PhysicsPipeline(rawPhysicsPipeline);
+ this.serializationPipeline = new SerializationPipeline(
+ rawSerializationPipeline,
+ );
+ this.debugRenderPipeline = new DebugRenderPipeline(
+ rawDebugRenderPipeline,
+ );
+ this.characterControllers = new Set();
+ this.pidControllers = new Set();
+
+ // #if DIM3
+ this.vehicleControllers = new Set();
+ // #endif
+
+ this.impulseJoints.finalizeDeserialization(this.bodies);
+ this.bodies.finalizeDeserialization(this.colliders);
+ this.colliders.finalizeDeserialization(this.bodies);
+ }
+
+ public static fromRaw(raw: RawDeserializedWorld): World {
+ if (!raw) return null;
+
+ return new World(
+ VectorOps.fromRaw(raw.takeGravity()),
+ raw.takeIntegrationParameters(),
+ raw.takeIslandManager(),
+ raw.takeBroadPhase(),
+ raw.takeNarrowPhase(),
+ raw.takeBodies(),
+ raw.takeColliders(),
+ raw.takeImpulseJoints(),
+ raw.takeMultibodyJoints(),
+ );
+ }
+
+ /**
+ * Takes a snapshot of this world.
+ *
+ * Use `World.restoreSnapshot` to create a new physics world with a state identical to
+ * the state when `.takeSnapshot()` is called.
+ */
+ public takeSnapshot(): Uint8Array {
+ return this.serializationPipeline.serializeAll(
+ this.gravity,
+ this.integrationParameters,
+ this.islands,
+ this.broadPhase,
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ this.impulseJoints,
+ this.multibodyJoints,
+ );
+ }
+
+ /**
+ * Creates a new physics world from a snapshot.
+ *
+ * This new physics world will be an identical copy of the snapshoted physics world.
+ */
+ public static restoreSnapshot(data: Uint8Array): World {
+ let deser = new SerializationPipeline();
+ return deser.deserializeAll(data);
+ }
+
+ /**
+ * Computes all the lines (and their colors) needed to render the scene.
+ *
+ * @param filterFlags - Flags for excluding whole subsets of colliders from rendering.
+ * @param filterPredicate - Any collider for which this closure returns `false` will be excluded from the
+ * debug rendering.
+ */
+ public debugRender(
+ filterFlags?: QueryFilterFlags,
+ filterPredicate?: (collider: Collider) => boolean,
+ ): DebugRenderBuffers {
+ this.debugRenderPipeline.render(
+ this.bodies,
+ this.colliders,
+ this.impulseJoints,
+ this.multibodyJoints,
+ this.narrowPhase,
+ filterFlags,
+ filterPredicate,
+ );
+ return new DebugRenderBuffers(
+ this.debugRenderPipeline.vertices,
+ this.debugRenderPipeline.colors,
+ );
+ }
+
+ /**
+ * Advance the simulation by one time step.
+ *
+ * All events generated by the physics engine are ignored.
+ *
+ * @param EventQueue - (optional) structure responsible for collecting
+ * events generated by the physics engine.
+ */
+ public step(eventQueue?: EventQueue, hooks?: PhysicsHooks) {
+ this.physicsPipeline.step(
+ this.gravity,
+ this.integrationParameters,
+ this.islands,
+ this.broadPhase,
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ this.impulseJoints,
+ this.multibodyJoints,
+ this.ccdSolver,
+ eventQueue,
+ hooks,
+ );
+ }
+
+ /**
+ * Update colliders positions after rigid-bodies moved.
+ *
+ * When a rigid-body moves, the positions of the colliders attached to it need to be updated. This update is
+ * generally automatically done at the beginning and the end of each simulation step with World.step.
+ * If the positions need to be updated without running a simulation step this method can be called manually.
+ */
+ public propagateModifiedBodyPositionsToColliders() {
+ this.bodies.raw.propagateModifiedBodyPositionsToColliders(
+ this.colliders.raw,
+ );
+ }
+
+ // TODO: This needs to trigger a broad-phase update but without emitting collision events?
+ // /**
+ // * Ensure subsequent scene queries take into account the collider positions set before this method is called.
+ // *
+ // * This does not step the physics simulation forward.
+ // */
+ // public updateSceneQueries() {
+ // this.propagateModifiedBodyPositionsToColliders();
+ // this.queryPipeline.update(this.colliders);
+ // }
+
+ /**
+ * The current simulation timestep.
+ */
+ get timestep(): number {
+ return this.integrationParameters.dt;
+ }
+
+ /**
+ * Sets the new simulation timestep.
+ *
+ * The simulation timestep governs by how much the physics state of the world will
+ * be integrated. A simulation timestep should:
+ * - be as small as possible. Typical values evolve around 0.016 (assuming the chosen unit is milliseconds,
+ * corresponds to the time between two frames of a game running at 60FPS).
+ * - not vary too much during the course of the simulation. A timestep with large variations may
+ * cause instabilities in the simulation.
+ *
+ * @param dt - The timestep length, in seconds.
+ */
+ set timestep(dt: number) {
+ this.integrationParameters.dt = dt;
+ }
+
+ /**
+ * The approximate size of most dynamic objects in the scene.
+ *
+ * See the documentation of the `World.lengthUnit` setter for further details.
+ */
+ get lengthUnit(): number {
+ return this.integrationParameters.lengthUnit;
+ }
+
+ /**
+ * The approximate size of most dynamic objects in the scene.
+ *
+ * This value is used internally to estimate some length-based tolerance. In particular, the
+ * values `IntegrationParameters.allowedLinearError`,
+ * `IntegrationParameters.maxPenetrationCorrection`,
+ * `IntegrationParameters.predictionDistance`, `RigidBodyActivation.linearThreshold`
+ * are scaled by this value implicitly.
+ *
+ * This value can be understood as the number of units-per-meter in your physical world compared
+ * to a human-sized world in meter. For example, in a 2d game, if your typical object size is 100
+ * pixels, set the `[`Self::length_unit`]` parameter to 100.0. The physics engine will interpret
+ * it as if 100 pixels is equivalent to 1 meter in its various internal threshold.
+ * (default `1.0`).
+ */
+ set lengthUnit(unitsPerMeter: number) {
+ this.integrationParameters.lengthUnit = unitsPerMeter;
+ }
+
+ /**
+ * The number of solver iterations run by the constraints solver for calculating forces (default: `4`).
+ */
+ get numSolverIterations(): number {
+ return this.integrationParameters.numSolverIterations;
+ }
+
+ /**
+ * Sets the number of solver iterations run by the constraints solver for calculating forces (default: `4`).
+ *
+ * The greater this value is, the most rigid and realistic the physics simulation will be.
+ * However a greater number of iterations is more computationally intensive.
+ *
+ * @param niter - The new number of solver iterations.
+ */
+ set numSolverIterations(niter: number) {
+ this.integrationParameters.numSolverIterations = niter;
+ }
+
+ /**
+ * Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
+ */
+ get numInternalPgsIterations(): number {
+ return this.integrationParameters.numInternalPgsIterations;
+ }
+
+ /**
+ * Sets the Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
+ *
+ * Increasing this parameter will improve stability of the simulation. It will have a lesser effect than
+ * increasing `numSolverIterations` but is also less computationally expensive.
+ *
+ * @param niter - The new number of internal PGS iterations.
+ */
+ set numInternalPgsIterations(niter: number) {
+ this.integrationParameters.numInternalPgsIterations = niter;
+ }
+
+ /**
+ * The number of substeps continuous collision-detection can run (default: `1`).
+ */
+ get maxCcdSubsteps(): number {
+ return this.integrationParameters.maxCcdSubsteps;
+ }
+
+ /**
+ * Sets the number of substeps continuous collision-detection can run (default: `1`).
+ *
+ * CCD operates using a "motion clamping" mechanism where all fast-moving object trajectories will
+ * be truncated to their first impact on their path. The number of CCD substeps beyond 1 indicate how
+ * many times that trajectory will be updated and continued after a hit. This can results in smoother
+ * paths, but at a significant computational cost.
+ *
+ * @param niter - The new maximum number of CCD substeps. Setting to `0` disables CCD entirely.
+ */
+ set maxCcdSubsteps(substeps: number) {
+ this.integrationParameters.maxCcdSubsteps = substeps;
+ }
+
+ /**
+ * Creates a new rigid-body from the given rigid-body descriptor.
+ *
+ * @param body - The description of the rigid-body to create.
+ */
+ public createRigidBody(body: RigidBodyDesc): RigidBody {
+ return this.bodies.createRigidBody(this.colliders, body);
+ }
+
+ /**
+ * Creates a new character controller.
+ *
+ * @param offset - The artificial gap added between the character’s chape and its environment.
+ */
+ public createCharacterController(
+ offset: number,
+ ): KinematicCharacterController {
+ let controller = new KinematicCharacterController(
+ offset,
+ this.integrationParameters,
+ this.broadPhase,
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ );
+ this.characterControllers.add(controller);
+ return controller;
+ }
+
+ /**
+ * Removes a character controller from this world.
+ *
+ * @param controller - The character controller to remove.
+ */
+ public removeCharacterController(controller: KinematicCharacterController) {
+ this.characterControllers.delete(controller);
+ controller.free();
+ }
+
+ /**
+ * Creates a new PID (Proportional-Integral-Derivative) controller.
+ *
+ * @param kp - The Proportional gain applied to the instantaneous linear position errors.
+ * This is usually set to a multiple of the inverse of simulation step time
+ * (e.g. `60` if the delta-time is `1.0 / 60.0`).
+ * @param ki - The linear gain applied to the Integral part of the PID controller.
+ * @param kd - The Derivative gain applied to the instantaneous linear velocity errors.
+ * This is usually set to a value in `[0.0, 1.0]` where `0.0` implies no damping
+ * (no correction of velocity errors) and `1.0` implies complete damping (velocity errors
+ * are corrected in a single simulation step).
+ * @param axes - The axes affected by this controller.
+ * Only coordinate axes with a bit flags set to `true` will be taken into
+ * account when calculating the errors and corrections.
+ */
+ public createPidController(
+ kp: number,
+ ki: number,
+ kd: number,
+ axes: PidAxesMask,
+ ): PidController {
+ let controller = new PidController(
+ this.integrationParameters,
+ this.bodies,
+ kp,
+ ki,
+ kd,
+ axes,
+ );
+ this.pidControllers.add(controller);
+ return controller;
+ }
+
+ /**
+ * Removes a PID controller from this world.
+ *
+ * @param controller - The PID controller to remove.
+ */
+ public removePidController(controller: PidController) {
+ this.pidControllers.delete(controller);
+ controller.free();
+ }
+
+ // #if DIM3
+ /**
+ * Creates a new vehicle controller.
+ *
+ * @param chassis - The rigid-body used as the chassis of the vehicle controller. When the vehicle
+ * controller is updated, it will change directly the rigid-body’s velocity. This
+ * rigid-body must be a dynamic or kinematic-velocity-based rigid-body.
+ */
+ public createVehicleController(
+ chassis: RigidBody,
+ ): DynamicRayCastVehicleController {
+ let controller = new DynamicRayCastVehicleController(
+ chassis,
+ this.broadPhase,
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ );
+ this.vehicleControllers.add(controller);
+ return controller;
+ }
+
+ /**
+ * Removes a vehicle controller from this world.
+ *
+ * @param controller - The vehicle controller to remove.
+ */
+ public removeVehicleController(
+ controller: DynamicRayCastVehicleController,
+ ) {
+ this.vehicleControllers.delete(controller);
+ controller.free();
+ }
+
+ // #endif
+
+ /**
+ * Creates a new collider.
+ *
+ * @param desc - The description of the collider.
+ * @param parent - The rigid-body this collider is attached to.
+ */
+ public createCollider(desc: ColliderDesc, parent?: RigidBody): Collider {
+ let parentHandle = parent ? parent.handle : undefined;
+ return this.colliders.createCollider(this.bodies, desc, parentHandle);
+ }
+
+ /**
+ * Creates a new impulse joint from the given joint descriptor.
+ *
+ * @param params - The description of the joint to create.
+ * @param parent1 - The first rigid-body attached to this joint.
+ * @param parent2 - The second rigid-body attached to this joint.
+ * @param wakeUp - Should the attached rigid-bodies be awakened?
+ */
+ public createImpulseJoint(
+ params: JointData,
+ parent1: RigidBody,
+ parent2: RigidBody,
+ wakeUp: boolean,
+ ): ImpulseJoint {
+ return this.impulseJoints.createJoint(
+ this.bodies,
+ params,
+ parent1.handle,
+ parent2.handle,
+ wakeUp,
+ );
+ }
+
+ /**
+ * Creates a new multibody joint from the given joint descriptor.
+ *
+ * @param params - The description of the joint to create.
+ * @param parent1 - The first rigid-body attached to this joint.
+ * @param parent2 - The second rigid-body attached to this joint.
+ * @param wakeUp - Should the attached rigid-bodies be awakened?
+ */
+ public createMultibodyJoint(
+ params: JointData,
+ parent1: RigidBody,
+ parent2: RigidBody,
+ wakeUp: boolean,
+ ): MultibodyJoint {
+ return this.multibodyJoints.createJoint(
+ params,
+ parent1.handle,
+ parent2.handle,
+ wakeUp,
+ );
+ }
+
+ /**
+ * Retrieves a rigid-body from its handle.
+ *
+ * @param handle - The integer handle of the rigid-body to retrieve.
+ */
+ public getRigidBody(handle: RigidBodyHandle): RigidBody {
+ return this.bodies.get(handle);
+ }
+
+ /**
+ * Retrieves a collider from its handle.
+ *
+ * @param handle - The integer handle of the collider to retrieve.
+ */
+ public getCollider(handle: ColliderHandle): Collider {
+ return this.colliders.get(handle);
+ }
+
+ /**
+ * Retrieves an impulse joint from its handle.
+ *
+ * @param handle - The integer handle of the impulse joint to retrieve.
+ */
+ public getImpulseJoint(handle: ImpulseJointHandle): ImpulseJoint {
+ return this.impulseJoints.get(handle);
+ }
+
+ /**
+ * Retrieves an multibody joint from its handle.
+ *
+ * @param handle - The integer handle of the multibody joint to retrieve.
+ */
+ public getMultibodyJoint(handle: MultibodyJointHandle): MultibodyJoint {
+ return this.multibodyJoints.get(handle);
+ }
+
+ /**
+ * Removes the given rigid-body from this physics world.
+ *
+ * This will remove this rigid-body as well as all its attached colliders and joints.
+ * Every other bodies touching or attached by joints to this rigid-body will be woken-up.
+ *
+ * @param body - The rigid-body to remove.
+ */
+ public removeRigidBody(body: RigidBody) {
+ if (this.bodies) {
+ this.bodies.remove(
+ body.handle,
+ this.islands,
+ this.colliders,
+ this.impulseJoints,
+ this.multibodyJoints,
+ );
+ }
+ }
+
+ /**
+ * Removes the given collider from this physics world.
+ *
+ * @param collider - The collider to remove.
+ * @param wakeUp - If set to `true`, the rigid-body this collider is attached to will be awaken.
+ */
+ public removeCollider(collider: Collider, wakeUp: boolean) {
+ if (this.colliders) {
+ this.colliders.remove(
+ collider.handle,
+ this.islands,
+ this.bodies,
+ wakeUp,
+ );
+ }
+ }
+
+ /**
+ * Removes the given impulse joint from this physics world.
+ *
+ * @param joint - The impulse joint to remove.
+ * @param wakeUp - If set to `true`, the rigid-bodies attached by this joint will be awaken.
+ */
+ public removeImpulseJoint(joint: ImpulseJoint, wakeUp: boolean) {
+ if (this.impulseJoints) {
+ this.impulseJoints.remove(joint.handle, wakeUp);
+ }
+ }
+
+ /**
+ * Removes the given multibody joint from this physics world.
+ *
+ * @param joint - The multibody joint to remove.
+ * @param wakeUp - If set to `true`, the rigid-bodies attached by this joint will be awaken.
+ */
+ public removeMultibodyJoint(joint: MultibodyJoint, wakeUp: boolean) {
+ if (this.impulseJoints) {
+ this.multibodyJoints.remove(joint.handle, wakeUp);
+ }
+ }
+
+ /**
+ * Applies the given closure to each collider managed by this physics world.
+ *
+ * @param f(collider) - The function to apply to each collider managed by this physics world. Called as `f(collider)`.
+ */
+ public forEachCollider(f: (collider: Collider) => void) {
+ this.colliders.forEach(f);
+ }
+
+ /**
+ * Applies the given closure to each rigid-body managed by this physics world.
+ *
+ * @param f(body) - The function to apply to each rigid-body managed by this physics world. Called as `f(collider)`.
+ */
+ public forEachRigidBody(f: (body: RigidBody) => void) {
+ this.bodies.forEach(f);
+ }
+
+ /**
+ * Applies the given closure to each active rigid-body managed by this physics world.
+ *
+ * After a short time of inactivity, a rigid-body is automatically deactivated ("asleep") by
+ * the physics engine in order to save computational power. A sleeping rigid-body never moves
+ * unless it is moved manually by the user.
+ *
+ * @param f - The function to apply to each active rigid-body managed by this physics world. Called as `f(collider)`.
+ */
+ public forEachActiveRigidBody(f: (body: RigidBody) => void) {
+ this.bodies.forEachActiveRigidBody(this.islands, f);
+ }
+
+ /**
+ * Find the closest intersection between a ray and the physics world.
+ *
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ * @param groups - Used to filter the colliders that can or cannot be hit by the ray.
+ * @param filter - The callback to filter out which collider will be hit.
+ */
+ public castRay(
+ ray: Ray,
+ maxToi: number,
+ solid: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ): RayColliderHit | null {
+ return this.broadPhase.castRay(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ ray,
+ maxToi,
+ solid,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Find the closest intersection between a ray and the physics world.
+ *
+ * This also computes the normal at the hit point.
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ * @param groups - Used to filter the colliders that can or cannot be hit by the ray.
+ */
+ public castRayAndGetNormal(
+ ray: Ray,
+ maxToi: number,
+ solid: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ): RayColliderIntersection | null {
+ return this.broadPhase.castRayAndGetNormal(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ ray,
+ maxToi,
+ solid,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Cast a ray and collects all the intersections between a ray and the scene.
+ *
+ * @param ray - The ray to cast.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the length of the ray to `ray.dir.norm() * maxToi`.
+ * @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
+ * origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
+ * whereas `false` implies that all shapes are hollow for this ray-cast.
+ * @param groups - Used to filter the colliders that can or cannot be hit by the ray.
+ * @param callback - The callback called once per hit (in no particular order) between a ray and a collider.
+ * If this callback returns `false`, then the cast will stop and no further hits will be detected/reported.
+ */
+ public intersectionsWithRay(
+ ray: Ray,
+ maxToi: number,
+ solid: boolean,
+ callback: (intersect: RayColliderIntersection) => boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ) {
+ this.broadPhase.intersectionsWithRay(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ ray,
+ maxToi,
+ solid,
+ callback,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Gets the handle of up to one collider intersecting the given shape.
+ *
+ * @param shapePos - The position of the shape used for the intersection test.
+ * @param shapeRot - The orientation of the shape used for the intersection test.
+ * @param shape - The shape used for the intersection test.
+ * @param groups - The bit groups and filter associated to the ray, in order to only
+ * hit the colliders with collision groups compatible with the ray's group.
+ */
+ public intersectionWithShape(
+ shapePos: Vector,
+ shapeRot: Rotation,
+ shape: Shape,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ): Collider | null {
+ let handle = this.broadPhase.intersectionWithShape(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ shapePos,
+ shapeRot,
+ shape,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ return handle != null ? this.colliders.get(handle) : null;
+ }
+
+ /**
+ * Find the projection of a point on the closest collider.
+ *
+ * @param point - The point to project.
+ * @param solid - If this is set to `true` then the collider shapes are considered to
+ * be plain (if the point is located inside of a plain shape, its projection is the point
+ * itself). If it is set to `false` the collider shapes are considered to be hollow
+ * (if the point is located inside of an hollow shape, it is projected on the shape's
+ * boundary).
+ * @param groups - The bit groups and filter associated to the point to project, in order to only
+ * project on colliders with collision groups compatible with the ray's group.
+ */
+ public projectPoint(
+ point: Vector,
+ solid: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ): PointColliderProjection | null {
+ return this.broadPhase.projectPoint(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ point,
+ solid,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Find the projection of a point on the closest collider.
+ *
+ * @param point - The point to project.
+ * @param groups - The bit groups and filter associated to the point to project, in order to only
+ * project on colliders with collision groups compatible with the ray's group.
+ */
+ public projectPointAndGetFeature(
+ point: Vector,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ): PointColliderProjection | null {
+ return this.broadPhase.projectPointAndGetFeature(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ point,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Find all the colliders containing the given point.
+ *
+ * @param point - The point used for the containment test.
+ * @param groups - The bit groups and filter associated to the point to test, in order to only
+ * test on colliders with collision groups compatible with the ray's group.
+ * @param callback - A function called with the handles of each collider with a shape
+ * containing the `point`.
+ */
+ public intersectionsWithPoint(
+ point: Vector,
+ callback: (handle: Collider) => boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ) {
+ this.broadPhase.intersectionsWithPoint(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ point,
+ this.colliders.castClosure(callback),
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Casts a shape at a constant linear velocity and retrieve the first collider it hits.
+ * This is similar to ray-casting except that we are casting a whole shape instead of
+ * just a point (the ray origin).
+ *
+ * @param shapePos - The initial position of the shape to cast.
+ * @param shapeRot - The initial rotation of the shape to cast.
+ * @param shapeVel - The constant velocity of the shape to cast (i.e. the cast direction).
+ * @param shape - The shape to cast.
+ * @param targetDistance − If the shape moves closer to this distance from a collider, a hit
+ * will be returned.
+ * @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
+ * limits the distance traveled by the shape to `shapeVel.norm() * maxToi`.
+ * @param stopAtPenetration - If set to `false`, the linear shape-cast won’t immediately stop if
+ * the shape is penetrating another shape at its starting point **and** its trajectory is such
+ * that it’s on a path to exit that penetration state.
+ * @param groups - The bit groups and filter associated to the shape to cast, in order to only
+ * test on colliders with collision groups compatible with this group.
+ */
+ public castShape(
+ shapePos: Vector,
+ shapeRot: Rotation,
+ shapeVel: Vector,
+ shape: Shape,
+ targetDistance: number,
+ maxToi: number,
+ stopAtPenetration: boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ): ColliderShapeCastHit | null {
+ return this.broadPhase.castShape(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ shapePos,
+ shapeRot,
+ shapeVel,
+ shape,
+ targetDistance,
+ maxToi,
+ stopAtPenetration,
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Retrieve all the colliders intersecting the given shape.
+ *
+ * @param shapePos - The position of the shape to test.
+ * @param shapeRot - The orientation of the shape to test.
+ * @param shape - The shape to test.
+ * @param groups - The bit groups and filter associated to the shape to test, in order to only
+ * test on colliders with collision groups compatible with this group.
+ * @param callback - A function called with the handles of each collider intersecting the `shape`.
+ */
+ public intersectionsWithShape(
+ shapePos: Vector,
+ shapeRot: Rotation,
+ shape: Shape,
+ callback: (collider: Collider) => boolean,
+ filterFlags?: QueryFilterFlags,
+ filterGroups?: InteractionGroups,
+ filterExcludeCollider?: Collider,
+ filterExcludeRigidBody?: RigidBody,
+ filterPredicate?: (collider: Collider) => boolean,
+ ) {
+ this.broadPhase.intersectionsWithShape(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ shapePos,
+ shapeRot,
+ shape,
+ this.colliders.castClosure(callback),
+ filterFlags,
+ filterGroups,
+ filterExcludeCollider ? filterExcludeCollider.handle : null,
+ filterExcludeRigidBody ? filterExcludeRigidBody.handle : null,
+ this.colliders.castClosure(filterPredicate),
+ );
+ }
+
+ /**
+ * Finds the handles of all the colliders with an AABB intersecting the given AABB.
+ *
+ * @param aabbCenter - The center of the AABB to test.
+ * @param aabbHalfExtents - The half-extents of the AABB to test.
+ * @param callback - The callback that will be called with the handles of all the colliders
+ * currently intersecting the given AABB.
+ */
+ public collidersWithAabbIntersectingAabb(
+ aabbCenter: Vector,
+ aabbHalfExtents: Vector,
+ callback: (handle: Collider) => boolean,
+ ) {
+ this.broadPhase.collidersWithAabbIntersectingAabb(
+ this.narrowPhase,
+ this.bodies,
+ this.colliders,
+ aabbCenter,
+ aabbHalfExtents,
+ this.colliders.castClosure(callback),
+ );
+ }
+
+ /**
+ * Enumerates all the colliders potentially in contact with the given collider.
+ *
+ * @param collider1 - The second collider involved in the contact.
+ * @param f - Closure that will be called on each collider that is in contact with `collider1`.
+ */
+ public contactPairsWith(
+ collider1: Collider,
+ f: (collider2: Collider) => void,
+ ) {
+ this.narrowPhase.contactPairsWith(
+ collider1.handle,
+ this.colliders.castClosure(f),
+ );
+ }
+
+ /**
+ * Enumerates all the colliders intersecting the given colliders, assuming one of them
+ * is a sensor.
+ */
+ public intersectionPairsWith(
+ collider1: Collider,
+ f: (collider2: Collider) => void,
+ ) {
+ this.narrowPhase.intersectionPairsWith(
+ collider1.handle,
+ this.colliders.castClosure(f),
+ );
+ }
+
+ /**
+ * Iterates through all the contact manifolds between the given pair of colliders.
+ *
+ * @param collider1 - The first collider involved in the contact.
+ * @param collider2 - The second collider involved in the contact.
+ * @param f - Closure that will be called on each contact manifold between the two colliders. If the second argument
+ * passed to this closure is `true`, then the contact manifold data is flipped, i.e., methods like `localNormal1`
+ * actually apply to the `collider2` and fields like `localNormal2` apply to the `collider1`.
+ */
+ public contactPair(
+ collider1: Collider,
+ collider2: Collider,
+ f: (manifold: TempContactManifold, flipped: boolean) => void,
+ ) {
+ this.narrowPhase.contactPair(collider1.handle, collider2.handle, f);
+ }
+
+ /**
+ * Returns `true` if `collider1` and `collider2` intersect and at least one of them is a sensor.
+ * @param collider1 − The first collider involved in the intersection.
+ * @param collider2 − The second collider involved in the intersection.
+ */
+ public intersectionPair(collider1: Collider, collider2: Collider): boolean {
+ return this.narrowPhase.intersectionPair(
+ collider1.handle,
+ collider2.handle,
+ );
+ }
+
+ /**
+ * Sets whether internal performance profiling is enabled (default: false).
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ set profilerEnabled(enabled: boolean) {
+ this.physicsPipeline.raw.set_profiler_enabled(enabled);
+ }
+
+ /**
+ * Indicates if the internal performance profiling is enabled.
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ get profilerEnabled(): boolean {
+ return this.physicsPipeline.raw.is_profiler_enabled();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the entire simulation step.
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingStep(): number {
+ return this.physicsPipeline.raw.timing_step();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the collision-detection
+ * (broad-phase + narrow-phase).
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingCollisionDetection(): number {
+ return this.physicsPipeline.raw.timing_collision_detection();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the broad-phase.
+ *
+ * This timing is included in `timingCollisionDetection`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingBroadPhase(): number {
+ return this.physicsPipeline.raw.timing_broad_phase();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the narrow-phase.
+ *
+ * This timing is included in `timingCollisionDetection`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingNarrowPhase(): number {
+ return this.physicsPipeline.raw.timing_narrow_phase();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the constraint solver.
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingSolver(): number {
+ return this.physicsPipeline.raw.timing_solver();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the constraint
+ * initialization.
+ *
+ * This timing is included in `timingSolver`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingVelocityAssembly(): number {
+ return this.physicsPipeline.raw.timing_velocity_assembly();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the constraint
+ * resolution.
+ *
+ * This timing is included in `timingSolver`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingVelocityResolution(): number {
+ return this.physicsPipeline.raw.timing_velocity_resolution();
+ }
+
+ /**
+ * The time spent in milliseconds by the last step to run the rigid-body
+ * velocity update.
+ *
+ * This timing is included in `timingSolver`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingVelocityUpdate(): number {
+ return this.physicsPipeline.raw.timing_velocity_update();
+ }
+
+ /**
+ * The time spent in milliseconds by writing rigid-body velocities
+ * calculated by the solver back into the rigid-bodies.
+ *
+ * This timing is included in `timingSolver`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingVelocityWriteback(): number {
+ return this.physicsPipeline.raw.timing_velocity_writeback();
+ }
+
+ /**
+ * The total time spent in CCD detection and resolution.
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingCcd(): number {
+ return this.physicsPipeline.raw.timing_ccd();
+ }
+
+ /**
+ * The total time spent searching for the continuous hits during CCD.
+ *
+ * This timing is included in `timingCcd`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingCcdToiComputation(): number {
+ return this.physicsPipeline.raw.timing_ccd_toi_computation();
+ }
+
+ /**
+ * The total time spent in the broad-phase during CCD.
+ *
+ * This timing is included in `timingCcd`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingCcdBroadPhase(): number {
+ return this.physicsPipeline.raw.timing_ccd_broad_phase();
+ }
+
+ /**
+ * The total time spent in the narrow-phase during CCD.
+ *
+ * This timing is included in `timingCcd`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingCcdNarrowPhase(): number {
+ return this.physicsPipeline.raw.timing_ccd_narrow_phase();
+ }
+
+ /**
+ * The total time spent in the constraints resolution during CCD.
+ *
+ * This timing is included in `timingCcd`.
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingCcdSolver(): number {
+ return this.physicsPipeline.raw.timing_ccd_solver();
+ }
+
+ /**
+ * The total time spent in the islands calculation during CCD.
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingIslandConstruction(): number {
+ return this.physicsPipeline.raw.timing_island_construction();
+ }
+
+ /**
+ * The total time spent propagating detected user changes.
+ *
+ * Only works if the internal profiler is enabled with `World.profilerEnabled = true`.
+ */
+ public timingUserChanges(): number {
+ return this.physicsPipeline.raw.timing_user_changes();
+ }
+}
diff --git a/thirdparty/rapier.js/src.ts/rapier.ts b/thirdparty/rapier.js/src.ts/rapier.ts
new file mode 100644
index 00000000..dbf4d053
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/rapier.ts
@@ -0,0 +1,3 @@
+import * as RAPIER from "./exports";
+export * from "./exports";
+export default RAPIER;
diff --git a/thirdparty/rapier.js/src.ts/raw.ts b/thirdparty/rapier.js/src.ts/raw.ts
new file mode 100644
index 00000000..51f837b7
--- /dev/null
+++ b/thirdparty/rapier.js/src.ts/raw.ts
@@ -0,0 +1 @@
+export * from "../rapier3d/pkg/rapier_wasm3d";
diff --git a/thirdparty/rapier.js/src/control/character_controller.rs b/thirdparty/rapier.js/src/control/character_controller.rs
new file mode 100644
index 00000000..03cbdc5d
--- /dev/null
+++ b/thirdparty/rapier.js/src/control/character_controller.rs
@@ -0,0 +1,290 @@
+use crate::dynamics::RawRigidBodySet;
+use crate::geometry::{RawBroadPhase, RawColliderSet, RawNarrowPhase};
+use crate::math::RawVector;
+use crate::utils::{self, FlatHandle};
+use na::{Isometry, Unit};
+use rapier::control::{
+ CharacterAutostep, CharacterCollision, CharacterLength, EffectiveCharacterMovement,
+ KinematicCharacterController,
+};
+use rapier::geometry::{ColliderHandle, ShapeCastHit};
+use rapier::math::{Point, Real, Vector};
+use rapier::parry::query::ShapeCastStatus;
+use rapier::pipeline::{QueryFilter, QueryFilterFlags};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawKinematicCharacterController {
+ controller: KinematicCharacterController,
+ result: EffectiveCharacterMovement,
+ events: Vec,
+}
+
+fn length_value(length: CharacterLength) -> Real {
+ match length {
+ CharacterLength::Absolute(val) => val,
+ CharacterLength::Relative(val) => val,
+ }
+}
+#[wasm_bindgen]
+impl RawKinematicCharacterController {
+ #[wasm_bindgen(constructor)]
+ pub fn new(offset: Real) -> Self {
+ let controller = KinematicCharacterController {
+ offset: CharacterLength::Absolute(offset),
+ autostep: None,
+ snap_to_ground: None,
+ ..KinematicCharacterController::default()
+ };
+
+ Self {
+ controller,
+ result: EffectiveCharacterMovement {
+ translation: Vector::zeros(),
+ grounded: false,
+ is_sliding_down_slope: false,
+ },
+ events: vec![],
+ }
+ }
+
+ pub fn up(&self) -> RawVector {
+ self.controller.up.into_inner().into()
+ }
+
+ pub fn setUp(&mut self, vector: &RawVector) {
+ self.controller.up = Unit::new_normalize(vector.0);
+ }
+
+ pub fn normalNudgeFactor(&self) -> Real {
+ self.controller.normal_nudge_factor
+ }
+
+ pub fn setNormalNudgeFactor(&mut self, value: Real) {
+ self.controller.normal_nudge_factor = value;
+ }
+
+ pub fn offset(&self) -> Real {
+ length_value(self.controller.offset)
+ }
+
+ pub fn setOffset(&mut self, value: Real) {
+ self.controller.offset = CharacterLength::Absolute(value);
+ }
+
+ pub fn slideEnabled(&self) -> bool {
+ self.controller.slide
+ }
+
+ pub fn setSlideEnabled(&mut self, enabled: bool) {
+ self.controller.slide = enabled
+ }
+
+ pub fn autostepMaxHeight(&self) -> Option {
+ self.controller.autostep.map(|e| length_value(e.max_height))
+ }
+
+ pub fn autostepMinWidth(&self) -> Option {
+ self.controller.autostep.map(|e| length_value(e.min_width))
+ }
+
+ pub fn autostepIncludesDynamicBodies(&self) -> Option {
+ self.controller.autostep.map(|e| e.include_dynamic_bodies)
+ }
+
+ pub fn autostepEnabled(&self) -> bool {
+ self.controller.autostep.is_some()
+ }
+
+ pub fn enableAutostep(&mut self, maxHeight: Real, minWidth: Real, includeDynamicBodies: bool) {
+ self.controller.autostep = Some(CharacterAutostep {
+ min_width: CharacterLength::Absolute(minWidth),
+ max_height: CharacterLength::Absolute(maxHeight),
+ include_dynamic_bodies: includeDynamicBodies,
+ })
+ }
+
+ pub fn disableAutostep(&mut self) {
+ self.controller.autostep = None;
+ }
+
+ pub fn maxSlopeClimbAngle(&self) -> Real {
+ self.controller.max_slope_climb_angle
+ }
+
+ pub fn setMaxSlopeClimbAngle(&mut self, angle: Real) {
+ self.controller.max_slope_climb_angle = angle;
+ }
+
+ pub fn minSlopeSlideAngle(&self) -> Real {
+ self.controller.min_slope_slide_angle
+ }
+
+ pub fn setMinSlopeSlideAngle(&mut self, angle: Real) {
+ self.controller.min_slope_slide_angle = angle
+ }
+
+ pub fn snapToGroundDistance(&self) -> Option {
+ self.controller.snap_to_ground.map(length_value)
+ }
+
+ pub fn enableSnapToGround(&mut self, distance: Real) {
+ self.controller.snap_to_ground = Some(CharacterLength::Absolute(distance));
+ }
+
+ pub fn disableSnapToGround(&mut self) {
+ self.controller.snap_to_ground = None;
+ }
+
+ pub fn snapToGroundEnabled(&self) -> bool {
+ self.controller.snap_to_ground.is_some()
+ }
+
+ pub fn computeColliderMovement(
+ &mut self,
+ dt: Real,
+ broad_phase: &RawBroadPhase,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &mut RawRigidBodySet,
+ colliders: &mut RawColliderSet,
+ collider_handle: FlatHandle,
+ desired_translation_delta: &RawVector,
+ apply_impulses_to_dynamic_bodies: bool,
+ character_mass: Option,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_predicate: &js_sys::Function,
+ ) {
+ let handle = crate::utils::collider_handle(collider_handle);
+ if let Some(collider) = colliders.0.get(handle) {
+ let collider_pose = *collider.position();
+ let collider_shape = collider.shared_shape().clone();
+ let collider_parent = collider.parent();
+
+ crate::utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: Some(handle),
+ exclude_rigid_body: collider_parent,
+ predicate,
+ };
+
+ let character_mass = character_mass
+ .or_else(|| {
+ collider_parent
+ .and_then(|h| bodies.0.get(h))
+ .map(|b| b.mass())
+ })
+ .unwrap_or(0.0);
+
+ let mut query_pipeline = broad_phase.0.as_query_pipeline_mut(
+ narrow_phase.0.query_dispatcher(),
+ &mut bodies.0,
+ &mut colliders.0,
+ query_filter,
+ );
+
+ self.events.clear();
+ let events = &mut self.events;
+ self.result = self.controller.move_shape(
+ dt,
+ &query_pipeline.as_ref(),
+ &*collider_shape,
+ &collider_pose,
+ desired_translation_delta.0,
+ |event| events.push(event),
+ );
+
+ if apply_impulses_to_dynamic_bodies {
+ self.controller.solve_character_collision_impulses(
+ dt,
+ &mut query_pipeline,
+ &*collider_shape,
+ character_mass,
+ self.events.iter(),
+ );
+ }
+ });
+ } else {
+ self.result.translation.fill(0.0);
+ }
+ }
+
+ pub fn computedMovement(&self) -> RawVector {
+ self.result.translation.into()
+ }
+
+ pub fn computedGrounded(&self) -> bool {
+ self.result.grounded
+ }
+
+ pub fn numComputedCollisions(&self) -> usize {
+ self.events.len()
+ }
+
+ pub fn computedCollision(&self, i: usize, collision: &mut RawCharacterCollision) -> bool {
+ if let Some(coll) = self.events.get(i) {
+ collision.0 = *coll;
+ }
+
+ i < self.events.len()
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawCharacterCollision(CharacterCollision);
+
+#[wasm_bindgen]
+impl RawCharacterCollision {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ Self(CharacterCollision {
+ handle: ColliderHandle::invalid(),
+ character_pos: Isometry::identity(),
+ translation_applied: Vector::zeros(),
+ translation_remaining: Vector::zeros(),
+ hit: ShapeCastHit {
+ time_of_impact: 0.0,
+ witness1: Point::origin(),
+ witness2: Point::origin(),
+ normal1: Vector::y_axis(),
+ normal2: Vector::y_axis(),
+ status: ShapeCastStatus::Failed,
+ },
+ })
+ }
+
+ pub fn handle(&self) -> FlatHandle {
+ utils::flat_handle(self.0.handle.0)
+ }
+
+ pub fn translationDeltaApplied(&self) -> RawVector {
+ self.0.translation_applied.into()
+ }
+
+ pub fn translationDeltaRemaining(&self) -> RawVector {
+ self.0.translation_remaining.into()
+ }
+
+ pub fn toi(&self) -> Real {
+ self.0.hit.time_of_impact
+ }
+
+ pub fn worldWitness1(&self) -> RawVector {
+ self.0.hit.witness1.coords.into() // Already in world-space.
+ }
+
+ pub fn worldWitness2(&self) -> RawVector {
+ (self.0.character_pos * self.0.hit.witness2).coords.into()
+ }
+
+ pub fn worldNormal1(&self) -> RawVector {
+ self.0.hit.normal1.into_inner().into() // Already in world-space.
+ }
+
+ pub fn worldNormal2(&self) -> RawVector {
+ (self.0.character_pos * self.0.hit.normal2.into_inner()).into()
+ }
+}
diff --git a/thirdparty/rapier.js/src/control/mod.rs b/thirdparty/rapier.js/src/control/mod.rs
new file mode 100644
index 00000000..74352c2b
--- /dev/null
+++ b/thirdparty/rapier.js/src/control/mod.rs
@@ -0,0 +1,11 @@
+pub use self::character_controller::RawKinematicCharacterController;
+pub use self::pid_controller::RawPidController;
+
+#[cfg(feature = "dim3")]
+pub use self::ray_cast_vehicle_controller::RawDynamicRayCastVehicleController;
+
+mod character_controller;
+mod pid_controller;
+
+#[cfg(feature = "dim3")]
+mod ray_cast_vehicle_controller;
diff --git a/thirdparty/rapier.js/src/control/pid_controller.rs b/thirdparty/rapier.js/src/control/pid_controller.rs
new file mode 100644
index 00000000..890e3391
--- /dev/null
+++ b/thirdparty/rapier.js/src/control/pid_controller.rs
@@ -0,0 +1,264 @@
+use crate::dynamics::RawRigidBodySet;
+use crate::math::RawVector;
+use crate::utils::{self, FlatHandle};
+use rapier::control::PidController;
+use rapier::dynamics::AxesMask;
+use rapier::math::Vector;
+use wasm_bindgen::prelude::*;
+
+#[cfg(feature = "dim3")]
+use crate::math::RawRotation;
+#[cfg(feature = "dim2")]
+use rapier::math::Rotation;
+
+#[wasm_bindgen]
+pub struct RawPidController {
+ controller: PidController,
+}
+
+#[wasm_bindgen]
+impl RawPidController {
+ #[wasm_bindgen(constructor)]
+ pub fn new(kp: f32, ki: f32, kd: f32, axes_mask: u8) -> Self {
+ let controller = PidController::new(
+ kp,
+ ki,
+ kd,
+ AxesMask::from_bits(axes_mask).unwrap_or(AxesMask::all()),
+ );
+ Self { controller }
+ }
+
+ pub fn set_kp(&mut self, kp: f32, axes: u8) {
+ let axes = AxesMask::from_bits(axes).unwrap_or(AxesMask::all());
+ if axes.contains(AxesMask::LIN_X) {
+ self.controller.pd.lin_kp.x = kp;
+ }
+ if axes.contains(AxesMask::LIN_Y) {
+ self.controller.pd.lin_kp.y = kp;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::LIN_Z) {
+ self.controller.pd.lin_kp.z = kp;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::ANG_X) {
+ self.controller.pd.ang_kp.x = kp;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::ANG_Y) {
+ self.controller.pd.ang_kp.y = kp;
+ }
+ if axes.contains(AxesMask::ANG_Z) {
+ #[cfg(feature = "dim2")]
+ {
+ self.controller.pd.ang_kp = kp;
+ }
+ #[cfg(feature = "dim3")]
+ {
+ self.controller.pd.ang_kp.z = kp;
+ }
+ }
+ }
+
+ pub fn set_ki(&mut self, ki: f32, axes: u8) {
+ let axes = AxesMask::from_bits(axes).unwrap_or(AxesMask::all());
+ if axes.contains(AxesMask::LIN_X) {
+ self.controller.lin_ki.x = ki;
+ }
+ if axes.contains(AxesMask::LIN_Y) {
+ self.controller.lin_ki.y = ki;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::LIN_Z) {
+ self.controller.lin_ki.z = ki;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::ANG_X) {
+ self.controller.ang_ki.x = ki;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::ANG_Y) {
+ self.controller.ang_ki.y = ki;
+ }
+ if axes.contains(AxesMask::ANG_Z) {
+ #[cfg(feature = "dim2")]
+ {
+ self.controller.ang_ki = ki;
+ }
+ #[cfg(feature = "dim3")]
+ {
+ self.controller.ang_ki.z = ki;
+ }
+ }
+ }
+
+ pub fn set_kd(&mut self, kd: f32, axes: u8) {
+ let axes = AxesMask::from_bits(axes).unwrap_or(AxesMask::all());
+ if axes.contains(AxesMask::LIN_X) {
+ self.controller.pd.lin_kd.x = kd;
+ }
+ if axes.contains(AxesMask::LIN_Y) {
+ self.controller.pd.lin_kd.x = kd;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::LIN_Z) {
+ self.controller.pd.lin_kd.x = kd;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::ANG_X) {
+ self.controller.pd.ang_kd.x = kd;
+ }
+ #[cfg(feature = "dim3")]
+ if axes.contains(AxesMask::ANG_Y) {
+ self.controller.pd.ang_kd.y = kd;
+ }
+ if axes.contains(AxesMask::ANG_Z) {
+ #[cfg(feature = "dim2")]
+ {
+ self.controller.pd.ang_kd = kd;
+ }
+ #[cfg(feature = "dim3")]
+ {
+ self.controller.pd.ang_kd.z = kd;
+ }
+ }
+ }
+
+ pub fn set_axes_mask(&mut self, axes_mask: u8) {
+ if let Some(mask) = AxesMask::from_bits(axes_mask) {
+ self.controller.pd.axes = mask;
+ }
+ }
+
+ pub fn reset_integrals(&mut self) {
+ self.controller.reset_integrals();
+ }
+
+ pub fn apply_linear_correction(
+ &mut self,
+ dt: f32,
+ bodies: &mut RawRigidBodySet,
+ rb_handle: FlatHandle,
+ target_translation: &RawVector,
+ target_linvel: &RawVector,
+ ) {
+ let rb_handle = utils::body_handle(rb_handle);
+ let Some(rb) = bodies.0.get_mut(rb_handle) else {
+ return;
+ };
+
+ let correction = self.controller.linear_rigid_body_correction(
+ dt,
+ rb,
+ target_translation.0.into(),
+ target_linvel.0,
+ );
+ rb.set_linvel(*rb.linvel() + correction, true);
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn apply_angular_correction(
+ &mut self,
+ dt: f32,
+ bodies: &mut RawRigidBodySet,
+ rb_handle: FlatHandle,
+ target_rotation: f32,
+ target_angvel: f32,
+ ) {
+ let rb_handle = crate::utils::body_handle(rb_handle);
+ let Some(rb) = bodies.0.get_mut(rb_handle) else {
+ return;
+ };
+
+ let correction = self.controller.angular_rigid_body_correction(
+ dt,
+ rb,
+ Rotation::new(target_rotation),
+ target_angvel,
+ );
+ rb.set_angvel(rb.angvel() + correction, true);
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn apply_angular_correction(
+ &mut self,
+ dt: f32,
+ bodies: &mut RawRigidBodySet,
+ rb_handle: FlatHandle,
+ target_rotation: &RawRotation,
+ target_angvel: &RawVector,
+ ) {
+ let rb_handle = crate::utils::body_handle(rb_handle);
+ let Some(rb) = bodies.0.get_mut(rb_handle) else {
+ return;
+ };
+
+ let correction = self.controller.angular_rigid_body_correction(
+ dt,
+ rb,
+ target_rotation.0,
+ target_angvel.0,
+ );
+ rb.set_angvel(rb.angvel() + correction, true);
+ }
+
+ pub fn linear_correction(
+ &mut self,
+ dt: f32,
+ bodies: &RawRigidBodySet,
+ rb_handle: FlatHandle,
+ target_translation: &RawVector,
+ target_linvel: &RawVector,
+ ) -> RawVector {
+ let rb_handle = crate::utils::body_handle(rb_handle);
+ let Some(rb) = bodies.0.get(rb_handle) else {
+ return RawVector(Vector::zeros());
+ };
+
+ self.controller
+ .linear_rigid_body_correction(dt, rb, target_translation.0.into(), target_linvel.0)
+ .into()
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn angular_correction(
+ &mut self,
+ dt: f32,
+ bodies: &RawRigidBodySet,
+ rb_handle: FlatHandle,
+ target_rotation: f32,
+ target_angvel: f32,
+ ) -> f32 {
+ let rb_handle = crate::utils::body_handle(rb_handle);
+ let Some(rb) = bodies.0.get(rb_handle) else {
+ return 0.0;
+ };
+
+ self.controller.angular_rigid_body_correction(
+ dt,
+ rb,
+ Rotation::new(target_rotation),
+ target_angvel,
+ )
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn angular_correction(
+ &mut self,
+ dt: f32,
+ bodies: &RawRigidBodySet,
+ rb_handle: FlatHandle,
+ target_rotation: &RawRotation,
+ target_angvel: &RawVector,
+ ) -> RawVector {
+ let rb_handle = crate::utils::body_handle(rb_handle);
+ let Some(rb) = bodies.0.get(rb_handle) else {
+ return RawVector(Vector::zeros());
+ };
+
+ self.controller
+ .angular_rigid_body_correction(dt, rb, target_rotation.0, target_angvel.0)
+ .into()
+ }
+}
diff --git a/thirdparty/rapier.js/src/control/ray_cast_vehicle_controller.rs b/thirdparty/rapier.js/src/control/ray_cast_vehicle_controller.rs
new file mode 100644
index 00000000..36bd7f61
--- /dev/null
+++ b/thirdparty/rapier.js/src/control/ray_cast_vehicle_controller.rs
@@ -0,0 +1,336 @@
+use crate::dynamics::RawRigidBodySet;
+use crate::geometry::{RawBroadPhase, RawColliderSet, RawNarrowPhase};
+use crate::math::RawVector;
+use crate::utils::{self, FlatHandle};
+use rapier::control::{DynamicRayCastVehicleController, WheelTuning};
+use rapier::math::Real;
+use rapier::pipeline::{QueryFilter, QueryFilterFlags};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawDynamicRayCastVehicleController {
+ controller: DynamicRayCastVehicleController,
+}
+
+#[wasm_bindgen]
+impl RawDynamicRayCastVehicleController {
+ #[wasm_bindgen(constructor)]
+ pub fn new(chassis: FlatHandle) -> Self {
+ Self {
+ controller: DynamicRayCastVehicleController::new(utils::body_handle(chassis)),
+ }
+ }
+
+ pub fn current_vehicle_speed(&self) -> Real {
+ self.controller.current_vehicle_speed
+ }
+
+ pub fn chassis(&self) -> FlatHandle {
+ utils::flat_handle(self.controller.chassis.0)
+ }
+
+ pub fn index_up_axis(&self) -> usize {
+ self.controller.index_up_axis
+ }
+ pub fn set_index_up_axis(&mut self, axis: usize) {
+ self.controller.index_up_axis = axis;
+ }
+
+ pub fn index_forward_axis(&self) -> usize {
+ self.controller.index_forward_axis
+ }
+ pub fn set_index_forward_axis(&mut self, axis: usize) {
+ self.controller.index_forward_axis = axis;
+ }
+
+ pub fn add_wheel(
+ &mut self,
+ chassis_connection_cs: &RawVector,
+ direction_cs: &RawVector,
+ axle_cs: &RawVector,
+ suspension_rest_length: Real,
+ radius: Real,
+ ) {
+ self.controller.add_wheel(
+ chassis_connection_cs.0.into(),
+ direction_cs.0,
+ axle_cs.0,
+ suspension_rest_length,
+ radius,
+ &WheelTuning::default(),
+ );
+ }
+
+ pub fn num_wheels(&self) -> usize {
+ self.controller.wheels().len()
+ }
+
+ pub fn update_vehicle(
+ &mut self,
+ dt: Real,
+ broad_phase: &RawBroadPhase,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &mut RawRigidBodySet,
+ colliders: &mut RawColliderSet,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_predicate: &js_sys::Function,
+ ) {
+ crate::utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ predicate,
+ exclude_rigid_body: Some(self.controller.chassis),
+ exclude_collider: None,
+ };
+
+ let query_pipeline = broad_phase.0.as_query_pipeline_mut(
+ narrow_phase.0.query_dispatcher(),
+ &mut bodies.0,
+ &mut colliders.0,
+ query_filter,
+ );
+
+ self.controller.update_vehicle(dt, query_pipeline);
+ });
+ }
+
+ /*
+ *
+ * Access to wheel properties.
+ *
+ */
+ /*
+ * Getters + setters
+ */
+ pub fn wheel_chassis_connection_point_cs(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.chassis_connection_point_cs.into())
+ }
+ pub fn set_wheel_chassis_connection_point_cs(&mut self, i: usize, value: &RawVector) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.chassis_connection_point_cs = value.0.into();
+ }
+ }
+
+ pub fn wheel_suspension_rest_length(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.suspension_rest_length)
+ }
+ pub fn set_wheel_suspension_rest_length(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.suspension_rest_length = value;
+ }
+ }
+
+ pub fn wheel_max_suspension_travel(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.max_suspension_travel)
+ }
+ pub fn set_wheel_max_suspension_travel(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.max_suspension_travel = value;
+ }
+ }
+
+ pub fn wheel_radius(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.radius)
+ }
+ pub fn set_wheel_radius(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.radius = value;
+ }
+ }
+
+ pub fn wheel_suspension_stiffness(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.suspension_stiffness)
+ }
+ pub fn set_wheel_suspension_stiffness(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.suspension_stiffness = value;
+ }
+ }
+
+ pub fn wheel_suspension_compression(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.damping_compression)
+ }
+ pub fn set_wheel_suspension_compression(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.damping_compression = value;
+ }
+ }
+
+ pub fn wheel_suspension_relaxation(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.damping_relaxation)
+ }
+ pub fn set_wheel_suspension_relaxation(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.damping_relaxation = value;
+ }
+ }
+
+ pub fn wheel_max_suspension_force(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.max_suspension_force)
+ }
+ pub fn set_wheel_max_suspension_force(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.max_suspension_force = value;
+ }
+ }
+
+ pub fn wheel_brake(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.brake)
+ }
+ pub fn set_wheel_brake(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.brake = value;
+ }
+ }
+
+ pub fn wheel_steering(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.steering)
+ }
+ pub fn set_wheel_steering(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.steering = value;
+ }
+ }
+
+ pub fn wheel_engine_force(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.engine_force)
+ }
+ pub fn set_wheel_engine_force(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.engine_force = value;
+ }
+ }
+
+ pub fn wheel_direction_cs(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.direction_cs.into())
+ }
+ pub fn set_wheel_direction_cs(&mut self, i: usize, value: &RawVector) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.direction_cs = value.0;
+ }
+ }
+
+ pub fn wheel_axle_cs(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.axle_cs.into())
+ }
+ pub fn set_wheel_axle_cs(&mut self, i: usize, value: &RawVector) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.axle_cs = value.0;
+ }
+ }
+
+ pub fn wheel_friction_slip(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.friction_slip)
+ }
+ pub fn set_wheel_friction_slip(&mut self, i: usize, value: Real) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.friction_slip = value;
+ }
+ }
+
+ pub fn wheel_side_friction_stiffness(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.side_friction_stiffness)
+ }
+
+ pub fn set_wheel_side_friction_stiffness(&mut self, i: usize, stiffness: f32) {
+ if let Some(wheel) = self.controller.wheels_mut().get_mut(i) {
+ wheel.side_friction_stiffness = stiffness;
+ }
+ }
+
+ /*
+ * Getters only.
+ */
+ pub fn wheel_rotation(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.rotation)
+ }
+
+ pub fn wheel_forward_impulse(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.forward_impulse)
+ }
+
+ pub fn wheel_side_impulse(&self, i: usize) -> Option {
+ self.controller.wheels().get(i).map(|w| w.side_impulse)
+ }
+
+ pub fn wheel_suspension_force(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.wheel_suspension_force)
+ }
+
+ pub fn wheel_contact_normal_ws(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.raycast_info().contact_normal_ws.into())
+ }
+
+ pub fn wheel_contact_point_ws(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.raycast_info().contact_point_ws.into())
+ }
+
+ pub fn wheel_suspension_length(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.raycast_info().suspension_length)
+ }
+
+ pub fn wheel_hard_point_ws(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.raycast_info().hard_point_ws.into())
+ }
+
+ pub fn wheel_is_in_contact(&self, i: usize) -> bool {
+ self.controller
+ .wheels()
+ .get(i)
+ .map(|w| w.raycast_info().is_in_contact)
+ .unwrap_or(false)
+ }
+
+ pub fn wheel_ground_object(&self, i: usize) -> Option {
+ self.controller
+ .wheels()
+ .get(i)
+ .and_then(|w| w.raycast_info().ground_object)
+ .map(|h| utils::flat_handle(h.0))
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/ccd_solver.rs b/thirdparty/rapier.js/src/dynamics/ccd_solver.rs
new file mode 100644
index 00000000..4c8b54f8
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/ccd_solver.rs
@@ -0,0 +1,13 @@
+use rapier::dynamics::CCDSolver;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawCCDSolver(pub(crate) CCDSolver);
+
+#[wasm_bindgen]
+impl RawCCDSolver {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawCCDSolver(CCDSolver::new())
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/impulse_joint.rs b/thirdparty/rapier.js/src/dynamics/impulse_joint.rs
new file mode 100644
index 00000000..bfbdf128
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/impulse_joint.rs
@@ -0,0 +1,215 @@
+use crate::dynamics::{RawImpulseJointSet, RawJointAxis, RawJointType, RawMotorModel};
+use crate::math::{RawRotation, RawVector};
+use crate::utils::{self, FlatHandle};
+use rapier::dynamics::JointAxis;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+impl RawImpulseJointSet {
+ /// The type of this joint.
+ pub fn jointType(&self, handle: FlatHandle) -> RawJointType {
+ self.map(handle, |j| j.data.locked_axes.into())
+ }
+
+ /// The unique integer identifier of the first rigid-body this joint it attached to.
+ pub fn jointBodyHandle1(&self, handle: FlatHandle) -> FlatHandle {
+ self.map(handle, |j| utils::flat_handle(j.body1.0))
+ }
+
+ /// The unique integer identifier of the second rigid-body this joint is attached to.
+ pub fn jointBodyHandle2(&self, handle: FlatHandle) -> FlatHandle {
+ self.map(handle, |j| utils::flat_handle(j.body2.0))
+ }
+
+ /// The angular part of the joint’s local frame relative to the first rigid-body it is attached to.
+ pub fn jointFrameX1(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |j| j.data.local_frame1.rotation.into())
+ }
+
+ /// The angular part of the joint’s local frame relative to the second rigid-body it is attached to.
+ pub fn jointFrameX2(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |j| j.data.local_frame2.rotation.into())
+ }
+
+ /// The position of the first anchor of this joint.
+ ///
+ /// The first anchor gives the position of the points application point on the
+ /// local frame of the first rigid-body it is attached to.
+ pub fn jointAnchor1(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |j| j.data.local_frame1.translation.vector.into())
+ }
+
+ /// The position of the second anchor of this joint.
+ ///
+ /// The second anchor gives the position of the points application point on the
+ /// local frame of the second rigid-body it is attached to.
+ pub fn jointAnchor2(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |j| j.data.local_frame2.translation.vector.into())
+ }
+
+ /// Sets the position of the first local anchor
+ pub fn jointSetAnchor1(&mut self, handle: FlatHandle, newPos: &RawVector) {
+ self.map_mut(handle, |j| {
+ j.data.set_local_anchor1(newPos.0.into());
+ });
+ }
+
+ /// Sets the position of the second local anchor
+ pub fn jointSetAnchor2(&mut self, handle: FlatHandle, newPos: &RawVector) {
+ self.map_mut(handle, |j| {
+ j.data.set_local_anchor2(newPos.0.into());
+ })
+ }
+
+ /// Are contacts between the rigid-bodies attached by this joint enabled?
+ pub fn jointContactsEnabled(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |j| j.data.contacts_enabled)
+ }
+
+ /// Sets whether contacts are enabled between the rigid-bodies attached by this joint.
+ pub fn jointSetContactsEnabled(&mut self, handle: FlatHandle, enabled: bool) {
+ self.map_mut(handle, |j| {
+ j.data.contacts_enabled = enabled;
+ });
+ }
+
+ /// Are the limits for this joint enabled?
+ pub fn jointLimitsEnabled(&self, handle: FlatHandle, axis: RawJointAxis) -> bool {
+ self.map(handle, |j| {
+ j.data.limit_axes.contains(JointAxis::from(axis).into())
+ })
+ }
+
+ /// Return the lower limit along the given joint axis.
+ pub fn jointLimitsMin(&self, handle: FlatHandle, axis: RawJointAxis) -> f32 {
+ self.map(handle, |j| j.data.limits[axis as usize].min)
+ }
+
+ /// If this is a prismatic joint, returns its upper limit.
+ pub fn jointLimitsMax(&self, handle: FlatHandle, axis: RawJointAxis) -> f32 {
+ self.map(handle, |j| j.data.limits[axis as usize].max)
+ }
+
+ /// Enables and sets the joint limits
+ pub fn jointSetLimits(&mut self, handle: FlatHandle, axis: RawJointAxis, min: f32, max: f32) {
+ self.map_mut(handle, |j| {
+ j.data.set_limits(axis.into(), [min, max]);
+ });
+ }
+
+ pub fn jointConfigureMotorModel(
+ &mut self,
+ handle: FlatHandle,
+ axis: RawJointAxis,
+ model: RawMotorModel,
+ ) {
+ self.map_mut(handle, |j| {
+ j.data.motors[axis as usize].model = model.into()
+ })
+ }
+
+ /*
+ #[cfg(feature = "dim3")]
+ pub fn jointConfigureBallMotorVelocity(
+ &mut self,
+ handle: FlatHandle,
+ vx: f32,
+ vy: f32,
+ vz: f32,
+ factor: f32,
+ ) {
+ let targetVel = Vector3::new(vx, vy, vz);
+
+ self.map_mut(handle, |j| match &mut j.params {
+ JointData::SphericalJoint(j) => j.configure_motor_velocity(targetVel, factor),
+ _ => {}
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn jointConfigureBallMotorPosition(
+ &mut self,
+ handle: FlatHandle,
+ qw: f32,
+ qx: f32,
+ qy: f32,
+ qz: f32,
+ stiffness: f32,
+ damping: f32,
+ ) {
+ let quat = Quaternion::new(qw, qx, qy, qz);
+
+ self.map_mut(handle, |j| match &mut j.params {
+ JointData::SphericalJoint(j) => {
+ if let Some(unit_quat) = UnitQuaternion::try_new(quat, 1.0e-5) {
+ j.configure_motor_position(unit_quat, stiffness, damping)
+ }
+ }
+ _ => {}
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn jointConfigureBallMotor(
+ &mut self,
+ handle: FlatHandle,
+ qw: f32,
+ qx: f32,
+ qy: f32,
+ qz: f32,
+ vx: f32,
+ vy: f32,
+ vz: f32,
+ stiffness: f32,
+ damping: f32,
+ ) {
+ let quat = Quaternion::new(qw, qx, qy, qz);
+ let vel = Vector3::new(vx, vy, vz);
+
+ self.map_mut(handle, |j| match &mut j.params {
+ JointData::SphericalJoint(j) => {
+ if let Some(unit_quat) = UnitQuaternion::try_new(quat, 1.0e-5) {
+ j.configure_motor(unit_quat, vel, stiffness, damping)
+ }
+ }
+ _ => {}
+ })
+ }
+ */
+
+ pub fn jointConfigureMotorVelocity(
+ &mut self,
+ handle: FlatHandle,
+ axis: RawJointAxis,
+ targetVel: f32,
+ factor: f32,
+ ) {
+ self.jointConfigureMotor(handle, axis, 0.0, targetVel, 0.0, factor)
+ }
+
+ pub fn jointConfigureMotorPosition(
+ &mut self,
+ handle: FlatHandle,
+ axis: RawJointAxis,
+ targetPos: f32,
+ stiffness: f32,
+ damping: f32,
+ ) {
+ self.jointConfigureMotor(handle, axis, targetPos, 0.0, stiffness, damping)
+ }
+
+ pub fn jointConfigureMotor(
+ &mut self,
+ handle: FlatHandle,
+ axis: RawJointAxis,
+ targetPos: f32,
+ targetVel: f32,
+ stiffness: f32,
+ damping: f32,
+ ) {
+ self.map_mut(handle, |j| {
+ j.data
+ .set_motor(axis.into(), targetPos, targetVel, stiffness, damping);
+ })
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/impulse_joint_set.rs b/thirdparty/rapier.js/src/dynamics/impulse_joint_set.rs
new file mode 100644
index 00000000..b7ba1bf0
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/impulse_joint_set.rs
@@ -0,0 +1,92 @@
+use crate::dynamics::RawGenericJoint;
+use crate::utils::{self, FlatHandle};
+use rapier::dynamics::{ImpulseJoint, ImpulseJointSet};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawImpulseJointSet(pub(crate) ImpulseJointSet);
+
+impl RawImpulseJointSet {
+ pub(crate) fn map(&self, handle: FlatHandle, f: impl FnOnce(&ImpulseJoint) -> T) -> T {
+ let body = self.0.get(utils::impulse_joint_handle(handle)).expect(
+ "Invalid ImpulseJoint reference. It may have been removed from the physics World.",
+ );
+ f(body)
+ }
+
+ pub(crate) fn map_mut(
+ &mut self,
+ handle: FlatHandle,
+ f: impl FnOnce(&mut ImpulseJoint) -> T,
+ ) -> T {
+ let body = self
+ .0
+ .get_mut(utils::impulse_joint_handle(handle), true)
+ .expect(
+ "Invalid ImpulseJoint reference. It may have been removed from the physics World.",
+ );
+ f(body)
+ }
+}
+
+#[wasm_bindgen]
+impl RawImpulseJointSet {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawImpulseJointSet(ImpulseJointSet::new())
+ }
+
+ pub fn createJoint(
+ &mut self,
+ params: &RawGenericJoint,
+ parent1: FlatHandle,
+ parent2: FlatHandle,
+ wake_up: bool,
+ ) -> FlatHandle {
+ utils::flat_handle(
+ self.0
+ .insert(
+ utils::body_handle(parent1),
+ utils::body_handle(parent2),
+ params.0.clone(),
+ wake_up,
+ )
+ .0,
+ )
+ }
+
+ pub fn remove(&mut self, handle: FlatHandle, wakeUp: bool) {
+ let handle = utils::impulse_joint_handle(handle);
+ self.0.remove(handle, wakeUp);
+ }
+
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+
+ pub fn contains(&self, handle: FlatHandle) -> bool {
+ self.0.get(utils::impulse_joint_handle(handle)).is_some()
+ }
+
+ /// Applies the given JavaScript function to the integer handle of each joint managed by this physics world.
+ ///
+ /// # Parameters
+ /// - `f(handle)`: the function to apply to the integer handle of each joint managed by this set. Called as `f(collider)`.
+ pub fn forEachJointHandle(&self, f: &js_sys::Function) {
+ let this = JsValue::null();
+ for (handle, _) in self.0.iter() {
+ let _ = f.call1(&this, &JsValue::from(utils::flat_handle(handle.0)));
+ }
+ }
+
+ /// Applies the given JavaScript function to the integer handle of each joint attached to the given rigid-body.
+ ///
+ /// # Parameters
+ /// - `f(handle)`: the function to apply to the integer handle of each joint attached to the rigid-body. Called as `f(collider)`.
+ pub fn forEachJointAttachedToRigidBody(&self, body: FlatHandle, f: &js_sys::Function) {
+ let this = JsValue::null();
+ for (_, _, handle, _) in self.0.attached_joints(utils::body_handle(body)) {
+ let _ = f.call1(&this, &JsValue::from(utils::flat_handle(handle.0)));
+ }
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/integration_parameters.rs b/thirdparty/rapier.js/src/dynamics/integration_parameters.rs
new file mode 100644
index 00000000..b273e057
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/integration_parameters.rs
@@ -0,0 +1,101 @@
+use rapier::dynamics::IntegrationParameters;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawIntegrationParameters(pub(crate) IntegrationParameters);
+
+#[wasm_bindgen]
+impl RawIntegrationParameters {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawIntegrationParameters(IntegrationParameters::default())
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn dt(&self) -> f32 {
+ self.0.dt
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn contact_erp(&self) -> f32 {
+ self.0.contact_erp()
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn normalizedAllowedLinearError(&self) -> f32 {
+ self.0.normalized_allowed_linear_error
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn normalizedPredictionDistance(&self) -> f32 {
+ self.0.normalized_prediction_distance
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn numSolverIterations(&self) -> usize {
+ self.0.num_solver_iterations
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn numInternalPgsIterations(&self) -> usize {
+ self.0.num_internal_pgs_iterations
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn minIslandSize(&self) -> usize {
+ self.0.min_island_size
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn maxCcdSubsteps(&self) -> usize {
+ self.0.max_ccd_substeps
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn lengthUnit(&self) -> f32 {
+ self.0.length_unit
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_dt(&mut self, value: f32) {
+ self.0.dt = value;
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_contact_natural_frequency(&mut self, value: f32) {
+ self.0.contact_natural_frequency = value
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_normalizedAllowedLinearError(&mut self, value: f32) {
+ self.0.normalized_allowed_linear_error = value
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_normalizedPredictionDistance(&mut self, value: f32) {
+ self.0.normalized_prediction_distance = value
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_numSolverIterations(&mut self, value: usize) {
+ self.0.num_solver_iterations = value;
+ }
+ #[wasm_bindgen(setter)]
+ pub fn set_numInternalPgsIterations(&mut self, value: usize) {
+ self.0.num_internal_pgs_iterations = value;
+ }
+ #[wasm_bindgen(setter)]
+ pub fn set_minIslandSize(&mut self, value: usize) {
+ self.0.min_island_size = value
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_maxCcdSubsteps(&mut self, value: usize) {
+ self.0.max_ccd_substeps = value
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_lengthUnit(&mut self, value: f32) {
+ self.0.length_unit = value
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/island_manager.rs b/thirdparty/rapier.js/src/dynamics/island_manager.rs
new file mode 100644
index 00000000..7b4cfae6
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/island_manager.rs
@@ -0,0 +1,31 @@
+use crate::utils;
+use rapier::dynamics::IslandManager;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawIslandManager(pub(crate) IslandManager);
+
+#[wasm_bindgen]
+impl RawIslandManager {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawIslandManager(IslandManager::new())
+ }
+
+ /// Applies the given JavaScript function to the integer handle of each active rigid-body
+ /// managed by this island manager.
+ ///
+ /// After a short time of inactivity, a rigid-body is automatically deactivated ("asleep") by
+ /// the physics engine in order to save computational power. A sleeping rigid-body never moves
+ /// unless it is moved manually by the user.
+ ///
+ /// # Parameters
+ /// - `f(handle)`: the function to apply to the integer handle of each active rigid-body managed by this
+ /// set. Called as `f(collider)`.
+ pub fn forEachActiveRigidBodyHandle(&self, f: &js_sys::Function) {
+ let this = JsValue::null();
+ for handle in self.0.active_bodies() {
+ let _ = f.call1(&this, &JsValue::from(utils::flat_handle(handle.0)));
+ }
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/joint.rs b/thirdparty/rapier.js/src/dynamics/joint.rs
new file mode 100644
index 00000000..74ab2b3d
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/joint.rs
@@ -0,0 +1,314 @@
+use crate::math::{RawRotation, RawVector};
+use na::Unit;
+use rapier::dynamics::{
+ FixedJointBuilder, GenericJoint, JointAxesMask, JointAxis, MotorModel, PrismaticJointBuilder,
+ RevoluteJointBuilder, RopeJointBuilder, SpringJointBuilder,
+};
+#[cfg(feature = "dim3")]
+use rapier::dynamics::{GenericJointBuilder, SphericalJointBuilder};
+use rapier::math::Isometry;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+#[cfg(feature = "dim2")]
+pub enum RawJointType {
+ Revolute,
+ Fixed,
+ Prismatic,
+ Rope,
+ Spring,
+ Generic,
+}
+
+#[wasm_bindgen]
+#[cfg(feature = "dim3")]
+pub enum RawJointType {
+ Revolute,
+ Fixed,
+ Prismatic,
+ Rope,
+ Spring,
+ Spherical,
+ Generic,
+}
+
+/// The type of this joint.
+#[cfg(feature = "dim2")]
+impl From for RawJointType {
+ fn from(ty: JointAxesMask) -> RawJointType {
+ let rev_axes = JointAxesMask::LIN_X | JointAxesMask::LIN_Y;
+ let pri_axes = JointAxesMask::LIN_Y | JointAxesMask::ANG_X;
+ let fix_axes = JointAxesMask::LIN_X | JointAxesMask::LIN_Y | JointAxesMask::ANG_X;
+
+ if ty == rev_axes {
+ RawJointType::Revolute
+ } else if ty == pri_axes {
+ RawJointType::Prismatic
+ } else if ty == fix_axes {
+ RawJointType::Fixed
+ } else {
+ RawJointType::Generic
+ }
+ }
+}
+
+/// The type of this joint.
+#[cfg(feature = "dim3")]
+impl From for RawJointType {
+ fn from(ty: JointAxesMask) -> RawJointType {
+ let rev_axes = JointAxesMask::LIN_X
+ | JointAxesMask::LIN_Y
+ | JointAxesMask::LIN_Z
+ | JointAxesMask::ANG_Y
+ | JointAxesMask::ANG_Z;
+ let pri_axes = JointAxesMask::LIN_Y
+ | JointAxesMask::LIN_Z
+ | JointAxesMask::ANG_X
+ | JointAxesMask::ANG_Y
+ | JointAxesMask::ANG_Z;
+ let sph_axes = JointAxesMask::ANG_X | JointAxesMask::ANG_Y | JointAxesMask::ANG_Z;
+ let fix_axes = JointAxesMask::LIN_X
+ | JointAxesMask::LIN_Y
+ | JointAxesMask::LIN_Z
+ | JointAxesMask::ANG_X
+ | JointAxesMask::ANG_Y
+ | JointAxesMask::ANG_Z;
+
+ if ty == rev_axes {
+ RawJointType::Revolute
+ } else if ty == pri_axes {
+ RawJointType::Prismatic
+ } else if ty == sph_axes {
+ RawJointType::Spherical
+ } else if ty == fix_axes {
+ RawJointType::Fixed
+ } else {
+ RawJointType::Generic
+ }
+ }
+}
+
+#[wasm_bindgen]
+pub enum RawMotorModel {
+ AccelerationBased,
+ ForceBased,
+}
+
+impl From for MotorModel {
+ fn from(model: RawMotorModel) -> MotorModel {
+ match model {
+ RawMotorModel::AccelerationBased => MotorModel::AccelerationBased,
+ RawMotorModel::ForceBased => MotorModel::ForceBased,
+ }
+ }
+}
+
+#[cfg(feature = "dim2")]
+#[wasm_bindgen]
+#[derive(Copy, Clone)]
+pub enum RawJointAxis {
+ LinX,
+ LinY,
+ AngX,
+}
+
+#[cfg(feature = "dim3")]
+#[wasm_bindgen]
+#[derive(Copy, Clone)]
+pub enum RawJointAxis {
+ LinX,
+ LinY,
+ LinZ,
+ AngX,
+ AngY,
+ AngZ,
+}
+
+impl From for JointAxis {
+ fn from(axis: RawJointAxis) -> JointAxis {
+ match axis {
+ RawJointAxis::LinX => JointAxis::LinX,
+ RawJointAxis::LinY => JointAxis::LinY,
+ #[cfg(feature = "dim3")]
+ RawJointAxis::LinZ => JointAxis::LinZ,
+ RawJointAxis::AngX => JointAxis::AngX,
+ #[cfg(feature = "dim3")]
+ RawJointAxis::AngY => JointAxis::AngY,
+ #[cfg(feature = "dim3")]
+ RawJointAxis::AngZ => JointAxis::AngZ,
+ }
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawGenericJoint(pub(crate) GenericJoint);
+
+#[wasm_bindgen]
+impl RawGenericJoint {
+ /// Creates a new joint descriptor that builds generic joints.
+ ///
+ /// Generic joints allow arbitrary axes of freedom to be selected
+ /// for the joint from the available 6 degrees of freedom.
+ #[cfg(feature = "dim3")]
+ pub fn generic(
+ anchor1: &RawVector,
+ anchor2: &RawVector,
+ axis: &RawVector,
+ lockedAxes: u8,
+ ) -> Option {
+ let axesMask: JointAxesMask = JointAxesMask::from_bits(lockedAxes)?;
+ let axis = Unit::try_new(axis.0, 0.0)?;
+ let joint: GenericJoint = GenericJointBuilder::new(axesMask)
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into())
+ .local_axis1(axis)
+ .local_axis2(axis)
+ .into();
+ Some(Self(joint))
+ }
+
+ pub fn spring(
+ rest_length: f32,
+ stiffness: f32,
+ damping: f32,
+ anchor1: &RawVector,
+ anchor2: &RawVector,
+ ) -> Self {
+ Self(
+ SpringJointBuilder::new(rest_length, stiffness, damping)
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into())
+ .into(),
+ )
+ }
+
+ pub fn rope(length: f32, anchor1: &RawVector, anchor2: &RawVector) -> Self {
+ Self(
+ RopeJointBuilder::new(length)
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into())
+ .into(),
+ )
+ }
+
+ /// Create a new joint descriptor that builds spherical joints.
+ ///
+ /// A spherical joints allows three relative rotational degrees of freedom
+ /// by preventing any relative translation between the anchors of the
+ /// two attached rigid-bodies.
+ #[cfg(feature = "dim3")]
+ pub fn spherical(anchor1: &RawVector, anchor2: &RawVector) -> Self {
+ Self(
+ SphericalJointBuilder::new()
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into())
+ .into(),
+ )
+ }
+
+ /// Creates a new joint descriptor that builds a Prismatic joint.
+ ///
+ /// A prismatic joint removes all the degrees of freedom between the
+ /// affected bodies, except for the translation along one axis.
+ ///
+ /// Returns `None` if any of the provided axes cannot be normalized.
+ #[cfg(feature = "dim2")]
+ pub fn prismatic(
+ anchor1: &RawVector,
+ anchor2: &RawVector,
+ axis: &RawVector,
+ limitsEnabled: bool,
+ limitsMin: f32,
+ limitsMax: f32,
+ ) -> Option {
+ let axis = Unit::try_new(axis.0, 0.0)?;
+ let mut joint = PrismaticJointBuilder::new(axis)
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into());
+
+ if limitsEnabled {
+ joint = joint.limits([limitsMin, limitsMax]);
+ }
+
+ Some(Self(joint.into()))
+ }
+
+ /// Creates a new joint descriptor that builds a Prismatic joint.
+ ///
+ /// A prismatic joint removes all the degrees of freedom between the
+ /// affected bodies, except for the translation along one axis.
+ ///
+ /// Returns `None` if any of the provided axes cannot be normalized.
+ #[cfg(feature = "dim3")]
+ pub fn prismatic(
+ anchor1: &RawVector,
+ anchor2: &RawVector,
+ axis: &RawVector,
+ limitsEnabled: bool,
+ limitsMin: f32,
+ limitsMax: f32,
+ ) -> Option {
+ let axis = Unit::try_new(axis.0, 0.0)?;
+ let mut joint = PrismaticJointBuilder::new(axis)
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into());
+
+ if limitsEnabled {
+ joint = joint.limits([limitsMin, limitsMax]);
+ }
+
+ Some(Self(joint.into()))
+ }
+
+ /// Creates a new joint descriptor that builds a Fixed joint.
+ ///
+ /// A fixed joint removes all the degrees of freedom between the affected bodies.
+ pub fn fixed(
+ anchor1: &RawVector,
+ axes1: &RawRotation,
+ anchor2: &RawVector,
+ axes2: &RawRotation,
+ ) -> RawGenericJoint {
+ let pos1 = Isometry::from_parts(anchor1.0.into(), axes1.0);
+ let pos2 = Isometry::from_parts(anchor2.0.into(), axes2.0);
+ Self(
+ FixedJointBuilder::new()
+ .local_frame1(pos1)
+ .local_frame2(pos2)
+ .into(),
+ )
+ }
+
+ /// Create a new joint descriptor that builds Revolute joints.
+ ///
+ /// A revolute joint removes all degrees of freedom between the affected
+ /// bodies except for the rotation.
+ #[cfg(feature = "dim2")]
+ pub fn revolute(anchor1: &RawVector, anchor2: &RawVector) -> Option {
+ Some(Self(
+ RevoluteJointBuilder::new()
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into())
+ .into(),
+ ))
+ }
+
+ /// Create a new joint descriptor that builds Revolute joints.
+ ///
+ /// A revolute joint removes all degrees of freedom between the affected
+ /// bodies except for the rotation along one axis.
+ #[cfg(feature = "dim3")]
+ pub fn revolute(
+ anchor1: &RawVector,
+ anchor2: &RawVector,
+ axis: &RawVector,
+ ) -> Option {
+ let axis = Unit::try_new(axis.0, 0.0)?;
+ Some(Self(
+ RevoluteJointBuilder::new(axis)
+ .local_anchor1(anchor1.0.into())
+ .local_anchor2(anchor2.0.into())
+ .into(),
+ ))
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/mod.rs b/thirdparty/rapier.js/src/dynamics/mod.rs
new file mode 100644
index 00000000..36cc7eaa
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/mod.rs
@@ -0,0 +1,20 @@
+//! Structures related to dynamics: bodies, joints, etc.
+
+pub use self::ccd_solver::*;
+pub use self::impulse_joint_set::*;
+pub use self::integration_parameters::*;
+pub use self::island_manager::*;
+pub use self::joint::*;
+pub use self::multibody_joint_set::*;
+pub use self::rigid_body_set::*;
+
+mod ccd_solver;
+mod impulse_joint;
+mod impulse_joint_set;
+mod integration_parameters;
+mod island_manager;
+mod joint;
+mod multibody_joint;
+mod multibody_joint_set;
+mod rigid_body;
+mod rigid_body_set;
diff --git a/thirdparty/rapier.js/src/dynamics/multibody_joint.rs b/thirdparty/rapier.js/src/dynamics/multibody_joint.rs
new file mode 100644
index 00000000..ff6e816e
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/multibody_joint.rs
@@ -0,0 +1,196 @@
+use crate::dynamics::{RawJointAxis, RawJointType, RawMultibodyJointSet};
+use crate::math::{RawRotation, RawVector};
+use crate::utils::FlatHandle;
+use rapier::dynamics::JointAxis;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+impl RawMultibodyJointSet {
+ /// The type of this joint.
+ pub fn jointType(&self, handle: FlatHandle) -> RawJointType {
+ self.map(handle, |j| j.data.locked_axes.into())
+ }
+
+ // /// The unique integer identifier of the first rigid-body this joint it attached to.
+ // pub fn jointBodyHandle1(&self, handle: FlatHandle) -> u32 {
+ // self.map(handle, |j| j.body1.into_raw_parts().0)
+ // }
+ //
+ // /// The unique integer identifier of the second rigid-body this joint is attached to.
+ // pub fn jointBodyHandle2(&self, handle: FlatHandle) -> u32 {
+ // self.map(handle, |j| j.body2.into_raw_parts().0)
+ // }
+
+ /// The angular part of the joint’s local frame relative to the first rigid-body it is attached to.
+ pub fn jointFrameX1(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |j| j.data.local_frame1.rotation.into())
+ }
+
+ /// The angular part of the joint’s local frame relative to the second rigid-body it is attached to.
+ pub fn jointFrameX2(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |j| j.data.local_frame2.rotation.into())
+ }
+
+ /// The position of the first anchor of this joint.
+ ///
+ /// The first anchor gives the position of the points application point on the
+ /// local frame of the first rigid-body it is attached to.
+ pub fn jointAnchor1(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |j| j.data.local_frame1.translation.vector.into())
+ }
+
+ /// The position of the second anchor of this joint.
+ ///
+ /// The second anchor gives the position of the points application point on the
+ /// local frame of the second rigid-body it is attached to.
+ pub fn jointAnchor2(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |j| j.data.local_frame2.translation.vector.into())
+ }
+
+ /// Are contacts between the rigid-bodies attached by this joint enabled?
+ pub fn jointContactsEnabled(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |j| j.data.contacts_enabled)
+ }
+
+ /// Sets whether contacts are enabled between the rigid-bodies attached by this joint.
+ pub fn jointSetContactsEnabled(&mut self, handle: FlatHandle, enabled: bool) {
+ self.map_mut(handle, |j| {
+ j.data.contacts_enabled = enabled;
+ });
+ }
+
+ /// Are the limits for this joint enabled?
+ pub fn jointLimitsEnabled(&self, handle: FlatHandle, axis: RawJointAxis) -> bool {
+ self.map(handle, |j| {
+ j.data.limit_axes.contains(JointAxis::from(axis).into())
+ })
+ }
+
+ /// Return the lower limit along the given joint axis.
+ pub fn jointLimitsMin(&self, handle: FlatHandle, axis: RawJointAxis) -> f32 {
+ self.map(handle, |j| j.data.limits[axis as usize].min)
+ }
+
+ /// If this is a prismatic joint, returns its upper limit.
+ pub fn jointLimitsMax(&self, handle: FlatHandle, axis: RawJointAxis) -> f32 {
+ self.map(handle, |j| j.data.limits[axis as usize].max)
+ }
+
+ // pub fn jointConfigureMotorModel(
+ // &mut self,
+ // handle: FlatHandle,
+ // axis: RawJointAxis,
+ // model: RawMotorModel,
+ // ) {
+ // self.map_mut(handle, |j| {
+ // j.data.motors[axis as usize].model = model.into()
+ // })
+ // }
+
+ /*
+ #[cfg(feature = "dim3")]
+ pub fn jointConfigureBallMotorVelocity(
+ &mut self,
+ handle: FlatHandle,
+ vx: f32,
+ vy: f32,
+ vz: f32,
+ factor: f32,
+ ) {
+ let targetVel = Vector3::new(vx, vy, vz);
+
+ self.map_mut(handle, |j| match &mut j.params {
+ JointData::SphericalJoint(j) => j.configure_motor_velocity(targetVel, factor),
+ _ => {}
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn jointConfigureBallMotorPosition(
+ &mut self,
+ handle: FlatHandle,
+ qw: f32,
+ qx: f32,
+ qy: f32,
+ qz: f32,
+ stiffness: f32,
+ damping: f32,
+ ) {
+ let quat = Quaternion::new(qw, qx, qy, qz);
+
+ self.map_mut(handle, |j| match &mut j.params {
+ JointData::SphericalJoint(j) => {
+ if let Some(unit_quat) = UnitQuaternion::try_new(quat, 1.0e-5) {
+ j.configure_motor_position(unit_quat, stiffness, damping)
+ }
+ }
+ _ => {}
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn jointConfigureBallMotor(
+ &mut self,
+ handle: FlatHandle,
+ qw: f32,
+ qx: f32,
+ qy: f32,
+ qz: f32,
+ vx: f32,
+ vy: f32,
+ vz: f32,
+ stiffness: f32,
+ damping: f32,
+ ) {
+ let quat = Quaternion::new(qw, qx, qy, qz);
+ let vel = Vector3::new(vx, vy, vz);
+
+ self.map_mut(handle, |j| match &mut j.params {
+ JointData::SphericalJoint(j) => {
+ if let Some(unit_quat) = UnitQuaternion::try_new(quat, 1.0e-5) {
+ j.configure_motor(unit_quat, vel, stiffness, damping)
+ }
+ }
+ _ => {}
+ })
+ }
+ */
+
+ // pub fn jointConfigureMotorVelocity(
+ // &mut self,
+ // handle: FlatHandle,
+ // axis: RawJointAxis,
+ // targetVel: f32,
+ // factor: f32,
+ // ) {
+ // self.jointConfigureMotor(handle, axis, 0.0, targetVel, 0.0, factor)
+ // }
+ //
+ // pub fn jointConfigureMotorPosition(
+ // &mut self,
+ // handle: FlatHandle,
+ // axis: RawJointAxis,
+ // targetPos: f32,
+ // stiffness: f32,
+ // damping: f32,
+ // ) {
+ // self.jointConfigureMotor(handle, axis, targetPos, 0.0, stiffness, damping)
+ // }
+
+ // pub fn jointConfigureMotor(
+ // &mut self,
+ // handle: FlatHandle,
+ // axis: RawJointAxis,
+ // targetPos: f32,
+ // targetVel: f32,
+ // stiffness: f32,
+ // damping: f32,
+ // ) {
+ // self.map_mut(handle, |j| {
+ // j.data.motors[axis as usize].target_pos = targetPos;
+ // j.data.motors[axis as usize].target_vel = targetVel;
+ // j.data.motors[axis as usize].stiffness = stiffness;
+ // j.data.motors[axis as usize].damping = damping;
+ // })
+ // }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/multibody_joint_set.rs b/thirdparty/rapier.js/src/dynamics/multibody_joint_set.rs
new file mode 100644
index 00000000..c7f4839e
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/multibody_joint_set.rs
@@ -0,0 +1,85 @@
+use crate::dynamics::RawGenericJoint;
+use crate::utils::{self, FlatHandle};
+use rapier::dynamics::{MultibodyJoint, MultibodyJointSet};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawMultibodyJointSet(pub(crate) MultibodyJointSet);
+
+impl RawMultibodyJointSet {
+ pub(crate) fn map(&self, handle: FlatHandle, f: impl FnOnce(&MultibodyJoint) -> T) -> T {
+ let (body, link_id) = self
+ .0
+ .get(utils::multibody_joint_handle(handle))
+ .expect("Invalid Joint reference. It may have been removed from the physics World.");
+ f(body.link(link_id).unwrap().joint())
+ }
+
+ pub(crate) fn map_mut(
+ &mut self,
+ handle: FlatHandle,
+ f: impl FnOnce(&mut MultibodyJoint) -> T,
+ ) -> T {
+ let (body, link_id) = self
+ .0
+ .get_mut(utils::multibody_joint_handle(handle))
+ .expect("Invalid Joint reference. It may have been removed from the physics World.");
+ f(&mut body.link_mut(link_id).unwrap().joint)
+ }
+}
+
+#[wasm_bindgen]
+impl RawMultibodyJointSet {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawMultibodyJointSet(MultibodyJointSet::new())
+ }
+
+ pub fn createJoint(
+ &mut self,
+ params: &RawGenericJoint,
+ parent1: FlatHandle,
+ parent2: FlatHandle,
+ wakeUp: bool,
+ ) -> FlatHandle {
+ // TODO: avoid the unwrap?
+ let parent1 = utils::body_handle(parent1);
+ let parent2 = utils::body_handle(parent2);
+
+ self.0
+ .insert(parent1, parent2, params.0.clone(), wakeUp)
+ .map(|h| utils::flat_handle(h.0))
+ .unwrap_or(FlatHandle::MAX)
+ }
+
+ pub fn remove(&mut self, handle: FlatHandle, wakeUp: bool) {
+ let handle = utils::multibody_joint_handle(handle);
+ self.0.remove(handle, wakeUp);
+ }
+
+ pub fn contains(&self, handle: FlatHandle) -> bool {
+ self.0.get(utils::multibody_joint_handle(handle)).is_some()
+ }
+
+ /// Applies the given JavaScript function to the integer handle of each joint managed by this physics world.
+ ///
+ /// # Parameters
+ /// - `f(handle)`: the function to apply to the integer handle of each joint managed by this set. Called as `f(collider)`.
+ pub fn forEachJointHandle(&self, f: &js_sys::Function) {
+ let this = JsValue::null();
+ for (handle, _, _, _) in self.0.iter() {
+ let _ = f.call1(&this, &JsValue::from(utils::flat_handle(handle.0)));
+ }
+ }
+
+ /// Applies the given JavaScript function to the integer handle of each joint attached to the given rigid-body.
+ ///
+ /// # Parameters
+ /// - `f(handle)`: the function to apply to the integer handle of each joint attached to the rigid-body. Called as `f(collider)`.
+ pub fn forEachJointAttachedToRigidBody(&self, body: FlatHandle, f: &js_sys::Function) {
+ let this = JsValue::null();
+ for (_, _, handle) in self.0.attached_joints(utils::body_handle(body)) {
+ let _ = f.call1(&this, &JsValue::from(utils::flat_handle(handle.0)));
+ }
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/rigid_body.rs b/thirdparty/rapier.js/src/dynamics/rigid_body.rs
new file mode 100644
index 00000000..d7174d77
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/rigid_body.rs
@@ -0,0 +1,753 @@
+use crate::dynamics::{RawRigidBodySet, RawRigidBodyType};
+use crate::geometry::RawColliderSet;
+#[cfg(feature = "dim3")]
+use crate::math::RawSdpMatrix3;
+use crate::math::{RawRotation, RawVector};
+use crate::utils::{self, FlatHandle};
+use na::Point;
+use rapier::dynamics::MassProperties;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+impl RawRigidBodySet {
+ /// The world-space translation of this rigid-body.
+ pub fn rbTranslation(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| RawVector(rb.position().translation.vector))
+ }
+
+ /// The world-space orientation of this rigid-body.
+ pub fn rbRotation(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |rb| RawRotation(rb.position().rotation))
+ }
+
+ /// Put the given rigid-body to sleep.
+ pub fn rbSleep(&mut self, handle: FlatHandle) {
+ self.map_mut(handle, |rb| rb.sleep());
+ }
+
+ /// Is this rigid-body sleeping?
+ pub fn rbIsSleeping(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |rb| rb.is_sleeping())
+ }
+
+ /// Is the velocity of this rigid-body not zero?
+ pub fn rbIsMoving(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |rb| rb.is_moving())
+ }
+
+ /// The world-space predicted translation of this rigid-body.
+ ///
+ /// If this rigid-body is kinematic this value is set by the `setNextKinematicTranslation`
+ /// method and is used for estimating the kinematic body velocity at the next timestep.
+ /// For non-kinematic bodies, this value is currently unspecified.
+ pub fn rbNextTranslation(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| {
+ RawVector(rb.next_position().translation.vector)
+ })
+ }
+
+ /// The world-space predicted orientation of this rigid-body.
+ ///
+ /// If this rigid-body is kinematic this value is set by the `setNextKinematicRotation`
+ /// method and is used for estimating the kinematic body velocity at the next timestep.
+ /// For non-kinematic bodies, this value is currently unspecified.
+ pub fn rbNextRotation(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |rb| RawRotation(rb.next_position().rotation))
+ }
+
+ /// Sets the translation of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `x`: the world-space position of the rigid-body along the `x` axis.
+ /// - `y`: the world-space position of the rigid-body along the `y` axis.
+ /// - `z`: the world-space position of the rigid-body along the `z` axis.
+ /// - `wakeUp`: forces the rigid-body to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim3")]
+ pub fn rbSetTranslation(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.set_translation(na::Vector3::new(x, y, z), wakeUp);
+ })
+ }
+
+ /// Sets the translation of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `x`: the world-space position of the rigid-body along the `x` axis.
+ /// - `y`: the world-space position of the rigid-body along the `y` axis.
+ /// - `wakeUp`: forces the rigid-body to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim2")]
+ pub fn rbSetTranslation(&mut self, handle: FlatHandle, x: f32, y: f32, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.set_translation(na::Vector2::new(x, y), wakeUp);
+ })
+ }
+
+ /// Sets the rotation quaternion of this rigid-body.
+ ///
+ /// This does nothing if a zero quaternion is provided.
+ ///
+ /// # Parameters
+ /// - `x`: the first vector component of the quaternion.
+ /// - `y`: the second vector component of the quaternion.
+ /// - `z`: the third vector component of the quaternion.
+ /// - `w`: the scalar component of the quaternion.
+ /// - `wakeUp`: forces the rigid-body to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim3")]
+ pub fn rbSetRotation(
+ &mut self,
+ handle: FlatHandle,
+ x: f32,
+ y: f32,
+ z: f32,
+ w: f32,
+ wakeUp: bool,
+ ) {
+ if let Some(q) = na::Unit::try_new(na::Quaternion::new(w, x, y, z), 0.0) {
+ self.map_mut(handle, |rb| rb.set_rotation(q, wakeUp))
+ }
+ }
+
+ /// Sets the rotation angle of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `angle`: the rotation angle, in radians.
+ /// - `wakeUp`: forces the rigid-body to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim2")]
+ pub fn rbSetRotation(&mut self, handle: FlatHandle, angle: f32, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.set_rotation(na::UnitComplex::new(angle), wakeUp)
+ })
+ }
+
+ /// Sets the linear velocity of this rigid-body.
+ pub fn rbSetLinvel(&mut self, handle: FlatHandle, linvel: &RawVector, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.set_linvel(linvel.0, wakeUp);
+ });
+ }
+
+ /// Sets the angular velocity of this rigid-body.
+ #[cfg(feature = "dim2")]
+ pub fn rbSetAngvel(&mut self, handle: FlatHandle, angvel: f32, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.set_angvel(angvel, wakeUp);
+ });
+ }
+
+ /// Sets the angular velocity of this rigid-body.
+ #[cfg(feature = "dim3")]
+ pub fn rbSetAngvel(&mut self, handle: FlatHandle, angvel: &RawVector, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.set_angvel(angvel.0, wakeUp);
+ });
+ }
+
+ /// If this rigid body is kinematic, sets its future translation after the next timestep integration.
+ ///
+ /// This should be used instead of `rigidBody.setTranslation` to make the dynamic object
+ /// interacting with this kinematic body behave as expected. Internally, Rapier will compute
+ /// an artificial velocity for this rigid-body from its current position and its next kinematic
+ /// position. This velocity will be used to compute forces on dynamic bodies interacting with
+ /// this body.
+ ///
+ /// # Parameters
+ /// - `x`: the world-space position of the rigid-body along the `x` axis.
+ /// - `y`: the world-space position of the rigid-body along the `y` axis.
+ /// - `z`: the world-space position of the rigid-body along the `z` axis.
+ #[cfg(feature = "dim3")]
+ pub fn rbSetNextKinematicTranslation(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32) {
+ self.map_mut(handle, |rb| {
+ rb.set_next_kinematic_translation(na::Vector3::new(x, y, z));
+ })
+ }
+
+ /// If this rigid body is kinematic, sets its future translation after the next timestep integration.
+ ///
+ /// This should be used instead of `rigidBody.setTranslation` to make the dynamic object
+ /// interacting with this kinematic body behave as expected. Internally, Rapier will compute
+ /// an artificial velocity for this rigid-body from its current position and its next kinematic
+ /// position. This velocity will be used to compute forces on dynamic bodies interacting with
+ /// this body.
+ ///
+ /// # Parameters
+ /// - `x`: the world-space position of the rigid-body along the `x` axis.
+ /// - `y`: the world-space position of the rigid-body along the `y` axis.
+ #[cfg(feature = "dim2")]
+ pub fn rbSetNextKinematicTranslation(&mut self, handle: FlatHandle, x: f32, y: f32) {
+ self.map_mut(handle, |rb| {
+ rb.set_next_kinematic_translation(na::Vector2::new(x, y));
+ })
+ }
+
+ /// If this rigid body is kinematic, sets its future rotation after the next timestep integration.
+ ///
+ /// This should be used instead of `rigidBody.setRotation` to make the dynamic object
+ /// interacting with this kinematic body behave as expected. Internally, Rapier will compute
+ /// an artificial velocity for this rigid-body from its current position and its next kinematic
+ /// position. This velocity will be used to compute forces on dynamic bodies interacting with
+ /// this body.
+ ///
+ /// # Parameters
+ /// - `x`: the first vector component of the quaternion.
+ /// - `y`: the second vector component of the quaternion.
+ /// - `z`: the third vector component of the quaternion.
+ /// - `w`: the scalar component of the quaternion.
+ #[cfg(feature = "dim3")]
+ pub fn rbSetNextKinematicRotation(
+ &mut self,
+ handle: FlatHandle,
+ x: f32,
+ y: f32,
+ z: f32,
+ w: f32,
+ ) {
+ if let Some(q) = na::Unit::try_new(na::Quaternion::new(w, x, y, z), 0.0) {
+ self.map_mut(handle, |rb| {
+ rb.set_next_kinematic_rotation(q);
+ })
+ }
+ }
+
+ /// If this rigid body is kinematic, sets its future rotation after the next timestep integration.
+ ///
+ /// This should be used instead of `rigidBody.setRotation` to make the dynamic object
+ /// interacting with this kinematic body behave as expected. Internally, Rapier will compute
+ /// an artificial velocity for this rigid-body from its current position and its next kinematic
+ /// position. This velocity will be used to compute forces on dynamic bodies interacting with
+ /// this body.
+ ///
+ /// # Parameters
+ /// - `angle`: the rotation angle, in radians.
+ #[cfg(feature = "dim2")]
+ pub fn rbSetNextKinematicRotation(&mut self, handle: FlatHandle, angle: f32) {
+ self.map_mut(handle, |rb| {
+ rb.set_next_kinematic_rotation(na::UnitComplex::new(angle));
+ })
+ }
+
+ pub fn rbRecomputeMassPropertiesFromColliders(
+ &mut self,
+ handle: FlatHandle,
+ colliders: &RawColliderSet,
+ ) {
+ self.map_mut(handle, |rb| {
+ rb.recompute_mass_properties_from_colliders(&colliders.0)
+ })
+ }
+
+ pub fn rbSetAdditionalMass(&mut self, handle: FlatHandle, mass: f32, wake_up: bool) {
+ self.map_mut(handle, |rb| {
+ rb.set_additional_mass(mass, wake_up);
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn rbSetAdditionalMassProperties(
+ &mut self,
+ handle: FlatHandle,
+ mass: f32,
+ centerOfMass: &RawVector,
+ principalAngularInertia: &RawVector,
+ angularInertiaFrame: &RawRotation,
+ wake_up: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ let mprops = MassProperties::with_principal_inertia_frame(
+ centerOfMass.0.into(),
+ mass,
+ principalAngularInertia.0,
+ angularInertiaFrame.0,
+ );
+ rb.set_additional_mass_properties(mprops, wake_up)
+ })
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn rbSetAdditionalMassProperties(
+ &mut self,
+ handle: FlatHandle,
+ mass: f32,
+ centerOfMass: &RawVector,
+ principalAngularInertia: f32,
+ wake_up: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ let props = MassProperties::new(centerOfMass.0.into(), mass, principalAngularInertia);
+ rb.set_additional_mass_properties(props, wake_up)
+ })
+ }
+
+ /// The linear velocity of this rigid-body.
+ pub fn rbLinvel(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| RawVector(*rb.linvel()))
+ }
+
+ /// The angular velocity of this rigid-body.
+ #[cfg(feature = "dim2")]
+ pub fn rbAngvel(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.angvel())
+ }
+
+ /// The angular velocity of this rigid-body.
+ #[cfg(feature = "dim3")]
+ pub fn rbAngvel(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| RawVector(*rb.angvel()))
+ }
+
+ /// The velocity of the given world-space point on this rigid-body.
+ pub fn rbVelocityAtPoint(&self, handle: FlatHandle, point: &RawVector) -> RawVector {
+ self.map(handle, |rb| {
+ rb.velocity_at_point(&Point::from(point.0)).into()
+ })
+ }
+
+ pub fn rbLockTranslations(&mut self, handle: FlatHandle, locked: bool, wake_up: bool) {
+ self.map_mut(handle, |rb| rb.lock_translations(locked, wake_up))
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn rbSetEnabledTranslations(
+ &mut self,
+ handle: FlatHandle,
+ allow_x: bool,
+ allow_y: bool,
+ wake_up: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ rb.set_enabled_translations(allow_x, allow_y, wake_up)
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn rbSetEnabledTranslations(
+ &mut self,
+ handle: FlatHandle,
+ allow_x: bool,
+ allow_y: bool,
+ allow_z: bool,
+ wake_up: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ rb.set_enabled_translations(allow_x, allow_y, allow_z, wake_up)
+ })
+ }
+
+ pub fn rbLockRotations(&mut self, handle: FlatHandle, locked: bool, wake_up: bool) {
+ self.map_mut(handle, |rb| rb.lock_rotations(locked, wake_up))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn rbSetEnabledRotations(
+ &mut self,
+ handle: FlatHandle,
+ allow_x: bool,
+ allow_y: bool,
+ allow_z: bool,
+ wake_up: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ rb.set_enabled_rotations(allow_x, allow_y, allow_z, wake_up)
+ })
+ }
+
+ pub fn rbDominanceGroup(&self, handle: FlatHandle) -> i8 {
+ self.map(handle, |rb| rb.dominance_group())
+ }
+
+ pub fn rbSetDominanceGroup(&mut self, handle: FlatHandle, group: i8) {
+ self.map_mut(handle, |rb| rb.set_dominance_group(group))
+ }
+
+ pub fn rbEnableCcd(&mut self, handle: FlatHandle, enabled: bool) {
+ self.map_mut(handle, |rb| rb.enable_ccd(enabled))
+ }
+
+ pub fn rbSetSoftCcdPrediction(&mut self, handle: FlatHandle, prediction: f32) {
+ self.map_mut(handle, |rb| rb.set_soft_ccd_prediction(prediction))
+ }
+
+ /// The mass of this rigid-body.
+ pub fn rbMass(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.mass())
+ }
+
+ /// The inverse of the mass of a rigid-body.
+ ///
+ /// If this is zero, the rigid-body is assumed to have infinite mass.
+ pub fn rbInvMass(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.mass_properties().local_mprops.inv_mass)
+ }
+
+ /// The inverse mass taking into account translation locking.
+ pub fn rbEffectiveInvMass(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| rb.mass_properties().effective_inv_mass.into())
+ }
+
+ /// The center of mass of a rigid-body expressed in its local-space.
+ pub fn rbLocalCom(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| {
+ rb.mass_properties().local_mprops.local_com.into()
+ })
+ }
+
+ /// The world-space center of mass of the rigid-body.
+ pub fn rbWorldCom(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| rb.mass_properties().world_com.into())
+ }
+
+ /// The inverse of the principal angular inertia of the rigid-body.
+ ///
+ /// Components set to zero are assumed to be infinite along the corresponding principal axis.
+ #[cfg(feature = "dim2")]
+ pub fn rbInvPrincipalInertia(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| {
+ rb.mass_properties()
+ .local_mprops
+ .inv_principal_inertia
+ .into()
+ })
+ }
+
+ /// The inverse of the principal angular inertia of the rigid-body.
+ ///
+ /// Components set to zero are assumed to be infinite along the corresponding principal axis.
+ #[cfg(feature = "dim3")]
+ pub fn rbInvPrincipalInertia(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| {
+ rb.mass_properties()
+ .local_mprops
+ .inv_principal_inertia
+ .into()
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ /// The principal vectors of the local angular inertia tensor of the rigid-body.
+ pub fn rbPrincipalInertiaLocalFrame(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |rb| {
+ RawRotation::from(
+ rb.mass_properties()
+ .local_mprops
+ .principal_inertia_local_frame,
+ )
+ })
+ }
+
+ /// The angular inertia along the principal inertia axes of the rigid-body.
+ #[cfg(feature = "dim2")]
+ pub fn rbPrincipalInertia(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| {
+ rb.mass_properties().local_mprops.principal_inertia().into()
+ })
+ }
+
+ /// The angular inertia along the principal inertia axes of the rigid-body.
+ #[cfg(feature = "dim3")]
+ pub fn rbPrincipalInertia(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| {
+ rb.mass_properties().local_mprops.principal_inertia().into()
+ })
+ }
+
+ /// The world-space inverse angular inertia tensor of the rigid-body,
+ /// taking into account rotation locking.
+ #[cfg(feature = "dim2")]
+ pub fn rbEffectiveWorldInvInertia(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| {
+ rb.mass_properties().effective_world_inv_inertia.into()
+ })
+ }
+
+ /// The world-space inverse angular inertia tensor of the rigid-body,
+ /// taking into account rotation locking.
+ #[cfg(feature = "dim3")]
+ pub fn rbEffectiveWorldInvInertia(&self, handle: FlatHandle) -> RawSdpMatrix3 {
+ self.map(handle, |rb| {
+ rb.mass_properties().effective_world_inv_inertia.into()
+ })
+ }
+
+ /// The effective world-space angular inertia (that takes the potential rotation locking into account) of
+ /// this rigid-body.
+ #[cfg(feature = "dim2")]
+ pub fn rbEffectiveAngularInertia(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| {
+ rb.mass_properties().effective_angular_inertia().into()
+ })
+ }
+
+ /// The effective world-space angular inertia (that takes the potential rotation locking into account) of
+ /// this rigid-body.
+ #[cfg(feature = "dim3")]
+ pub fn rbEffectiveAngularInertia(&self, handle: FlatHandle) -> RawSdpMatrix3 {
+ self.map(handle, |rb| {
+ rb.mass_properties().effective_angular_inertia().into()
+ })
+ }
+
+ /// Wakes this rigid-body up.
+ ///
+ /// A dynamic rigid-body that does not move during several consecutive frames will
+ /// be put to sleep by the physics engine, i.e., it will stop being simulated in order
+ /// to avoid useless computations.
+ /// This method forces a sleeping rigid-body to wake-up. This is useful, e.g., before modifying
+ /// the position of a dynamic body so that it is properly simulated afterwards.
+ pub fn rbWakeUp(&mut self, handle: FlatHandle) {
+ self.map_mut(handle, |rb| rb.wake_up(true))
+ }
+
+ /// Is Continuous Collision Detection enabled for this rigid-body?
+ pub fn rbIsCcdEnabled(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |rb| rb.is_ccd_enabled())
+ }
+ pub fn rbSoftCcdPrediction(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.soft_ccd_prediction())
+ }
+
+ /// The number of colliders attached to this rigid-body.
+ pub fn rbNumColliders(&self, handle: FlatHandle) -> usize {
+ self.map(handle, |rb| rb.colliders().len())
+ }
+
+ /// Retrieves the `i-th` collider attached to this rigid-body.
+ ///
+ /// # Parameters
+ /// - `at`: The index of the collider to retrieve. Must be a number in `[0, this.numColliders()[`.
+ /// This index is **not** the same as the unique identifier of the collider.
+ pub fn rbCollider(&self, handle: FlatHandle, at: usize) -> FlatHandle {
+ self.map(handle, |rb| utils::flat_handle(rb.colliders()[at].0))
+ }
+
+ /// The status of this rigid-body: fixed, dynamic, or kinematic.
+ pub fn rbBodyType(&self, handle: FlatHandle) -> RawRigidBodyType {
+ self.map(handle, |rb| rb.body_type().into())
+ }
+
+ /// Set a new status for this rigid-body: fixed, dynamic, or kinematic.
+ pub fn rbSetBodyType(&mut self, handle: FlatHandle, status: RawRigidBodyType, wake_up: bool) {
+ self.map_mut(handle, |rb| rb.set_body_type(status.into(), wake_up));
+ }
+
+ /// Is this rigid-body fixed?
+ pub fn rbIsFixed(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |rb| rb.is_fixed())
+ }
+
+ /// Is this rigid-body kinematic?
+ pub fn rbIsKinematic(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |rb| rb.is_kinematic())
+ }
+
+ /// Is this rigid-body dynamic?
+ pub fn rbIsDynamic(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |rb| rb.is_dynamic())
+ }
+
+ /// The linear damping coefficient of this rigid-body.
+ pub fn rbLinearDamping(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.linear_damping())
+ }
+
+ /// The angular damping coefficient of this rigid-body.
+ pub fn rbAngularDamping(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.angular_damping())
+ }
+
+ pub fn rbSetLinearDamping(&mut self, handle: FlatHandle, factor: f32) {
+ self.map_mut(handle, |rb| rb.set_linear_damping(factor));
+ }
+
+ pub fn rbSetAngularDamping(&mut self, handle: FlatHandle, factor: f32) {
+ self.map_mut(handle, |rb| rb.set_angular_damping(factor));
+ }
+
+ pub fn rbSetEnabled(&mut self, handle: FlatHandle, enabled: bool) {
+ self.map_mut(handle, |rb| rb.set_enabled(enabled))
+ }
+
+ pub fn rbIsEnabled(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |rb| rb.is_enabled())
+ }
+
+ pub fn rbGravityScale(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.gravity_scale())
+ }
+
+ pub fn rbSetGravityScale(&mut self, handle: FlatHandle, factor: f32, wakeUp: bool) {
+ self.map_mut(handle, |rb| rb.set_gravity_scale(factor, wakeUp));
+ }
+
+ /// Resets to zero all user-added forces added to this rigid-body.
+ pub fn rbResetForces(&mut self, handle: FlatHandle, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.reset_forces(wakeUp);
+ })
+ }
+
+ /// Resets to zero all user-added torques added to this rigid-body.
+ pub fn rbResetTorques(&mut self, handle: FlatHandle, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.reset_torques(wakeUp);
+ })
+ }
+
+ /// Adds a force at the center-of-mass of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `force`: the world-space force to apply on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ pub fn rbAddForce(&mut self, handle: FlatHandle, force: &RawVector, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.add_force(force.0, wakeUp);
+ })
+ }
+
+ /// Applies an impulse at the center-of-mass of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `impulse`: the world-space impulse to apply on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ pub fn rbApplyImpulse(&mut self, handle: FlatHandle, impulse: &RawVector, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.apply_impulse(impulse.0, wakeUp);
+ })
+ }
+
+ /// Adds a torque at the center-of-mass of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `torque`: the torque to apply on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ #[cfg(feature = "dim2")]
+ pub fn rbAddTorque(&mut self, handle: FlatHandle, torque: f32, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.add_torque(torque, wakeUp);
+ })
+ }
+
+ /// Adds a torque at the center-of-mass of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `torque`: the world-space torque to apply on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ #[cfg(feature = "dim3")]
+ pub fn rbAddTorque(&mut self, handle: FlatHandle, torque: &RawVector, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.add_torque(torque.0, wakeUp);
+ })
+ }
+
+ /// Applies an impulsive torque at the center-of-mass of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `torque impulse`: the torque impulse to apply on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ #[cfg(feature = "dim2")]
+ pub fn rbApplyTorqueImpulse(&mut self, handle: FlatHandle, torque_impulse: f32, wakeUp: bool) {
+ self.map_mut(handle, |rb| {
+ rb.apply_torque_impulse(torque_impulse, wakeUp);
+ })
+ }
+
+ /// Applies an impulsive torque at the center-of-mass of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `torque impulse`: the world-space torque impulse to apply on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ #[cfg(feature = "dim3")]
+ pub fn rbApplyTorqueImpulse(
+ &mut self,
+ handle: FlatHandle,
+ torque_impulse: &RawVector,
+ wakeUp: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ rb.apply_torque_impulse(torque_impulse.0, wakeUp);
+ })
+ }
+
+ /// Adds a force at the given world-space point of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `force`: the world-space force to apply on the rigid-body.
+ /// - `point`: the world-space point where the impulse is to be applied on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ pub fn rbAddForceAtPoint(
+ &mut self,
+ handle: FlatHandle,
+ force: &RawVector,
+ point: &RawVector,
+ wakeUp: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ rb.add_force_at_point(force.0, point.0.into(), wakeUp);
+ })
+ }
+
+ /// Applies an impulse at the given world-space point of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `impulse`: the world-space impulse to apply on the rigid-body.
+ /// - `point`: the world-space point where the impulse is to be applied on the rigid-body.
+ /// - `wakeUp`: should the rigid-body be automatically woken-up?
+ pub fn rbApplyImpulseAtPoint(
+ &mut self,
+ handle: FlatHandle,
+ impulse: &RawVector,
+ point: &RawVector,
+ wakeUp: bool,
+ ) {
+ self.map_mut(handle, |rb| {
+ rb.apply_impulse_at_point(impulse.0, point.0.into(), wakeUp);
+ })
+ }
+
+ pub fn rbAdditionalSolverIterations(&self, handle: FlatHandle) -> usize {
+ self.map(handle, |rb| rb.additional_solver_iterations())
+ }
+
+ pub fn rbSetAdditionalSolverIterations(&mut self, handle: FlatHandle, iters: usize) {
+ self.map_mut(handle, |rb| {
+ rb.set_additional_solver_iterations(iters as usize);
+ })
+ }
+
+ /// An arbitrary user-defined 32-bit integer
+ pub fn rbUserData(&self, handle: FlatHandle) -> u32 {
+ self.map(handle, |rb| rb.user_data as u32)
+ }
+
+ /// Sets the user-defined 32-bit integer of this rigid-body.
+ ///
+ /// # Parameters
+ /// - `data`: an arbitrary user-defined 32-bit integer.
+ pub fn rbSetUserData(&mut self, handle: FlatHandle, data: u32) {
+ self.map_mut(handle, |rb| {
+ rb.user_data = data as u128;
+ })
+ }
+
+ /// Retrieves the constant force(s) the user added to this rigid-body.
+ /// Returns zero if the rigid-body is not dynamic.
+ pub fn rbUserForce(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| rb.user_force().into())
+ }
+
+ /// Retrieves the constant torque(s) the user added to this rigid-body.
+ /// Returns zero if the rigid-body is not dynamic.
+ #[cfg(feature = "dim2")]
+ pub fn rbUserTorque(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |rb| rb.user_torque())
+ }
+
+ /// Retrieves the constant torque(s) the user added to this rigid-body.
+ /// Returns zero if the rigid-body is not dynamic.
+ #[cfg(feature = "dim3")]
+ pub fn rbUserTorque(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |rb| rb.user_torque().into())
+ }
+}
diff --git a/thirdparty/rapier.js/src/dynamics/rigid_body_set.rs b/thirdparty/rapier.js/src/dynamics/rigid_body_set.rs
new file mode 100644
index 00000000..83b963e3
--- /dev/null
+++ b/thirdparty/rapier.js/src/dynamics/rigid_body_set.rs
@@ -0,0 +1,237 @@
+use crate::dynamics::{RawImpulseJointSet, RawIslandManager, RawMultibodyJointSet};
+use crate::geometry::RawColliderSet;
+use crate::math::{RawRotation, RawVector};
+use crate::utils::{self, FlatHandle};
+use rapier::dynamics::{MassProperties, RigidBody, RigidBodyBuilder, RigidBodySet, RigidBodyType};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub enum RawRigidBodyType {
+ Dynamic,
+ Fixed,
+ KinematicPositionBased,
+ KinematicVelocityBased,
+}
+
+impl Into for RawRigidBodyType {
+ fn into(self) -> RigidBodyType {
+ match self {
+ RawRigidBodyType::Dynamic => RigidBodyType::Dynamic,
+ RawRigidBodyType::Fixed => RigidBodyType::Fixed,
+ RawRigidBodyType::KinematicPositionBased => RigidBodyType::KinematicPositionBased,
+ RawRigidBodyType::KinematicVelocityBased => RigidBodyType::KinematicVelocityBased,
+ }
+ }
+}
+
+impl Into for RigidBodyType {
+ fn into(self) -> RawRigidBodyType {
+ match self {
+ RigidBodyType::Dynamic => RawRigidBodyType::Dynamic,
+ RigidBodyType::Fixed => RawRigidBodyType::Fixed,
+ RigidBodyType::KinematicPositionBased => RawRigidBodyType::KinematicPositionBased,
+ RigidBodyType::KinematicVelocityBased => RawRigidBodyType::KinematicVelocityBased,
+ }
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawRigidBodySet(pub(crate) RigidBodySet);
+
+impl RawRigidBodySet {
+ pub(crate) fn map(&self, handle: FlatHandle, f: impl FnOnce(&RigidBody) -> T) -> T {
+ let body = self.0.get(utils::body_handle(handle)).expect(
+ "Invalid RigidBody reference. It may have been removed from the physics World.",
+ );
+ f(body)
+ }
+
+ pub(crate) fn map_mut(
+ &mut self,
+ handle: FlatHandle,
+ f: impl FnOnce(&mut RigidBody) -> T,
+ ) -> T {
+ let body = self.0.get_mut(utils::body_handle(handle)).expect(
+ "Invalid RigidBody reference. It may have been removed from the physics World.",
+ );
+ f(body)
+ }
+}
+
+#[wasm_bindgen]
+impl RawRigidBodySet {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawRigidBodySet(RigidBodySet::new())
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn createRigidBody(
+ &mut self,
+ enabled: bool,
+ translation: &RawVector,
+ rotation: &RawRotation,
+ gravityScale: f32,
+ mass: f32,
+ massOnly: bool,
+ centerOfMass: &RawVector,
+ linvel: &RawVector,
+ angvel: &RawVector,
+ principalAngularInertia: &RawVector,
+ angularInertiaFrame: &RawRotation,
+ translationEnabledX: bool,
+ translationEnabledY: bool,
+ translationEnabledZ: bool,
+ rotationEnabledX: bool,
+ rotationEnabledY: bool,
+ rotationEnabledZ: bool,
+ linearDamping: f32,
+ angularDamping: f32,
+ rb_type: RawRigidBodyType,
+ canSleep: bool,
+ sleeping: bool,
+ softCcdPrediction: f32,
+ ccdEnabled: bool,
+ dominanceGroup: i8,
+ additional_solver_iterations: usize,
+ ) -> FlatHandle {
+ let pos = na::Isometry3::from_parts(translation.0.into(), rotation.0);
+
+ let mut rigid_body = RigidBodyBuilder::new(rb_type.into())
+ .enabled(enabled)
+ .pose(pos)
+ .gravity_scale(gravityScale)
+ .enabled_translations(
+ translationEnabledX,
+ translationEnabledY,
+ translationEnabledZ,
+ )
+ .enabled_rotations(rotationEnabledX, rotationEnabledY, rotationEnabledZ)
+ .linvel(linvel.0)
+ .angvel(angvel.0)
+ .linear_damping(linearDamping)
+ .angular_damping(angularDamping)
+ .can_sleep(canSleep)
+ .sleeping(sleeping)
+ .ccd_enabled(ccdEnabled)
+ .dominance_group(dominanceGroup)
+ .additional_solver_iterations(additional_solver_iterations)
+ .soft_ccd_prediction(softCcdPrediction);
+
+ rigid_body = if massOnly {
+ rigid_body.additional_mass(mass)
+ } else {
+ let props = MassProperties::with_principal_inertia_frame(
+ centerOfMass.0.into(),
+ mass,
+ principalAngularInertia.0,
+ angularInertiaFrame.0,
+ );
+ rigid_body.additional_mass_properties(props)
+ };
+
+ utils::flat_handle(self.0.insert(rigid_body.build()).0)
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn createRigidBody(
+ &mut self,
+ enabled: bool,
+ translation: &RawVector,
+ rotation: &RawRotation,
+ gravityScale: f32,
+ mass: f32,
+ massOnly: bool,
+ centerOfMass: &RawVector,
+ linvel: &RawVector,
+ angvel: f32,
+ principalAngularInertia: f32,
+ translationEnabledX: bool,
+ translationEnabledY: bool,
+ rotationsEnabled: bool,
+ linearDamping: f32,
+ angularDamping: f32,
+ rb_type: RawRigidBodyType,
+ canSleep: bool,
+ sleeping: bool,
+ softCcdPrediciton: f32,
+ ccdEnabled: bool,
+ dominanceGroup: i8,
+ additional_solver_iterations: usize,
+ ) -> FlatHandle {
+ let pos = na::Isometry2::from_parts(translation.0.into(), rotation.0);
+ let mut rigid_body = RigidBodyBuilder::new(rb_type.into())
+ .enabled(enabled)
+ .pose(pos)
+ .gravity_scale(gravityScale)
+ .enabled_translations(translationEnabledX, translationEnabledY)
+ .linvel(linvel.0)
+ .angvel(angvel)
+ .linear_damping(linearDamping)
+ .angular_damping(angularDamping)
+ .can_sleep(canSleep)
+ .sleeping(sleeping)
+ .ccd_enabled(ccdEnabled)
+ .dominance_group(dominanceGroup)
+ .additional_solver_iterations(additional_solver_iterations)
+ .soft_ccd_prediction(softCcdPrediciton);
+
+ rigid_body = if massOnly {
+ rigid_body.additional_mass(mass)
+ } else {
+ let props = MassProperties::new(centerOfMass.0.into(), mass, principalAngularInertia);
+ rigid_body.additional_mass_properties(props)
+ };
+
+ if !rotationsEnabled {
+ rigid_body = rigid_body.lock_rotations();
+ }
+
+ utils::flat_handle(self.0.insert(rigid_body.build()).0)
+ }
+
+ pub fn remove(
+ &mut self,
+ handle: FlatHandle,
+ islands: &mut RawIslandManager,
+ colliders: &mut RawColliderSet,
+ joints: &mut RawImpulseJointSet,
+ articulations: &mut RawMultibodyJointSet,
+ ) {
+ let handle = utils::body_handle(handle);
+ self.0.remove(
+ handle,
+ &mut islands.0,
+ &mut colliders.0,
+ &mut joints.0,
+ &mut articulations.0,
+ true,
+ );
+ }
+
+ /// The number of rigid-bodies on this set.
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+
+ /// Checks if a rigid-body with the given integer handle exists.
+ pub fn contains(&self, handle: FlatHandle) -> bool {
+ self.0.get(utils::body_handle(handle)).is_some()
+ }
+
+ /// Applies the given JavaScript function to the integer handle of each rigid-body managed by this set.
+ ///
+ /// # Parameters
+ /// - `f(handle)`: the function to apply to the integer handle of each rigid-body managed by this set. Called as `f(collider)`.
+ pub fn forEachRigidBodyHandle(&self, f: &js_sys::Function) {
+ let this = JsValue::null();
+ for (handle, _) in self.0.iter() {
+ let _ = f.call1(&this, &JsValue::from(utils::flat_handle(handle.0)));
+ }
+ }
+
+ pub fn propagateModifiedBodyPositionsToColliders(&mut self, colliders: &mut RawColliderSet) {
+ self.0
+ .propagate_modified_body_positions_to_colliders(&mut colliders.0);
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/broad_phase.rs b/thirdparty/rapier.js/src/geometry/broad_phase.rs
new file mode 100644
index 00000000..6f81a862
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/broad_phase.rs
@@ -0,0 +1,462 @@
+use crate::dynamics::RawRigidBodySet;
+use crate::geometry::{
+ RawColliderSet, RawColliderShapeCastHit, RawNarrowPhase, RawPointColliderProjection,
+ RawRayColliderHit, RawRayColliderIntersection, RawShape,
+};
+use crate::math::{RawRotation, RawVector};
+use crate::utils::{self, FlatHandle};
+use rapier::geometry::DefaultBroadPhase;
+use rapier::geometry::{Aabb, ColliderHandle, Ray};
+use rapier::math::{Isometry, Point};
+use rapier::parry::query::ShapeCastOptions;
+use rapier::pipeline::{QueryFilter, QueryFilterFlags};
+use rapier::prelude::FeatureId;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawBroadPhase(pub(crate) DefaultBroadPhase);
+
+#[wasm_bindgen]
+impl RawBroadPhase {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawBroadPhase(DefaultBroadPhase::new())
+ }
+
+ pub fn castRay(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ solid: bool,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) -> Option {
+ let (handle, timeOfImpact) = utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ let ray = Ray::new(rayOrig.0.into(), rayDir.0);
+ query_pipeline.cast_ray(&ray, maxToi, solid)
+ })?;
+
+ Some(RawRayColliderHit {
+ handle,
+ timeOfImpact,
+ })
+ }
+
+ pub fn castRayAndGetNormal(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ solid: bool,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) -> Option {
+ let (handle, inter) = utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ let ray = Ray::new(rayOrig.0.into(), rayDir.0);
+ query_pipeline.cast_ray_and_get_normal(&ray, maxToi, solid)
+ })?;
+
+ Some(RawRayColliderIntersection { handle, inter })
+ }
+
+ // The callback is of type (RawRayColliderIntersection) => bool
+ pub fn intersectionsWithRay(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ solid: bool,
+ callback: &js_sys::Function,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) {
+ utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let ray = Ray::new(rayOrig.0.into(), rayDir.0);
+ let rcallback = |handle, inter| {
+ let result = RawRayColliderIntersection { handle, inter };
+ match callback.call1(&JsValue::null(), &JsValue::from(result)) {
+ Err(_) => true,
+ Ok(val) => val.as_bool().unwrap_or(true),
+ }
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ for (handle, _, inter) in query_pipeline.intersect_ray(ray, maxToi, solid) {
+ if !rcallback(handle, inter) {
+ break;
+ }
+ }
+ });
+ }
+
+ pub fn intersectionWithShape(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ shape: &RawShape,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) -> Option {
+ utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+
+ // TODO: take a callback as argument so we can yield all the intersecting shapes?
+ for (handle, _) in query_pipeline.intersect_shape(pos, &*shape.0) {
+ // Return the first intersection we find.
+ return Some(utils::flat_handle(handle.0));
+ }
+
+ None
+ })
+ }
+
+ pub fn projectPoint(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ point: &RawVector,
+ solid: bool,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) -> Option {
+ utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ query_pipeline
+ .project_point(&point.0.into(), f32::MAX, solid)
+ .map(|(handle, proj)| RawPointColliderProjection {
+ handle,
+ proj,
+ feature: FeatureId::Unknown,
+ })
+ })
+ }
+
+ pub fn projectPointAndGetFeature(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ point: &RawVector,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) -> Option {
+ utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ query_pipeline
+ .project_point_and_get_feature(&point.0.into())
+ .map(|(handle, proj, feature)| RawPointColliderProjection {
+ handle,
+ proj,
+ feature,
+ })
+ })
+ }
+
+ // The callback is of type (u32) => bool
+ pub fn intersectionsWithPoint(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ point: &RawVector,
+ callback: &js_sys::Function,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) {
+ utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ let rcallback = |handle: ColliderHandle| match callback.call1(
+ &JsValue::null(),
+ &JsValue::from(utils::flat_handle(handle.0)),
+ ) {
+ Err(_) => true,
+ Ok(val) => val.as_bool().unwrap_or(true),
+ };
+
+ for (handle, _) in query_pipeline.intersect_point(point.0.into()) {
+ if !rcallback(handle) {
+ break;
+ }
+ }
+ });
+ }
+
+ pub fn castShape(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ shapeVel: &RawVector,
+ shape: &RawShape,
+ target_distance: f32,
+ maxToi: f32,
+ stop_at_penetration: bool,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) -> Option {
+ utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+ query_pipeline
+ .cast_shape(
+ &pos,
+ &shapeVel.0,
+ &*shape.0,
+ ShapeCastOptions {
+ max_time_of_impact: maxToi,
+ stop_at_penetration,
+ compute_impact_geometry_on_penetration: true,
+ target_distance,
+ },
+ )
+ .map(|(handle, hit)| RawColliderShapeCastHit { handle, hit })
+ })
+ }
+
+ // The callback has type (u32) => boolean
+ pub fn intersectionsWithShape(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ shape: &RawShape,
+ callback: &js_sys::Function,
+ filter_flags: u32,
+ filter_groups: Option,
+ filter_exclude_collider: Option,
+ filter_exclude_rigid_body: Option,
+ filter_predicate: &js_sys::Function,
+ ) {
+ utils::with_filter(filter_predicate, |predicate| {
+ let query_filter = QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: filter_groups.map(crate::geometry::unpack_interaction_groups),
+ exclude_collider: filter_exclude_collider.map(crate::utils::collider_handle),
+ exclude_rigid_body: filter_exclude_rigid_body.map(crate::utils::body_handle),
+ predicate,
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ query_filter,
+ );
+
+ let rcallback = |handle: ColliderHandle| match callback.call1(
+ &JsValue::null(),
+ &JsValue::from(utils::flat_handle(handle.0)),
+ ) {
+ Err(_) => true,
+ Ok(val) => val.as_bool().unwrap_or(true),
+ };
+
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+ for (handle, _) in query_pipeline.intersect_shape(pos, &*shape.0) {
+ if !rcallback(handle) {
+ break;
+ }
+ }
+ })
+ }
+
+ pub fn collidersWithAabbIntersectingAabb(
+ &self,
+ narrow_phase: &RawNarrowPhase,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ aabbCenter: &RawVector,
+ aabbHalfExtents: &RawVector,
+ callback: &js_sys::Function,
+ ) {
+ let rcallback = |handle: &ColliderHandle| match callback.call1(
+ &JsValue::null(),
+ &JsValue::from(utils::flat_handle(handle.0)),
+ ) {
+ Err(_) => true,
+ Ok(val) => val.as_bool().unwrap_or(true),
+ };
+
+ let query_pipeline = self.0.as_query_pipeline(
+ narrow_phase.0.query_dispatcher(),
+ &bodies.0,
+ &colliders.0,
+ Default::default(),
+ );
+
+ let center = Point::from(aabbCenter.0);
+ let aabb = Aabb::new(center - aabbHalfExtents.0, center + aabbHalfExtents.0);
+
+ for (handle, _) in query_pipeline.intersect_aabb_conservative(aabb) {
+ if !rcallback(&handle) {
+ break;
+ }
+ }
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/collider.rs b/thirdparty/rapier.js/src/geometry/collider.rs
new file mode 100644
index 00000000..e8019044
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/collider.rs
@@ -0,0 +1,993 @@
+use crate::geometry::shape::SharedShapeUtility;
+use crate::geometry::{
+ RawColliderSet, RawColliderShapeCastHit, RawPointProjection, RawRayIntersection, RawShape,
+ RawShapeCastHit, RawShapeContact, RawShapeType,
+};
+use crate::math::{RawRotation, RawVector};
+use crate::utils::{self, FlatHandle};
+use rapier::dynamics::MassProperties;
+use rapier::geometry::{ActiveCollisionTypes, ShapeType};
+use rapier::math::{Isometry, Point, Real, Vector};
+use rapier::parry::query;
+use rapier::parry::query::ShapeCastOptions;
+use rapier::pipeline::{ActiveEvents, ActiveHooks};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+impl RawColliderSet {
+ /// The world-space translation of this collider.
+ pub fn coTranslation(&self, handle: FlatHandle) -> RawVector {
+ self.map(handle, |co| co.position().translation.vector.into())
+ }
+
+ /// The world-space orientation of this collider.
+ pub fn coRotation(&self, handle: FlatHandle) -> RawRotation {
+ self.map(handle, |co| co.position().rotation.into())
+ }
+
+ /// The translation of this collider relative to its parent rigid-body.
+ ///
+ /// Returns the `None` if it doesn’t have a parent.
+ pub fn coTranslationWrtParent(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| {
+ co.position_wrt_parent()
+ .map(|pose| pose.translation.vector.into())
+ })
+ }
+
+ /// The orientation of this collider relative to its parent rigid-body.
+ ///
+ /// Returns the `None` if it doesn’t have a parent.
+ pub fn coRotationWrtParent(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| {
+ co.position_wrt_parent().map(|pose| pose.rotation.into())
+ })
+ }
+
+ /// Sets the translation of this collider.
+ ///
+ /// # Parameters
+ /// - `x`: the world-space position of the collider along the `x` axis.
+ /// - `y`: the world-space position of the collider along the `y` axis.
+ /// - `z`: the world-space position of the collider along the `z` axis.
+ /// - `wakeUp`: forces the collider to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim3")]
+ pub fn coSetTranslation(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32) {
+ self.map_mut(handle, |co| {
+ co.set_translation(na::Vector3::new(x, y, z));
+ })
+ }
+
+ /// Sets the translation of this collider.
+ ///
+ /// # Parameters
+ /// - `x`: the world-space position of the collider along the `x` axis.
+ /// - `y`: the world-space position of the collider along the `y` axis.
+ /// - `wakeUp`: forces the collider to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim2")]
+ pub fn coSetTranslation(&mut self, handle: FlatHandle, x: f32, y: f32) {
+ self.map_mut(handle, |co| {
+ co.set_translation(na::Vector2::new(x, y));
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn coSetTranslationWrtParent(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32) {
+ self.map_mut(handle, |co| {
+ co.set_translation_wrt_parent(na::Vector3::new(x, y, z));
+ })
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn coSetTranslationWrtParent(&mut self, handle: FlatHandle, x: f32, y: f32) {
+ self.map_mut(handle, |co| {
+ co.set_translation_wrt_parent(na::Vector2::new(x, y));
+ })
+ }
+
+ /// Sets the rotation quaternion of this collider.
+ ///
+ /// This does nothing if a zero quaternion is provided.
+ ///
+ /// # Parameters
+ /// - `x`: the first vector component of the quaternion.
+ /// - `y`: the second vector component of the quaternion.
+ /// - `z`: the third vector component of the quaternion.
+ /// - `w`: the scalar component of the quaternion.
+ /// - `wakeUp`: forces the collider to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim3")]
+ pub fn coSetRotation(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32, w: f32) {
+ if let Some(q) = na::Unit::try_new(na::Quaternion::new(w, x, y, z), 0.0) {
+ self.map_mut(handle, |co| co.set_rotation(q))
+ }
+ }
+
+ /// Sets the rotation angle of this collider.
+ ///
+ /// # Parameters
+ /// - `angle`: the rotation angle, in radians.
+ /// - `wakeUp`: forces the collider to wake-up so it is properly affected by forces if it
+ /// wasn't moving before modifying its position.
+ #[cfg(feature = "dim2")]
+ pub fn coSetRotation(&mut self, handle: FlatHandle, angle: f32) {
+ self.map_mut(handle, |co| co.set_rotation(na::UnitComplex::new(angle)))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn coSetRotationWrtParent(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32, w: f32) {
+ if let Some(q) = na::Unit::try_new(na::Quaternion::new(w, x, y, z), 0.0) {
+ self.map_mut(handle, |co| co.set_rotation_wrt_parent(q.scaled_axis()))
+ }
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn coSetRotationWrtParent(&mut self, handle: FlatHandle, angle: f32) {
+ self.map_mut(handle, |co| co.set_rotation_wrt_parent(angle))
+ }
+
+ /// Is this collider a sensor?
+ pub fn coIsSensor(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |co| co.is_sensor())
+ }
+
+ /// The type of the shape of this collider.
+ pub fn coShapeType(&self, handle: FlatHandle) -> RawShapeType {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::Ball => RawShapeType::Ball,
+ ShapeType::Cuboid => RawShapeType::Cuboid,
+ ShapeType::Capsule => RawShapeType::Capsule,
+ ShapeType::Segment => RawShapeType::Segment,
+ ShapeType::Polyline => RawShapeType::Polyline,
+ ShapeType::Triangle => RawShapeType::Triangle,
+ ShapeType::TriMesh => RawShapeType::TriMesh,
+ ShapeType::HeightField => RawShapeType::HeightField,
+ ShapeType::Compound => RawShapeType::Compound,
+ ShapeType::HalfSpace => RawShapeType::HalfSpace,
+ ShapeType::Voxels => RawShapeType::Voxels,
+ #[cfg(feature = "dim3")]
+ ShapeType::ConvexPolyhedron => RawShapeType::ConvexPolyhedron,
+ #[cfg(feature = "dim2")]
+ ShapeType::ConvexPolygon => RawShapeType::ConvexPolygon,
+ #[cfg(feature = "dim3")]
+ ShapeType::Cylinder => RawShapeType::Cylinder,
+ #[cfg(feature = "dim3")]
+ ShapeType::Cone => RawShapeType::Cone,
+ ShapeType::RoundCuboid => RawShapeType::RoundCuboid,
+ ShapeType::RoundTriangle => RawShapeType::RoundTriangle,
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCylinder => RawShapeType::RoundCylinder,
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCone => RawShapeType::RoundCone,
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundConvexPolyhedron => RawShapeType::RoundConvexPolyhedron,
+ #[cfg(feature = "dim2")]
+ ShapeType::RoundConvexPolygon => RawShapeType::RoundConvexPolygon,
+ ShapeType::Custom => panic!("Not yet implemented."),
+ })
+ }
+
+ pub fn coHalfspaceNormal(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| {
+ co.shape()
+ .as_halfspace()
+ .map(|h| h.normal.into_inner().into())
+ })
+ }
+
+ /// The half-extents of this collider if it is has a cuboid shape.
+ pub fn coHalfExtents(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| {
+ co.shape()
+ .as_cuboid()
+ .map(|c| c.half_extents.into())
+ .or_else(|| {
+ co.shape()
+ .as_round_cuboid()
+ .map(|c| c.inner_shape.half_extents.into())
+ })
+ })
+ }
+
+ /// Set the half-extents of this collider if it has a cuboid shape.
+ pub fn coSetHalfExtents(&mut self, handle: FlatHandle, newHalfExtents: &RawVector) {
+ self.map_mut(handle, |co| match co.shape().shape_type() {
+ ShapeType::Cuboid => co
+ .shape_mut()
+ .as_cuboid_mut()
+ .map(|b| b.half_extents = newHalfExtents.0.into()),
+ ShapeType::RoundCuboid => co
+ .shape_mut()
+ .as_round_cuboid_mut()
+ .map(|b| b.inner_shape.half_extents = newHalfExtents.0.into()),
+ _ => None,
+ });
+ }
+
+ /// The radius of this collider if it is a ball, capsule, cylinder, or cone shape.
+ pub fn coRadius(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::Ball => co.shape().as_ball().map(|b| b.radius),
+ ShapeType::Capsule => co.shape().as_capsule().map(|b| b.radius),
+ #[cfg(feature = "dim3")]
+ ShapeType::Cylinder => co.shape().as_cylinder().map(|b| b.radius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCylinder => {
+ co.shape().as_round_cylinder().map(|b| b.inner_shape.radius)
+ }
+ #[cfg(feature = "dim3")]
+ ShapeType::Cone => co.shape().as_cone().map(|b| b.radius),
+ _ => None,
+ })
+ }
+
+ /// Set the radius of this collider if it is a ball, capsule, cylinder, or cone shape.
+ pub fn coSetRadius(&mut self, handle: FlatHandle, newRadius: Real) {
+ self.map_mut(handle, |co| match co.shape().shape_type() {
+ ShapeType::Ball => co.shape_mut().as_ball_mut().map(|b| b.radius = newRadius),
+ ShapeType::Capsule => co
+ .shape_mut()
+ .as_capsule_mut()
+ .map(|b| b.radius = newRadius),
+ #[cfg(feature = "dim3")]
+ ShapeType::Cylinder => co
+ .shape_mut()
+ .as_cylinder_mut()
+ .map(|b| b.radius = newRadius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCylinder => co
+ .shape_mut()
+ .as_round_cylinder_mut()
+ .map(|b| b.inner_shape.radius = newRadius),
+ #[cfg(feature = "dim3")]
+ ShapeType::Cone => co.shape_mut().as_cone_mut().map(|b| b.radius = newRadius),
+ _ => None,
+ });
+ }
+
+ /// The half height of this collider if it is a capsule, cylinder, or cone shape.
+ pub fn coHalfHeight(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::Capsule => co.shape().as_capsule().map(|b| b.half_height()),
+ #[cfg(feature = "dim3")]
+ ShapeType::Cylinder => co.shape().as_cylinder().map(|b| b.half_height),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCylinder => co
+ .shape()
+ .as_round_cylinder()
+ .map(|b| b.inner_shape.half_height),
+ #[cfg(feature = "dim3")]
+ ShapeType::Cone => co.shape().as_cone().map(|b| b.half_height),
+ _ => None,
+ })
+ }
+
+ /// Set the half height of this collider if it is a capsule, cylinder, or cone shape.
+ pub fn coSetHalfHeight(&mut self, handle: FlatHandle, newHalfheight: Real) {
+ self.map_mut(handle, |co| match co.shape().shape_type() {
+ ShapeType::Capsule => {
+ let point = Point::from(Vector::y() * newHalfheight);
+ co.shape_mut().as_capsule_mut().map(|b| {
+ b.segment.a = -point;
+ b.segment.b = point;
+ })
+ }
+ #[cfg(feature = "dim3")]
+ ShapeType::Cylinder => co
+ .shape_mut()
+ .as_cylinder_mut()
+ .map(|b| b.half_height = newHalfheight),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCylinder => co
+ .shape_mut()
+ .as_round_cylinder_mut()
+ .map(|b| b.inner_shape.half_height = newHalfheight),
+ #[cfg(feature = "dim3")]
+ ShapeType::Cone => co
+ .shape_mut()
+ .as_cone_mut()
+ .map(|b| b.half_height = newHalfheight),
+ _ => None,
+ });
+ }
+
+ /// The radius of the round edges of this collider.
+ pub fn coRoundRadius(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::RoundCuboid => co.shape().as_round_cuboid().map(|b| b.border_radius),
+ ShapeType::RoundTriangle => co.shape().as_round_triangle().map(|b| b.border_radius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCylinder => co.shape().as_round_cylinder().map(|b| b.border_radius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCone => co.shape().as_round_cone().map(|b| b.border_radius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundConvexPolyhedron => co
+ .shape()
+ .as_round_convex_polyhedron()
+ .map(|b| b.border_radius),
+ #[cfg(feature = "dim2")]
+ ShapeType::RoundConvexPolygon => co
+ .shape()
+ .as_round_convex_polygon()
+ .map(|b| b.border_radius),
+ _ => None,
+ })
+ }
+
+ /// Set the radius of the round edges of this collider.
+ pub fn coSetRoundRadius(&mut self, handle: FlatHandle, newBorderRadius: Real) {
+ self.map_mut(handle, |co| match co.shape().shape_type() {
+ ShapeType::RoundCuboid => co
+ .shape_mut()
+ .as_round_cuboid_mut()
+ .map(|b| b.border_radius = newBorderRadius),
+ ShapeType::RoundTriangle => co
+ .shape_mut()
+ .as_round_triangle_mut()
+ .map(|b| b.border_radius = newBorderRadius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCylinder => co
+ .shape_mut()
+ .as_round_cylinder_mut()
+ .map(|b| b.border_radius = newBorderRadius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundCone => co
+ .shape_mut()
+ .as_round_cone_mut()
+ .map(|b| b.border_radius = newBorderRadius),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundConvexPolyhedron => co
+ .shape_mut()
+ .as_round_convex_polyhedron_mut()
+ .map(|b| b.border_radius = newBorderRadius),
+ #[cfg(feature = "dim2")]
+ ShapeType::RoundConvexPolygon => co
+ .shape_mut()
+ .as_round_convex_polygon_mut()
+ .map(|b| b.border_radius = newBorderRadius),
+ _ => None,
+ });
+ }
+
+ pub fn coVoxelData(&self, handle: FlatHandle) -> Option> {
+ self.map(handle, |co| {
+ let vox = co.shape().as_voxels()?;
+ let coords = vox
+ .voxels()
+ .filter_map(|vox| (!vox.state.is_empty()).then_some(vox.grid_coords))
+ .flat_map(|ids| ids.coords.data.0[0])
+ .collect();
+ Some(coords)
+ })
+ }
+
+ pub fn coVoxelSize(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| {
+ let vox = co.shape().as_voxels()?;
+ Some(RawVector(vox.voxel_size()))
+ })
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn coSetVoxel(&mut self, handle: FlatHandle, ix: i32, iy: i32, filled: bool) {
+ self.map_mut(handle, |co| {
+ if let Some(vox) = co.shape_mut().as_voxels_mut() {
+ vox.set_voxel(Point::new(ix, iy), filled);
+ }
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn coSetVoxel(&mut self, handle: FlatHandle, ix: i32, iy: i32, iz: i32, filled: bool) {
+ self.map_mut(handle, |co| {
+ if let Some(vox) = co.shape_mut().as_voxels_mut() {
+ vox.set_voxel(Point::new(ix, iy, iz), filled);
+ }
+ })
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn coPropagateVoxelChange(
+ &mut self,
+ handle1: FlatHandle,
+ handle2: FlatHandle,
+ ix: i32,
+ iy: i32,
+ shift_x: i32,
+ shift_y: i32,
+ ) {
+ self.map_pair_mut(handle1, handle2, |co1, co2| {
+ if let (Some(co1), Some(co2)) = (co1, co2) {
+ if let (Some(vox1), Some(vox2)) = (
+ co1.shape_mut().as_voxels_mut(),
+ co2.shape_mut().as_voxels_mut(),
+ ) {
+ vox1.propagate_voxel_change(
+ vox2,
+ Point::new(ix, iy),
+ Vector::new(shift_x, shift_y),
+ );
+ }
+ }
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn coPropagateVoxelChange(
+ &mut self,
+ handle1: FlatHandle,
+ handle2: FlatHandle,
+ ix: i32,
+ iy: i32,
+ iz: i32,
+ shift_x: i32,
+ shift_y: i32,
+ shift_z: i32,
+ ) {
+ self.map_pair_mut(handle1, handle2, |co1, co2| {
+ if let (Some(co1), Some(co2)) = (co1, co2) {
+ if let (Some(vox1), Some(vox2)) = (
+ co1.shape_mut().as_voxels_mut(),
+ co2.shape_mut().as_voxels_mut(),
+ ) {
+ vox1.propagate_voxel_change(
+ vox2,
+ Point::new(ix, iy, iz),
+ Vector::new(shift_x, shift_y, shift_z),
+ );
+ }
+ }
+ })
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn coCombineVoxelStates(
+ &mut self,
+ handle1: FlatHandle,
+ handle2: FlatHandle,
+ shift_x: i32,
+ shift_y: i32,
+ ) {
+ self.map_pair_mut(handle1, handle2, |co1, co2| {
+ if let (Some(co1), Some(co2)) = (co1, co2) {
+ if let (Some(vox1), Some(vox2)) = (
+ co1.shape_mut().as_voxels_mut(),
+ co2.shape_mut().as_voxels_mut(),
+ ) {
+ vox1.combine_voxel_states(vox2, Vector::new(shift_x, shift_y));
+ }
+ }
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn coCombineVoxelStates(
+ &mut self,
+ handle1: FlatHandle,
+ handle2: FlatHandle,
+ shift_x: i32,
+ shift_y: i32,
+ shift_z: i32,
+ ) {
+ self.map_pair_mut(handle1, handle2, |co1, co2| {
+ if let (Some(co1), Some(co2)) = (co1, co2) {
+ if let (Some(vox1), Some(vox2)) = (
+ co1.shape_mut().as_voxels_mut(),
+ co2.shape_mut().as_voxels_mut(),
+ ) {
+ vox1.combine_voxel_states(vox2, Vector::new(shift_x, shift_y, shift_z));
+ }
+ }
+ })
+ }
+
+ /// The vertices of this triangle mesh, polyline, convex polyhedron, segment, triangle or convex polyhedron, if it is one.
+ pub fn coVertices(&self, handle: FlatHandle) -> Option> {
+ let flatten =
+ |vertices: &[Point]| vertices.iter().flat_map(|p| p.iter()).copied().collect();
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::TriMesh => co.shape().as_trimesh().map(|t| flatten(t.vertices())),
+ #[cfg(feature = "dim2")]
+ ShapeType::Polyline => co.shape().as_polyline().map(|p| flatten(p.vertices())),
+ #[cfg(feature = "dim3")]
+ ShapeType::ConvexPolyhedron => co
+ .shape()
+ .as_convex_polyhedron()
+ .map(|p| flatten(p.points())),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundConvexPolyhedron => co
+ .shape()
+ .as_round_convex_polyhedron()
+ .map(|p| flatten(p.inner_shape.points())),
+ #[cfg(feature = "dim2")]
+ ShapeType::ConvexPolygon => co.shape().as_convex_polygon().map(|p| flatten(p.points())),
+ #[cfg(feature = "dim2")]
+ ShapeType::RoundConvexPolygon => co
+ .shape()
+ .as_round_convex_polygon()
+ .map(|p| flatten(p.inner_shape.points())),
+ ShapeType::Segment => co.shape().as_segment().map(|s| flatten(&[s.a, s.b])),
+ ShapeType::RoundTriangle => co
+ .shape()
+ .as_round_triangle()
+ .map(|t| flatten(&[t.inner_shape.a, t.inner_shape.b, t.inner_shape.c])),
+ ShapeType::Triangle => co.shape().as_triangle().map(|t| flatten(&[t.a, t.b, t.c])),
+ _ => None,
+ })
+ }
+
+ /// The indices of this triangle mesh, polyline, or convex polyhedron, if it is one.
+ pub fn coIndices(&self, handle: FlatHandle) -> Option> {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::TriMesh => co
+ .shape()
+ .as_trimesh()
+ .map(|t| t.indices().iter().flat_map(|p| p.iter()).copied().collect()),
+ ShapeType::Polyline => co
+ .shape()
+ .as_polyline()
+ .map(|p| p.indices().iter().flat_map(|p| p.iter()).copied().collect()),
+ #[cfg(feature = "dim3")]
+ ShapeType::ConvexPolyhedron => co.shape().as_convex_polyhedron().map(|p| {
+ // TODO: avoid the `.to_trimesh()`.
+ p.to_trimesh()
+ .1
+ .iter()
+ .flat_map(|p| p.iter())
+ .copied()
+ .collect()
+ }),
+ #[cfg(feature = "dim3")]
+ ShapeType::RoundConvexPolyhedron => co.shape().as_round_convex_polyhedron().map(|p| {
+ // TODO: avoid the `.to_trimesh()`.
+ p.inner_shape
+ .to_trimesh()
+ .1
+ .iter()
+ .flat_map(|p| p.iter())
+ .copied()
+ .collect()
+ }),
+ _ => None,
+ })
+ }
+
+ pub fn coTriMeshFlags(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| {
+ co.shape().as_trimesh().map(|tri| tri.flags().bits() as u32)
+ })
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn coHeightFieldFlags(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| {
+ co.shape()
+ .as_heightfield()
+ .map(|hf| hf.flags().bits() as u32)
+ })
+ }
+
+ /// The height of this heightfield if it is one.
+ pub fn coHeightfieldHeights(&self, handle: FlatHandle) -> Option> {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::HeightField => co
+ .shape()
+ .as_heightfield()
+ .map(|h| h.heights().as_slice().to_vec()),
+ _ => None,
+ })
+ }
+
+ /// The scaling factor applied of this heightfield if it is one.
+ pub fn coHeightfieldScale(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::HeightField => co.shape().as_heightfield().map(|h| RawVector(*h.scale())),
+ _ => None,
+ })
+ }
+
+ /// The number of rows on this heightfield's height matrix, if it is one.
+ #[cfg(feature = "dim3")]
+ pub fn coHeightfieldNRows(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::HeightField => co.shape().as_heightfield().map(|h| h.nrows()),
+ _ => None,
+ })
+ }
+
+ /// The number of columns on this heightfield's height matrix, if it is one.
+ #[cfg(feature = "dim3")]
+ pub fn coHeightfieldNCols(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| match co.shape().shape_type() {
+ ShapeType::HeightField => co.shape().as_heightfield().map(|h| h.ncols()),
+ _ => None,
+ })
+ }
+
+ /// The unique integer identifier of the collider this collider is attached to.
+ pub fn coParent(&self, handle: FlatHandle) -> Option {
+ self.map(handle, |co| co.parent().map(|p| utils::flat_handle(p.0)))
+ }
+
+ pub fn coSetEnabled(&mut self, handle: FlatHandle, enabled: bool) {
+ self.map_mut(handle, |co| co.set_enabled(enabled))
+ }
+
+ pub fn coIsEnabled(&self, handle: FlatHandle) -> bool {
+ self.map(handle, |co| co.is_enabled())
+ }
+
+ pub fn coSetContactSkin(&mut self, handle: FlatHandle, contact_skin: f32) {
+ self.map_mut(handle, |co| co.set_contact_skin(contact_skin))
+ }
+
+ pub fn coContactSkin(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |co| co.contact_skin())
+ }
+
+ /// The friction coefficient of this collider.
+ pub fn coFriction(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |co| co.material().friction)
+ }
+ /// The restitution coefficient of this collider.
+ pub fn coRestitution(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |co| co.material().restitution)
+ }
+
+ /// The density of this collider.
+ pub fn coDensity(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |co| co.density())
+ }
+
+ /// The mass of this collider.
+ pub fn coMass(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |co| co.mass())
+ }
+
+ /// The volume of this collider.
+ pub fn coVolume(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |co| co.volume())
+ }
+
+ /// The collision groups of this collider.
+ pub fn coCollisionGroups(&self, handle: FlatHandle) -> u32 {
+ self.map(handle, |co| {
+ super::pack_interaction_groups(co.collision_groups())
+ })
+ }
+
+ /// The solver groups of this collider.
+ pub fn coSolverGroups(&self, handle: FlatHandle) -> u32 {
+ self.map(handle, |co| {
+ super::pack_interaction_groups(co.solver_groups())
+ })
+ }
+
+ /// The physics hooks enabled for this collider.
+ pub fn coActiveHooks(&self, handle: FlatHandle) -> u32 {
+ self.map(handle, |co| co.active_hooks().bits())
+ }
+
+ /// The collision types enabled for this collider.
+ pub fn coActiveCollisionTypes(&self, handle: FlatHandle) -> u16 {
+ self.map(handle, |co| co.active_collision_types().bits())
+ }
+
+ /// The events enabled for this collider.
+ pub fn coActiveEvents(&self, handle: FlatHandle) -> u32 {
+ self.map(handle, |co| co.active_events().bits())
+ }
+
+ /// The total force magnitude beyond which a contact force event can be emitted.
+ pub fn coContactForceEventThreshold(&self, handle: FlatHandle) -> f32 {
+ self.map(handle, |co| co.contact_force_event_threshold())
+ }
+
+ pub fn coContainsPoint(&self, handle: FlatHandle, point: &RawVector) -> bool {
+ self.map(handle, |co| {
+ co.shared_shape()
+ .containsPoint(co.position(), &point.0.into())
+ })
+ }
+
+ pub fn coCastShape(
+ &self,
+ handle: FlatHandle,
+ colliderVel: &RawVector,
+ shape2: &RawShape,
+ shape2Pos: &RawVector,
+ shape2Rot: &RawRotation,
+ shape2Vel: &RawVector,
+ target_distance: f32,
+ maxToi: f32,
+ stop_at_penetration: bool,
+ ) -> Option {
+ let pos2 = Isometry::from_parts(shape2Pos.0.into(), shape2Rot.0);
+
+ self.map(handle, |co| {
+ let pos1 = co.position();
+ co.shared_shape().castShape(
+ pos1,
+ &colliderVel.0.into(),
+ &*shape2.0,
+ &pos2,
+ &shape2Vel.0.into(),
+ target_distance,
+ maxToi,
+ stop_at_penetration,
+ )
+ })
+ }
+
+ pub fn coCastCollider(
+ &self,
+ handle: FlatHandle,
+ collider1Vel: &RawVector,
+ collider2handle: FlatHandle,
+ collider2Vel: &RawVector,
+ target_distance: f32,
+ max_toi: f32,
+ stop_at_penetration: bool,
+ ) -> Option {
+ let handle2 = utils::collider_handle(collider2handle);
+ let co2 = self
+ .0
+ .get(handle2)
+ .expect("Invalid Collider reference. It may have been removed from the physics World.");
+
+ self.map(handle, |co| {
+ query::cast_shapes(
+ co.position(),
+ &collider1Vel.0,
+ co.shape(),
+ co2.position(),
+ &collider2Vel.0,
+ co2.shape(),
+ ShapeCastOptions {
+ max_time_of_impact: max_toi,
+ stop_at_penetration,
+ target_distance,
+ compute_impact_geometry_on_penetration: true,
+ },
+ )
+ .unwrap_or(None)
+ .map_or(None, |hit| {
+ Some(RawColliderShapeCastHit {
+ handle: handle2,
+ hit,
+ })
+ })
+ })
+ }
+
+ pub fn coIntersectsShape(
+ &self,
+ handle: FlatHandle,
+ shape2: &RawShape,
+ shapePos2: &RawVector,
+ shapeRot2: &RawRotation,
+ ) -> bool {
+ let pos2 = Isometry::from_parts(shapePos2.0.into(), shapeRot2.0);
+
+ self.map(handle, |co| {
+ co.shared_shape()
+ .intersectsShape(co.position(), &*shape2.0, &pos2)
+ })
+ }
+
+ pub fn coContactShape(
+ &self,
+ handle: FlatHandle,
+ shape2: &RawShape,
+ shapePos2: &RawVector,
+ shapeRot2: &RawRotation,
+ prediction: f32,
+ ) -> Option {
+ let pos2 = Isometry::from_parts(shapePos2.0.into(), shapeRot2.0);
+
+ self.map(handle, |co| {
+ co.shared_shape()
+ .contactShape(co.position(), &*shape2.0, &pos2, prediction)
+ })
+ }
+
+ pub fn coContactCollider(
+ &self,
+ handle: FlatHandle,
+ collider2handle: FlatHandle,
+ prediction: f32,
+ ) -> Option {
+ let co2 = self
+ .0
+ .get(utils::collider_handle(collider2handle))
+ .expect("Invalid Collider reference. It may have been removed from the physics World.");
+
+ self.map(handle, |co| {
+ query::contact(
+ co.position(),
+ co.shape(),
+ &co2.position(),
+ co2.shape(),
+ prediction,
+ )
+ .ok()
+ .flatten()
+ .map(|contact| RawShapeContact { contact })
+ })
+ }
+
+ pub fn coProjectPoint(
+ &self,
+ handle: FlatHandle,
+ point: &RawVector,
+ solid: bool,
+ ) -> RawPointProjection {
+ self.map(handle, |co| {
+ co.shared_shape()
+ .projectPoint(co.position(), &point.0.into(), solid)
+ })
+ }
+
+ pub fn coIntersectsRay(
+ &self,
+ handle: FlatHandle,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ ) -> bool {
+ self.map(handle, |co| {
+ co.shared_shape().intersectsRay(
+ co.position(),
+ rayOrig.0.into(),
+ rayDir.0.into(),
+ maxToi,
+ )
+ })
+ }
+
+ pub fn coCastRay(
+ &self,
+ handle: FlatHandle,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ solid: bool,
+ ) -> f32 {
+ self.map(handle, |co| {
+ co.shared_shape().castRay(
+ co.position(),
+ rayOrig.0.into(),
+ rayDir.0.into(),
+ maxToi,
+ solid,
+ )
+ })
+ }
+
+ pub fn coCastRayAndGetNormal(
+ &self,
+ handle: FlatHandle,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ solid: bool,
+ ) -> Option {
+ self.map(handle, |co| {
+ co.shared_shape().castRayAndGetNormal(
+ co.position(),
+ rayOrig.0.into(),
+ rayDir.0.into(),
+ maxToi,
+ solid,
+ )
+ })
+ }
+
+ pub fn coSetSensor(&mut self, handle: FlatHandle, is_sensor: bool) {
+ self.map_mut(handle, |co| co.set_sensor(is_sensor))
+ }
+
+ pub fn coSetRestitution(&mut self, handle: FlatHandle, restitution: f32) {
+ self.map_mut(handle, |co| co.set_restitution(restitution))
+ }
+
+ pub fn coSetFriction(&mut self, handle: FlatHandle, friction: f32) {
+ self.map_mut(handle, |co| co.set_friction(friction))
+ }
+
+ pub fn coFrictionCombineRule(&self, handle: FlatHandle) -> u32 {
+ self.map(handle, |co| co.friction_combine_rule() as u32)
+ }
+
+ pub fn coSetFrictionCombineRule(&mut self, handle: FlatHandle, rule: u32) {
+ let rule = super::combine_rule_from_u32(rule);
+ self.map_mut(handle, |co| co.set_friction_combine_rule(rule))
+ }
+
+ pub fn coRestitutionCombineRule(&self, handle: FlatHandle) -> u32 {
+ self.map(handle, |co| co.restitution_combine_rule() as u32)
+ }
+
+ pub fn coSetRestitutionCombineRule(&mut self, handle: FlatHandle, rule: u32) {
+ let rule = super::combine_rule_from_u32(rule);
+ self.map_mut(handle, |co| co.set_restitution_combine_rule(rule))
+ }
+
+ pub fn coSetCollisionGroups(&mut self, handle: FlatHandle, groups: u32) {
+ let groups = super::unpack_interaction_groups(groups);
+ self.map_mut(handle, |co| co.set_collision_groups(groups))
+ }
+
+ pub fn coSetSolverGroups(&mut self, handle: FlatHandle, groups: u32) {
+ let groups = super::unpack_interaction_groups(groups);
+ self.map_mut(handle, |co| co.set_solver_groups(groups))
+ }
+
+ pub fn coSetActiveHooks(&mut self, handle: FlatHandle, hooks: u32) {
+ let hooks = ActiveHooks::from_bits(hooks).unwrap_or(ActiveHooks::empty());
+ self.map_mut(handle, |co| co.set_active_hooks(hooks));
+ }
+
+ pub fn coSetActiveEvents(&mut self, handle: FlatHandle, events: u32) {
+ let events = ActiveEvents::from_bits(events).unwrap_or(ActiveEvents::empty());
+ self.map_mut(handle, |co| co.set_active_events(events))
+ }
+
+ pub fn coSetActiveCollisionTypes(&mut self, handle: FlatHandle, types: u16) {
+ let types = ActiveCollisionTypes::from_bits(types).unwrap_or(ActiveCollisionTypes::empty());
+ self.map_mut(handle, |co| co.set_active_collision_types(types));
+ }
+
+ pub fn coSetShape(&mut self, handle: FlatHandle, shape: &RawShape) {
+ self.map_mut(handle, |co| co.set_shape(shape.0.clone()));
+ }
+
+ pub fn coSetContactForceEventThreshold(&mut self, handle: FlatHandle, threshold: f32) {
+ self.map_mut(handle, |co| co.set_contact_force_event_threshold(threshold))
+ }
+
+ pub fn coSetDensity(&mut self, handle: FlatHandle, density: f32) {
+ self.map_mut(handle, |co| co.set_density(density))
+ }
+
+ pub fn coSetMass(&mut self, handle: FlatHandle, mass: f32) {
+ self.map_mut(handle, |co| co.set_mass(mass))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn coSetMassProperties(
+ &mut self,
+ handle: FlatHandle,
+ mass: f32,
+ centerOfMass: &RawVector,
+ principalAngularInertia: &RawVector,
+ angularInertiaFrame: &RawRotation,
+ ) {
+ self.map_mut(handle, |co| {
+ let mprops = MassProperties::with_principal_inertia_frame(
+ centerOfMass.0.into(),
+ mass,
+ principalAngularInertia.0,
+ angularInertiaFrame.0,
+ );
+
+ co.set_mass_properties(mprops)
+ })
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn coSetMassProperties(
+ &mut self,
+ handle: FlatHandle,
+ mass: f32,
+ centerOfMass: &RawVector,
+ principalAngularInertia: f32,
+ ) {
+ self.map_mut(handle, |co| {
+ let props = MassProperties::new(centerOfMass.0.into(), mass, principalAngularInertia);
+ co.set_mass_properties(props)
+ })
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/collider_set.rs b/thirdparty/rapier.js/src/geometry/collider_set.rs
new file mode 100644
index 00000000..6e31d8eb
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/collider_set.rs
@@ -0,0 +1,293 @@
+use crate::dynamics::{RawIslandManager, RawRigidBodySet};
+use crate::geometry::RawShape;
+use crate::math::{RawRotation, RawVector};
+use crate::utils::{self, FlatHandle};
+use rapier::prelude::*;
+use wasm_bindgen::prelude::*;
+
+// NOTE: this MUST match the same enum on the TS side.
+enum MassPropsMode {
+ Density = 0,
+ Mass,
+ MassProps,
+}
+
+#[wasm_bindgen]
+pub struct RawColliderSet(pub(crate) ColliderSet);
+
+impl RawColliderSet {
+ pub(crate) fn map(&self, handle: FlatHandle, f: impl FnOnce(&Collider) -> T) -> T {
+ let collider = self
+ .0
+ .get(utils::collider_handle(handle))
+ .expect("Invalid Collider reference. It may have been removed from the physics World.");
+ f(collider)
+ }
+
+ pub(crate) fn map_mut(
+ &mut self,
+ handle: FlatHandle,
+ f: impl FnOnce(&mut Collider) -> T,
+ ) -> T {
+ let collider = self
+ .0
+ .get_mut(utils::collider_handle(handle))
+ .expect("Invalid Collider reference. It may have been removed from the physics World.");
+ f(collider)
+ }
+
+ pub(crate) fn map_pair_mut(
+ &mut self,
+ handle1: FlatHandle,
+ handle2: FlatHandle,
+ f: impl FnOnce(Option<&mut Collider>, Option<&mut Collider>) -> T,
+ ) -> T {
+ let (collider1, collider2) = self.0.get_pair_mut(
+ utils::collider_handle(handle1),
+ utils::collider_handle(handle2),
+ );
+ f(collider1, collider2)
+ }
+}
+
+impl RawColliderSet {
+ // This is a workaround because wasm-bindgen doesn't support the `cfg(feature = ...)`
+ // for the method arguments.
+ pub fn do_create_collider(
+ &mut self,
+ enabled: bool,
+ shape: &RawShape,
+ translation: &RawVector,
+ rotation: &RawRotation,
+ massPropsMode: u32,
+ mass: f32,
+ centerOfMass: &RawVector,
+ #[cfg(feature = "dim2")] principalAngularInertia: f32,
+ #[cfg(feature = "dim3")] principalAngularInertia: &RawVector,
+ #[cfg(feature = "dim3")] angularInertiaFrame: &RawRotation,
+ density: f32,
+ friction: f32,
+ restitution: f32,
+ frictionCombineRule: u32,
+ restitutionCombineRule: u32,
+ isSensor: bool,
+ collisionGroups: u32,
+ solverGroups: u32,
+ activeCollisionTypes: u16,
+ activeHooks: u32,
+ activeEvents: u32,
+ contactForceEventThreshold: f32,
+ contactSkin: f32,
+ hasParent: bool,
+ parent: FlatHandle,
+ bodies: &mut RawRigidBodySet,
+ ) -> Option {
+ let pos = Isometry::from_parts(translation.0.into(), rotation.0);
+ let mut builder = ColliderBuilder::new(shape.0.clone())
+ .enabled(enabled)
+ .position(pos)
+ .friction(friction)
+ .restitution(restitution)
+ .collision_groups(super::unpack_interaction_groups(collisionGroups))
+ .solver_groups(super::unpack_interaction_groups(solverGroups))
+ .active_hooks(ActiveHooks::from_bits(activeHooks).unwrap_or(ActiveHooks::empty()))
+ .active_events(ActiveEvents::from_bits(activeEvents).unwrap_or(ActiveEvents::empty()))
+ .active_collision_types(
+ ActiveCollisionTypes::from_bits(activeCollisionTypes)
+ .unwrap_or(ActiveCollisionTypes::empty()),
+ )
+ .sensor(isSensor)
+ .friction_combine_rule(super::combine_rule_from_u32(frictionCombineRule))
+ .restitution_combine_rule(super::combine_rule_from_u32(restitutionCombineRule))
+ .contact_force_event_threshold(contactForceEventThreshold)
+ .contact_skin(contactSkin);
+
+ if massPropsMode == MassPropsMode::MassProps as u32 {
+ #[cfg(feature = "dim2")]
+ let mprops = MassProperties::new(centerOfMass.0.into(), mass, principalAngularInertia);
+ #[cfg(feature = "dim3")]
+ let mprops = MassProperties::with_principal_inertia_frame(
+ centerOfMass.0.into(),
+ mass,
+ principalAngularInertia.0,
+ angularInertiaFrame.0,
+ );
+ builder = builder.mass_properties(mprops);
+ } else if massPropsMode == MassPropsMode::Density as u32 {
+ builder = builder.density(density);
+ } else {
+ assert_eq!(massPropsMode, MassPropsMode::Mass as u32);
+ builder = builder.mass(mass);
+ };
+
+ let collider = builder.build();
+
+ if hasParent {
+ Some(utils::flat_handle(
+ self.0
+ .insert_with_parent(collider, utils::body_handle(parent), &mut bodies.0)
+ .0,
+ ))
+ } else {
+ Some(utils::flat_handle(self.0.insert(collider).0))
+ }
+ }
+}
+
+#[wasm_bindgen]
+impl RawColliderSet {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawColliderSet(ColliderSet::new())
+ }
+
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+
+ pub fn contains(&self, handle: FlatHandle) -> bool {
+ self.0.get(utils::collider_handle(handle)).is_some()
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn createCollider(
+ &mut self,
+ enabled: bool,
+ shape: &RawShape,
+ translation: &RawVector,
+ rotation: &RawRotation,
+ massPropsMode: u32,
+ mass: f32,
+ centerOfMass: &RawVector,
+ principalAngularInertia: f32,
+ density: f32,
+ friction: f32,
+ restitution: f32,
+ frictionCombineRule: u32,
+ restitutionCombineRule: u32,
+ isSensor: bool,
+ collisionGroups: u32,
+ solverGroups: u32,
+ activeCollisionTypes: u16,
+ activeHooks: u32,
+ activeEvents: u32,
+ contactForceEventThreshold: f32,
+ contactSkin: f32,
+ hasParent: bool,
+ parent: FlatHandle,
+ bodies: &mut RawRigidBodySet,
+ ) -> Option {
+ self.do_create_collider(
+ enabled,
+ shape,
+ translation,
+ rotation,
+ massPropsMode,
+ mass,
+ centerOfMass,
+ principalAngularInertia,
+ density,
+ friction,
+ restitution,
+ frictionCombineRule,
+ restitutionCombineRule,
+ isSensor,
+ collisionGroups,
+ solverGroups,
+ activeCollisionTypes,
+ activeHooks,
+ activeEvents,
+ contactForceEventThreshold,
+ contactSkin,
+ hasParent,
+ parent,
+ bodies,
+ )
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn createCollider(
+ &mut self,
+ enabled: bool,
+ shape: &RawShape,
+ translation: &RawVector,
+ rotation: &RawRotation,
+ massPropsMode: u32,
+ mass: f32,
+ centerOfMass: &RawVector,
+ principalAngularInertia: &RawVector,
+ angularInertiaFrame: &RawRotation,
+ density: f32,
+ friction: f32,
+ restitution: f32,
+ frictionCombineRule: u32,
+ restitutionCombineRule: u32,
+ isSensor: bool,
+ collisionGroups: u32,
+ solverGroups: u32,
+ activeCollisionTypes: u16,
+ activeHooks: u32,
+ activeEvents: u32,
+ contactForceEventThreshold: f32,
+ contactSkin: f32,
+ hasParent: bool,
+ parent: FlatHandle,
+ bodies: &mut RawRigidBodySet,
+ ) -> Option {
+ self.do_create_collider(
+ enabled,
+ shape,
+ translation,
+ rotation,
+ massPropsMode,
+ mass,
+ centerOfMass,
+ principalAngularInertia,
+ angularInertiaFrame,
+ density,
+ friction,
+ restitution,
+ frictionCombineRule,
+ restitutionCombineRule,
+ isSensor,
+ collisionGroups,
+ solverGroups,
+ activeCollisionTypes,
+ activeHooks,
+ activeEvents,
+ contactForceEventThreshold,
+ contactSkin,
+ hasParent,
+ parent,
+ bodies,
+ )
+ }
+
+ /// Removes a collider from this set and wake-up the rigid-body it is attached to.
+ pub fn remove(
+ &mut self,
+ handle: FlatHandle,
+ islands: &mut RawIslandManager,
+ bodies: &mut RawRigidBodySet,
+ wakeUp: bool,
+ ) {
+ let handle = utils::collider_handle(handle);
+ self.0.remove(handle, &mut islands.0, &mut bodies.0, wakeUp);
+ }
+
+ /// Checks if a collider with the given integer handle exists.
+ pub fn isHandleValid(&self, handle: FlatHandle) -> bool {
+ self.0.get(utils::collider_handle(handle)).is_some()
+ }
+
+ /// Applies the given JavaScript function to the integer handle of each collider managed by this collider set.
+ ///
+ /// # Parameters
+ /// - `f(handle)`: the function to apply to the integer handle of each collider managed by this collider set. Called as `f(handle)`.
+ pub fn forEachColliderHandle(&self, f: &js_sys::Function) {
+ let this = JsValue::null();
+ for (handle, _) in self.0.iter() {
+ let _ = f.call1(&this, &JsValue::from(utils::flat_handle(handle.0)));
+ }
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/contact.rs b/thirdparty/rapier.js/src/geometry/contact.rs
new file mode 100644
index 00000000..47043a27
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/contact.rs
@@ -0,0 +1,31 @@
+use crate::math::RawVector;
+use rapier::parry::query;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawShapeContact {
+ pub(crate) contact: query::Contact,
+}
+
+#[wasm_bindgen]
+impl RawShapeContact {
+ pub fn distance(&self) -> f32 {
+ self.contact.dist
+ }
+
+ pub fn point1(&self) -> RawVector {
+ self.contact.point1.coords.into()
+ }
+
+ pub fn point2(&self) -> RawVector {
+ self.contact.point2.coords.into()
+ }
+
+ pub fn normal1(&self) -> RawVector {
+ self.contact.normal1.into_inner().into()
+ }
+
+ pub fn normal2(&self) -> RawVector {
+ self.contact.normal2.into_inner().into()
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/feature.rs b/thirdparty/rapier.js/src/geometry/feature.rs
new file mode 100644
index 00000000..96e8f511
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/feature.rs
@@ -0,0 +1,47 @@
+use rapier::prelude::FeatureId;
+use wasm_bindgen::prelude::wasm_bindgen;
+
+#[cfg(feature = "dim2")]
+#[wasm_bindgen]
+#[derive(Copy, Clone)]
+pub enum RawFeatureType {
+ Vertex,
+ Face,
+ Unknown,
+}
+
+#[cfg(feature = "dim3")]
+#[wasm_bindgen]
+#[derive(Copy, Clone)]
+pub enum RawFeatureType {
+ Vertex,
+ Edge,
+ Face,
+ Unknown,
+}
+
+pub trait IntoTypeValue {
+ fn into_type(self) -> RawFeatureType;
+ fn into_value(self) -> Option;
+}
+
+impl IntoTypeValue for FeatureId {
+ fn into_type(self) -> RawFeatureType {
+ match self {
+ FeatureId::Vertex(_) => RawFeatureType::Vertex,
+ #[cfg(feature = "dim3")]
+ FeatureId::Edge(_) => RawFeatureType::Edge,
+ FeatureId::Face(_) => RawFeatureType::Face,
+ _ => RawFeatureType::Unknown,
+ }
+ }
+
+ fn into_value(self) -> Option {
+ match self {
+ FeatureId::Vertex(id) | FeatureId::Face(id) => Some(id),
+ #[cfg(feature = "dim3")]
+ FeatureId::Edge(id) => Some(id),
+ _ => None,
+ }
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/mod.rs b/thirdparty/rapier.js/src/geometry/mod.rs
new file mode 100644
index 00000000..08918b88
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/mod.rs
@@ -0,0 +1,49 @@
+//! Structures related to geometry: colliders, shapes, etc.
+
+pub use self::broad_phase::*;
+pub use self::collider_set::*;
+pub use self::contact::*;
+pub use self::feature::*;
+pub use self::narrow_phase::*;
+pub use self::point::*;
+pub use self::ray::*;
+pub use self::shape::*;
+pub use self::toi::*;
+
+mod broad_phase;
+mod collider;
+mod collider_set;
+mod contact;
+mod feature;
+mod narrow_phase;
+mod point;
+mod ray;
+mod shape;
+mod toi;
+
+use rapier::dynamics::CoefficientCombineRule;
+use rapier::geometry::InteractionGroups;
+use rapier::prelude::Group;
+
+pub const fn unpack_interaction_groups(memberships_filter: u32) -> InteractionGroups {
+ InteractionGroups::new(
+ Group::from_bits_retain((memberships_filter >> 16) as u32),
+ Group::from_bits_retain((memberships_filter & 0x0000_ffff) as u32),
+ )
+}
+
+pub const fn pack_interaction_groups(groups: InteractionGroups) -> u32 {
+ (groups.memberships.bits() << 16) | groups.filter.bits()
+}
+
+pub const fn combine_rule_from_u32(rule: u32) -> CoefficientCombineRule {
+ if rule == CoefficientCombineRule::Average as u32 {
+ CoefficientCombineRule::Average
+ } else if rule == CoefficientCombineRule::Min as u32 {
+ CoefficientCombineRule::Min
+ } else if rule == CoefficientCombineRule::Multiply as u32 {
+ CoefficientCombineRule::Multiply
+ } else {
+ CoefficientCombineRule::Max
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/narrow_phase.rs b/thirdparty/rapier.js/src/geometry/narrow_phase.rs
new file mode 100644
index 00000000..be3fee25
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/narrow_phase.rs
@@ -0,0 +1,217 @@
+use crate::math::RawVector;
+use crate::utils::{self, FlatHandle};
+use rapier::geometry::{ContactManifold, ContactPair, NarrowPhase};
+use rapier::math::Real;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawNarrowPhase(pub(crate) NarrowPhase);
+
+#[wasm_bindgen]
+impl RawNarrowPhase {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawNarrowPhase(NarrowPhase::new())
+ }
+
+ pub fn contact_pairs_with(&self, handle1: FlatHandle, f: js_sys::Function) {
+ let this = JsValue::null();
+ let handle1 = utils::collider_handle(handle1);
+ for pair in self.0.contact_pairs_with(handle1) {
+ let handle2 = if pair.collider1 == handle1 {
+ utils::flat_handle(pair.collider2.0)
+ } else {
+ utils::flat_handle(pair.collider1.0)
+ };
+
+ let _ = f.call1(&this, &JsValue::from(handle2));
+ }
+ }
+
+ pub fn contact_pair(&self, handle1: FlatHandle, handle2: FlatHandle) -> Option {
+ let handle1 = utils::collider_handle(handle1);
+ let handle2 = utils::collider_handle(handle2);
+ self.0
+ .contact_pair(handle1, handle2)
+ .map(|p| RawContactPair(p as *const ContactPair))
+ }
+
+ pub fn intersection_pairs_with(&self, handle1: FlatHandle, f: js_sys::Function) {
+ let this = JsValue::null();
+ let handle1 = utils::collider_handle(handle1);
+ for (h1, h2, inter) in self.0.intersection_pairs_with(handle1) {
+ if inter {
+ let handle2 = if h1 == handle1 {
+ utils::flat_handle(h2.0)
+ } else {
+ utils::flat_handle(h1.0)
+ };
+
+ let _ = f.call1(&this, &JsValue::from(handle2));
+ }
+ }
+ }
+
+ pub fn intersection_pair(&self, handle1: FlatHandle, handle2: FlatHandle) -> bool {
+ let handle1 = utils::collider_handle(handle1);
+ let handle2 = utils::collider_handle(handle2);
+ self.0.intersection_pair(handle1, handle2) == Some(true)
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawContactPair(*const ContactPair);
+#[wasm_bindgen]
+pub struct RawContactManifold(*const ContactManifold);
+
+// SAFETY: the use of a raw pointer is very unsafe.
+// We need this because wasm-bindgen doesn't support
+// lifetimes. So for the moment, we have to make sure
+// that our TypeScript wrapper properly free the pair
+// before the user has a chance to invalidate this pointer.
+#[wasm_bindgen]
+impl RawContactPair {
+ pub fn collider1(&self) -> FlatHandle {
+ unsafe { utils::flat_handle((*self.0).collider1.0) }
+ }
+
+ pub fn collider2(&self) -> FlatHandle {
+ unsafe { utils::flat_handle((*self.0).collider2.0) }
+ }
+
+ pub fn numContactManifolds(&self) -> usize {
+ unsafe { (*self.0).manifolds.len() }
+ }
+ pub fn contactManifold(&self, i: usize) -> Option {
+ unsafe {
+ (&(*self.0).manifolds)
+ .get(i)
+ .map(|m| RawContactManifold(m as *const ContactManifold))
+ }
+ }
+}
+
+#[wasm_bindgen]
+impl RawContactManifold {
+ pub fn normal(&self) -> RawVector {
+ unsafe { RawVector((*self.0).data.normal) }
+ }
+
+ // pub fn user_data(&self) -> u32 {
+ // unsafe { (*self.0).data.user_data }
+ // }
+
+ pub fn local_n1(&self) -> RawVector {
+ unsafe { (*self.0).local_n1.into() }
+ }
+
+ pub fn local_n2(&self) -> RawVector {
+ unsafe { (*self.0).local_n2.into() }
+ }
+
+ pub fn subshape1(&self) -> u32 {
+ unsafe { (*self.0).subshape1 }
+ }
+
+ pub fn subshape2(&self) -> u32 {
+ unsafe { (*self.0).subshape2 }
+ }
+
+ pub fn num_contacts(&self) -> usize {
+ unsafe { (*self.0).points.len() }
+ }
+
+ pub fn contact_local_p1(&self, i: usize) -> Option {
+ unsafe { (&(*self.0).points).get(i).map(|c| c.local_p1.coords.into()) }
+ }
+
+ pub fn contact_local_p2(&self, i: usize) -> Option {
+ unsafe { (&(*self.0).points).get(i).map(|c| c.local_p2.coords.into()) }
+ }
+
+ pub fn contact_dist(&self, i: usize) -> Real {
+ unsafe { (&(*self.0).points).get(i).map(|c| c.dist).unwrap_or(0.0) }
+ }
+
+ pub fn contact_fid1(&self, i: usize) -> u32 {
+ unsafe { (&(*self.0).points).get(i).map(|c| c.fid1.0).unwrap_or(0) }
+ }
+
+ pub fn contact_fid2(&self, i: usize) -> u32 {
+ unsafe { (&(*self.0).points).get(i).map(|c| c.fid2.0).unwrap_or(0) }
+ }
+
+ pub fn contact_impulse(&self, i: usize) -> Real {
+ unsafe {
+ (&(*self.0).points)
+ .get(i)
+ .map(|c| c.data.impulse)
+ .unwrap_or(0.0)
+ }
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn contact_tangent_impulse(&self, i: usize) -> Real {
+ unsafe {
+ (&(*self.0).points)
+ .get(i)
+ .map(|c| c.data.tangent_impulse.x)
+ .unwrap_or(0.0)
+ }
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn contact_tangent_impulse_x(&self, i: usize) -> Real {
+ unsafe {
+ (&(*self.0).points)
+ .get(i)
+ .map(|c| c.data.tangent_impulse.x)
+ .unwrap_or(0.0)
+ }
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn contact_tangent_impulse_y(&self, i: usize) -> Real {
+ unsafe {
+ (&(*self.0).points)
+ .get(i)
+ .map(|c| c.data.tangent_impulse.y)
+ .unwrap_or(0.0)
+ }
+ }
+
+ pub fn num_solver_contacts(&self) -> usize {
+ unsafe { (*self.0).data.solver_contacts.len() }
+ }
+
+ pub fn solver_contact_point(&self, i: usize) -> Option {
+ unsafe {
+ (&(*self.0).data)
+ .solver_contacts
+ .get(i)
+ .map(|c| c.point.coords.into())
+ }
+ }
+
+ pub fn solver_contact_dist(&self, i: usize) -> Real {
+ unsafe {
+ (&(*self.0).data)
+ .solver_contacts
+ .get(i)
+ .map(|c| c.dist)
+ .unwrap_or(0.0)
+ }
+ }
+
+ pub fn solver_contact_friction(&self, i: usize) -> Real {
+ unsafe { (&(*self.0).data).solver_contacts[i].friction }
+ }
+
+ pub fn solver_contact_restitution(&self, i: usize) -> Real {
+ unsafe { (&(*self.0).data).solver_contacts[i].restitution }
+ }
+
+ pub fn solver_contact_tangent_velocity(&self, i: usize) -> RawVector {
+ unsafe { (&(*self.0).data).solver_contacts[i].tangent_velocity.into() }
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/point.rs b/thirdparty/rapier.js/src/geometry/point.rs
new file mode 100644
index 00000000..e7bb4a93
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/point.rs
@@ -0,0 +1,53 @@
+use crate::geometry::feature::IntoTypeValue;
+use crate::geometry::RawFeatureType;
+use crate::math::RawVector;
+use crate::utils::{self, FlatHandle};
+use rapier::{
+ geometry::{ColliderHandle, PointProjection},
+ prelude::FeatureId,
+};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawPointProjection(pub(crate) PointProjection);
+
+#[wasm_bindgen]
+impl RawPointProjection {
+ pub fn point(&self) -> RawVector {
+ self.0.point.coords.into()
+ }
+
+ pub fn isInside(&self) -> bool {
+ self.0.is_inside
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawPointColliderProjection {
+ pub(crate) handle: ColliderHandle,
+ pub(crate) proj: PointProjection,
+ pub(crate) feature: FeatureId,
+}
+
+#[wasm_bindgen]
+impl RawPointColliderProjection {
+ pub fn colliderHandle(&self) -> FlatHandle {
+ utils::flat_handle(self.handle.0)
+ }
+
+ pub fn point(&self) -> RawVector {
+ self.proj.point.coords.into()
+ }
+
+ pub fn isInside(&self) -> bool {
+ self.proj.is_inside
+ }
+
+ pub fn featureType(&self) -> RawFeatureType {
+ self.feature.into_type()
+ }
+
+ pub fn featureId(&self) -> Option {
+ self.feature.into_value()
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/ray.rs b/thirdparty/rapier.js/src/geometry/ray.rs
new file mode 100644
index 00000000..5285d5da
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/ray.rs
@@ -0,0 +1,74 @@
+use crate::geometry::feature::IntoTypeValue;
+use crate::geometry::RawFeatureType;
+use crate::math::RawVector;
+use crate::utils::{self, FlatHandle};
+use rapier::geometry::{ColliderHandle, RayIntersection};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawRayIntersection(pub(crate) RayIntersection);
+
+#[wasm_bindgen]
+impl RawRayIntersection {
+ pub fn normal(&self) -> RawVector {
+ self.0.normal.into()
+ }
+
+ pub fn time_of_impact(&self) -> f32 {
+ self.0.time_of_impact
+ }
+
+ pub fn featureType(&self) -> RawFeatureType {
+ self.0.feature.into_type()
+ }
+
+ pub fn featureId(&self) -> Option {
+ self.0.feature.into_value()
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawRayColliderIntersection {
+ pub(crate) handle: ColliderHandle,
+ pub(crate) inter: RayIntersection,
+}
+
+#[wasm_bindgen]
+impl RawRayColliderIntersection {
+ pub fn colliderHandle(&self) -> FlatHandle {
+ utils::flat_handle(self.handle.0)
+ }
+
+ pub fn normal(&self) -> RawVector {
+ self.inter.normal.into()
+ }
+
+ pub fn time_of_impact(&self) -> f32 {
+ self.inter.time_of_impact
+ }
+
+ pub fn featureType(&self) -> RawFeatureType {
+ self.inter.feature.into_type()
+ }
+
+ pub fn featureId(&self) -> Option {
+ self.inter.feature.into_value()
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawRayColliderHit {
+ pub(crate) handle: ColliderHandle,
+ pub(crate) timeOfImpact: f32,
+}
+
+#[wasm_bindgen]
+impl RawRayColliderHit {
+ pub fn colliderHandle(&self) -> FlatHandle {
+ utils::flat_handle(self.handle.0)
+ }
+
+ pub fn timeOfImpact(&self) -> f32 {
+ self.timeOfImpact
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/shape.rs b/thirdparty/rapier.js/src/geometry/shape.rs
new file mode 100644
index 00000000..5f01d9f5
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/shape.rs
@@ -0,0 +1,527 @@
+use crate::geometry::{RawPointProjection, RawRayIntersection, RawShapeCastHit, RawShapeContact};
+use crate::math::{RawRotation, RawVector};
+#[cfg(feature = "dim3")]
+use na::DMatrix;
+#[cfg(feature = "dim2")]
+use na::DVector;
+use na::Unit;
+use rapier::geometry::{Shape, SharedShape, TriMeshFlags};
+use rapier::math::{Isometry, Point, Real, Vector, DIM};
+use rapier::parry::query;
+use rapier::parry::query::{Ray, ShapeCastOptions};
+use wasm_bindgen::prelude::*;
+
+pub trait SharedShapeUtility {
+ fn castShape(
+ &self,
+ shapePos1: &Isometry,
+ shapeVel1: &Vector,
+ shape2: &dyn Shape,
+ shapePos2: &Isometry,
+ shapeVel2: &Vector,
+ target_distance: f32,
+ maxToi: f32,
+ stop_at_penetration: bool,
+ ) -> Option;
+
+ fn intersectsShape(
+ &self,
+ shapePos1: &Isometry,
+ shape2: &dyn Shape,
+ shapePos2: &Isometry,
+ ) -> bool;
+
+ fn contactShape(
+ &self,
+ shapePos1: &Isometry,
+ shape2: &dyn Shape,
+ shapePos2: &Isometry,
+ prediction: f32,
+ ) -> Option;
+
+ fn containsPoint(&self, shapePos: &Isometry, point: &Point) -> bool;
+
+ fn projectPoint(
+ &self,
+ shapePos: &Isometry,
+ point: &Point,
+ solid: bool,
+ ) -> RawPointProjection;
+
+ fn intersectsRay(
+ &self,
+ shapePos: &Isometry,
+ rayOrig: Point,
+ rayDir: Vector,
+ maxToi: f32,
+ ) -> bool;
+
+ fn castRay(
+ &self,
+ shapePos: &Isometry,
+ rayOrig: Point,
+ rayDir: Vector,
+ maxToi: f32,
+ solid: bool,
+ ) -> f32;
+
+ fn castRayAndGetNormal(
+ &self,
+ shapePos: &Isometry,
+ rayOrig: Point,
+ rayDir: Vector,
+ maxToi: f32,
+ solid: bool,
+ ) -> Option;
+}
+
+// for RawShape & Collider
+impl SharedShapeUtility for SharedShape {
+ fn castShape(
+ &self,
+ shapePos1: &Isometry,
+ shapeVel1: &Vector,
+ shape2: &dyn Shape,
+ shapePos2: &Isometry,
+ shapeVel2: &Vector,
+ target_distance: f32,
+ maxToi: f32,
+ stop_at_penetration: bool,
+ ) -> Option {
+ query::cast_shapes(
+ shapePos1,
+ shapeVel1,
+ &*self.0,
+ shapePos2,
+ &shapeVel2,
+ shape2,
+ ShapeCastOptions {
+ max_time_of_impact: maxToi,
+ target_distance,
+ stop_at_penetration,
+ compute_impact_geometry_on_penetration: true,
+ },
+ )
+ .ok()
+ .flatten()
+ .map(|hit| RawShapeCastHit { hit })
+ }
+
+ fn intersectsShape(
+ &self,
+ shapePos1: &Isometry,
+ shape2: &dyn Shape,
+ shapePos2: &Isometry,
+ ) -> bool {
+ query::intersection_test(shapePos1, &*self.0, shapePos2, shape2).unwrap_or(false)
+ }
+
+ fn contactShape(
+ &self,
+ shapePos1: &Isometry,
+ shape2: &dyn Shape,
+ shapePos2: &Isometry,
+ prediction: f32,
+ ) -> Option {
+ query::contact(shapePos1, &*self.0, shapePos2, shape2, prediction)
+ .ok()
+ .flatten()
+ .map(|contact| RawShapeContact { contact })
+ }
+
+ fn containsPoint(&self, shapePos: &Isometry, point: &Point) -> bool {
+ self.as_ref().contains_point(shapePos, point)
+ }
+
+ fn projectPoint(
+ &self,
+ shapePos: &Isometry,
+ point: &Point,
+ solid: bool,
+ ) -> RawPointProjection {
+ RawPointProjection(self.as_ref().project_point(shapePos, point, solid))
+ }
+
+ fn intersectsRay(
+ &self,
+ shapePos: &Isometry,
+ rayOrig: Point,
+ rayDir: Vector,
+ maxToi: f32,
+ ) -> bool {
+ self.as_ref()
+ .intersects_ray(shapePos, &Ray::new(rayOrig, rayDir), maxToi)
+ }
+
+ fn castRay(
+ &self,
+ shapePos: &Isometry,
+ rayOrig: Point,
+ rayDir: Vector,
+ maxToi: f32,
+ solid: bool,
+ ) -> f32 {
+ self.as_ref()
+ .cast_ray(shapePos, &Ray::new(rayOrig, rayDir), maxToi, solid)
+ .unwrap_or(-1.0) // Negative value = no hit.
+ }
+
+ fn castRayAndGetNormal(
+ &self,
+ shapePos: &Isometry,
+ rayOrig: Point,
+ rayDir: Vector,
+ maxToi: f32,
+ solid: bool,
+ ) -> Option {
+ self.as_ref()
+ .cast_ray_and_get_normal(shapePos, &Ray::new(rayOrig, rayDir), maxToi, solid)
+ .map(|inter| RawRayIntersection(inter))
+ }
+}
+
+#[wasm_bindgen]
+#[cfg(feature = "dim2")]
+pub enum RawShapeType {
+ Ball = 0,
+ Cuboid = 1,
+ Capsule = 2,
+ Segment = 3,
+ Polyline = 4,
+ Triangle = 5,
+ TriMesh = 6,
+ HeightField = 7,
+ Compound = 8,
+ ConvexPolygon = 9,
+ RoundCuboid = 10,
+ RoundTriangle = 11,
+ RoundConvexPolygon = 12,
+ HalfSpace = 13,
+ Voxels = 14,
+}
+
+#[wasm_bindgen]
+#[cfg(feature = "dim3")]
+pub enum RawShapeType {
+ Ball = 0,
+ Cuboid = 1,
+ Capsule = 2,
+ Segment = 3,
+ Polyline = 4,
+ Triangle = 5,
+ TriMesh = 6,
+ HeightField = 7,
+ Compound = 8,
+ ConvexPolyhedron = 9,
+ Cylinder = 10,
+ Cone = 11,
+ RoundCuboid = 12,
+ RoundTriangle = 13,
+ RoundCylinder = 14,
+ RoundCone = 15,
+ RoundConvexPolyhedron = 16,
+ HalfSpace = 17,
+ Voxels = 18,
+}
+
+#[wasm_bindgen]
+pub struct RawShape(pub(crate) SharedShape);
+
+#[wasm_bindgen]
+impl RawShape {
+ #[cfg(feature = "dim2")]
+ pub fn cuboid(hx: f32, hy: f32) -> Self {
+ Self(SharedShape::cuboid(hx, hy))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn cuboid(hx: f32, hy: f32, hz: f32) -> Self {
+ Self(SharedShape::cuboid(hx, hy, hz))
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn roundCuboid(hx: f32, hy: f32, borderRadius: f32) -> Self {
+ Self(SharedShape::round_cuboid(hx, hy, borderRadius))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn roundCuboid(hx: f32, hy: f32, hz: f32, borderRadius: f32) -> Self {
+ Self(SharedShape::round_cuboid(hx, hy, hz, borderRadius))
+ }
+
+ pub fn ball(radius: f32) -> Self {
+ Self(SharedShape::ball(radius))
+ }
+
+ pub fn halfspace(normal: &RawVector) -> Self {
+ Self(SharedShape::halfspace(Unit::new_normalize(normal.0)))
+ }
+
+ pub fn capsule(halfHeight: f32, radius: f32) -> Self {
+ let p2 = Point::from(Vector::y() * halfHeight);
+ let p1 = -p2;
+ Self(SharedShape::capsule(p1, p2, radius))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn cylinder(halfHeight: f32, radius: f32) -> Self {
+ Self(SharedShape::cylinder(halfHeight, radius))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn roundCylinder(halfHeight: f32, radius: f32, borderRadius: f32) -> Self {
+ Self(SharedShape::round_cylinder(
+ halfHeight,
+ radius,
+ borderRadius,
+ ))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn cone(halfHeight: f32, radius: f32) -> Self {
+ Self(SharedShape::cone(halfHeight, radius))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn roundCone(halfHeight: f32, radius: f32, borderRadius: f32) -> Self {
+ Self(SharedShape::round_cone(halfHeight, radius, borderRadius))
+ }
+
+ pub fn voxels(voxel_size: &RawVector, grid_coords: Vec) -> Self {
+ let grid_coords: Vec<_> = grid_coords
+ .chunks_exact(DIM)
+ .map(Point::from_slice)
+ .collect();
+ Self(SharedShape::voxels(voxel_size.0, &grid_coords))
+ }
+
+ pub fn voxelsFromPoints(voxel_size: &RawVector, points: Vec) -> Self {
+ let points: Vec<_> = points.chunks_exact(DIM).map(Point::from_slice).collect();
+ Self(SharedShape::voxels_from_points(voxel_size.0, &points))
+ }
+
+ pub fn polyline(vertices: Vec, indices: Vec) -> Self {
+ let vertices = vertices.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ let indices: Vec<_> = indices.chunks(2).map(|v| [v[0], v[1]]).collect();
+ if indices.is_empty() {
+ Self(SharedShape::polyline(vertices, None))
+ } else {
+ Self(SharedShape::polyline(vertices, Some(indices)))
+ }
+ }
+
+ pub fn trimesh(vertices: Vec, indices: Vec, flags: u32) -> Option {
+ let flags = TriMeshFlags::from_bits(flags as u16).unwrap_or_default();
+ let vertices = vertices.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ let indices = indices.chunks(3).map(|v| [v[0], v[1], v[2]]).collect();
+ SharedShape::trimesh_with_flags(vertices, indices, flags)
+ .ok()
+ .map(Self)
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn heightfield(heights: Vec, scale: &RawVector) -> Self {
+ let heights = DVector::from_vec(heights);
+ Self(SharedShape::heightfield(heights, scale.0))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn heightfield(
+ nrows: u32,
+ ncols: u32,
+ heights: Vec,
+ scale: &RawVector,
+ flags: u32,
+ ) -> Self {
+ let flags =
+ rapier::parry::shape::HeightFieldFlags::from_bits(flags as u8).unwrap_or_default();
+ let heights = DMatrix::from_vec(nrows as usize + 1, ncols as usize + 1, heights);
+ Self(SharedShape::heightfield_with_flags(heights, scale.0, flags))
+ }
+
+ pub fn segment(p1: &RawVector, p2: &RawVector) -> Self {
+ Self(SharedShape::segment(p1.0.into(), p2.0.into()))
+ }
+
+ pub fn triangle(p1: &RawVector, p2: &RawVector, p3: &RawVector) -> Self {
+ Self(SharedShape::triangle(p1.0.into(), p2.0.into(), p3.0.into()))
+ }
+
+ pub fn roundTriangle(
+ p1: &RawVector,
+ p2: &RawVector,
+ p3: &RawVector,
+ borderRadius: f32,
+ ) -> Self {
+ Self(SharedShape::round_triangle(
+ p1.0.into(),
+ p2.0.into(),
+ p3.0.into(),
+ borderRadius,
+ ))
+ }
+
+ pub fn convexHull(points: Vec) -> Option {
+ let points: Vec<_> = points.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ SharedShape::convex_hull(&points).map(|s| Self(s))
+ }
+
+ pub fn roundConvexHull(points: Vec, borderRadius: f32) -> Option {
+ let points: Vec<_> = points.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ SharedShape::round_convex_hull(&points, borderRadius).map(|s| Self(s))
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn convexPolyline(vertices: Vec) -> Option {
+ let vertices = vertices.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ SharedShape::convex_polyline(vertices).map(|s| Self(s))
+ }
+
+ #[cfg(feature = "dim2")]
+ pub fn roundConvexPolyline(vertices: Vec, borderRadius: f32) -> Option {
+ let vertices = vertices.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ SharedShape::round_convex_polyline(vertices, borderRadius).map(|s| Self(s))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn convexMesh(vertices: Vec, indices: Vec) -> Option {
+ let vertices = vertices.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ let indices: Vec<_> = indices.chunks(3).map(|v| [v[0], v[1], v[2]]).collect();
+ SharedShape::convex_mesh(vertices, &indices).map(|s| Self(s))
+ }
+
+ #[cfg(feature = "dim3")]
+ pub fn roundConvexMesh(
+ vertices: Vec,
+ indices: Vec,
+ borderRadius: f32,
+ ) -> Option {
+ let vertices = vertices.chunks(DIM).map(|v| Point::from_slice(v)).collect();
+ let indices: Vec<_> = indices.chunks(3).map(|v| [v[0], v[1], v[2]]).collect();
+ SharedShape::round_convex_mesh(vertices, &indices, borderRadius).map(|s| Self(s))
+ }
+
+ pub fn castShape(
+ &self,
+ shapePos1: &RawVector,
+ shapeRot1: &RawRotation,
+ shapeVel1: &RawVector,
+ shape2: &RawShape,
+ shapePos2: &RawVector,
+ shapeRot2: &RawRotation,
+ shapeVel2: &RawVector,
+ target_distance: f32,
+ maxToi: f32,
+ stop_at_penetration: bool,
+ ) -> Option {
+ let pos1 = Isometry::from_parts(shapePos1.0.into(), shapeRot1.0);
+ let pos2 = Isometry::from_parts(shapePos2.0.into(), shapeRot2.0);
+
+ self.0.castShape(
+ &pos1,
+ &shapeVel1.0,
+ &*shape2.0,
+ &pos2,
+ &shapeVel2.0,
+ target_distance,
+ maxToi,
+ stop_at_penetration,
+ )
+ }
+
+ pub fn intersectsShape(
+ &self,
+ shapePos1: &RawVector,
+ shapeRot1: &RawRotation,
+ shape2: &RawShape,
+ shapePos2: &RawVector,
+ shapeRot2: &RawRotation,
+ ) -> bool {
+ let pos1 = Isometry::from_parts(shapePos1.0.into(), shapeRot1.0);
+ let pos2 = Isometry::from_parts(shapePos2.0.into(), shapeRot2.0);
+
+ self.0.intersectsShape(&pos1, &*shape2.0, &pos2)
+ }
+
+ pub fn contactShape(
+ &self,
+ shapePos1: &RawVector,
+ shapeRot1: &RawRotation,
+ shape2: &RawShape,
+ shapePos2: &RawVector,
+ shapeRot2: &RawRotation,
+ prediction: f32,
+ ) -> Option {
+ let pos1 = Isometry::from_parts(shapePos1.0.into(), shapeRot1.0);
+ let pos2 = Isometry::from_parts(shapePos2.0.into(), shapeRot2.0);
+
+ self.0.contactShape(&pos1, &*shape2.0, &pos2, prediction)
+ }
+
+ pub fn containsPoint(
+ &self,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ point: &RawVector,
+ ) -> bool {
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+
+ self.0.containsPoint(&pos, &point.0.into())
+ }
+
+ pub fn projectPoint(
+ &self,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ point: &RawVector,
+ solid: bool,
+ ) -> RawPointProjection {
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+
+ self.0.projectPoint(&pos, &point.0.into(), solid)
+ }
+
+ pub fn intersectsRay(
+ &self,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ ) -> bool {
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+
+ self.0
+ .intersectsRay(&pos, rayOrig.0.into(), rayDir.0.into(), maxToi)
+ }
+
+ pub fn castRay(
+ &self,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ solid: bool,
+ ) -> f32 {
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+
+ self.0
+ .castRay(&pos, rayOrig.0.into(), rayDir.0.into(), maxToi, solid)
+ }
+
+ pub fn castRayAndGetNormal(
+ &self,
+ shapePos: &RawVector,
+ shapeRot: &RawRotation,
+ rayOrig: &RawVector,
+ rayDir: &RawVector,
+ maxToi: f32,
+ solid: bool,
+ ) -> Option {
+ let pos = Isometry::from_parts(shapePos.0.into(), shapeRot.0);
+
+ self.0
+ .castRayAndGetNormal(&pos, rayOrig.0.into(), rayDir.0.into(), maxToi, solid)
+ }
+}
diff --git a/thirdparty/rapier.js/src/geometry/toi.rs b/thirdparty/rapier.js/src/geometry/toi.rs
new file mode 100644
index 00000000..1e00675d
--- /dev/null
+++ b/thirdparty/rapier.js/src/geometry/toi.rs
@@ -0,0 +1,65 @@
+use crate::math::RawVector;
+use crate::utils::{self, FlatHandle};
+use rapier::geometry::{ColliderHandle, ShapeCastHit};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawShapeCastHit {
+ pub(crate) hit: ShapeCastHit,
+}
+
+#[wasm_bindgen]
+impl RawShapeCastHit {
+ pub fn time_of_impact(&self) -> f32 {
+ self.hit.time_of_impact
+ }
+
+ pub fn witness1(&self) -> RawVector {
+ self.hit.witness1.coords.into()
+ }
+
+ pub fn witness2(&self) -> RawVector {
+ self.hit.witness2.coords.into()
+ }
+
+ pub fn normal1(&self) -> RawVector {
+ self.hit.normal1.into_inner().into()
+ }
+
+ pub fn normal2(&self) -> RawVector {
+ self.hit.normal2.into_inner().into()
+ }
+}
+
+#[wasm_bindgen]
+pub struct RawColliderShapeCastHit {
+ pub(crate) handle: ColliderHandle,
+ pub(crate) hit: ShapeCastHit,
+}
+
+#[wasm_bindgen]
+impl RawColliderShapeCastHit {
+ pub fn colliderHandle(&self) -> FlatHandle {
+ utils::flat_handle(self.handle.0)
+ }
+
+ pub fn time_of_impact(&self) -> f32 {
+ self.hit.time_of_impact
+ }
+
+ pub fn witness1(&self) -> RawVector {
+ self.hit.witness1.coords.into()
+ }
+
+ pub fn witness2(&self) -> RawVector {
+ self.hit.witness2.coords.into()
+ }
+
+ pub fn normal1(&self) -> RawVector {
+ self.hit.normal1.into_inner().into()
+ }
+
+ pub fn normal2(&self) -> RawVector {
+ self.hit.normal2.into_inner().into()
+ }
+}
diff --git a/thirdparty/rapier.js/src/lib.rs b/thirdparty/rapier.js/src/lib.rs
new file mode 100644
index 00000000..132809dc
--- /dev/null
+++ b/thirdparty/rapier.js/src/lib.rs
@@ -0,0 +1,32 @@
+//! # Rapier
+//! Fast and deterministic WASM physics engine.
+
+#![allow(non_snake_case)] // JS uses camelCase, so we will follow its convention for the generated bindings.
+ // #![deny(missing_docs)]
+
+extern crate nalgebra as na;
+#[cfg(feature = "dim2")]
+extern crate rapier2d as rapier;
+#[cfg(feature = "dim3")]
+extern crate rapier3d as rapier;
+#[macro_use]
+extern crate serde;
+
+#[wasm_bindgen::prelude::wasm_bindgen]
+pub fn version() -> String {
+ env!("CARGO_PKG_VERSION").to_string()
+}
+
+#[wasm_bindgen::prelude::wasm_bindgen]
+pub fn reserve_memory(extra_bytes_count: u32) {
+ let mut unused: Vec = vec![];
+ unused.reserve(extra_bytes_count as usize);
+ std::hint::black_box(&unused);
+}
+
+pub mod control;
+pub mod dynamics;
+pub mod geometry;
+pub mod math;
+pub mod pipeline;
+pub mod utils;
diff --git a/thirdparty/rapier.js/src/math.rs b/thirdparty/rapier.js/src/math.rs
new file mode 100644
index 00000000..5c50c142
--- /dev/null
+++ b/thirdparty/rapier.js/src/math.rs
@@ -0,0 +1,266 @@
+//! Linear algebra primitives.
+
+#[cfg(feature = "dim3")]
+use js_sys::Float32Array;
+#[cfg(feature = "dim3")]
+use na::{Quaternion, Unit};
+#[cfg(feature = "dim3")]
+use rapier::math::Real;
+use rapier::math::{Point, Rotation, Vector};
+#[cfg(feature = "dim3")]
+use rapier::parry::utils::SdpMatrix3;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+/// A rotation quaternion.
+pub struct RawRotation(pub(crate) Rotation);
+
+impl From> for RawRotation {
+ fn from(v: Rotation) -> Self {
+ RawRotation(v)
+ }
+}
+
+#[wasm_bindgen]
+#[cfg(feature = "dim2")]
+/// A unit complex number describing the orientation of a Rapier entity.
+impl RawRotation {
+ /// The identity rotation.
+ pub fn identity() -> Self {
+ Self(Rotation::identity())
+ }
+
+ /// The rotation with thegiven angle.
+ pub fn fromAngle(angle: f32) -> Self {
+ Self(Rotation::new(angle))
+ }
+
+ /// The imaginary part of this complex number.
+ #[wasm_bindgen(getter)]
+ pub fn im(&self) -> f32 {
+ self.0.im
+ }
+
+ /// The real part of this complex number.
+ #[wasm_bindgen(getter)]
+ pub fn re(&self) -> f32 {
+ self.0.re
+ }
+
+ /// The rotation angle in radians.
+ #[wasm_bindgen(getter)]
+ pub fn angle(&self) -> f32 {
+ self.0.angle()
+ }
+}
+
+#[wasm_bindgen]
+#[cfg(feature = "dim3")]
+/// A unit quaternion describing the orientation of a Rapier entity.
+impl RawRotation {
+ #[wasm_bindgen(constructor)]
+ pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
+ RawRotation(Unit::new_unchecked(Quaternion::new(w, x, y, z)))
+ }
+
+ /// The identity quaternion.
+ pub fn identity() -> Self {
+ Self(Rotation::identity())
+ }
+
+ /// The `x` component of this quaternion.
+ #[wasm_bindgen(getter)]
+ pub fn x(&self) -> f32 {
+ self.0.i
+ }
+
+ /// The `y` component of this quaternion.
+ #[wasm_bindgen(getter)]
+ pub fn y(&self) -> f32 {
+ self.0.j
+ }
+
+ /// The `z` component of this quaternion.
+ #[wasm_bindgen(getter)]
+ pub fn z(&self) -> f32 {
+ self.0.k
+ }
+
+ /// The `w` component of this quaternion.
+ #[wasm_bindgen(getter)]
+ pub fn w(&self) -> f32 {
+ self.0.w
+ }
+}
+
+#[wasm_bindgen]
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+/// A vector.
+pub struct RawVector(pub(crate) Vector);
+
+impl From> for RawVector {
+ fn from(v: Vector) -> Self {
+ RawVector(v)
+ }
+}
+
+impl From> for RawVector {
+ fn from(pt: Point) -> Self {
+ pt.coords.into()
+ }
+}
+
+#[wasm_bindgen]
+impl RawVector {
+ /// Creates a new vector filled with zeros.
+ pub fn zero() -> Self {
+ Self(Vector::zeros())
+ }
+
+ /// Creates a new 2D vector from its two components.
+ ///
+ /// # Parameters
+ /// - `x`: the `x` component of this 2D vector.
+ /// - `y`: the `y` component of this 2D vector.
+ #[cfg(feature = "dim2")]
+ #[wasm_bindgen(constructor)]
+ pub fn new(x: f32, y: f32) -> Self {
+ Self(Vector::new(x, y))
+ }
+
+ /// Creates a new 3D vector from its two components.
+ ///
+ /// # Parameters
+ /// - `x`: the `x` component of this 3D vector.
+ /// - `y`: the `y` component of this 3D vector.
+ /// - `z`: the `z` component of this 3D vector.
+ #[cfg(feature = "dim3")]
+ #[wasm_bindgen(constructor)]
+ pub fn new(x: f32, y: f32, z: f32) -> Self {
+ Self(Vector::new(x, y, z))
+ }
+
+ /// The `x` component of this vector.
+ #[wasm_bindgen(getter)]
+ pub fn x(&self) -> f32 {
+ self.0.x
+ }
+
+ /// Sets the `x` component of this vector.
+ #[wasm_bindgen(setter)]
+ pub fn set_x(&mut self, x: f32) {
+ self.0.x = x
+ }
+
+ /// The `y` component of this vector.
+ #[wasm_bindgen(getter)]
+ pub fn y(&self) -> f32 {
+ self.0.y
+ }
+
+ /// Sets the `y` component of this vector.
+ #[wasm_bindgen(setter)]
+ pub fn set_y(&mut self, y: f32) {
+ self.0.y = y
+ }
+
+ /// The `z` component of this vector.
+ #[cfg(feature = "dim3")]
+ #[wasm_bindgen(getter)]
+ pub fn z(&self) -> f32 {
+ self.0.z
+ }
+
+ /// Sets the `z` component of this vector.
+ #[cfg(feature = "dim3")]
+ #[wasm_bindgen(setter)]
+ pub fn set_z(&mut self, z: f32) {
+ self.0.z = z
+ }
+
+ /// Create a new 2D vector from this vector with its components rearranged as `{x, y}`.
+ #[cfg(feature = "dim2")]
+ pub fn xy(&self) -> Self {
+ Self(self.0.xy())
+ }
+
+ /// Create a new 2D vector from this vector with its components rearranged as `{y, x}`.
+ #[cfg(feature = "dim2")]
+ pub fn yx(&self) -> Self {
+ Self(self.0.yx())
+ }
+
+ /// Create a new 2D vector from this vector with its components rearranged as `{z, y}`.
+ #[cfg(feature = "dim2")]
+ #[cfg(feature = "dim3")]
+ pub fn zy(&self) -> Self {
+ Self(self.0.zy())
+ }
+
+ /// Create a new 3D vector from this vector with its components rearranged as `{x, y, z}`.
+ ///
+ /// This will effectively return a copy of `this`. This method exist for completeness with the
+ /// other swizzling functions.
+ #[cfg(feature = "dim3")]
+ pub fn xyz(&self) -> Self {
+ Self(self.0.xyz())
+ }
+
+ /// Create a new 3D vector from this vector with its components rearranged as `{y, x, z}`.
+ #[cfg(feature = "dim3")]
+ pub fn yxz(&self) -> Self {
+ Self(self.0.yxz())
+ }
+
+ /// Create a new 3D vector from this vector with its components rearranged as `{z, x, y}`.
+ #[cfg(feature = "dim3")]
+ pub fn zxy(&self) -> Self {
+ Self(self.0.zxy())
+ }
+
+ /// Create a new 3D vector from this vector with its components rearranged as `{x, z, y}`.
+ #[cfg(feature = "dim3")]
+ pub fn xzy(&self) -> Self {
+ Self(self.0.xzy())
+ }
+
+ /// Create a new 3D vector from this vector with its components rearranged as `{y, z, x}`.
+ #[cfg(feature = "dim3")]
+ pub fn yzx(&self) -> Self {
+ Self(self.0.yzx())
+ }
+
+ /// Create a new 3D vector from this vector with its components rearranged as `{z, y, x}`.
+ #[cfg(feature = "dim3")]
+ pub fn zyx(&self) -> Self {
+ Self(self.0.zyx())
+ }
+}
+
+#[wasm_bindgen]
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+#[cfg(feature = "dim3")]
+pub struct RawSdpMatrix3(pub(crate) SdpMatrix3);
+
+#[cfg(feature = "dim3")]
+impl From> for RawSdpMatrix3 {
+ fn from(v: SdpMatrix3) -> Self {
+ RawSdpMatrix3(v)
+ }
+}
+
+#[wasm_bindgen]
+#[cfg(feature = "dim3")]
+impl RawSdpMatrix3 {
+ /// Row major list of the upper-triangular part of the symmetric matrix.
+ pub fn elements(&self) -> Float32Array {
+ let m = self.0;
+ let output = Float32Array::new_with_length(6);
+ output.copy_from(&[m.m11, m.m12, m.m13, m.m22, m.m23, m.m33]);
+ output
+ }
+}
diff --git a/thirdparty/rapier.js/src/pipeline/debug_render_pipeline.rs b/thirdparty/rapier.js/src/pipeline/debug_render_pipeline.rs
new file mode 100644
index 00000000..1676e59b
--- /dev/null
+++ b/thirdparty/rapier.js/src/pipeline/debug_render_pipeline.rs
@@ -0,0 +1,152 @@
+use crate::dynamics::{RawImpulseJointSet, RawMultibodyJointSet, RawRigidBodySet};
+use crate::geometry::{RawColliderSet, RawNarrowPhase};
+use js_sys::Float32Array;
+use palette::convert::IntoColorUnclamped;
+use palette::rgb::Rgba;
+use palette::Hsla;
+use rapier::dynamics::{RigidBody, RigidBodySet};
+use rapier::geometry::ColliderSet;
+use rapier::math::{Point, Real};
+use rapier::pipeline::{DebugRenderBackend, DebugRenderObject, DebugRenderPipeline};
+use rapier::prelude::{QueryFilter, QueryFilterFlags};
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawDebugRenderPipeline {
+ pub(crate) raw: DebugRenderPipeline,
+ vertices: Vec,
+ colors: Vec,
+}
+
+#[wasm_bindgen]
+impl RawDebugRenderPipeline {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ RawDebugRenderPipeline {
+ raw: DebugRenderPipeline::default(),
+ vertices: vec![],
+ colors: vec![],
+ }
+ }
+
+ pub fn vertices(&self) -> Float32Array {
+ let output = Float32Array::new_with_length(self.vertices.len() as u32);
+ output.copy_from(&self.vertices);
+ output
+ }
+
+ pub fn colors(&self) -> Float32Array {
+ let output = Float32Array::new_with_length(self.colors.len() as u32);
+ output.copy_from(&self.colors);
+ output
+ }
+
+ pub fn render(
+ &mut self,
+ bodies: &RawRigidBodySet,
+ colliders: &RawColliderSet,
+ impulse_joints: &RawImpulseJointSet,
+ multibody_joints: &RawMultibodyJointSet,
+ narrow_phase: &RawNarrowPhase,
+ filter_flags: u32,
+ filter_predicate: &js_sys::Function,
+ ) {
+ self.vertices.clear();
+ self.colors.clear();
+
+ crate::utils::with_filter(filter_predicate, |predicate| {
+ let mut backend = CopyToBuffersBackend {
+ filter: QueryFilter {
+ flags: QueryFilterFlags::from_bits(filter_flags)
+ .unwrap_or(QueryFilterFlags::empty()),
+ groups: None,
+ exclude_collider: None,
+ exclude_rigid_body: None,
+ predicate,
+ },
+ bodies: &bodies.0,
+ colliders: &colliders.0,
+ vertices: &mut self.vertices,
+ colors: &mut self.colors,
+ };
+
+ self.raw.render(
+ &mut backend,
+ &bodies.0,
+ &colliders.0,
+ &impulse_joints.0,
+ &multibody_joints.0,
+ &narrow_phase.0,
+ )
+ })
+ }
+}
+
+struct CopyToBuffersBackend<'a> {
+ filter: QueryFilter<'a>,
+ bodies: &'a RigidBodySet,
+ colliders: &'a ColliderSet,
+ vertices: &'a mut Vec,
+ colors: &'a mut Vec,
+}
+
+impl<'a> DebugRenderBackend for CopyToBuffersBackend<'a> {
+ fn filter_object(&self, object: DebugRenderObject) -> bool {
+ let test_rigid_body = |rb: &RigidBody| {
+ rb.colliders().iter().all(|handle| {
+ let Some(co) = self.colliders.get(*handle) else {
+ return false;
+ };
+ self.filter.test(self.bodies, *handle, co)
+ })
+ };
+
+ match object {
+ DebugRenderObject::Collider(handle, co)
+ | DebugRenderObject::ColliderAabb(handle, co, _) => {
+ self.filter.test(self.bodies, handle, co)
+ }
+ DebugRenderObject::ContactPair(pair, co1, co2) => {
+ self.filter.test(self.bodies, pair.collider1, co1)
+ && self.filter.test(self.bodies, pair.collider2, co2)
+ }
+ DebugRenderObject::ImpulseJoint(_, joint) => {
+ let Some(rb1) = self.bodies.get(joint.body1) else {
+ return false;
+ };
+ let Some(rb2) = self.bodies.get(joint.body2) else {
+ return false;
+ };
+ test_rigid_body(rb1) && test_rigid_body(rb2)
+ }
+ DebugRenderObject::MultibodyJoint(_, _, link) => {
+ let Some(rb) = self.bodies.get(link.rigid_body_handle()) else {
+ return false;
+ };
+ test_rigid_body(rb)
+ }
+ DebugRenderObject::RigidBody(_, rb) => test_rigid_body(rb),
+ }
+ }
+
+ /// Draws a colored line.
+ ///
+ /// Note that this method can be called multiple time for the same `object`.
+ fn draw_line(
+ &mut self,
+ _object: DebugRenderObject,
+ a: Point,
+ b: Point,
+ color: [f32; 4],
+ ) {
+ self.vertices.extend_from_slice(a.coords.as_slice());
+ self.vertices.extend_from_slice(b.coords.as_slice());
+
+ // Convert to RGB which will be easier to handle in JS.
+ let hsl = Hsla::new(color[0], color[1], color[2], color[3]);
+ let rgb: Rgba = hsl.into_color_unclamped();
+ self.colors.extend_from_slice(&[
+ rgb.red, rgb.green, rgb.blue, rgb.alpha, rgb.red, rgb.green, rgb.blue, rgb.alpha,
+ ]);
+ }
+}
diff --git a/thirdparty/rapier.js/src/pipeline/event_queue.rs b/thirdparty/rapier.js/src/pipeline/event_queue.rs
new file mode 100644
index 00000000..fb6034d9
--- /dev/null
+++ b/thirdparty/rapier.js/src/pipeline/event_queue.rs
@@ -0,0 +1,140 @@
+use crate::math::RawVector;
+use crate::utils;
+use crate::utils::FlatHandle;
+use rapier::geometry::{CollisionEvent, ContactForceEvent};
+use rapier::pipeline::ChannelEventCollector;
+use std::sync::mpsc::Receiver;
+use wasm_bindgen::prelude::*;
+
+/// A structure responsible for collecting events generated
+/// by the physics engine.
+#[wasm_bindgen]
+pub struct RawEventQueue {
+ pub(crate) collector: ChannelEventCollector,
+ collision_events: Receiver,
+ contact_force_events: Receiver,
+ pub(crate) auto_drain: bool,
+}
+
+#[wasm_bindgen]
+pub struct RawContactForceEvent(ContactForceEvent);
+
+#[wasm_bindgen]
+impl RawContactForceEvent {
+ /// The first collider involved in the contact.
+ pub fn collider1(&self) -> FlatHandle {
+ crate::utils::flat_handle(self.0.collider1.0)
+ }
+
+ /// The second collider involved in the contact.
+ pub fn collider2(&self) -> FlatHandle {
+ crate::utils::flat_handle(self.0.collider2.0)
+ }
+
+ /// The sum of all the forces between the two colliders.
+ pub fn total_force(&self) -> RawVector {
+ RawVector(self.0.total_force)
+ }
+
+ /// The sum of the magnitudes of each force between the two colliders.
+ ///
+ /// Note that this is **not** the same as the magnitude of `self.total_force`.
+ /// Here we are summing the magnitude of all the forces, instead of taking
+ /// the magnitude of their sum.
+ pub fn total_force_magnitude(&self) -> f32 {
+ self.0.total_force_magnitude
+ }
+
+ /// The world-space (unit) direction of the force with strongest magnitude.
+ pub fn max_force_direction(&self) -> RawVector {
+ RawVector(self.0.max_force_direction)
+ }
+
+ /// The magnitude of the largest force at a contact point of this contact pair.
+ pub fn max_force_magnitude(&self) -> f32 {
+ self.0.max_force_magnitude
+ }
+}
+
+// #[wasm_bindgen]
+// /// The proximity state of a sensor collider and another collider.
+// pub enum RawIntersection {
+// /// The sensor is intersecting the other collider.
+// Intersecting = 0,
+// /// The sensor is within tolerance margin of the other collider.
+// WithinMargin = 1,
+// /// The sensor is disjoint from the other collider.
+// Disjoint = 2,
+// }
+
+#[wasm_bindgen]
+impl RawEventQueue {
+ /// Creates a new event collector.
+ ///
+ /// # Parameters
+ /// - `autoDrain`: setting this to `true` is strongly recommended. If true, the collector will
+ /// be automatically drained before each `world.step(collector)`. If false, the collector will
+ /// keep all events in memory unless it is manually drained/cleared; this may lead to unbounded use of
+ /// RAM if no drain is performed.
+ #[wasm_bindgen(constructor)]
+ pub fn new(autoDrain: bool) -> Self {
+ let collision_channel = std::sync::mpsc::channel();
+ let contact_force_channel = std::sync::mpsc::channel();
+ let collector = ChannelEventCollector::new(collision_channel.0, contact_force_channel.0);
+
+ Self {
+ collector,
+ collision_events: collision_channel.1,
+ contact_force_events: contact_force_channel.1,
+ auto_drain: autoDrain,
+ }
+ }
+
+ /// Applies the given javascript closure on each collision event of this collector, then clear
+ /// the internal collision event buffer.
+ ///
+ /// # Parameters
+ /// - `f(handle1, handle2, started)`: JavaScript closure applied to each collision event. The
+ /// closure should take three arguments: two integers representing the handles of the colliders
+ /// involved in the collision, and a boolean indicating if the collision started (true) or stopped
+ /// (false).
+ pub fn drainCollisionEvents(&mut self, f: &js_sys::Function) {
+ let this = JsValue::null();
+ while let Ok(event) = self.collision_events.try_recv() {
+ match event {
+ CollisionEvent::Started(co1, co2, _) => {
+ let h1 = utils::flat_handle(co1.0);
+ let h2 = utils::flat_handle(co2.0);
+ let _ = f.call3(
+ &this,
+ &JsValue::from(h1),
+ &JsValue::from(h2),
+ &JsValue::from_bool(true),
+ );
+ }
+ CollisionEvent::Stopped(co1, co2, _) => {
+ let h1 = utils::flat_handle(co1.0);
+ let h2 = utils::flat_handle(co2.0);
+ let _ = f.call3(
+ &this,
+ &JsValue::from(h1),
+ &JsValue::from(h2),
+ &JsValue::from_bool(false),
+ );
+ }
+ }
+ }
+ }
+
+ pub fn drainContactForceEvents(&mut self, f: &js_sys::Function) {
+ let this = JsValue::null();
+ while let Ok(event) = self.contact_force_events.try_recv() {
+ let _ = f.call1(&this, &JsValue::from(RawContactForceEvent(event)));
+ }
+ }
+
+ /// Removes all events contained by this collector.
+ pub fn clear(&self) {
+ while let Ok(_) = self.collision_events.try_recv() {}
+ }
+}
diff --git a/thirdparty/rapier.js/src/pipeline/mod.rs b/thirdparty/rapier.js/src/pipeline/mod.rs
new file mode 100644
index 00000000..23e19bfb
--- /dev/null
+++ b/thirdparty/rapier.js/src/pipeline/mod.rs
@@ -0,0 +1,11 @@
+pub use self::debug_render_pipeline::*;
+pub use self::event_queue::*;
+pub use self::physics_hooks::*;
+pub use self::physics_pipeline::*;
+pub use self::serialization_pipeline::*;
+
+mod debug_render_pipeline;
+mod event_queue;
+mod physics_hooks;
+mod physics_pipeline;
+mod serialization_pipeline;
diff --git a/thirdparty/rapier.js/src/pipeline/physics_hooks.rs b/thirdparty/rapier.js/src/pipeline/physics_hooks.rs
new file mode 100644
index 00000000..2bbc8034
--- /dev/null
+++ b/thirdparty/rapier.js/src/pipeline/physics_hooks.rs
@@ -0,0 +1,217 @@
+use crate::utils;
+use rapier::geometry::SolverFlags;
+use rapier::pipeline::{ContactModificationContext, PairFilterContext, PhysicsHooks};
+use wasm_bindgen::prelude::*;
+
+pub struct RawPhysicsHooks {
+ pub this: js_sys::Object,
+ pub filter_contact_pair: js_sys::Function,
+ pub filter_intersection_pair: js_sys::Function,
+ // pub modify_solver_contacts: &'a js_sys::Function,
+}
+
+// HACK: the RawPhysicsHooks is no longer Send+Sync because the JS objects are
+// no longer Send+Sync since https://github.com/rustwasm/wasm-bindgen/pull/955
+// As far as this is confined to the bindings this should be fine since we
+// never use threading in wasm.
+unsafe impl Send for RawPhysicsHooks {}
+unsafe impl Sync for RawPhysicsHooks {}
+
+#[wasm_bindgen]
+extern "C" {
+ // Use `js_namespace` here to bind `console.log(..)` instead of just
+ // `log(..)`
+ #[wasm_bindgen(js_namespace = console)]
+ fn log(s: &str);
+}
+
+impl PhysicsHooks for RawPhysicsHooks {
+ fn filter_contact_pair(&self, ctxt: &PairFilterContext) -> Option {
+ let rb1 = ctxt
+ .rigid_body1
+ .map(|rb| JsValue::from(utils::flat_handle(rb.0)))
+ .unwrap_or(JsValue::NULL);
+ let rb2 = ctxt
+ .rigid_body2
+ .map(|rb| JsValue::from(utils::flat_handle(rb.0)))
+ .unwrap_or(JsValue::NULL);
+
+ let result = self
+ .filter_contact_pair
+ .bind2(
+ &self.this,
+ &JsValue::from(utils::flat_handle(ctxt.collider1.0)),
+ &JsValue::from(utils::flat_handle(ctxt.collider2.0)),
+ )
+ .call2(&self.this, &rb1, &rb2)
+ .ok()?;
+ let flags = result.as_f64()?;
+ // TODO: not sure exactly why we have to do `flags as u32` instead
+ // of `flags.to_bits() as u32`.
+ SolverFlags::from_bits(flags as u32)
+ }
+
+ fn filter_intersection_pair(&self, ctxt: &PairFilterContext) -> bool {
+ let rb1 = ctxt
+ .rigid_body1
+ .map(|rb| JsValue::from(utils::flat_handle(rb.0)))
+ .unwrap_or(JsValue::NULL);
+ let rb2 = ctxt
+ .rigid_body2
+ .map(|rb| JsValue::from(utils::flat_handle(rb.0)))
+ .unwrap_or(JsValue::NULL);
+
+ self.filter_intersection_pair
+ .bind2(
+ &self.this,
+ &JsValue::from(utils::flat_handle(ctxt.collider1.0)),
+ &JsValue::from(utils::flat_handle(ctxt.collider2.0)),
+ )
+ .call2(&self.this, &rb1, &rb2)
+ .ok()
+ .and_then(|res| res.as_bool())
+ .unwrap_or(false)
+ }
+
+ fn modify_solver_contacts(&self, _ctxt: &mut ContactModificationContext) {}
+}
+
+/* NOTE: the following is an attempt to make contact modification work.
+ *
+#[wasm_bindgen]
+#[derive(Copy, Clone, Debug)]
+pub struct RawContactManifold(*const ContactManifold);
+pub struct RawSolverContact(*const SolverContact);
+
+#[wasm_bindgen]
+pub struct RawContactModificationContext {
+ pub collider1: u32,
+ pub collider2: u32,
+ pub rigid_body1: Option,
+ pub rigid_body2: Option,
+ pub manifold: *const ContactManifold,
+ pub solver_contacts: *mut Vec,
+ normal: *mut Vector,
+ user_data: *mut u32,
+}
+
+#[wasm_bindgen]
+impl RawContactModificationContext {
+ pub fn collider1(&self) -> u32 {
+ self.collider1
+ }
+
+ pub fn collider2(&self) -> u32 {
+ self.collider2
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn normal(&self) -> RawVector {
+ unsafe { RawVector(*self.normal) }
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_normal(&mut self, normal: RawVector) {
+ unsafe {
+ *self.normal = normal.0;
+ }
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn user_data(&self) -> u32 {
+ unsafe { *self.user_data }
+ }
+
+ #[wasm_bindgen(setter)]
+ pub fn set_user_data(&mut self, user_data: u32) {
+ unsafe {
+ *self.user_data = user_data;
+ }
+ }
+
+ pub fn num_solver_contacts(&self) -> usize {
+ unsafe { (*self.solver_contacts).len() }
+ }
+
+ pub fn clear_solver_contacts(&mut self) {
+ unsafe { (*self.solver_contacts).clear() }
+ }
+
+ pub fn remove_solver_contact(&mut self, i: usize) {
+ unsafe {
+ if i < self.num_solver_contacts() {
+ (*self.solver_contacts).swap_remove(i);
+ }
+ }
+ }
+
+ pub fn solver_contact_point(&self, i: usize) -> Option {
+ unsafe {
+ (*self.solver_contacts)
+ .get(i)
+ .map(|c| c.point.coords.into())
+ }
+ }
+
+ pub fn set_solver_contact_point(&mut self, i: usize, pt: &RawVector) {
+ unsafe {
+ if let Some(c) = (*self.solver_contacts).get_mut(i) {
+ c.point = pt.0.into()
+ }
+ }
+ }
+
+ pub fn solver_contact_dist(&self, i: usize) -> Real {
+ unsafe {
+ (*self.solver_contacts)
+ .get(i)
+ .map(|c| c.dist)
+ .unwrap_or(0.0)
+ }
+ }
+
+ pub fn set_solver_contact_dist(&mut self, i: usize, dist: Real) {
+ unsafe {
+ if let Some(c) = (*self.solver_contacts).get_mut(i) {
+ c.dist = dist
+ }
+ }
+ }
+
+ pub fn solver_contact_friction(&self, i: usize) -> Real {
+ unsafe { (*self.solver_contacts)[i].friction }
+ }
+
+ pub fn set_solver_contact_friction(&mut self, i: usize, friction: Real) {
+ unsafe {
+ if let Some(c) = (*self.solver_contacts).get_mut(i) {
+ c.friction = friction
+ }
+ }
+ }
+
+ pub fn solver_contact_restitution(&self, i: usize) -> Real {
+ unsafe { (*self.solver_contacts)[i].restitution }
+ }
+
+ pub fn set_solver_contact_restitution(&mut self, i: usize, restitution: Real) {
+ unsafe {
+ if let Some(c) = (*self.solver_contacts).get_mut(i) {
+ c.restitution = restitution
+ }
+ }
+ }
+
+ pub fn solver_contact_tangent_velocity(&self, i: usize) -> RawVector {
+ unsafe { (*self.solver_contacts)[i].tangent_velocity.into() }
+ }
+
+ pub fn set_solver_contact_tangent_velocity(&mut self, i: usize, vel: &RawVector) {
+ unsafe {
+ if let Some(c) = (*self.solver_contacts).get_mut(i) {
+ c.tangent_velocity = vel.0.into()
+ }
+ }
+ }
+}
+*/
diff --git a/thirdparty/rapier.js/src/pipeline/physics_pipeline.rs b/thirdparty/rapier.js/src/pipeline/physics_pipeline.rs
new file mode 100644
index 00000000..287cfeea
--- /dev/null
+++ b/thirdparty/rapier.js/src/pipeline/physics_pipeline.rs
@@ -0,0 +1,170 @@
+use crate::dynamics::{
+ RawCCDSolver, RawImpulseJointSet, RawIntegrationParameters, RawIslandManager,
+ RawMultibodyJointSet, RawRigidBodySet,
+};
+use crate::geometry::{RawBroadPhase, RawColliderSet, RawNarrowPhase};
+use crate::math::RawVector;
+use crate::pipeline::{RawEventQueue, RawPhysicsHooks};
+use crate::rapier::pipeline::PhysicsPipeline;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub struct RawPhysicsPipeline(pub(crate) PhysicsPipeline);
+
+#[wasm_bindgen]
+impl RawPhysicsPipeline {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ let mut pipeline = PhysicsPipeline::new();
+ pipeline.counters.disable(); // Disable perf counters by default.
+ RawPhysicsPipeline(pipeline)
+ }
+
+ pub fn set_profiler_enabled(&mut self, enabled: bool) {
+ if enabled {
+ self.0.counters.enable();
+ } else {
+ self.0.counters.disable();
+ }
+ }
+
+ pub fn is_profiler_enabled(&self) -> bool {
+ self.0.counters.enabled()
+ }
+
+ pub fn timing_step(&self) -> f64 {
+ self.0.counters.step_time_ms()
+ }
+
+ pub fn timing_collision_detection(&self) -> f64 {
+ self.0.counters.collision_detection_time_ms()
+ }
+
+ pub fn timing_broad_phase(&self) -> f64 {
+ self.0.counters.broad_phase_time_ms()
+ }
+
+ pub fn timing_narrow_phase(&self) -> f64 {
+ self.0.counters.narrow_phase_time_ms()
+ }
+
+ pub fn timing_solver(&self) -> f64 {
+ self.0.counters.solver_time_ms()
+ }
+
+ pub fn timing_velocity_assembly(&self) -> f64 {
+ self.0.counters.solver.velocity_assembly_time.time_ms()
+ }
+
+ pub fn timing_velocity_resolution(&self) -> f64 {
+ self.0.counters.velocity_resolution_time_ms()
+ }
+
+ pub fn timing_velocity_update(&self) -> f64 {
+ self.0.counters.velocity_update_time_ms()
+ }
+
+ pub fn timing_velocity_writeback(&self) -> f64 {
+ self.0.counters.solver.velocity_writeback_time.time_ms()
+ }
+
+ pub fn timing_ccd(&self) -> f64 {
+ self.0.counters.ccd_time_ms()
+ }
+
+ pub fn timing_ccd_toi_computation(&self) -> f64 {
+ self.0.counters.ccd.toi_computation_time.time_ms()
+ }
+
+ pub fn timing_ccd_broad_phase(&self) -> f64 {
+ self.0.counters.ccd.broad_phase_time.time_ms()
+ }
+
+ pub fn timing_ccd_narrow_phase(&self) -> f64 {
+ self.0.counters.ccd.narrow_phase_time.time_ms()
+ }
+
+ pub fn timing_ccd_solver(&self) -> f64 {
+ self.0.counters.ccd.solver_time.time_ms()
+ }
+
+ pub fn timing_island_construction(&self) -> f64 {
+ self.0.counters.island_construction_time_ms()
+ }
+
+ pub fn timing_user_changes(&self) -> f64 {
+ self.0.counters.stages.user_changes.time_ms()
+ }
+
+ pub fn step(
+ &mut self,
+ gravity: &RawVector,
+ integrationParameters: &RawIntegrationParameters,
+ islands: &mut RawIslandManager,
+ broadPhase: &mut RawBroadPhase,
+ narrowPhase: &mut RawNarrowPhase,
+ bodies: &mut RawRigidBodySet,
+ colliders: &mut RawColliderSet,
+ joints: &mut RawImpulseJointSet,
+ articulations: &mut RawMultibodyJointSet,
+ ccd_solver: &mut RawCCDSolver,
+ ) {
+ self.0.step(
+ &gravity.0,
+ &integrationParameters.0,
+ &mut islands.0,
+ &mut broadPhase.0,
+ &mut narrowPhase.0,
+ &mut bodies.0,
+ &mut colliders.0,
+ &mut joints.0,
+ &mut articulations.0,
+ &mut ccd_solver.0,
+ &(),
+ &(),
+ );
+ }
+
+ pub fn stepWithEvents(
+ &mut self,
+ gravity: &RawVector,
+ integrationParameters: &RawIntegrationParameters,
+ islands: &mut RawIslandManager,
+ broadPhase: &mut RawBroadPhase,
+ narrowPhase: &mut RawNarrowPhase,
+ bodies: &mut RawRigidBodySet,
+ colliders: &mut RawColliderSet,
+ joints: &mut RawImpulseJointSet,
+ articulations: &mut RawMultibodyJointSet,
+ ccd_solver: &mut RawCCDSolver,
+ eventQueue: &mut RawEventQueue,
+ hookObject: js_sys::Object,
+ hookFilterContactPair: js_sys::Function,
+ hookFilterIntersectionPair: js_sys::Function,
+ ) {
+ if eventQueue.auto_drain {
+ eventQueue.clear();
+ }
+
+ let hooks = RawPhysicsHooks {
+ this: hookObject,
+ filter_contact_pair: hookFilterContactPair,
+ filter_intersection_pair: hookFilterIntersectionPair,
+ };
+
+ self.0.step(
+ &gravity.0,
+ &integrationParameters.0,
+ &mut islands.0,
+ &mut broadPhase.0,
+ &mut narrowPhase.0,
+ &mut bodies.0,
+ &mut colliders.0,
+ &mut joints.0,
+ &mut articulations.0,
+ &mut ccd_solver.0,
+ &hooks,
+ &eventQueue.collector,
+ );
+ }
+}
diff --git a/thirdparty/rapier.js/src/pipeline/serialization_pipeline.rs b/thirdparty/rapier.js/src/pipeline/serialization_pipeline.rs
new file mode 100644
index 00000000..97076f63
--- /dev/null
+++ b/thirdparty/rapier.js/src/pipeline/serialization_pipeline.rs
@@ -0,0 +1,145 @@
+use crate::dynamics::{
+ RawImpulseJointSet, RawIntegrationParameters, RawIslandManager, RawMultibodyJointSet,
+ RawRigidBodySet,
+};
+use crate::geometry::{RawBroadPhase, RawColliderSet, RawNarrowPhase};
+use crate::math::RawVector;
+use js_sys::Uint8Array;
+use rapier::dynamics::{
+ ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet, RigidBodySet,
+};
+use rapier::geometry::{ColliderSet, DefaultBroadPhase, NarrowPhase};
+use rapier::math::Vector;
+use wasm_bindgen::prelude::*;
+
+#[derive(Serialize)]
+struct SerializableWorld<'a> {
+ gravity: &'a Vector,
+ integration_parameters: &'a IntegrationParameters,
+ islands: &'a IslandManager,
+ broad_phase: &'a DefaultBroadPhase,
+ narrow_phase: &'a NarrowPhase,
+ bodies: &'a RigidBodySet,
+ colliders: &'a ColliderSet,
+ impulse_joints: &'a ImpulseJointSet,
+ multibody_joints: &'a MultibodyJointSet,
+}
+
+#[derive(Deserialize)]
+struct DeserializableWorld {
+ gravity: Vector,
+ integration_parameters: IntegrationParameters,
+ islands: IslandManager,
+ broad_phase: DefaultBroadPhase,
+ narrow_phase: NarrowPhase,
+ bodies: RigidBodySet,
+ colliders: ColliderSet,
+ impulse_joints: ImpulseJointSet,
+ multibody_joints: MultibodyJointSet,
+}
+
+#[wasm_bindgen]
+pub struct RawDeserializedWorld {
+ gravity: Option