chore: 添加第三方依赖库

This commit is contained in:
yhh
2025-12-03 16:24:08 +08:00
parent cb1b171216
commit 83aee02540
172 changed files with 27480 additions and 0 deletions

View File

@@ -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']

View File

@@ -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 }}

21
thirdparty/rapier.js/.gitignore vendored Normal file
View File

@@ -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/

7
thirdparty/rapier.js/.prettierignore vendored Normal file
View File

@@ -0,0 +1,7 @@
pkg
gen2d
gen3d
docs
target
**/CHANGELOG.md
**/README.md

5
thirdparty/rapier.js/.prettierrc vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"tabWidth": 4,
"bracketSpacing": false,
"trailingComma": "all"
}

937
thirdparty/rapier.js/CHANGELOG.md vendored Normal file
View File

@@ -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 rapiers state on wasm.
- Add `collider.translationWrtParent()` and `collider.rotationWrtParent()` to get the colliders 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 dont 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 colliders
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 controllers snapping to ground not triggering sometimes.
- Fix character controllers 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
controllers 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 colliders
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 wont 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-bodys
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 users 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-bodys 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 colliders 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 isnt desired, use `RigidBody::setAdditionalMassProperties` instead.
- Add `Collider.setDensity`, `.setMass`, `.setMassProperties`, to alter a colliders mass
properties. Note that `.setMass` will result in the colliders 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 colliders
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 hasnt 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 dont 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.

1711
thirdparty/rapier.js/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

23
thirdparty/rapier.js/Cargo.toml vendored Normal file
View File

@@ -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" }

201
thirdparty/rapier.js/LICENSE vendored Normal file
View File

@@ -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.

82
thirdparty/rapier.js/README.md vendored Normal file
View File

@@ -0,0 +1,82 @@
<p align="center">
<img src="https://www.rapier.rs/img/rapier_logo_color_textpath_dark.svg" alt="crates.io">
</p>
<p align="center">
<a href="https://discord.gg/vt9DJSW">
<img src="https://img.shields.io/discord/507548572338880513.svg?logo=discord&colorB=7289DA">
</a>
<a href="https://github.com/dimforge/rapier.js/actions">
<img src="https://github.com/dimforge/rapier.js/workflows/main/badge.svg" alt="Build status">
</a>
<a href="https://opensource.org/licenses/Apache-2.0">
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg">
</a>
</p>
<p align = "center">
<strong>
<a href="https://rapier.rs">Website</a> | <a href="https://rapier.rs/docs/">Documentation</a> |
<a href="https://github.com/dimforge/rapier.js/tree/master/testbed2d/src/demos">2D examples (sources)</a> |
<a href="https://github.com/dimforge/rapier.js/tree/master/testbed3d/src/demos">3D examples (sources)</a>
</strong>
</p>
---
<p align = "center">
<b>2D and 3D physics engines</b>
<i>for the JavaScript programming language (official bindings).</i>
</p>
---
## 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.

View File

@@ -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"

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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<String>,
pub target_dir: PathBuf,
pub template_dir: PathBuf,
pub additional_rust_flags: String,
pub additional_wasm_opt_flags: Vec<String>,
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<Path>,
dest: impl AsRef<std::ffi::OsStr>,
) -> 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(())
}

View File

@@ -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 <developer@crozet.re>"]
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']

View File

@@ -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.

View File

@@ -0,0 +1,70 @@
<p align="center">
<img src="https://www.rapier.rs/img/rapier_logo_color_textpath_dark.svg" alt="crates.io">
</p>
<p align="center">
<a href="https://discord.gg/vt9DJSW">
<img src="https://img.shields.io/discord/507548572338880513.svg?logo=discord&colorB=7289DA">
</a>
<a href="https://github.com/dimforge/rapier.js/actions">
<img src="https://github.com/dimforge/rapier.js/workflows/main/badge.svg" alt="Build status">
</a>
<a href="https://crates.io/crates/rapier{{ dimension }}d">
<img src="https://img.shields.io/crates/v/rapier{{ dimension }}d.svg?style=flat-square" alt="crates.io">
</a>
<a href="https://www.npmjs.com/package/@dimforge/rapier{{ dimension }}d">
<img src="https://badge.fury.io/js/%40dimforge%2Frapier{{ dimension }}d.svg" alt="npm version">
</a>
<a href="https://opensource.org/licenses/Apache-2.0">
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg">
</a>
</p>
<p align = "center">
<strong>
<a href="https://rapier.rs">Website</a> | <a href="https://rapier.rs/docs/">Documentation</a>
</strong>
</p>
---
<p align = "center">
<b>{{ dimension }}D physics engine</b>
<i>for the JavaScript programming language (official bindings).</i>
</p>
---
## 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.

View File

@@ -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

View File

@@ -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

View File

@@ -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"
]
}

View File

@@ -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"]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"outDir": "./pkg",
"moduleResolution": "node",
"sourceMap": true,
"declaration": true,
"lib": ["es6"]
},
"files": ["./pkg/rapier.d.ts"]
}

View File

@@ -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"
]
}

12
thirdparty/rapier.js/package.json vendored Normal file
View File

@@ -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"
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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`

View File

@@ -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"

View File

@@ -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;

View File

@@ -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/"<deleted>"/g' ./builds/${feature}/pkg/rapier_wasm${dimension}d.js
# Clean up backup files.
find ./builds/${feature}/pkg/ -type f -name '*.bak' | xargs rm
done

View File

@@ -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": [
"<rootDir>/tests"
],
"preset": "ts-jest",
"collectCoverageFrom": [
"builds/**/pkg/**/*.js"
],
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|ts)$",
"builds/.*/[/\\\\]pkg[/\\\\].+\\.(js|ts)$"
],
"moduleFileExtensions": [
"ts",
"js"
]
}
}

View File

@@ -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"),
];

View File

@@ -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<void> {
if (initialized) {
return;
}
await wasmInit(input);
initialized = true;
}
/**
* Check if RAPIER is already initialized.
* 检查 RAPIER 是否已初始化。
*/
export function isInitialized(): boolean {
return initialized;
}

View File

@@ -0,0 +1 @@
export * from "../pkg/rapier_wasm2d";

View File

@@ -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);
}

View File

@@ -0,0 +1 @@
export * from "../pkg/rapier_wasm3d";

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -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
}
}

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.common.json",
"compilerOptions": {
"lib": ["es6", "DOM"],
"esModuleInterop": true,
"types": ["jest"],
"outDir": "./dist"
},
"include": ["./tests/**/*.test.ts"]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.common.json",
"compilerOptions": {
"rootDir": "./gen2d",
"outDir": "./2d"
},
"files": ["./gen2d/rapier.ts"]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.common.json",
"compilerOptions": {
"rootDir": "./gen3d",
"outDir": "."
},
"files": ["./gen3d/rapier.ts"]
}

70
thirdparty/rapier.js/src.ts/coarena.ts vendored Normal file
View File

@@ -0,0 +1,70 @@
export class Coarena<T> {
fconv: Float64Array;
uconv: Uint32Array;
data: Array<T>;
size: number;
public constructor() {
this.fconv = new Float64Array(1);
this.uconv = new Uint32Array(this.fconv.buffer);
this.data = new Array<T>();
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<T>();
}
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<T> {
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 dont really need.
this.fconv[0] = handle;
return this.uconv[0];
}
}

View File

@@ -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 floors angle.
*/
public up(): Vector {
return this.raw.up();
}
/**
* Sets the direction that goes "up". Used to determine where the floor is, and the floors 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
* isnt 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 shouldnt 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 shouldnt 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 floors 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 floors 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 floors 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 floors 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;
}
}
}

View File

@@ -0,0 +1,6 @@
export * from "./character_controller";
export * from "./pid_controller";
// #if DIM3
export * from "./ray_cast_vehicle_controller";
// #endif

View File

@@ -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
}

View File

@@ -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 vehicles 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 wheels suspension, relative to the chassis. The ray-casting will
* happen following this direction to detect the ground.
* @param axleCs - The wheels axle axis, relative to the chassis.
* @param suspensionRestLength - The rest length of the wheels suspension spring.
* @param radius - The wheels 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 wheels suspension spring.
*/
public wheelSuspensionRestLength(i: number): number | null {
return this.raw.wheel_suspension_rest_length(i);
}
/**
* Sets the rest length of the i-th wheels 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 wheels radius.
*/
public wheelRadius(i: number): number | null {
return this.raw.wheel_radius(i);
}
/**
* Sets the i-th wheels radius.
*/
public setWheelRadius(i: number, value: number) {
this.raw.set_wheel_radius(i, value);
}
/**
* The i-th wheels 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 wheels 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 wheels suspensions damping when it is being compressed.
*/
public wheelSuspensionCompression(i: number): number | null {
return this.raw.wheel_suspension_compression(i);
}
/**
* The i-th wheels suspensions damping when it is being compressed.
*/
public setWheelSuspensionCompression(i: number, value: number) {
this.raw.set_wheel_suspension_compression(i, value);
}
/**
* The i-th wheels suspensions 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 wheels suspensions 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 wheels suspension.
*/
public wheelMaxSuspensionForce(i: number): number | null {
return this.raw.wheel_max_suspension_force(i);
}
/**
* Sets the maximum force applied by the i-th wheels 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 wheels 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 wheels 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 wheels 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 wheels 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 its 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 its 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 its 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 its 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 wheels 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 wheels 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));
}
}

View File

@@ -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();
}
}

View File

@@ -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,
}

View File

@@ -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 wont 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 joints free coordinate.
* @param max - The maximum bound of this joints 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<number>;
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;
}
}

View File

@@ -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<ImpulseJoint>;
/**
* 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<ImpulseJoint>();
// 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();
}
}

View File

@@ -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";

View File

@@ -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 wont 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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<MultibodyJoint>;
/**
* 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<MultibodyJoint>();
// 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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<RigidBody>;
/**
* 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<RigidBody>();
// 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();
}
}

27
thirdparty/rapier.js/src.ts/exports.ts vendored Normal file
View File

@@ -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";

View File

@@ -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 wont immediately stop if
* the shape is penetrating another shape at its starting point **and** its trajectory is such
* that its 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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<Collider>;
/**
* 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<Collider>();
// 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<Res>(
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();
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,16 @@
// #if DIM2
export enum FeatureType {
Vertex,
Face,
Unknown,
}
// #endif
// #if DIM3
export enum FeatureType {
Vertex,
Edge,
Face,
Unknown,
}
// #endif

View File

@@ -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";

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}

2
thirdparty/rapier.js/src.ts/init.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
// Placeholder for the `init` function used in rapier-compat.
export {};

263
thirdparty/rapier.js/src.ts/math.ts vendored Normal file
View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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,
}

View File

@@ -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));
}
}

File diff suppressed because it is too large Load Diff

3
thirdparty/rapier.js/src.ts/rapier.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
import * as RAPIER from "./exports";
export * from "./exports";
export default RAPIER;

1
thirdparty/rapier.js/src.ts/raw.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from "../rapier3d/pkg/rapier_wasm3d";

View File

@@ -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<CharacterCollision>,
}
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<Real> {
self.controller.autostep.map(|e| length_value(e.max_height))
}
pub fn autostepMinWidth(&self) -> Option<Real> {
self.controller.autostep.map(|e| length_value(e.min_width))
}
pub fn autostepIncludesDynamicBodies(&self) -> Option<bool> {
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<Real> {
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<Real>,
filter_flags: u32,
filter_groups: Option<u32>,
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()
}
}

11
thirdparty/rapier.js/src/control/mod.rs vendored Normal file
View File

@@ -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;

View File

@@ -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()
}
}

View File

@@ -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<u32>,
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<RawVector> {
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<Real> {
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<Real> {
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<Real> {
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<Real> {
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<Real> {
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<Real> {
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<Real> {
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<Real> {
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<Real> {
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<Real> {
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<RawVector> {
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<RawVector> {
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<Real> {
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<f32> {
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<Real> {
self.controller.wheels().get(i).map(|w| w.rotation)
}
pub fn wheel_forward_impulse(&self, i: usize) -> Option<Real> {
self.controller.wheels().get(i).map(|w| w.forward_impulse)
}
pub fn wheel_side_impulse(&self, i: usize) -> Option<Real> {
self.controller.wheels().get(i).map(|w| w.side_impulse)
}
pub fn wheel_suspension_force(&self, i: usize) -> Option<Real> {
self.controller
.wheels()
.get(i)
.map(|w| w.wheel_suspension_force)
}
pub fn wheel_contact_normal_ws(&self, i: usize) -> Option<RawVector> {
self.controller
.wheels()
.get(i)
.map(|w| w.raycast_info().contact_normal_ws.into())
}
pub fn wheel_contact_point_ws(&self, i: usize) -> Option<RawVector> {
self.controller
.wheels()
.get(i)
.map(|w| w.raycast_info().contact_point_ws.into())
}
pub fn wheel_suspension_length(&self, i: usize) -> Option<Real> {
self.controller
.wheels()
.get(i)
.map(|w| w.raycast_info().suspension_length)
}
pub fn wheel_hard_point_ws(&self, i: usize) -> Option<RawVector> {
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<FlatHandle> {
self.controller
.wheels()
.get(i)
.and_then(|w| w.raycast_info().ground_object)
.map(|h| utils::flat_handle(h.0))
}
}

View File

@@ -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())
}
}

View File

@@ -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 joints 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 joints 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);
})
}
}

View File

@@ -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<T>(&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<T>(
&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)));
}
}
}

View File

@@ -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
}
}

View File

@@ -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)));
}
}
}

View File

@@ -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<JointAxesMask> 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<JointAxesMask> 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<RawMotorModel> 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<RawJointAxis> 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<RawGenericJoint> {
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<RawGenericJoint> {
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<RawGenericJoint> {
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<RawGenericJoint> {
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<RawGenericJoint> {
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(),
))
}
}

View File

@@ -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;

View File

@@ -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 joints 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 joints 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;
// })
// }
}

View File

@@ -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<T>(&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<T>(
&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)));
}
}
}

View File

@@ -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())
}
}

Some files were not shown because too many files have changed in this diff Show More