Merge branch 'master' into collider-builder-debug
This commit is contained in:
8
.github/workflows/rapier-ci-build.yml
vendored
8
.github/workflows/rapier-ci-build.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
check-fmt:
|
check-fmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
build-native:
|
build-native:
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: sudo apt-get install -y cmake libxcb-composite0-dev
|
- run: sudo apt-get install -y cmake libxcb-composite0-dev
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy
|
run: cargo clippy
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: rustup target add wasm32-unknown-unknown
|
- run: rustup target add wasm32-unknown-unknown
|
||||||
- name: build rapier2d
|
- name: build rapier2d
|
||||||
run: cd crates/rapier2d && cargo build --verbose --features wasm-bindgen --target wasm32-unknown-unknown;
|
run: cd crates/rapier2d && cargo build --verbose --features wasm-bindgen --target wasm32-unknown-unknown;
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: rustup target add wasm32-unknown-emscripten
|
- run: rustup target add wasm32-unknown-emscripten
|
||||||
- name: build rapier2d
|
- name: build rapier2d
|
||||||
run: cd crates/rapier2d && cargo build --verbose --target wasm32-unknown-emscripten;
|
run: cd crates/rapier2d && cargo build --verbose --target wasm32-unknown-emscripten;
|
||||||
|
|||||||
305
CHANGELOG.md
305
CHANGELOG.md
@@ -1,8 +1,96 @@
|
|||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `Multibody::inverse_kinematics`, `Multibody::inverse_kinematics_delta`,
|
||||||
|
and `::inverse_kinematics_delta_with_jacobian`
|
||||||
|
for running inverse kinematics on a multibody to align one its links pose to the given prescribed pose.
|
||||||
|
- Add `InverseKinematicsOption` to customize some behaviors of the inverse-kinematics solver.
|
||||||
|
- Add `Multibody::body_jacobian` to get the jacobian of a specific link.
|
||||||
|
- Add `Multibody::update_rigid_bodies` to update rigid-bodies based on the multibody links poses.
|
||||||
|
- Add `Multibody::forward_kinematics_single_link` to run forward-kinematics to compute the new pose and jacobian of a
|
||||||
|
single link without mutating the multibody. This can take an optional displacement on generalized coordinates that are
|
||||||
|
taken into account during transform propagation.
|
||||||
|
|
||||||
|
### Modified
|
||||||
|
|
||||||
|
- The contact constraints regularization parameters have been changed from `erp/damping_ratio` to
|
||||||
|
`natural_frequency/damping_ratio`. This helps define them in a timestep-length independent way. The new variables
|
||||||
|
are named `IntegrationParameters::contact_natural_frequency` and `IntegrationParameters::contact_damping_ratio`.
|
||||||
|
- The `IntegrationParameters::normalized_max_penetration_correction` has been replaced
|
||||||
|
by `::normalized_max_corrective_velocity`
|
||||||
|
to make the parameter more timestep-length independent. It is now set to a non-infinite value to eliminate aggressive
|
||||||
|
"popping effects".
|
||||||
|
- The `Multibody::forward_kinematics` method will no longer automatically update the poses of the `RigidBody` associated
|
||||||
|
to each joint. Instead `Multibody::update_rigid_bodies` has to be called explicitly.
|
||||||
|
- The `Multibody::forward_kinematics` method will automatically adjust the multibody’s degrees of freedom if the root
|
||||||
|
rigid-body changed type (between dynamic and non-dynamic). It can also optionally apply the root’s rigid-body pose
|
||||||
|
instead of the root link’s pose (useful for example if you modified the root rigid-body pose externally and wanted
|
||||||
|
to propagate it to the multibody).
|
||||||
|
- Remove an internal special-case for contact constraints on fast contacts. The doesn’t seem necessary with the substep
|
||||||
|
solver.
|
||||||
|
|
||||||
|
## v0.19.0 (05 May 2024)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Fix crash when simulating a spring joint between two dynamic bodies.
|
||||||
|
- Fix kinematic bodies not being affected by gravity after being switched back to dynamic.
|
||||||
|
- Fix regression on contact force reporting from contact force events.
|
||||||
|
- Fix kinematic character controller getting stuck against vertical walls.
|
||||||
|
- Fix joint limits/motors occasionally not being applied properly when one of the attached
|
||||||
|
rigid-bodies is fixed.
|
||||||
|
- Fix an issue where contacts would be completely ignored between two convex shapes.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
**Many stability improvements were added as part of this release. To see illustrations of some of these
|
||||||
|
changes, see [#625](https://github.com/dimforge/rapier/pull/625).**
|
||||||
|
|
||||||
|
- Add `RigidBody::predict_position_using_velocity` to predict the next position of the rigid-body
|
||||||
|
based only on its current velocity.
|
||||||
|
- Add `Collider::copy_from` to copy most collider attributes to an existing collider.
|
||||||
|
- Add `RigidBody::copy_from` to copy most rigid-body attributes to an existing rigid-body.
|
||||||
|
- Add the `BroadPhase` trait and expect an implementor of this trait as input to `PhysicsPipeline::step`.
|
||||||
|
- Implement a 2D block-solver as well as warmstarting. Significantly improves stacking capabilities. Generally reduces
|
||||||
|
the "pop" effect that can happen due to penetration corrections.
|
||||||
|
- Add `RigidBodyBuilder::soft_ccd_prediction` and `RigidBody::set_soft_ccd_prediction` to enable `soft-ccd`: a form of
|
||||||
|
CCD based on predictive contacts. This is helpful for objects moving moderately fast. This form of CCD is generally
|
||||||
|
cheaper than the normal (time-dropping) CCD implemented so far. It is possible to combine both soft-ccd and
|
||||||
|
time-dropping ccd.
|
||||||
|
- Add a `ColliderBuilder::contact_skin`, `Collider::set_contact_skin`, and `Collider::contact_skin`. This forces the
|
||||||
|
solver te maintain a gap between colliders with non-zero contact skin, as if they had a slight margin around them.
|
||||||
|
This helps performance and stability for thin objects (like triangle meshes).
|
||||||
|
- Internal edges were reworked to avoid dropping contacts that would help with stability, and improve stability of
|
||||||
|
collisions between two triangle meshes. The `TriMeshFlags::FIX_INTERNAL_EDGES` and
|
||||||
|
`HeightFieldFlags::FIX_INTERNAL_EDGES` flags were added to enable internal edges handling.
|
||||||
|
- Add `IntegrationParameters::length_units` to automatically adjust internal thresholds when the user relies on custom
|
||||||
|
length units (e.g. pixels in 2D).
|
||||||
|
|
||||||
|
### Modified
|
||||||
|
|
||||||
|
**Many shape-casting related functions/structs were renamed. Check out the CHANGELOG for parry 0.15.0 for
|
||||||
|
additional details.**
|
||||||
|
|
||||||
|
- Renamed `BroadPhase` to `BroadPhaseMultiSap`. The `BroadPhase` is now a trait that can be
|
||||||
|
implemented for providing a custom broad-phase to rapier. Equivalently, the `DefaultBroadPhase` type
|
||||||
|
alias can be used in place of `BroadPhaseMultiSap`.
|
||||||
|
- The kinematic character controller autostepping is now disabled by default.
|
||||||
|
- Add `KinematicCharacterController::normal_nudge_factor` used to help getting the character unstuck
|
||||||
|
due to rounding errors.
|
||||||
|
- Rename `TOI` to `ShapeCastHit`.
|
||||||
|
- Rename many fields from `toi` to `time_of_impact`.
|
||||||
|
- The `QueryPipeline::cast_shape` method now takes a `ShapeCastOptions` instead of the `max_toi`
|
||||||
|
and `stop_at_penetration` parameters. This allows a couple of extra configurations, including the
|
||||||
|
ability to have the shape-cast target a specific distance instead of actual shape overlap.
|
||||||
|
|
||||||
## v0.18.0 (24 Jan. 2024)
|
## v0.18.0 (24 Jan. 2024)
|
||||||
|
|
||||||
The main highlight of this release is the implementation of a new non-linear constraints solver for better stability
|
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.
|
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 `IntegrationParameters::num_solver_iterations`.
|
In order to adjust the number of iterations of the new solver, simply
|
||||||
|
adjust `IntegrationParameters::num_solver_iterations`.
|
||||||
If recovering the old solver behavior is useful to you, call `IntegrationParameters::switch_to_standard_pgs_solver()`.
|
If recovering the old solver behavior is useful to you, call `IntegrationParameters::switch_to_standard_pgs_solver()`.
|
||||||
|
|
||||||
It is now possible to specify some additional solver iteration for specific rigid-bodies (and everything interacting
|
It is now possible to specify some additional solver iteration for specific rigid-bodies (and everything interacting
|
||||||
@@ -11,11 +99,15 @@ with it directly or indirectly through contacts and joints): `RigidBodyBuilder::
|
|||||||
without affecting performance of the other parts of the simulation.
|
without affecting performance of the other parts of the simulation.
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
- Fix bug causing angular joint limits and motor to sometimes only take into account half of the angles specified by the user.
|
|
||||||
|
- Fix bug causing angular joint limits and motor to sometimes only take into account half of the angles specified by the
|
||||||
|
user.
|
||||||
- Fix bug where collisions would not be re-computed after a collider was re-enabled.
|
- Fix bug where collisions would not be re-computed after a collider was re-enabled.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Add a `SpringJoint` and `SpringJointBuilder` for simulating springs with customizable stiffness and damping coefficients.
|
|
||||||
|
- Add a `SpringJoint` and `SpringJointBuilder` for simulating springs with customizable stiffness and damping
|
||||||
|
coefficients.
|
||||||
- Add `SphericalJoint::local_frame1/2`, `::set_local_frame1/2`, and `SphericalJointBuilder::local_frame1/2` to set both
|
- Add `SphericalJoint::local_frame1/2`, `::set_local_frame1/2`, and `SphericalJointBuilder::local_frame1/2` to set both
|
||||||
the joint’s anchor and reference orientation.
|
the joint’s anchor and reference orientation.
|
||||||
- Add `EffectiveCharacterMovement::is_sliding_down_slope` to indicate if the character controlled by the kinematic
|
- Add `EffectiveCharacterMovement::is_sliding_down_slope` to indicate if the character controlled by the kinematic
|
||||||
@@ -27,6 +119,7 @@ without affecting performance of the other parts of the simulation.
|
|||||||
- Fix debug-renderer showing moved kinematic rigid-bodies only at their initial position.
|
- Fix debug-renderer showing moved kinematic rigid-bodies only at their initial position.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- Make `Wheel::friction_slip` public to customize the front friction applied to the vehicle controller’s wheels.
|
- Make `Wheel::friction_slip` public to customize the front friction applied to the vehicle controller’s wheels.
|
||||||
- Add the `DebugRenderBackend::filter_object` predicate that can be implemented to apply custom filtering rules
|
- Add the `DebugRenderBackend::filter_object` predicate that can be implemented to apply custom filtering rules
|
||||||
on the objects being rendered.
|
on the objects being rendered.
|
||||||
@@ -35,19 +128,24 @@ without affecting performance of the other parts of the simulation.
|
|||||||
- Rename `NarrowPhase::intersections_with` to `NarrowPhase::intersection_pairs_with`.
|
- Rename `NarrowPhase::intersections_with` to `NarrowPhase::intersection_pairs_with`.
|
||||||
|
|
||||||
## v0.17.2 (26 Feb. 2023)
|
## v0.17.2 (26 Feb. 2023)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
||||||
- Fix issue with convex polyhedron jitter due to missing contacts.
|
- Fix issue with convex polyhedron jitter due to missing contacts.
|
||||||
- Fix character controller getting stuck against vertical walls.
|
- Fix character controller getting stuck against vertical walls.
|
||||||
- Fix character controller’s snapping to ground not triggering sometimes.
|
- Fix character controller’s snapping to ground not triggering sometimes.
|
||||||
- Fix character controller’s horizontal offset being mostly ignored and some instances of vertical offset being ignored.
|
- Fix character controller’s horizontal offset being mostly ignored and some instances of vertical offset being ignored.
|
||||||
|
|
||||||
## v0.17.1 (22 Jan. 2023)
|
## v0.17.1 (22 Jan. 2023)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
||||||
- Fix bug resulting in dynamic rigid-bodies acting as kinematic bodies after being disabled and then re-enabled.
|
- Fix bug resulting in dynamic rigid-bodies acting as kinematic bodies after being disabled and then re-enabled.
|
||||||
|
|
||||||
|
|
||||||
## v0.17.0 (15 Jan. 2023)
|
## v0.17.0 (15 Jan. 2023)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add `RigidBody::set_enabled`, `RigidBody::is_enabled`, `RigidBodyBuilder::enabled` to enable/disable a rigid-body
|
- Add `RigidBody::set_enabled`, `RigidBody::is_enabled`, `RigidBodyBuilder::enabled` to enable/disable a rigid-body
|
||||||
without having to delete it. Disabling a rigid-body attached to a multibody joint isn’t supported yet.
|
without having to delete it. Disabling a rigid-body attached to a multibody joint isn’t supported yet.
|
||||||
- Add `Collider::set_enabled`, `Collider::is_enabled`, `ColliderBuilder::enabled` to enable/disable a collider
|
- Add `Collider::set_enabled`, `Collider::is_enabled`, `ColliderBuilder::enabled` to enable/disable a collider
|
||||||
@@ -61,37 +159,48 @@ without affecting performance of the other parts of the simulation.
|
|||||||
- Add `RigidBody::locked_axes` to get the rigid-body axes that were locked by the user.
|
- Add `RigidBody::locked_axes` to get the rigid-body axes that were locked by the user.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- Add the `QueryPipeline` as an optional argument to `PhysicsPipeline::step` and `CollisionPipeline::step`. If this
|
- Add the `QueryPipeline` as an optional argument to `PhysicsPipeline::step` and `CollisionPipeline::step`. If this
|
||||||
argument is specified, then the query pipeline will be incrementally (i.e. more efficiently) update at the same time as
|
argument is specified, then the query pipeline will be incrementally (i.e. more efficiently) update at the same time
|
||||||
|
as
|
||||||
these other pipelines. In that case, calling `QueryPipeline::update` a `PhysicsPipeline::step` isn’t needed.
|
these other pipelines. In that case, calling `QueryPipeline::update` a `PhysicsPipeline::step` isn’t needed.
|
||||||
- `RigidBody::set_body_type` now takes an extra boolean argument indicating if the rigid-body should be woken-up
|
- `RigidBody::set_body_type` now takes an extra boolean argument indicating if the rigid-body should be woken-up
|
||||||
(if it becomes dynamic).
|
(if it becomes dynamic).
|
||||||
- `RigidBody::mass_properties` now also returns the world-space mass-properties of the rigid-body.
|
- `RigidBody::mass_properties` now also returns the world-space mass-properties of the rigid-body.
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
||||||
- Fix bug resulting in rigid-bodies being awakened after they are created, even if they are created sleeping.
|
- Fix bug resulting in rigid-bodies being awakened after they are created, even if they are created sleeping.
|
||||||
|
|
||||||
## v0.16.1 (10 Nov. 2022)
|
## v0.16.1 (10 Nov. 2022)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
||||||
- Fixed docs build on `docs.rs`.
|
- Fixed docs build on `docs.rs`.
|
||||||
|
|
||||||
## v0.16.0 (30 Oct. 2022)
|
## v0.16.0 (30 Oct. 2022)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Implement `Copy` for `CharacterCollision`.
|
- Implement `Copy` for `CharacterCollision`.
|
||||||
- Implement conversion (`From` trait) between `Group` and `u32`.
|
- Implement conversion (`From` trait) between `Group` and `u32`.
|
||||||
- Add `ColliderBuilder::trimesh_with_flags` to build a triangle mesh with specific flags controlling
|
- Add `ColliderBuilder::trimesh_with_flags` to build a triangle mesh with specific flags controlling
|
||||||
its initialization.
|
its initialization.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- Rename `AABB` to `Aabb` to comply with Rust’s style guide.
|
- Rename `AABB` to `Aabb` to comply with Rust’s style guide.
|
||||||
- Switch to `parry 0.11`.
|
- Switch to `parry 0.11`.
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
||||||
- Fix internal edges of 3D triangle meshes or 3D heightfields generating invalid contacts preventing
|
- Fix internal edges of 3D triangle meshes or 3D heightfields generating invalid contacts preventing
|
||||||
balls from moving straight.
|
balls from moving straight.
|
||||||
|
|
||||||
## v0.15.0 (02 Oct. 2022)
|
## v0.15.0 (02 Oct. 2022)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add a **kinematic character** controller implementation. See the `control` module. The character controller currently
|
- Add a **kinematic character** controller implementation. See the `control` module. The character controller currently
|
||||||
supports the following features:
|
supports the following features:
|
||||||
- Slide on uneven terrains
|
- Slide on uneven terrains
|
||||||
@@ -104,8 +213,8 @@ without affecting performance of the other parts of the simulation.
|
|||||||
- Report information on the obstacles it hit on its path.
|
- Report information on the obstacles it hit on its path.
|
||||||
- Implement `serde` serialization/deserialization for `CollisionEvents` when the `serde-serialize` feature is enabled
|
- Implement `serde` serialization/deserialization for `CollisionEvents` when the `serde-serialize` feature is enabled
|
||||||
|
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- The methods `Collider::set_rotation`, `RigidBody::set_rotation`, and `RigidBody::set_next_kinematic_rotation` now
|
- The methods `Collider::set_rotation`, `RigidBody::set_rotation`, and `RigidBody::set_next_kinematic_rotation` now
|
||||||
take a rotation (`UnitQuaternion` or `UnitComplex`) instead of a vector/angle.
|
take a rotation (`UnitQuaternion` or `UnitComplex`) instead of a vector/angle.
|
||||||
- The method `QueryFilter::exclude_dynamic` is now a static method (the `self` argument was removed).
|
- The method `QueryFilter::exclude_dynamic` is now a static method (the `self` argument was removed).
|
||||||
@@ -117,7 +226,9 @@ without affecting performance of the other parts of the simulation.
|
|||||||
position.
|
position.
|
||||||
|
|
||||||
## v0.14.0 (09 July 2022)
|
## v0.14.0 (09 July 2022)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix unpredictable broad-phase panic when using small colliders in the simulation.
|
- Fix unpredictable broad-phase panic when using small colliders in the simulation.
|
||||||
- Fix collision events being incorrectly generated for any shape that produces multiple
|
- Fix collision events being incorrectly generated for any shape that produces multiple
|
||||||
contact manifolds (like triangle meshes).
|
contact manifolds (like triangle meshes).
|
||||||
@@ -125,6 +236,7 @@ without affecting performance of the other parts of the simulation.
|
|||||||
to `CollisionPipeline::step`.
|
to `CollisionPipeline::step`.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- The `RigidBodyBuilder::additional_mass` method will now result in the additional angular inertia
|
- The `RigidBodyBuilder::additional_mass` method will now result in the additional angular inertia
|
||||||
being automatically computed based on the shapes of the colliders attached to the rigid-body.
|
being automatically computed based on the shapes of the colliders attached to the rigid-body.
|
||||||
- Remove the deprecated methods `RigidBodyBuilder::mass`, `::principal_angular_inertia`, `::principal_inertia`.
|
- Remove the deprecated methods `RigidBodyBuilder::mass`, `::principal_angular_inertia`, `::principal_inertia`.
|
||||||
@@ -137,6 +249,7 @@ without affecting performance of the other parts of the simulation.
|
|||||||
`RigidBodyBuilder::enabled_rotations` and `RigidBodyBuilder::enabled_translations`.
|
`RigidBodyBuilder::enabled_rotations` and `RigidBodyBuilder::enabled_translations`.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add `RigidBody::recompute_mass_properties_from_colliders` to force the immediate computation
|
- Add `RigidBody::recompute_mass_properties_from_colliders` to force the immediate computation
|
||||||
of a rigid-body’s mass properties (instead of waiting for them to be recomputed during the next
|
of a rigid-body’s mass properties (instead of waiting for them to be recomputed during the next
|
||||||
timestep). This is useful to be able to read immediately the result of a change of a rigid-body
|
timestep). This is useful to be able to read immediately the result of a change of a rigid-body
|
||||||
@@ -149,7 +262,8 @@ without affecting performance of the other parts of the simulation.
|
|||||||
- Add `ColliderBuilder::mass` to set the mass of the collider instead of its density. Its angular
|
- Add `ColliderBuilder::mass` 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.
|
inertia tensor will be automatically computed based on this mass and its shape.
|
||||||
- Add `Collider::mass` and `Collider::volume` to retrieve the mass or volume of a collider.
|
- Add `Collider::mass` and `Collider::volume` to retrieve the mass or volume of a collider.
|
||||||
- Add the `QueryFilter` that is now used by all the scene queries instead of the `CollisionGroups` and `Fn(ColliderHandle) -> bool`
|
- Add the `QueryFilter` that is now used by all the scene queries instead of the `CollisionGroups`
|
||||||
|
and `Fn(ColliderHandle) -> bool`
|
||||||
closure. This `QueryFilter` provides easy access to most common filtering strategies (e.g. dynamic bodies only,
|
closure. This `QueryFilter` provides easy access to most common filtering strategies (e.g. dynamic bodies only,
|
||||||
excluding one particular collider, etc.) for scene queries.
|
excluding one particular collider, etc.) for scene queries.
|
||||||
- Add force reporting based on contact force events. The `EventHandler` trait has been modified to include
|
- Add force reporting based on contact force events. The `EventHandler` trait has been modified to include
|
||||||
@@ -161,12 +275,15 @@ without affecting performance of the other parts of the simulation.
|
|||||||
contact force events.
|
contact force events.
|
||||||
|
|
||||||
## v0.13.0 (31 May 2022)
|
## v0.13.0 (31 May 2022)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix incorrect sensor events being generated after collider removal.
|
- Fix incorrect sensor events being generated after collider removal.
|
||||||
- Fix bug where the CCD thickness wasn’t initialized properly.
|
- Fix bug where the CCD thickness wasn’t initialized properly.
|
||||||
- Fix bug where the contact compliance would result in undesired tunneling, despite CCD being enabled.
|
- Fix bug where the contact compliance would result in undesired tunneling, despite CCD being enabled.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- Add a `wake_up: bool` argument to the `ImpulseJointSet::insert` and `MultibodyJointSet::insert` to
|
- Add a `wake_up: bool` argument to the `ImpulseJointSet::insert` and `MultibodyJointSet::insert` to
|
||||||
automatically wake-up the rigid-bodies attached to the inserted joint.
|
automatically wake-up the rigid-bodies attached to the inserted joint.
|
||||||
- The methods `ImpulseJointSet::remove/remove_joints_attached_to_rigid_body`,
|
- The methods `ImpulseJointSet::remove/remove_joints_attached_to_rigid_body`,
|
||||||
@@ -180,11 +297,14 @@ without affecting performance of the other parts of the simulation.
|
|||||||
by default.
|
by default.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Debug-renderer: add rendering of contacts, solver contacts, and collider Aabbs
|
- Debug-renderer: add rendering of contacts, solver contacts, and collider Aabbs
|
||||||
- Add `MultibodyJointSet::attached_joints` to return all the multibody joints attached to a given rigid-body.
|
- Add `MultibodyJointSet::attached_joints` to return all the multibody joints attached to a given rigid-body.
|
||||||
|
|
||||||
## v0.12.0 (30 Apr. 2022)
|
## v0.12.0 (30 Apr. 2022)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix the simulation when the `parallel` feature is enabled.
|
- Fix the simulation when the `parallel` feature is enabled.
|
||||||
- Fix bug where damping would not be applied properly to some bodies.
|
- Fix bug where damping would not be applied properly to some bodies.
|
||||||
- Fix panics caused by various situations (contact or joints) involving rigid-bodies with locked translations/rotations.
|
- Fix panics caused by various situations (contact or joints) involving rigid-bodies with locked translations/rotations.
|
||||||
@@ -194,6 +314,7 @@ without affecting performance of the other parts of the simulation.
|
|||||||
- Fix the broad-phase becoming potentially invalid after a change of collision groups.
|
- Fix the broad-phase becoming potentially invalid after a change of collision groups.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- Switch to `nalgebra` 0.31.
|
- Switch to `nalgebra` 0.31.
|
||||||
- Switch to `parry` 0.9.
|
- Switch to `parry` 0.9.
|
||||||
- Rename `JointHandle` to `ImpulseJointHandle`.
|
- Rename `JointHandle` to `ImpulseJointHandle`.
|
||||||
@@ -212,14 +333,16 @@ without affecting performance of the other parts of the simulation.
|
|||||||
- Joint motors no longer have a `VelocityBased` model. The new choices are `AccelerationBased` and `ForceBased`
|
- Joint motors no longer have a `VelocityBased` model. The new choices are `AccelerationBased` and `ForceBased`
|
||||||
which are more stable.
|
which are more stable.
|
||||||
- Calling the `.build()` function from builders (`RigidBodyBuilder`, `ColliderBuilder`, etc.) is no longer necessary
|
- Calling the `.build()` function from builders (`RigidBodyBuilder`, `ColliderBuilder`, etc.) is no longer necessary
|
||||||
whan adding them to sets. It is automatically called thanks to `Into<_>` implementations.
|
when adding them to sets. It is automatically called thanks to `Into<_>` implementations.
|
||||||
- The `ComponentSet` abstractions (and related `_generic` methods like `PhysicsPipeline::step_generic`) have been
|
- The `ComponentSet` abstractions (and related `_generic` methods like `PhysicsPipeline::step_generic`) have been
|
||||||
removed. Custom storage for colliders and rigid-bodies are no longer possible: use the built-in `RigidBodySet` and
|
removed. Custom storage for colliders and rigid-bodies are no longer possible: use the built-in `RigidBodySet` and
|
||||||
`ColliderSet` instead.
|
`ColliderSet` instead.
|
||||||
|
|
||||||
### Semantic modifications
|
### Semantic modifications
|
||||||
|
|
||||||
These are changes in the behavior of the physics engine that are not necessarily
|
These are changes in the behavior of the physics engine that are not necessarily
|
||||||
reflected by an API change. See [#304](https://github.com/dimforge/rapier/pull/304) for extensive details.
|
reflected by an API change. See [#304](https://github.com/dimforge/rapier/pull/304) for extensive details.
|
||||||
|
|
||||||
- `RigidBody::set_linvel` and `RigidBody::set_angvel` no longer modify the velocity of static bodies.
|
- `RigidBody::set_linvel` and `RigidBody::set_angvel` no longer modify the velocity of static bodies.
|
||||||
- `RigidBody::set_body_type` will reset the velocity of a rigid-body to zero if it is static.
|
- `RigidBody::set_body_type` will reset the velocity of a rigid-body to zero if it is static.
|
||||||
- Don’t automatically clear forces at the end of a timestep.
|
- Don’t automatically clear forces at the end of a timestep.
|
||||||
@@ -227,10 +350,11 @@ reflected by an API change. See [#304](https://github.com/dimforge/rapier/pull/3
|
|||||||
- Events `CollisionEvent::Stopped` are now generated after a collider is removed.
|
- Events `CollisionEvent::Stopped` are now generated after a collider is removed.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Significantly improve the API of joints by adding:
|
- Significantly improve the API of joints by adding:
|
||||||
* Builders based on the builder pattern.
|
* Builders based on the builder pattern.
|
||||||
* Getters and setters for all joints.
|
* Getters and setters for all joints.
|
||||||
* Method to convert a `GenericJoint` to one of the more specific joint type.
|
* Method to convert a `GenericJoint` to one of the more specific joint type.
|
||||||
- Improve stability of joint motors.
|
- Improve stability of joint motors.
|
||||||
- Adds a `bool` argument to `RigidBodySet::remove`. If set to `false`, the colliders attached to the rigid-body
|
- Adds a `bool` argument to `RigidBodySet::remove`. If set to `false`, the colliders attached to the rigid-body
|
||||||
won’t be automatically deleted (they will only be detached from the deleted rigid-body instead).
|
won’t be automatically deleted (they will only be detached from the deleted rigid-body instead).
|
||||||
@@ -240,12 +364,16 @@ reflected by an API change. See [#304](https://github.com/dimforge/rapier/pull/3
|
|||||||
renderer to debug the state of the physics engine.
|
renderer to debug the state of the physics engine.
|
||||||
|
|
||||||
## v0.12.0-alpha.0 (2 Jan. 2022)
|
## v0.12.0-alpha.0 (2 Jan. 2022)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `RigidBody::restrict_rotations` to properly take into account the axes to lock.
|
- Fixed `RigidBody::restrict_rotations` to properly take into account the axes to lock.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- All the impulse-based joints have been replaced by a single generic 6-Dofs joint in 3D
|
- All the impulse-based joints have been replaced by a single generic 6-Dofs joint in 3D
|
||||||
(or 3-Dofs joint in 2D) named `ImpulseJoint`. The `RevoluteJoint, PrismaticJoint, FixedJoint`,
|
(or 3-Dofs joint in 2D) named `ImpulseJoint`. The `RevoluteJoint, PrismaticJoint, FixedJoint`,
|
||||||
and `SphericalJoint` (formely named `BallJoint`) structures still exist but are just convenient
|
and `SphericalJoint` (formerly named `BallJoint`) structures still exist but are just convenient
|
||||||
ways to initialize the generic `ImpulseJoint`.
|
ways to initialize the generic `ImpulseJoint`.
|
||||||
- Our constraints solver has been modified. Before we used one velocity-based resolution followed
|
- Our constraints solver has been modified. Before we used one velocity-based resolution followed
|
||||||
by one position-based resolution. We are now using two velocity-based resolution: the first one
|
by one position-based resolution. We are now using two velocity-based resolution: the first one
|
||||||
@@ -253,63 +381,81 @@ reflected by an API change. See [#304](https://github.com/dimforge/rapier/pull/3
|
|||||||
code significantly while offering stiffer results.
|
code significantly while offering stiffer results.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added multibody joints: joints based on the reduced-coordinates modeling. These joints can’t
|
- Added multibody joints: joints based on the reduced-coordinates modeling. These joints can’t
|
||||||
violate their positional constraint.
|
violate their positional constraint.
|
||||||
- Implement `Default` for most of the struct that supports it.
|
- Implement `Default` for most of the struct that supports it.
|
||||||
|
|
||||||
## v0.11.1
|
## v0.11.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix a bug causing large moving colliders to miss some collisions after some time.
|
- 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 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
|
- Fix a bug where two colliders without parent would not have their collision computed even if the
|
||||||
appropriate flags were set.
|
appropriate flags were set.
|
||||||
|
|
||||||
## v0.11.0
|
## v0.11.0
|
||||||
|
|
||||||
Check out the user-guide for the JS/Typescript bindings for rapier. It has been fully rewritten and is now exhaustive!
|
Check out the user-guide for the JS/Typescript bindings for rapier. It has been fully rewritten and is now exhaustive!
|
||||||
Check it out on [rapier.rs](https://www.rapier.rs/docs/user_guides/javascript/getting_started_js)
|
Check it out on [rapier.rs](https://www.rapier.rs/docs/user_guides/javascript/getting_started_js)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Joint limits are now implemented for all joints that can support them (prismatic, revolute, and ball joints).
|
- Joint limits are now implemented for all joints that can support them (prismatic, revolute, and ball joints).
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- Switch to `nalgebra 0.29`.
|
- Switch to `nalgebra 0.29`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix the build of Rapier when targeting emscripten.
|
- Fix the build of Rapier when targeting emscripten.
|
||||||
|
|
||||||
## v0.10.1
|
## v0.10.1
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Add `Collider::set_translation_wrt_parent` to change the translation of a collider with respect to its parent rigid-body.
|
|
||||||
|
- Add `Collider::set_translation_wrt_parent` to change the translation of a collider with respect to its parent
|
||||||
|
rigid-body.
|
||||||
- Add `Collider::set_rotation_wrt_parent` to change the translation of a collider with respect to its parent rigid-body.
|
- Add `Collider::set_rotation_wrt_parent` to change the translation of a collider with respect to its parent rigid-body.
|
||||||
|
|
||||||
|
|
||||||
## v0.10.0
|
## v0.10.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Implement `Clone` for `IslandManager`.
|
- Implement `Clone` for `IslandManager`.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- `JointSet::insert` no longer takes the rigid-body set in its arguments.
|
- `JointSet::insert` no longer takes the rigid-body set in its arguments.
|
||||||
- Modify the testbed's plugin system to let plugins interact with the rendering.
|
- Modify the testbed's plugin system to let plugins interact with the rendering.
|
||||||
- Implement `PartialEq` for most collider and rigid-body components.
|
- Implement `PartialEq` for most collider and rigid-body components.
|
||||||
|
|
||||||
## v0.9.2
|
## v0.9.2
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Make the method JointSet::remove_joints_attached_to_rigid_body public so that it can can be called externally for
|
- Make the method JointSet::remove_joints_attached_to_rigid_body public so that it can can be called externally for
|
||||||
letting component-based Rapier integration call it to cleanup joints after a rigid-body removal.
|
letting component-based Rapier integration call it to cleanup joints after a rigid-body removal.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix a panic that could happen when the same collider is listed twice in the removed_colliders array.
|
- Fix a panic that could happen when the same collider is listed twice in the removed_colliders array.
|
||||||
|
|
||||||
|
|
||||||
## v0.9.1
|
## v0.9.1
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add `rapier::prelude::nalgebra` so that the `vector!` and `point!` macros work out-of-the-box after importing
|
- Add `rapier::prelude::nalgebra` so that the `vector!` and `point!` macros work out-of-the-box after importing
|
||||||
the prelude: `use rapier::prelude::*`
|
the prelude: `use rapier::prelude::*`
|
||||||
|
|
||||||
## v0.9.0
|
## v0.9.0
|
||||||
|
|
||||||
The user-guide has been fully rewritten and is now exhaustive! Check it out on [rapier.rs](https://rapier.rs/)
|
The user-guide has been fully rewritten and is now exhaustive! Check it out on [rapier.rs](https://rapier.rs/)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- A prelude has been added in order to simplify the most common imports. For example: `use rapier3d::prelude::*`
|
- A prelude has been added in order to simplify the most common imports. For example: `use rapier3d::prelude::*`
|
||||||
- Add `RigidBody::set_translation` and `RigidBody.translation()`.
|
- Add `RigidBody::set_translation` and `RigidBody.translation()`.
|
||||||
- Add `RigidBody::set_rotation` and `RigidBody.rotation()`.
|
- Add `RigidBody::set_rotation` and `RigidBody.rotation()`.
|
||||||
@@ -322,6 +468,7 @@ The user-guide has been fully rewritten and is now exhaustive! Check it out on [
|
|||||||
some SIMD code do generate NaNs which are filtered out by lane-wise selection).
|
some SIMD code do generate NaNs which are filtered out by lane-wise selection).
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
The use of `RigidBodySet, ColliderSet, RigidBody, Collider` is no longer mandatory. Rigid-bodies and colliders have
|
The use of `RigidBodySet, ColliderSet, RigidBody, Collider` is no longer mandatory. Rigid-bodies and colliders have
|
||||||
been split into multiple components that can be stored in a user-defined set. This is useful for integrating Rapier
|
been split into multiple components that can be stored in a user-defined set. This is useful for integrating Rapier
|
||||||
with other engines (for example this allows us to use Bevy's Query as our rigid-body/collider sets).
|
with other engines (for example this allows us to use Bevy's Query as our rigid-body/collider sets).
|
||||||
@@ -330,6 +477,7 @@ The `RigidBodySet, ColliderSet, RigidBody, Collider` are still the best option f
|
|||||||
provide their own component sets.
|
provide their own component sets.
|
||||||
|
|
||||||
#### Rigid-bodies
|
#### Rigid-bodies
|
||||||
|
|
||||||
- Renamed `BodyStatus` to `RigidBodyType`.
|
- Renamed `BodyStatus` to `RigidBodyType`.
|
||||||
- `RigidBodyBuilder::translation` now takes a vector instead of individual components.
|
- `RigidBodyBuilder::translation` now takes a vector instead of individual components.
|
||||||
- `RigidBodyBuilder::linvel` now takes a vector instead of individual components.
|
- `RigidBodyBuilder::linvel` now takes a vector instead of individual components.
|
||||||
@@ -339,6 +487,7 @@ provide their own component sets.
|
|||||||
`RigidBodyType::KinematicPositionBased`.
|
`RigidBodyType::KinematicPositionBased`.
|
||||||
|
|
||||||
#### Colliders
|
#### Colliders
|
||||||
|
|
||||||
- `Colliderbuilder::translation` now takes a vector instead of individual components.
|
- `Colliderbuilder::translation` now takes a vector instead of individual components.
|
||||||
- The way `PhysicsHooks` are enabled changed. Now, a physics hooks is executed if any of the two
|
- The way `PhysicsHooks` are enabled changed. Now, a physics hooks is executed if any of the two
|
||||||
colliders involved in the contact/intersection pair contains the related `PhysicsHooksFlag`.
|
colliders involved in the contact/intersection pair contains the related `PhysicsHooksFlag`.
|
||||||
@@ -360,10 +509,12 @@ provide their own component sets.
|
|||||||
- Fixed a bug where collision groups were ignored by CCD.
|
- Fixed a bug where collision groups were ignored by CCD.
|
||||||
|
|
||||||
#### Joints
|
#### Joints
|
||||||
|
|
||||||
- The fields `FixedJoint::local_anchor1` and `FixedJoint::local_anchor2` have been renamed to
|
- The fields `FixedJoint::local_anchor1` and `FixedJoint::local_anchor2` have been renamed to
|
||||||
`FixedJoint::local_frame1` and `FixedJoint::local_frame2`.
|
`FixedJoint::local_frame1` and `FixedJoint::local_frame2`.
|
||||||
|
|
||||||
#### Pipelines and others
|
#### Pipelines and others
|
||||||
|
|
||||||
- The field `ContactPair::pair` (which contained two collider handles) has been replaced by two
|
- The field `ContactPair::pair` (which contained two collider handles) has been replaced by two
|
||||||
fields: `ContactPair::collider1` and `ContactPair::collider2`.
|
fields: `ContactPair::collider1` and `ContactPair::collider2`.
|
||||||
- The list of active dynamic bodies is now retrieved with `IslandManager::active_dynamic_bodies`
|
- The list of active dynamic bodies is now retrieved with `IslandManager::active_dynamic_bodies`
|
||||||
@@ -377,55 +528,66 @@ provide their own component sets.
|
|||||||
- Rename `PhysicsHooksFlags` to `ActiveHooks`.
|
- Rename `PhysicsHooksFlags` to `ActiveHooks`.
|
||||||
- Add the contact pair as an argument to `EventHandler::handle_contact_event`
|
- Add the contact pair as an argument to `EventHandler::handle_contact_event`
|
||||||
|
|
||||||
|
|
||||||
## v0.8.0
|
## v0.8.0
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- Switch to nalgebra 0.26.
|
- Switch to nalgebra 0.26.
|
||||||
|
|
||||||
## v0.7.2
|
## v0.7.2
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Implement `Serialize` and `Deserialize` for the `CCDSolver`.
|
- Implement `Serialize` and `Deserialize` for the `CCDSolver`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix a crash that could happen after adding and then removing a collider right away,
|
- Fix a crash that could happen after adding and then removing a collider right away,
|
||||||
before stepping the simulation.
|
before stepping the simulation.
|
||||||
|
|
||||||
## v0.7.1
|
## v0.7.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed a bug in the broad-phase that could cause non-determinism after snapshot restoration.
|
- Fixed a bug in the broad-phase that could cause non-determinism after snapshot restoration.
|
||||||
|
|
||||||
## v0.7.0
|
## v0.7.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add the support of **Continuous Collision Detection** (CCD) to
|
- Add the support of **Continuous Collision Detection** (CCD) to
|
||||||
make sure that some fast-moving objects (chosen by the user) don't miss any contacts.
|
make sure that some fast-moving objects (chosen by the user) don't miss any contacts.
|
||||||
This is done by using motion-clamping, i.e., each fast-moving rigid-body with CCD enabled will
|
This is done by using motion-clamping, i.e., each fast-moving rigid-body with CCD enabled will
|
||||||
be stopped at the time where their first contact happen. This will result in some "time loss" for that
|
be stopped at the time where their first contact happen. This will result in some "time loss" for that
|
||||||
rigid-body. This loss of time can be reduced by increasing the maximum number of CCD substeps executed
|
rigid-body. This loss of time can be reduced by increasing the maximum number of CCD substeps executed
|
||||||
(the default being 1).
|
(the default being 1).
|
||||||
- Add the support of **collider modification**. Now, most of the characteristics of a collider can be
|
- Add the support of **collider modification**. Now, most of the characteristics of a collider can be
|
||||||
modified after the collider has been created.
|
modified after the collider has been created.
|
||||||
- We now use an **implicit friction cone** for handling friction, instead of a pyramidal approximation
|
- We now use an **implicit friction cone** for handling friction, instead of a pyramidal approximation
|
||||||
of the friction cone. This means that friction will now behave in a more isotropic way (i.e. more realistic
|
of the friction cone. This means that friction will now behave in a more isotropic way (i.e. more realistic
|
||||||
Coulomb friction).
|
Coulomb friction).
|
||||||
- Add the support of **custom filters** for the `QueryPipeline`. So far, interaction groups (bit masks)
|
- Add the support of **custom filters** for the `QueryPipeline`. So far, interaction groups (bit masks)
|
||||||
had to be used to exclude from colliders from a query made with the `QueryPipeline`. Now it is also
|
had to be used to exclude from colliders from a query made with the `QueryPipeline`. Now it is also
|
||||||
possible to provide a custom closures to apply arbitrary user-defined filters.
|
possible to provide a custom closures to apply arbitrary user-defined filters.
|
||||||
- It is now possible to solve penetrations using the velocity solver instead of (or alongside) the
|
- It is now possible to solve penetrations using the velocity solver instead of (or alongside) the
|
||||||
position solver (this is disabled by default, set `IntegrationParameters::velocity_based_erp` to
|
position solver (this is disabled by default, set `IntegrationParameters::velocity_based_erp` to
|
||||||
a value `> 0.0` to enable.).
|
a value `> 0.0` to enable.).
|
||||||
|
|
||||||
Added the methods:
|
Added the methods:
|
||||||
|
|
||||||
- `ColliderBuilder::halfspace` to create a collider with an unbounded plane shape.
|
- `ColliderBuilder::halfspace` to create a collider with an unbounded plane shape.
|
||||||
- `Collider::shape_mut` to get a mutable reference to its shape.
|
- `Collider::shape_mut` to get a mutable reference to its shape.
|
||||||
- `Collider::set_shape`, `::set_restitution_combine_rule`, `::set_position_wrt_parent`, `::set_collision_groups`
|
- `Collider::set_shape`, `::set_restitution_combine_rule`, `::set_position_wrt_parent`, `::set_collision_groups`
|
||||||
`::set_solver_groups` to change various properties of a collider after its creation.
|
`::set_solver_groups` to change various properties of a collider after its creation.
|
||||||
- `RigidBodyBuilder::ccd_enabled` to enable CCD for a rigid-body.
|
- `RigidBodyBuilder::ccd_enabled` to enable CCD for a rigid-body.
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|
||||||
- The `target_dist` argument of `QueryPipeline::cast_shape` was removed.
|
- The `target_dist` argument of `QueryPipeline::cast_shape` was removed.
|
||||||
- `RigidBodyBuilder::mass_properties` has been deprecated, replaced by `::additional_mass_properties`.
|
- `RigidBodyBuilder::mass_properties` has been deprecated, replaced by `::additional_mass_properties`.
|
||||||
- `RigidBodyBuilder::mass` has been deprecated, replaced by `::additional_mass`.
|
- `RigidBodyBuilder::mass` has been deprecated, replaced by `::additional_mass`.
|
||||||
- `RigidBodyBuilder::principal_angular_inertia` has been deprecated, replaced by `::additional_principal_angular_inertia`.
|
- `RigidBodyBuilder::principal_angular_inertia` has been deprecated, replaced
|
||||||
|
by `::additional_principal_angular_inertia`.
|
||||||
- The field `SolveContact::data` has been replaced by the fields `SolverContact::warmstart_impulse`,
|
- The field `SolveContact::data` has been replaced by the fields `SolverContact::warmstart_impulse`,
|
||||||
`SolverContact::warmstart_tangent_impulse`, and `SolverContact::prev_rhs`.
|
`SolverContact::warmstart_tangent_impulse`, and `SolverContact::prev_rhs`.
|
||||||
- All the fields of `IntegrationParameters` that we don't use have been removed.
|
- All the fields of `IntegrationParameters` that we don't use have been removed.
|
||||||
@@ -433,42 +595,53 @@ Added the methods:
|
|||||||
- `BroadPhase::maintain` has been removed. Use ` BroadPhase::update` directly.
|
- `BroadPhase::maintain` has been removed. Use ` BroadPhase::update` directly.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- The Broad-Phase algorithm has been completely reworked to support large colliders properly (until now
|
- The Broad-Phase algorithm has been completely reworked to support large colliders properly (until now
|
||||||
they could result in very large memory and CPU usage).
|
they could result in very large memory and CPU usage).
|
||||||
|
|
||||||
## v0.6.1
|
## v0.6.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix a determinism problem that may happen after snapshot restoration, if a rigid-body is sleeping at
|
- Fix a determinism problem that may happen after snapshot restoration, if a rigid-body is sleeping at
|
||||||
the time the snapshot is taken.
|
the time the snapshot is taken.
|
||||||
|
|
||||||
## v0.6.0
|
## v0.6.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- The support of **dominance groups** have been added. Each rigid-body is part of a dominance group in [-127; 127]
|
- The support of **dominance groups** have been added. Each rigid-body is part of a dominance group in [-127; 127]
|
||||||
(the default is 0). If two rigid-body are in contact, the one with the highest dominance will act as if it has
|
(the default is 0). If two rigid-body are in contact, the one with the highest dominance will act as if it has
|
||||||
an infinite mass, making it immune to the forces the other body would apply on it. See [#122](https://github.com/dimforge/rapier/pull/122)
|
an infinite mass, making it immune to the forces the other body would apply on it.
|
||||||
for further details.
|
See [#122](https://github.com/dimforge/rapier/pull/122)
|
||||||
|
for further details.
|
||||||
- The support for **contact modification** has been added. This can bee used to simulate conveyor belts,
|
- The support for **contact modification** has been added. This can bee used to simulate conveyor belts,
|
||||||
one-way platforms and other non-physical effects. It can also be used to simulate materials with
|
one-way platforms and other non-physical effects. It can also be used to simulate materials with
|
||||||
variable friction and restitution coefficient on a single collider. See [#120](https://github.com/dimforge/rapier/pull/120)
|
variable friction and restitution coefficient on a single collider.
|
||||||
for further details.
|
See [#120](https://github.com/dimforge/rapier/pull/120)
|
||||||
|
for further details.
|
||||||
- The support for **joint motors** have been added. This can be used to control the position and/or
|
- The support for **joint motors** have been added. This can be used to control the position and/or
|
||||||
velocity of a joint based on a spring-like equation. See [#119](https://github.com/dimforge/rapier/pull/119)
|
velocity of a joint based on a spring-like equation. See [#119](https://github.com/dimforge/rapier/pull/119)
|
||||||
for further details.
|
for further details.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- The `ContactPairFilter` and `IntersectionPairFilter` traits have been removed. They are both
|
- The `ContactPairFilter` and `IntersectionPairFilter` traits have been removed. They are both
|
||||||
combined in a single new trait: `PhysicsHooks`.
|
combined in a single new trait: `PhysicsHooks`.
|
||||||
|
|
||||||
## v0.5.0
|
## v0.5.0
|
||||||
|
|
||||||
In this release we are dropping `ncollide` and use our new crate [`parry`](https://parry.rs)
|
In this release we are dropping `ncollide` and use our new crate [`parry`](https://parry.rs)
|
||||||
instead! This comes with a lot of new features, as well as two new crates: `rapier2d-f64` and
|
instead! This comes with a lot of new features, as well as two new crates: `rapier2d-f64` and
|
||||||
`rapier3d-f64` for physics simulation with 64-bits floats.
|
`rapier3d-f64` for physics simulation with 64-bits floats.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added a `RAPIER.version()` function at the root of the package to retrieve the version of Rapier
|
- Added a `RAPIER.version()` function at the root of the package to retrieve the version of Rapier
|
||||||
as a string.
|
as a string.
|
||||||
|
|
||||||
Several geometric queries have been added to the `QueryPipeline`:
|
Several geometric queries have been added to the `QueryPipeline`:
|
||||||
|
|
||||||
- `QueryPipeline::intersections_with_ray`: get all colliders intersecting a ray.
|
- `QueryPipeline::intersections_with_ray`: get all colliders intersecting a ray.
|
||||||
- `QueryPipeline::intersection_with_shape`: get one collider intersecting a shape.
|
- `QueryPipeline::intersection_with_shape`: get one collider intersecting a shape.
|
||||||
- `QueryPipeline::project_point`: get the projection of a point on the closest collider.
|
- `QueryPipeline::project_point`: get the projection of a point on the closest collider.
|
||||||
@@ -478,22 +651,27 @@ Several geometric queries have been added to the `QueryPipeline`:
|
|||||||
- `QueryPipeline::intersections_with_shape`: get all the colliders intersecting a shape.
|
- `QueryPipeline::intersections_with_shape`: get all the colliders intersecting a shape.
|
||||||
|
|
||||||
Several new shape types are now supported:
|
Several new shape types are now supported:
|
||||||
|
|
||||||
- `RoundCuboid`, `Segment`, `Triangle`, `RoundTriangle`, `Polyline`, `ConvexPolygon` (2D only),
|
- `RoundCuboid`, `Segment`, `Triangle`, `RoundTriangle`, `Polyline`, `ConvexPolygon` (2D only),
|
||||||
`RoundConvexPolygon` (2D only), `ConvexPolyhedron` (3D only), `RoundConvexPolyhedron` (3D only),
|
`RoundConvexPolygon` (2D only), `ConvexPolyhedron` (3D only), `RoundConvexPolyhedron` (3D only),
|
||||||
`RoundCone` (3D only).
|
`RoundCone` (3D only).
|
||||||
|
|
||||||
It is possible to build `ColliderDesc` using these new shapes:
|
It is possible to build `ColliderDesc` using these new shapes:
|
||||||
|
|
||||||
- `ColliderBuilder::round_cuboid`, `ColliderBuilder::segment`, `ColliderBuilder::triangle`, `ColliderBuilder::round_triangle`,
|
- `ColliderBuilder::round_cuboid`, `ColliderBuilder::segment`, `ColliderBuilder::triangle`, `ColliderBuilder::round_triangle`,
|
||||||
`ColliderBuilder::convex_hull`, `ColliderBuilder::round_convex_hull`, `ColliderBuilder::polyline`,
|
`ColliderBuilder::convex_hull`, `ColliderBuilder::round_convex_hull`, `ColliderBuilder::polyline`,
|
||||||
`ColliderBuilder::convex_decomposition`, `ColliderBuilder::round_convex_decomposition`,
|
`ColliderBuilder::convex_decomposition`, `ColliderBuilder::round_convex_decomposition`,
|
||||||
`ColliderBuilder::convex_polyline` (2D only), `ColliderBuilder::round_convex_polyline` (2D only),
|
`ColliderBuilder::convex_polyline` (2D only), `ColliderBuilder::round_convex_polyline` (2D only),
|
||||||
`ColliderBuilder::convex_mesh` (3D only),`ColliderBuilder::round_convex_mesh` (3D only), `ColliderBuilder::round_cone` (3D only).
|
`ColliderBuilder::convex_mesh` (3D only),`ColliderBuilder::round_convex_mesh` (3D
|
||||||
|
only), `ColliderBuilder::round_cone` (3D only).
|
||||||
|
|
||||||
It is possible to specify different rules for combining friction and restitution coefficients
|
It is possible to specify different rules for combining friction and restitution coefficients
|
||||||
of the two colliders involved in a contact with:
|
of the two colliders involved in a contact with:
|
||||||
|
|
||||||
- `ColliderDesc::friction_combine_rule`, and `ColliderDesc::restitution_combine_rule`.
|
- `ColliderDesc::friction_combine_rule`, and `ColliderDesc::restitution_combine_rule`.
|
||||||
|
|
||||||
Various RigidBody-related getter and setters have been added:
|
Various RigidBody-related getter and setters have been added:
|
||||||
|
|
||||||
- `RigidBodyBuilder::gravity_scale`, `RigidBody::gravity_scale`, `RigidBody::set_gravity_scale` to get/set the scale
|
- `RigidBodyBuilder::gravity_scale`, `RigidBody::gravity_scale`, `RigidBody::set_gravity_scale` 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.
|
factor applied to the gravity affecting a rigid-body. Setting this to 0.0 will make the rigid-body ignore gravity.
|
||||||
- `RigidBody::set_linear_damping` and `RigidBody::set_angular_damping` to set the linear and angular damping of
|
- `RigidBody::set_linear_damping` and `RigidBody::set_angular_damping` to set the linear and angular damping of
|
||||||
@@ -502,42 +680,54 @@ Various RigidBody-related getter and setters have been added:
|
|||||||
boolean arguments previously passed to `.set_principal_angular_inertia`.
|
boolean arguments previously passed to `.set_principal_angular_inertia`.
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
|
|
||||||
Breaking changes related to contacts:
|
Breaking changes related to contacts:
|
||||||
- The way contacts are represented changed. Refer to the documentation of `parry::query::ContactManifold`, `parry::query::TrackedContact`
|
|
||||||
|
- The way contacts are represented changed. Refer to the documentation
|
||||||
|
of `parry::query::ContactManifold`, `parry::query::TrackedContact`
|
||||||
and `rapier::geometry::ContactManifoldData` and `rapier::geometry::ContactData` for details.
|
and `rapier::geometry::ContactManifoldData` and `rapier::geometry::ContactData` for details.
|
||||||
|
|
||||||
Breaking changes related to rigid-bodies:
|
Breaking changes related to rigid-bodies:
|
||||||
- The `RigidBodyDesc.setMass` takes only one argument now. Use `RigidBodyDesc.lockTranslations` to lock the translational
|
|
||||||
|
- The `RigidBodyDesc.setMass` takes only one argument now. Use `RigidBodyDesc.lockTranslations` to lock the
|
||||||
|
translational
|
||||||
motion of the rigid-body.
|
motion of the rigid-body.
|
||||||
- The `RigidBodyDesc.setPrincipalAngularInertia` no longer have boolean parameters to lock rotations.
|
- 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.
|
Use `RigidBodyDesc.lockRotations` or `RigidBodyDesc.restrictRotations` to lock the rotational motion of the
|
||||||
|
rigid-body.
|
||||||
|
|
||||||
Breaking changes related to colliders:
|
Breaking changes related to colliders:
|
||||||
|
|
||||||
- The collider shape type has been renamed from `ColliderShape` to `SharedShape` (now part of the Parry crate).
|
- The collider shape type has been renamed from `ColliderShape` to `SharedShape` (now part of the Parry crate).
|
||||||
- The `Polygon` shape no longer exists. For a 2D convex polygon, use a `ConvexPolygon` instead.
|
- The `Polygon` shape no longer exists. For a 2D convex polygon, use a `ConvexPolygon` instead.
|
||||||
- All occurrences of `Trimesh` have been replaced by `TriMesh` (note the change in case).
|
- All occurrences of `Trimesh` have been replaced by `TriMesh` (note the change in case).
|
||||||
|
|
||||||
Breaking changes related to events:
|
Breaking changes related to events:
|
||||||
|
|
||||||
- Rename all occurrences of `Proximity` to `Intersection`.
|
- Rename all occurrences of `Proximity` to `Intersection`.
|
||||||
- The `Proximity` enum has been removed, it's replaced by a boolean.
|
- The `Proximity` enum has been removed, it's replaced by a boolean.
|
||||||
|
|
||||||
## v0.4.2
|
## v0.4.2
|
||||||
|
|
||||||
- Fix a bug in angular inertia tensor computation that could cause rotations not to
|
- Fix a bug in angular inertia tensor computation that could cause rotations not to
|
||||||
work properly.
|
work properly.
|
||||||
- Add `RigidBody::set_mass_properties` to set the mass properties of an already-constructed
|
- Add `RigidBody::set_mass_properties` to set the mass properties of an already-constructed
|
||||||
rigid-body.
|
rigid-body.
|
||||||
|
|
||||||
## v0.4.1
|
## v0.4.1
|
||||||
|
|
||||||
- The `RigidBodyBuilder::principal_inertia` method has been deprecated and renamed to
|
- The `RigidBodyBuilder::principal_inertia` method has been deprecated and renamed to
|
||||||
`principal_angular_inertia` for clarity.
|
`principal_angular_inertia` for clarity.
|
||||||
|
|
||||||
## v0.4.0
|
## v0.4.0
|
||||||
|
|
||||||
- The rigid-body `linvel`, `angvel`, and `position` fields are no longer public. Access using
|
- The rigid-body `linvel`, `angvel`, and `position` fields are no longer public. Access using
|
||||||
their corresponding getters/setters. For example: `rb.linvel()`, `rb.set_linvel(vel, true)`.
|
their corresponding getters/setters. For example: `rb.linvel()`, `rb.set_linvel(vel, true)`.
|
||||||
- Add `RigidBodyBuilder::sleeping(true)` to allow the creation of a rigid-body that is asleep
|
- Add `RigidBodyBuilder::sleeping(true)` to allow the creation of a rigid-body that is asleep
|
||||||
at initialization-time.
|
at initialization-time.
|
||||||
|
|
||||||
#### Locking translation and rotations of a rigid-body
|
#### Locking translation and rotations of a rigid-body
|
||||||
|
|
||||||
- Add `RigidBodyBuilder::lock_rotations` to prevent a rigid-body from rotating because of forces.
|
- Add `RigidBodyBuilder::lock_rotations` to prevent a rigid-body from rotating because of forces.
|
||||||
- Add `RigidBodyBuilder::lock_translations` to prevent a rigid-body from translating because of forces.
|
- Add `RigidBodyBuilder::lock_translations` to prevent a rigid-body from translating because of forces.
|
||||||
- Add `RigidBodyBuilder::principal_inertia` for setting the principal inertia of a rigid-body, and/or
|
- Add `RigidBodyBuilder::principal_inertia` for setting the principal inertia of a rigid-body, and/or
|
||||||
@@ -546,6 +736,7 @@ Breaking changes related to events:
|
|||||||
contributions should be taken into account in the future too.
|
contributions should be taken into account in the future too.
|
||||||
|
|
||||||
#### Reading contact and proximity information
|
#### Reading contact and proximity information
|
||||||
|
|
||||||
- Add `NarrowPhase::contacts_with` and `NarrowPhase::proximities_with` to retrieve all the contact
|
- Add `NarrowPhase::contacts_with` and `NarrowPhase::proximities_with` to retrieve all the contact
|
||||||
pairs and proximity pairs involving a specific collider.
|
pairs and proximity pairs involving a specific collider.
|
||||||
- Add `NarrowPhase::contact_pair` and `NarrowPhase::proximity_pair` to retrieve one specific contact
|
- Add `NarrowPhase::contact_pair` and `NarrowPhase::proximity_pair` to retrieve one specific contact
|
||||||
@@ -554,6 +745,7 @@ Breaking changes related to events:
|
|||||||
proximity pairs detected by the narrow-phase.
|
proximity pairs detected by the narrow-phase.
|
||||||
|
|
||||||
## v0.3.2
|
## v0.3.2
|
||||||
|
|
||||||
- Add linear and angular damping. The damping factor can be set with `RigidBodyBuilder::linear_damping` and
|
- Add linear and angular damping. The damping factor can be set with `RigidBodyBuilder::linear_damping` and
|
||||||
`RigidBodyBuilder::angular_damping`.
|
`RigidBodyBuilder::angular_damping`.
|
||||||
- Implement `Clone` for almost everything that can be worth cloning.
|
- Implement `Clone` for almost everything that can be worth cloning.
|
||||||
@@ -562,34 +754,43 @@ Breaking changes related to events:
|
|||||||
- The restitution coefficient of colliders is now taken into account by the physics solver.
|
- The restitution coefficient of colliders is now taken into account by the physics solver.
|
||||||
|
|
||||||
## v0.3.1
|
## v0.3.1
|
||||||
|
|
||||||
- Fix non-determinism problem when using triangle-meshes, cone, cylinders, or capsules.
|
- Fix non-determinism problem when using triangle-meshes, cone, cylinders, or capsules.
|
||||||
- Add `JointSet::remove(...)` to remove a joint from the `JointSet`.
|
- Add `JointSet::remove(...)` to remove a joint from the `JointSet`.
|
||||||
|
|
||||||
## v0.3.0
|
## v0.3.0
|
||||||
|
|
||||||
- Collider shapes are now trait-objects instead of a `Shape` enum.
|
- Collider shapes are now trait-objects instead of a `Shape` enum.
|
||||||
- Add a user-defined `u128` to each colliders and rigid-bodies for storing user data.
|
- Add a user-defined `u128` to each colliders and rigid-bodies for storing user data.
|
||||||
- Add the support for `Cylinder`, `RoundCylinder`, and `Cone` shapes.
|
- Add the support for `Cylinder`, `RoundCylinder`, and `Cone` shapes.
|
||||||
- Added the support for collision filtering based on bit masks (often known as collision groups, collision masks, or
|
- Added the support for collision filtering based on bit masks (often known as collision groups, collision masks, or
|
||||||
collision layers in other physics engines). Each collider has two groups. Their `collision_groups` is used for filtering
|
collision layers in other physics engines). Each collider has two groups. Their `collision_groups` is used for
|
||||||
what pair of colliders should have their contacts computed by the narrow-phase. Their `solver_groups` is used for filtering
|
filtering
|
||||||
|
what pair of colliders should have their contacts computed by the narrow-phase. Their `solver_groups` is used for
|
||||||
|
filtering
|
||||||
what pair of colliders should have their contact forces computed by the constraints solver.
|
what pair of colliders should have their contact forces computed by the constraints solver.
|
||||||
- Collision groups can also be used to filter what collider should be hit by a ray-cast performed by the `QueryPipeline`.
|
- Collision groups can also be used to filter what collider should be hit by a ray-cast performed by
|
||||||
|
the `QueryPipeline`.
|
||||||
- Added collision filters based on user-defined trait-objects. This adds two traits `ContactPairFilter` and
|
- Added collision filters based on user-defined trait-objects. This adds two traits `ContactPairFilter` and
|
||||||
`ProximityPairFilter` that allows user-defined logic for determining if two colliders/sensors are allowed to interact.
|
`ProximityPairFilter` that allows user-defined logic for determining if two colliders/sensors are allowed to interact.
|
||||||
- The `PhysicsPipeline::step` method now takes two additional arguments: the optional `&ContactPairFilter` and `&ProximityPairFilter`
|
- The `PhysicsPipeline::step` method now takes two additional arguments: the optional `&ContactPairFilter`
|
||||||
for filtering contact and proximity pairs.
|
and `&ProximityPairFilter`
|
||||||
|
for filtering contact and proximity pairs.
|
||||||
|
|
||||||
## v0.2.1
|
## v0.2.1
|
||||||
|
|
||||||
- Fix panic in TriMesh construction and QueryPipeline update caused by a stack overflow or a subtraction underflow.
|
- Fix panic in TriMesh construction and QueryPipeline update caused by a stack overflow or a subtraction underflow.
|
||||||
|
|
||||||
## v0.2.0
|
## v0.2.0
|
||||||
|
|
||||||
The most significant change on this version is the addition of the `QueryPipeline` responsible for performing
|
The most significant change on this version is the addition of the `QueryPipeline` responsible for performing
|
||||||
scene-wide queries. So far only ray-casting has been implemented.
|
scene-wide queries. So far only ray-casting has been implemented.
|
||||||
|
|
||||||
- Add `ColliderSet::remove(...)` to remove a collider from the `ColliderSet`.
|
- Add `ColliderSet::remove(...)` to remove a collider from the `ColliderSet`.
|
||||||
- Replace `PhysicsPipeline::remove_rigid_body` by `RigidBodySet::remove`.
|
- Replace `PhysicsPipeline::remove_rigid_body` by `RigidBodySet::remove`.
|
||||||
- The `JointSet.iter()` now returns an iterator yielding `(JointHandle, &Joint)` instead of just `&Joint`.
|
- The `JointSet.iter()` now returns an iterator yielding `(JointHandle, &Joint)` instead of just `&Joint`.
|
||||||
- Add `ColliderDesc::translation(...)` to set the translation of a collider relative to the rigid-body it is attached to.
|
- Add `ColliderDesc::translation(...)` to set the translation of a collider relative to the rigid-body it is attached
|
||||||
|
to.
|
||||||
- Add `ColliderDesc::rotation(...)` to set the rotation of a collider relative to the rigid-body it is attached to.
|
- Add `ColliderDesc::rotation(...)` to set the rotation of a collider relative to the rigid-body it is attached to.
|
||||||
- Add `ColliderDesc::position(...)` to set the position of a collider relative to the rigid-body it is attached to.
|
- Add `ColliderDesc::position(...)` to set the position of a collider relative to the rigid-body it is attached to.
|
||||||
- Add `Collider::position_wrt_parent()` to get the position of a collider relative to the rigid-body it is attached to.
|
- Add `Collider::position_wrt_parent()` to get the position of a collider relative to the rigid-body it is attached to.
|
||||||
@@ -597,8 +798,8 @@ scene-wide queries. So far only ray-casting has been implemented.
|
|||||||
- Deprecate `Collider::delta()` in favor of the new `Collider::position_wrt_parent()`.
|
- Deprecate `Collider::delta()` in favor of the new `Collider::position_wrt_parent()`.
|
||||||
- Fix multiple issues occurring when having colliders resulting in a non-zero center-of-mass.
|
- Fix multiple issues occurring when having colliders resulting in a non-zero center-of-mass.
|
||||||
- Fix a crash happening when removing a rigid-body with a collider, stepping the simulation, adding another rigid-body
|
- Fix a crash happening when removing a rigid-body with a collider, stepping the simulation, adding another rigid-body
|
||||||
with a collider, and stepping the simulation again.
|
with a collider, and stepping the simulation again.
|
||||||
- Fix NaN when detection contacts between two polygonal faces where one has a normal perfectly perpendicular to the
|
- Fix NaN when detection contacts between two polygonal faces where one has a normal perfectly perpendicular to the
|
||||||
separating vector.
|
separating vector.
|
||||||
- Fix bug collision detection between trimeshes and other shapes. The bug appeared depending on whether the trimesh
|
- Fix bug collision detection between trimeshes and other shapes. The bug appeared depending on whether the trimesh
|
||||||
collider was added before the other shape's collider or after.
|
collider was added before the other shape's collider or after.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [ "crates/rapier2d", "crates/rapier2d-f64", "crates/rapier_testbed2d", "crates/rapier_testbed2d-f64", "examples2d", "benchmarks2d",
|
members = ["crates/rapier2d", "crates/rapier2d-f64", "crates/rapier_testbed2d", "crates/rapier_testbed2d-f64", "examples2d", "benchmarks2d",
|
||||||
"crates/rapier3d", "crates/rapier3d-f64", "crates/rapier_testbed3d", "crates/rapier_testbed3d-f64", "examples3d", "examples3d-f64", "benchmarks3d" ]
|
"crates/rapier3d", "crates/rapier3d-f64", "crates/rapier_testbed3d", "crates/rapier_testbed3d-f64", "examples3d", "examples3d-f64", "benchmarks3d"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -41,18 +41,22 @@ programming language, by the [Dimforge](https://dimforge.com) organization. It i
|
|||||||
and open-source!
|
and open-source!
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
We update our roadmap at the beginning of each year. Our 2021 roadmap can be seen
|
We update our roadmap at the beginning of each year. Our 2021 roadmap can be seen
|
||||||
[there](https://www.dimforge.com/blog/2021/01/01/physics-simulation-with-rapier-2021-roadmap/#rapier-roadmap-for-2021).
|
[there](https://www.dimforge.com/blog/2021/01/01/physics-simulation-with-rapier-2021-roadmap/#rapier-roadmap-for-2021).
|
||||||
We regularly give updates about our progress on [our blog](https://www.dimforge.com/blog).
|
We regularly give updates about our progress on [our blog](https://www.dimforge.com/blog).
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
The easiest way to get started with Rapier is to:
|
The easiest way to get started with Rapier is to:
|
||||||
|
|
||||||
1. Read the [user-guides](https://www.rapier.rs/docs/).
|
1. Read the [user-guides](https://www.rapier.rs/docs/).
|
||||||
2. Play with the examples: `cargo run --release --bin all_examples2` and `cargo run --release --bin all_examples3`.
|
2. Play with the examples: `cargo run --release --bin all_examples2` and `cargo run --release --bin all_examples3`.
|
||||||
Their source code are available on the `examples2d/` and `examples3d/` directory.
|
Their source code are available on the `examples2d/` and `examples3d/` directory.
|
||||||
3. Don't hesitate to ask for help on [Discord](https://discord.gg/vt9DJSW), or by opening an issue on GitHub.
|
3. Don't hesitate to ask for help on [Discord](https://discord.gg/vt9DJSW), or by opening an issue on GitHub.
|
||||||
|
|
||||||
## Resources and discussions
|
## Resources and discussions
|
||||||
|
|
||||||
- [Dimforge](https://dimforge.com): See all the open-source projects we are working on! Follow our announcements
|
- [Dimforge](https://dimforge.com): See all the open-source projects we are working on! Follow our announcements
|
||||||
on our [blog](https://www.dimforge.com/blog).
|
on our [blog](https://www.dimforge.com/blog).
|
||||||
- [User guide](https://www.rapier.rs/docs/): Learn to use Rapier in your project by reading the official User Guides.
|
- [User guide](https://www.rapier.rs/docs/): Learn to use Rapier in your project by reading the official User Guides.
|
||||||
@@ -63,23 +67,3 @@ The easiest way to get started with Rapier is to:
|
|||||||
Please make sure to familiarize yourself with our [Code of Conduct](CODE_OF_CONDUCT.md)
|
Please make sure to familiarize yourself with our [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||||
and our [Contribution Guidelines](CONTRIBUTING.md) before contributing or participating in
|
and our [Contribution Guidelines](CONTRIBUTING.md) before contributing or participating in
|
||||||
discussions with the community.
|
discussions with the community.
|
||||||
|
|
||||||
|
|
||||||
## Acknowledgements
|
|
||||||
Rapier is supported by our **platinum** sponsors:
|
|
||||||
<p>
|
|
||||||
<a href="https://embark-studios.com">
|
|
||||||
<img src="https://www.embark.dev/img/logo_black.png" width="301px">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
And our gold sponsors:
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://fragcolor.com">
|
|
||||||
<img src="https://dimforge.com/img/fragcolor_logo2_color_black.svg" width="300px">
|
|
||||||
</a>
|
|
||||||
<a href="https://resolutiongames.com/">
|
|
||||||
<img src="https://dimforge.com/img/logo_resolution_games.png" width="300px" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
@@ -17,6 +17,7 @@ mod joint_ball2;
|
|||||||
mod joint_fixed2;
|
mod joint_fixed2;
|
||||||
mod joint_prismatic2;
|
mod joint_prismatic2;
|
||||||
mod pyramid2;
|
mod pyramid2;
|
||||||
|
mod vertical_stacks2;
|
||||||
|
|
||||||
fn demo_name_from_command_line() -> Option<String> {
|
fn demo_name_from_command_line() -> Option<String> {
|
||||||
let mut args = std::env::args();
|
let mut args = std::env::args();
|
||||||
@@ -57,6 +58,7 @@ pub fn main() {
|
|||||||
("Convex polygons", convex_polygons2::init_world),
|
("Convex polygons", convex_polygons2::init_world),
|
||||||
("Heightfield", heightfield2::init_world),
|
("Heightfield", heightfield2::init_world),
|
||||||
("Pyramid", pyramid2::init_world),
|
("Pyramid", pyramid2::init_world),
|
||||||
|
("Verticals stacks", vertical_stacks2::init_world),
|
||||||
("(Stress test) joint ball", joint_ball2::init_world),
|
("(Stress test) joint ball", joint_ball2::init_world),
|
||||||
("(Stress test) joint fixed", joint_fixed2::init_world),
|
("(Stress test) joint fixed", joint_fixed2::init_world),
|
||||||
(
|
(
|
||||||
@@ -66,7 +68,7 @@ pub fn main() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Lexicographic sort, with stress tests moved at the end of the list.
|
// Lexicographic sort, with stress tests moved at the end of the list.
|
||||||
builders.sort_by(|a, b| match (a.0.starts_with('('), b.0.starts_with(')')) {
|
builders.sort_by(|a, b| match (a.0.starts_with('('), b.0.starts_with('(')) {
|
||||||
(true, true) | (false, false) => a.0.cmp(b.0),
|
(true, true) | (false, false) => a.0.cmp(b.0),
|
||||||
(true, false) => Ordering::Greater,
|
(true, false) => Ordering::Greater,
|
||||||
(false, true) => Ordering::Less,
|
(false, true) => Ordering::Less,
|
||||||
|
|||||||
61
benchmarks2d/vertical_stacks2.rs
Normal file
61
benchmarks2d/vertical_stacks2.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let num = 80;
|
||||||
|
let rad = 0.5;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_size = num as f32 * rad * 10.0;
|
||||||
|
let ground_thickness = 1.0;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed();
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(ground_size, ground_thickness);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
|
||||||
|
let shiftx_centerx = [
|
||||||
|
(rad * 2.0 + 0.0002, -(num as f32) * rad * 2.0 * 1.5),
|
||||||
|
(rad * 2.0 + rad, num as f32 * rad * 2.0 * 1.5),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (shiftx, centerx) in shiftx_centerx {
|
||||||
|
let shifty = rad * 2.0;
|
||||||
|
let centery = shifty / 2.0 + ground_thickness;
|
||||||
|
|
||||||
|
for i in 0..num {
|
||||||
|
for j in 0usize..1 + i * 2 {
|
||||||
|
let fj = j as f32;
|
||||||
|
let fi = i as f32;
|
||||||
|
let x = (fj - fi) * shiftx + centerx;
|
||||||
|
let y = (num as f32 - fi - 1.0) * shifty + centery;
|
||||||
|
|
||||||
|
// Build the rigid body.
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(rad, rad);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 5.0);
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ mod joint_fixed3;
|
|||||||
mod joint_prismatic3;
|
mod joint_prismatic3;
|
||||||
mod joint_revolute3;
|
mod joint_revolute3;
|
||||||
mod keva3;
|
mod keva3;
|
||||||
|
mod many_pyramids3;
|
||||||
|
mod many_sleep3;
|
||||||
mod many_static3;
|
mod many_static3;
|
||||||
mod pyramid3;
|
mod pyramid3;
|
||||||
mod stacks3;
|
mod stacks3;
|
||||||
@@ -56,6 +58,7 @@ pub fn main() {
|
|||||||
("Compound", compound3::init_world),
|
("Compound", compound3::init_world),
|
||||||
("Convex polyhedron", convex_polyhedron3::init_world),
|
("Convex polyhedron", convex_polyhedron3::init_world),
|
||||||
("Many static", many_static3::init_world),
|
("Many static", many_static3::init_world),
|
||||||
|
("Many sleep", many_sleep3::init_world),
|
||||||
("Heightfield", heightfield3::init_world),
|
("Heightfield", heightfield3::init_world),
|
||||||
("Stacks", stacks3::init_world),
|
("Stacks", stacks3::init_world),
|
||||||
("Pyramid", pyramid3::init_world),
|
("Pyramid", pyramid3::init_world),
|
||||||
@@ -64,6 +67,7 @@ pub fn main() {
|
|||||||
("ImpulseJoint fixed", joint_fixed3::init_world),
|
("ImpulseJoint fixed", joint_fixed3::init_world),
|
||||||
("ImpulseJoint revolute", joint_revolute3::init_world),
|
("ImpulseJoint revolute", joint_revolute3::init_world),
|
||||||
("ImpulseJoint prismatic", joint_prismatic3::init_world),
|
("ImpulseJoint prismatic", joint_prismatic3::init_world),
|
||||||
|
("Many pyramids", many_pyramids3::init_world),
|
||||||
("Keva tower", keva3::init_world),
|
("Keva tower", keva3::init_world),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
1 => ColliderBuilder::ball(rad),
|
1 => ColliderBuilder::ball(rad),
|
||||||
// Rounded cylinders are much more efficient that cylinder, even if the
|
// Rounded cylinders are much more efficient that cylinder, even if the
|
||||||
// rounding margin is small.
|
// rounding margin is small.
|
||||||
// 2 => ColliderBuilder::round_cylinder(rad, rad, rad / 10.0),
|
2 => ColliderBuilder::round_cylinder(rad, rad, rad / 10.0),
|
||||||
// 3 => ColliderBuilder::cone(rad, rad),
|
3 => ColliderBuilder::cone(rad, rad),
|
||||||
_ => ColliderBuilder::capsule_y(rad, rad),
|
_ => ColliderBuilder::capsule_y(rad, rad),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
80
benchmarks3d/many_pyramids3.rs
Normal file
80
benchmarks3d/many_pyramids3.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
use rapier3d::prelude::*;
|
||||||
|
use rapier_testbed3d::Testbed;
|
||||||
|
|
||||||
|
fn create_pyramid(
|
||||||
|
bodies: &mut RigidBodySet,
|
||||||
|
colliders: &mut ColliderSet,
|
||||||
|
offset: Vector<f32>,
|
||||||
|
stack_height: usize,
|
||||||
|
rad: f32,
|
||||||
|
) {
|
||||||
|
let shift = rad * 2.0;
|
||||||
|
|
||||||
|
for i in 0usize..stack_height {
|
||||||
|
for j in i..stack_height {
|
||||||
|
let fj = j as f32;
|
||||||
|
let fi = i as f32;
|
||||||
|
let x = (fi * shift / 2.0) + (fj - fi) * shift;
|
||||||
|
let y = fi * shift;
|
||||||
|
|
||||||
|
// Build the rigid body.
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y, 0.0] + offset);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(rad, rad, rad);
|
||||||
|
colliders.insert_with_parent(collider, handle, bodies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let rad = 0.5;
|
||||||
|
let pyramid_count = 40;
|
||||||
|
let spacing = 4.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_size = 50.0;
|
||||||
|
let ground_height = 0.1;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(
|
||||||
|
ground_size,
|
||||||
|
ground_height,
|
||||||
|
pyramid_count as f32 * spacing / 2.0 + ground_size,
|
||||||
|
);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
for pyramid_index in 0..pyramid_count {
|
||||||
|
let bottomy = rad;
|
||||||
|
create_pyramid(
|
||||||
|
&mut bodies,
|
||||||
|
&mut colliders,
|
||||||
|
vector![
|
||||||
|
0.0,
|
||||||
|
bottomy,
|
||||||
|
(pyramid_index as f32 - pyramid_count as f32 / 2.0) * spacing
|
||||||
|
],
|
||||||
|
20,
|
||||||
|
rad,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![100.0, 100.0, 100.0], Point::origin());
|
||||||
|
}
|
||||||
54
benchmarks3d/many_sleep3.rs
Normal file
54
benchmarks3d/many_sleep3.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use rapier3d::prelude::*;
|
||||||
|
use rapier_testbed3d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the balls
|
||||||
|
*/
|
||||||
|
let num = 50;
|
||||||
|
let rad = 1.0;
|
||||||
|
|
||||||
|
let shift = rad * 2.0 + 1.0;
|
||||||
|
let centerx = shift * (num as f32) / 2.0;
|
||||||
|
let centery = shift / 2.0;
|
||||||
|
let centerz = shift * (num as f32) / 2.0;
|
||||||
|
|
||||||
|
for i in 0..num {
|
||||||
|
for j in 0usize..num {
|
||||||
|
for k in 0..num {
|
||||||
|
let x = i as f32 * shift - centerx;
|
||||||
|
let y = j as f32 * shift + centery;
|
||||||
|
let z = k as f32 * shift - centerz;
|
||||||
|
|
||||||
|
let status = if j == 0 {
|
||||||
|
RigidBodyType::Fixed
|
||||||
|
} else {
|
||||||
|
RigidBodyType::Dynamic
|
||||||
|
};
|
||||||
|
let density = 0.477;
|
||||||
|
|
||||||
|
// Build the rigid body.
|
||||||
|
let rigid_body = RigidBodyBuilder::new(status)
|
||||||
|
.translation(vector![x, y, z])
|
||||||
|
.sleeping(true); // j < num - 1);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::ball(rad).density(density);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![100.0, 100.0, 100.0], Point::origin());
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier2d-f64"
|
name = "rapier2d-f64"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "2-dimensional physics engine in Rust."
|
description = "2-dimensional physics engine in Rust."
|
||||||
documentation = "https://docs.rs/rapier2d"
|
documentation = "https://docs.rs/rapier2d"
|
||||||
homepage = "https://rapier.rs"
|
homepage = "https://rapier.rs"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,23 +16,23 @@ edition = "2021"
|
|||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim2", "f64" ]
|
default = ["dim2", "f64"]
|
||||||
dim2 = [ ]
|
dim2 = []
|
||||||
f64 = [ ]
|
f64 = []
|
||||||
parallel = [ "rayon" ]
|
parallel = ["rayon"]
|
||||||
simd-stable = [ "simba/wide", "simd-is-enabled" ]
|
simd-stable = ["simba/wide", "simd-is-enabled"]
|
||||||
simd-nightly = [ "simba/packed_simd", "simd-is-enabled" ]
|
simd-nightly = ["simba/packed_simd", "simd-is-enabled"]
|
||||||
# Do not enable this feature directly. It is automatically
|
# Do not enable this feature directly. It is automatically
|
||||||
# enabled with the "simd-stable" or "simd-nightly" feature.
|
# enabled with the "simd-stable" or "simd-nightly" feature.
|
||||||
simd-is-enabled = [ "vec_map" ]
|
simd-is-enabled = ["vec_map"]
|
||||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||||
serde-serialize = [ "nalgebra/serde-serialize", "parry2d-f64/serde-serialize", "serde", "bit-vec/serde", "arrayvec/serde" ]
|
serde-serialize = ["nalgebra/serde-serialize", "parry2d-f64/serde-serialize", "serde", "bit-vec/serde", "arrayvec/serde"]
|
||||||
enhanced-determinism = [ "simba/libm_force", "parry2d-f64/enhanced-determinism" ]
|
enhanced-determinism = ["simba/libm_force", "parry2d-f64/enhanced-determinism"]
|
||||||
debug-render = [ ]
|
debug-render = []
|
||||||
profiler = [ "instant" ] # Enables the internal profiler.
|
profiler = ["instant"] # Enables the internal profiler.
|
||||||
|
|
||||||
# Feature used for debugging only.
|
# Feature used for debugging only.
|
||||||
debug-disable-legitimate-fe-exceptions = [ ]
|
debug-disable-legitimate-fe-exceptions = []
|
||||||
|
|
||||||
# Feature used for development and debugging only.
|
# Feature used for development and debugging only.
|
||||||
# Do not enable this unless you are working on the engine internals.
|
# Do not enable this unless you are working on the engine internals.
|
||||||
@@ -44,15 +44,15 @@ features = ["parallel", "simd-stable", "serde-serialize", "debug-render"]
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier2d_f64"
|
name = "rapier2d_f64"
|
||||||
path = "../../src/lib.rs"
|
path = "../../src/lib.rs"
|
||||||
required-features = [ "dim2", "f64" ]
|
required-features = ["dim2", "f64"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vec_map = { version = "0.8", optional = true }
|
vec_map = { version = "0.8", optional = true }
|
||||||
instant = { version = "0.1", features = [ "now" ], optional = true }
|
instant = { version = "0.1", features = ["now"], optional = true }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
nalgebra = "0.32"
|
nalgebra = "0.32"
|
||||||
parry2d-f64 = "0.13.1"
|
parry2d-f64 = "0.15.0"
|
||||||
simba = "0.8"
|
simba = "0.8"
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
@@ -60,11 +60,13 @@ crossbeam = "0.8"
|
|||||||
arrayvec = "0.7"
|
arrayvec = "0.7"
|
||||||
bit-vec = "0.6"
|
bit-vec = "0.6"
|
||||||
rustc-hash = "1"
|
rustc-hash = "1"
|
||||||
serde = { version = "1", features = [ "derive" ], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
downcast-rs = "1.2"
|
downcast-rs = "1.2"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
|
log = "0.4"
|
||||||
|
ordered-float = "4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier2d"
|
name = "rapier2d"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "2-dimensional physics engine in Rust."
|
description = "2-dimensional physics engine in Rust."
|
||||||
documentation = "https://docs.rs/rapier2d"
|
documentation = "https://docs.rs/rapier2d"
|
||||||
homepage = "https://rapier.rs"
|
homepage = "https://rapier.rs"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,23 +16,23 @@ edition = "2021"
|
|||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim2", "f32" ]
|
default = ["dim2", "f32"]
|
||||||
dim2 = [ ]
|
dim2 = []
|
||||||
f32 = [ ]
|
f32 = []
|
||||||
parallel = [ "rayon" ]
|
parallel = ["rayon"]
|
||||||
simd-stable = [ "simba/wide", "simd-is-enabled" ]
|
simd-stable = ["simba/wide", "simd-is-enabled"]
|
||||||
simd-nightly = [ "simba/packed_simd", "simd-is-enabled" ]
|
simd-nightly = ["simba/packed_simd", "simd-is-enabled"]
|
||||||
# Do not enable this feature directly. It is automatically
|
# Do not enable this feature directly. It is automatically
|
||||||
# enabled with the "simd-stable" or "simd-nightly" feature.
|
# enabled with the "simd-stable" or "simd-nightly" feature.
|
||||||
simd-is-enabled = [ "vec_map" ]
|
simd-is-enabled = ["vec_map"]
|
||||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||||
serde-serialize = [ "nalgebra/serde-serialize", "parry2d/serde-serialize", "serde", "bit-vec/serde", "arrayvec/serde" ]
|
serde-serialize = ["nalgebra/serde-serialize", "parry2d/serde-serialize", "serde", "bit-vec/serde", "arrayvec/serde"]
|
||||||
enhanced-determinism = [ "simba/libm_force", "parry2d/enhanced-determinism" ]
|
enhanced-determinism = ["simba/libm_force", "parry2d/enhanced-determinism"]
|
||||||
debug-render = [ ]
|
debug-render = []
|
||||||
profiler = [ "instant" ] # Enables the internal profiler.
|
profiler = ["instant"] # Enables the internal profiler.
|
||||||
|
|
||||||
# Feature used for debugging only.
|
# Feature used for debugging only.
|
||||||
debug-disable-legitimate-fe-exceptions = [ ]
|
debug-disable-legitimate-fe-exceptions = []
|
||||||
|
|
||||||
# Feature used for development and debugging only.
|
# Feature used for development and debugging only.
|
||||||
# Do not enable this unless you are working on the engine internals.
|
# Do not enable this unless you are working on the engine internals.
|
||||||
@@ -44,15 +44,15 @@ features = ["parallel", "simd-stable", "serde-serialize", "debug-render"]
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier2d"
|
name = "rapier2d"
|
||||||
path = "../../src/lib.rs"
|
path = "../../src/lib.rs"
|
||||||
required-features = [ "dim2", "f32" ]
|
required-features = ["dim2", "f32"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vec_map = { version = "0.8", optional = true }
|
vec_map = { version = "0.8", optional = true }
|
||||||
instant = { version = "0.1", features = [ "now" ], optional = true }
|
instant = { version = "0.1", features = ["now"], optional = true }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
nalgebra = "0.32"
|
nalgebra = "0.32"
|
||||||
parry2d = "0.13.1"
|
parry2d = "0.15.0"
|
||||||
simba = "0.8"
|
simba = "0.8"
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
@@ -60,11 +60,13 @@ crossbeam = "0.8"
|
|||||||
arrayvec = "0.7"
|
arrayvec = "0.7"
|
||||||
bit-vec = "0.6"
|
bit-vec = "0.6"
|
||||||
rustc-hash = "1"
|
rustc-hash = "1"
|
||||||
serde = { version = "1", features = [ "derive" ], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
downcast-rs = "1.2"
|
downcast-rs = "1.2"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
|
log = "0.4"
|
||||||
|
ordered-float = "4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier3d-f64"
|
name = "rapier3d-f64"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "3-dimensional physics engine in Rust."
|
description = "3-dimensional physics engine in Rust."
|
||||||
documentation = "https://docs.rs/rapier3d"
|
documentation = "https://docs.rs/rapier3d"
|
||||||
homepage = "https://rapier.rs"
|
homepage = "https://rapier.rs"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,23 +16,23 @@ edition = "2021"
|
|||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim3", "f64" ]
|
default = ["dim3", "f64"]
|
||||||
dim3 = [ ]
|
dim3 = []
|
||||||
f64 = [ ]
|
f64 = []
|
||||||
parallel = [ "rayon" ]
|
parallel = ["rayon"]
|
||||||
simd-stable = [ "parry3d-f64/simd-stable", "simba/wide", "simd-is-enabled" ]
|
simd-stable = ["parry3d-f64/simd-stable", "simba/wide", "simd-is-enabled"]
|
||||||
simd-nightly = [ "parry3d-f64/simd-nightly", "simba/packed_simd", "simd-is-enabled" ]
|
simd-nightly = ["parry3d-f64/simd-nightly", "simba/packed_simd", "simd-is-enabled"]
|
||||||
# Do not enable this feature directly. It is automatically
|
# Do not enable this feature directly. It is automatically
|
||||||
# enabled with the "simd-stable" or "simd-nightly" feature.
|
# enabled with the "simd-stable" or "simd-nightly" feature.
|
||||||
simd-is-enabled = [ "vec_map" ]
|
simd-is-enabled = ["vec_map"]
|
||||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||||
serde-serialize = [ "nalgebra/serde-serialize", "parry3d-f64/serde-serialize", "serde", "bit-vec/serde" ]
|
serde-serialize = ["nalgebra/serde-serialize", "parry3d-f64/serde-serialize", "serde", "bit-vec/serde"]
|
||||||
enhanced-determinism = [ "simba/libm_force", "parry3d-f64/enhanced-determinism" ]
|
enhanced-determinism = ["simba/libm_force", "parry3d-f64/enhanced-determinism"]
|
||||||
debug-render = []
|
debug-render = []
|
||||||
profiler = [ "instant" ] # Enables the internal profiler.
|
profiler = ["instant"] # Enables the internal profiler.
|
||||||
|
|
||||||
# Feature used for debugging only.
|
# Feature used for debugging only.
|
||||||
debug-disable-legitimate-fe-exceptions = [ ]
|
debug-disable-legitimate-fe-exceptions = []
|
||||||
|
|
||||||
# Feature used for development and debugging only.
|
# Feature used for development and debugging only.
|
||||||
# Do not enable this unless you are working on the engine internals.
|
# Do not enable this unless you are working on the engine internals.
|
||||||
@@ -44,15 +44,15 @@ features = ["parallel", "simd-stable", "serde-serialize", "debug-render"]
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier3d_f64"
|
name = "rapier3d_f64"
|
||||||
path = "../../src/lib.rs"
|
path = "../../src/lib.rs"
|
||||||
required-features = [ "dim3", "f64" ]
|
required-features = ["dim3", "f64"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vec_map = { version = "0.8", optional = true }
|
vec_map = { version = "0.8", optional = true }
|
||||||
instant = { version = "0.1", features = [ "now" ], optional = true }
|
instant = { version = "0.1", features = ["now"], optional = true }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
nalgebra = "0.32"
|
nalgebra = "0.32"
|
||||||
parry3d-f64 = "0.13.1"
|
parry3d-f64 = "0.15.0"
|
||||||
simba = "0.8"
|
simba = "0.8"
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
@@ -60,11 +60,13 @@ crossbeam = "0.8"
|
|||||||
arrayvec = "0.7"
|
arrayvec = "0.7"
|
||||||
bit-vec = "0.6"
|
bit-vec = "0.6"
|
||||||
rustc-hash = "1"
|
rustc-hash = "1"
|
||||||
serde = { version = "1", features = [ "derive" ], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
downcast-rs = "1.2"
|
downcast-rs = "1.2"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
|
log = "0.4"
|
||||||
|
ordered-float = "4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier3d"
|
name = "rapier3d"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "3-dimensional physics engine in Rust."
|
description = "3-dimensional physics engine in Rust."
|
||||||
documentation = "https://docs.rs/rapier3d"
|
documentation = "https://docs.rs/rapier3d"
|
||||||
homepage = "https://rapier.rs"
|
homepage = "https://rapier.rs"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,23 +16,23 @@ edition = "2021"
|
|||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim3", "f32" ]
|
default = ["dim3", "f32"]
|
||||||
dim3 = [ ]
|
dim3 = []
|
||||||
f32 = [ ]
|
f32 = []
|
||||||
parallel = [ "rayon" ]
|
parallel = ["rayon"]
|
||||||
simd-stable = [ "parry3d/simd-stable", "simba/wide", "simd-is-enabled" ]
|
simd-stable = ["parry3d/simd-stable", "simba/wide", "simd-is-enabled"]
|
||||||
simd-nightly = [ "parry3d/simd-nightly", "simba/packed_simd", "simd-is-enabled" ]
|
simd-nightly = ["parry3d/simd-nightly", "simba/packed_simd", "simd-is-enabled"]
|
||||||
# Do not enable this feature directly. It is automatically
|
# Do not enable this feature directly. It is automatically
|
||||||
# enabled with the "simd-stable" or "simd-nightly" feature.
|
# enabled with the "simd-stable" or "simd-nightly" feature.
|
||||||
simd-is-enabled = [ "vec_map" ]
|
simd-is-enabled = ["vec_map"]
|
||||||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||||
serde-serialize = [ "nalgebra/serde-serialize", "parry3d/serde-serialize", "serde", "bit-vec/serde" ]
|
serde-serialize = ["nalgebra/serde-serialize", "parry3d/serde-serialize", "serde", "bit-vec/serde"]
|
||||||
enhanced-determinism = [ "simba/libm_force", "parry3d/enhanced-determinism" ]
|
enhanced-determinism = ["simba/libm_force", "parry3d/enhanced-determinism"]
|
||||||
debug-render = [ ]
|
debug-render = []
|
||||||
profiler = [ "instant" ] # Enables the internal profiler.
|
profiler = ["instant"] # Enables the internal profiler.
|
||||||
|
|
||||||
# Feature used for debugging only.
|
# Feature used for debugging only.
|
||||||
debug-disable-legitimate-fe-exceptions = [ ]
|
debug-disable-legitimate-fe-exceptions = []
|
||||||
|
|
||||||
# Feature used for development and debugging only.
|
# Feature used for development and debugging only.
|
||||||
# Do not enable this unless you are working on the engine internals.
|
# Do not enable this unless you are working on the engine internals.
|
||||||
@@ -44,15 +44,15 @@ features = ["parallel", "simd-stable", "serde-serialize", "debug-render"]
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier3d"
|
name = "rapier3d"
|
||||||
path = "../../src/lib.rs"
|
path = "../../src/lib.rs"
|
||||||
required-features = [ "dim3", "f32" ]
|
required-features = ["dim3", "f32"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vec_map = { version = "0.8", optional = true }
|
vec_map = { version = "0.8", optional = true }
|
||||||
instant = { version = "0.1", features = [ "now" ], optional = true }
|
instant = { version = "0.1", features = ["now"], optional = true }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
nalgebra = "0.32"
|
nalgebra = "0.32"
|
||||||
parry3d = "0.13.1"
|
parry3d = "0.15.0"
|
||||||
simba = "0.8"
|
simba = "0.8"
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
@@ -60,11 +60,13 @@ crossbeam = "0.8"
|
|||||||
arrayvec = "0.7"
|
arrayvec = "0.7"
|
||||||
bit-vec = "0.6"
|
bit-vec = "0.6"
|
||||||
rustc-hash = "1"
|
rustc-hash = "1"
|
||||||
serde = { version = "1", features = [ "derive" ], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
downcast-rs = "1.2"
|
downcast-rs = "1.2"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
|
log = "0.4"
|
||||||
|
ordered-float = "4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier_testbed2d-f64"
|
name = "rapier_testbed2d-f64"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "Testbed for the Rapier 2-dimensional physics engine in Rust."
|
description = "Testbed for the Rapier 2-dimensional physics engine in Rust."
|
||||||
homepage = "http://rapier.org"
|
homepage = "http://rapier.org"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,48 +16,48 @@ maintenance = { status = "actively-developed" }
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier_testbed2d"
|
name = "rapier_testbed2d"
|
||||||
path = "../../src_testbed/lib.rs"
|
path = "../../src_testbed/lib.rs"
|
||||||
required-features = [ "dim2" ]
|
required-features = ["dim2"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim2" ]
|
default = ["dim2"]
|
||||||
dim2 = [ ]
|
dim2 = []
|
||||||
parallel = [ "rapier/parallel", "num_cpus" ]
|
parallel = ["rapier/parallel", "num_cpus"]
|
||||||
other-backends = [ "wrapped2d" ]
|
other-backends = ["wrapped2d"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["parallel", "other-backends"]
|
features = ["parallel", "other-backends"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra = { version = "0.32", features = [ "rand" ] }
|
nalgebra = { version = "0.32", features = ["rand", "glam025"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand_pcg = "0.3"
|
rand_pcg = "0.3"
|
||||||
instant = { version = "0.1", features = [ "web-sys", "now" ]}
|
instant = { version = "0.1", features = ["web-sys", "now"] }
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
num_cpus = { version = "1", optional = true }
|
num_cpus = { version = "1", optional = true }
|
||||||
wrapped2d = { version = "0.4", optional = true }
|
wrapped2d = { version = "0.4", optional = true }
|
||||||
crossbeam = "0.8"
|
crossbeam = "0.8"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
md5 = "0.7"
|
md5 = "0.7"
|
||||||
|
|
||||||
bevy_egui = "0.23"
|
bevy_egui = "0.26"
|
||||||
bevy_ecs = "0.12"
|
bevy_ecs = "0.13"
|
||||||
bevy_core_pipeline = "0.12"
|
bevy_core_pipeline = "0.13"
|
||||||
bevy_pbr = "0.12"
|
bevy_pbr = "0.13"
|
||||||
bevy_sprite = "0.12"
|
bevy_sprite = "0.13"
|
||||||
#bevy_prototype_debug_lines = "0.7"
|
#bevy_prototype_debug_lines = "0.7"
|
||||||
|
|
||||||
# Dependencies for native only.
|
# Dependencies for native only.
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_asset", "bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_asset", "bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
|
|
||||||
# Dependencies for WASM only.
|
# Dependencies for WASM only.
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_asset", "bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_asset", "bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
#bevy_webgl2 = "0.5"
|
#bevy_webgl2 = "0.5"
|
||||||
|
|
||||||
[dependencies.rapier]
|
[dependencies.rapier]
|
||||||
package = "rapier2d-f64"
|
package = "rapier2d-f64"
|
||||||
path = "../rapier2d-f64"
|
path = "../rapier2d-f64"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
features = [ "serde-serialize", "debug-render", "profiler" ]
|
features = ["serde-serialize", "debug-render", "profiler"]
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier_testbed2d"
|
name = "rapier_testbed2d"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "Testbed for the Rapier 2-dimensional physics engine in Rust."
|
description = "Testbed for the Rapier 2-dimensional physics engine in Rust."
|
||||||
homepage = "http://rapier.org"
|
homepage = "http://rapier.org"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,48 +16,48 @@ maintenance = { status = "actively-developed" }
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier_testbed2d"
|
name = "rapier_testbed2d"
|
||||||
path = "../../src_testbed/lib.rs"
|
path = "../../src_testbed/lib.rs"
|
||||||
required-features = [ "dim2" ]
|
required-features = ["dim2"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim2" ]
|
default = ["dim2"]
|
||||||
dim2 = [ ]
|
dim2 = []
|
||||||
parallel = [ "rapier/parallel", "num_cpus" ]
|
parallel = ["rapier/parallel", "num_cpus"]
|
||||||
other-backends = [ "wrapped2d" ]
|
other-backends = ["wrapped2d"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["parallel", "other-backends"]
|
features = ["parallel", "other-backends"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra = { version = "0.32", features = [ "rand" ] }
|
nalgebra = { version = "0.32", features = ["rand", "glam025"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand_pcg = "0.3"
|
rand_pcg = "0.3"
|
||||||
instant = { version = "0.1", features = [ "web-sys", "now" ]}
|
instant = { version = "0.1", features = ["web-sys", "now"] }
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
num_cpus = { version = "1", optional = true }
|
num_cpus = { version = "1", optional = true }
|
||||||
wrapped2d = { version = "0.4", optional = true }
|
wrapped2d = { version = "0.4", optional = true }
|
||||||
crossbeam = "0.8"
|
crossbeam = "0.8"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
md5 = "0.7"
|
md5 = "0.7"
|
||||||
|
|
||||||
bevy_egui = "0.23"
|
bevy_egui = "0.26"
|
||||||
bevy_ecs = "0.12"
|
bevy_ecs = "0.13"
|
||||||
bevy_core_pipeline = "0.12"
|
bevy_core_pipeline = "0.13"
|
||||||
bevy_pbr = "0.12"
|
bevy_pbr = "0.13"
|
||||||
bevy_sprite = "0.12"
|
bevy_sprite = "0.13"
|
||||||
#bevy_prototype_debug_lines = "0.7"
|
#bevy_prototype_debug_lines = "0.7"
|
||||||
|
|
||||||
# Dependencies for native only.
|
# Dependencies for native only.
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_sprite", "bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_sprite", "bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
|
|
||||||
# Dependencies for WASM only.
|
# Dependencies for WASM only.
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_sprite", "bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_sprite", "bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
#bevy_webgl2 = "0.5"
|
#bevy_webgl2 = "0.5"
|
||||||
|
|
||||||
[dependencies.rapier]
|
[dependencies.rapier]
|
||||||
package = "rapier2d"
|
package = "rapier2d"
|
||||||
path = "../rapier2d"
|
path = "../rapier2d"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
features = [ "serde-serialize", "debug-render", "profiler" ]
|
features = ["serde-serialize", "debug-render", "profiler"]
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier_testbed3d-f64"
|
name = "rapier_testbed3d-f64"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "Testbed for the Rapier 3-dimensional physics engine in Rust."
|
description = "Testbed for the Rapier 3-dimensional physics engine in Rust."
|
||||||
homepage = "http://rapier.org"
|
homepage = "http://rapier.org"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,47 +16,47 @@ maintenance = { status = "actively-developed" }
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier_testbed3d"
|
name = "rapier_testbed3d"
|
||||||
path = "../../src_testbed/lib.rs"
|
path = "../../src_testbed/lib.rs"
|
||||||
required-features = [ "dim3" ]
|
required-features = ["dim3"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim3" ]
|
default = ["dim3"]
|
||||||
dim3 = [ ]
|
dim3 = []
|
||||||
parallel = [ "rapier/parallel", "num_cpus" ]
|
parallel = ["rapier/parallel", "num_cpus"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["parallel"]
|
features = ["parallel"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra = { version = "0.32", features = [ "rand" ] }
|
nalgebra = { version = "0.32", features = ["rand", "glam025"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand_pcg = "0.3"
|
rand_pcg = "0.3"
|
||||||
instant = { version = "0.1", features = [ "web-sys", "now" ]}
|
instant = { version = "0.1", features = ["web-sys", "now"] }
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
num_cpus = { version = "1", optional = true }
|
num_cpus = { version = "1", optional = true }
|
||||||
crossbeam = "0.8"
|
crossbeam = "0.8"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
md5 = "0.7"
|
md5 = "0.7"
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
bevy_egui = "0.23"
|
bevy_egui = "0.26"
|
||||||
bevy_ecs = "0.12"
|
bevy_ecs = "0.13"
|
||||||
bevy_core_pipeline = "0.12"
|
bevy_core_pipeline = "0.13"
|
||||||
bevy_pbr = "0.12"
|
bevy_pbr = "0.13"
|
||||||
bevy_sprite = "0.12"
|
bevy_sprite = "0.13"
|
||||||
#bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] }
|
#bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] }
|
||||||
|
|
||||||
# Dependencies for native only.
|
# Dependencies for native only.
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
|
|
||||||
# Dependencies for WASM only.
|
# Dependencies for WASM only.
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
#bevy_webgl2 = "0.5"
|
#bevy_webgl2 = "0.5"
|
||||||
|
|
||||||
[dependencies.rapier]
|
[dependencies.rapier]
|
||||||
package = "rapier3d-f64"
|
package = "rapier3d-f64"
|
||||||
path = "../rapier3d-f64"
|
path = "../rapier3d-f64"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
features = [ "serde-serialize", "debug-render", "profiler" ]
|
features = ["serde-serialize", "debug-render", "profiler"]
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapier_testbed3d"
|
name = "rapier_testbed3d"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
authors = [ "Sébastien Crozet <developer@crozet.re>" ]
|
authors = ["Sébastien Crozet <developer@crozet.re>"]
|
||||||
description = "Testbed for the Rapier 3-dimensional physics engine in Rust."
|
description = "Testbed for the Rapier 3-dimensional physics engine in Rust."
|
||||||
homepage = "http://rapier.org"
|
homepage = "http://rapier.org"
|
||||||
repository = "https://github.com/dimforge/rapier"
|
repository = "https://github.com/dimforge/rapier"
|
||||||
categories = [ "science", "game-development", "mathematics", "simulation", "wasm"]
|
categories = ["science", "game-development", "mathematics", "simulation", "wasm"]
|
||||||
keywords = [ "physics", "dynamics", "rigid", "real-time", "impulse_joints" ]
|
keywords = ["physics", "dynamics", "rigid", "real-time", "impulse_joints"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -16,51 +16,51 @@ maintenance = { status = "actively-developed" }
|
|||||||
[lib]
|
[lib]
|
||||||
name = "rapier_testbed3d"
|
name = "rapier_testbed3d"
|
||||||
path = "../../src_testbed/lib.rs"
|
path = "../../src_testbed/lib.rs"
|
||||||
required-features = [ "dim3" ]
|
required-features = ["dim3"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "dim3" ]
|
default = ["dim3"]
|
||||||
dim3 = [ ]
|
dim3 = []
|
||||||
parallel = [ "rapier/parallel", "num_cpus" ]
|
parallel = ["rapier/parallel", "num_cpus"]
|
||||||
other-backends = [ "physx", "physx-sys", "glam" ]
|
other-backends = ["physx", "physx-sys", "glam"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["parallel", "other-backends"]
|
features = ["parallel", "other-backends"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra = { version = "0.32", features = [ "rand" ] }
|
nalgebra = { version = "0.32", features = ["rand", "glam025"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand_pcg = "0.3"
|
rand_pcg = "0.3"
|
||||||
instant = { version = "0.1", features = [ "web-sys", "now" ]}
|
instant = { version = "0.1", features = ["web-sys", "now"] }
|
||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
glam = { version = "0.24", optional = true } # For Physx
|
glam = { version = "0.24", optional = true } # For Physx
|
||||||
num_cpus = { version = "1", optional = true }
|
num_cpus = { version = "1", optional = true }
|
||||||
physx = { version = "0.19", features = [ "glam" ], optional = true }
|
physx = { version = "0.19", features = ["glam"], optional = true }
|
||||||
physx-sys = { version = "0.11", optional = true }
|
physx-sys = { version = "0.11", optional = true }
|
||||||
crossbeam = "0.8"
|
crossbeam = "0.8"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
md5 = "0.7"
|
md5 = "0.7"
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
bevy_egui = "0.23"
|
bevy_egui = "0.26"
|
||||||
bevy_ecs = "0.12"
|
bevy_ecs = "0.13"
|
||||||
bevy_core_pipeline = "0.12"
|
bevy_core_pipeline = "0.13"
|
||||||
bevy_pbr = "0.12"
|
bevy_pbr = "0.13"
|
||||||
bevy_sprite = "0.12"
|
bevy_sprite = "0.13"
|
||||||
#bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] }
|
#bevy_prototype_debug_lines = { version = "0.7", features = [ "3d" ] }
|
||||||
|
|
||||||
# Dependencies for native only.
|
# Dependencies for native only.
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_winit", "x11", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
|
|
||||||
# Dependencies for WASM only.
|
# Dependencies for WASM only.
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
bevy = {version = "0.12", default-features = false, features = ["bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"]}
|
bevy = { version = "0.13", default-features = false, features = ["bevy_winit", "tonemapping_luts", "ktx2", "zstd", "bevy_render", "bevy_pbr", "bevy_gizmos"] }
|
||||||
#bevy_webgl2 = "0.5"
|
#bevy_webgl2 = "0.5"
|
||||||
|
|
||||||
[dependencies.rapier]
|
[dependencies.rapier]
|
||||||
package = "rapier3d"
|
package = "rapier3d"
|
||||||
path = "../rapier3d"
|
path = "../rapier3d"
|
||||||
version = "0.18.0"
|
version = "0.19.0"
|
||||||
features = [ "serde-serialize", "debug-render", "profiler" ]
|
features = ["serde-serialize", "debug-render", "profiler"]
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ mod collision_groups2;
|
|||||||
mod convex_polygons2;
|
mod convex_polygons2;
|
||||||
mod damping2;
|
mod damping2;
|
||||||
mod debug_box_ball2;
|
mod debug_box_ball2;
|
||||||
|
mod debug_compression2;
|
||||||
|
mod debug_total_overlap2;
|
||||||
|
mod debug_vertical_column2;
|
||||||
mod drum2;
|
mod drum2;
|
||||||
mod heightfield2;
|
mod heightfield2;
|
||||||
|
mod inverse_kinematics2;
|
||||||
mod joint_motor_position2;
|
mod joint_motor_position2;
|
||||||
mod joints2;
|
mod joints2;
|
||||||
mod locked_rotations2;
|
mod locked_rotations2;
|
||||||
@@ -26,6 +30,17 @@ mod polyline2;
|
|||||||
mod pyramid2;
|
mod pyramid2;
|
||||||
mod restitution2;
|
mod restitution2;
|
||||||
mod rope_joints2;
|
mod rope_joints2;
|
||||||
|
mod s2d_arch;
|
||||||
|
mod s2d_ball_and_chain;
|
||||||
|
mod s2d_bridge;
|
||||||
|
mod s2d_card_house;
|
||||||
|
mod s2d_confined;
|
||||||
|
mod s2d_far_pyramid;
|
||||||
|
mod s2d_high_mass_ratio_1;
|
||||||
|
mod s2d_high_mass_ratio_2;
|
||||||
|
mod s2d_high_mass_ratio_3;
|
||||||
|
mod s2d_joint_grid;
|
||||||
|
mod s2d_pyramid;
|
||||||
mod sensor2;
|
mod sensor2;
|
||||||
mod trimesh2;
|
mod trimesh2;
|
||||||
|
|
||||||
@@ -70,6 +85,7 @@ pub fn main() {
|
|||||||
("Damping", damping2::init_world),
|
("Damping", damping2::init_world),
|
||||||
("Drum", drum2::init_world),
|
("Drum", drum2::init_world),
|
||||||
("Heightfield", heightfield2::init_world),
|
("Heightfield", heightfield2::init_world),
|
||||||
|
("Inverse kinematics", inverse_kinematics2::init_world),
|
||||||
("Joints", joints2::init_world),
|
("Joints", joints2::init_world),
|
||||||
("Locked rotations", locked_rotations2::init_world),
|
("Locked rotations", locked_rotations2::init_world),
|
||||||
("One-way platforms", one_way_platforms2::init_world),
|
("One-way platforms", one_way_platforms2::init_world),
|
||||||
@@ -82,6 +98,23 @@ pub fn main() {
|
|||||||
("Trimesh", trimesh2::init_world),
|
("Trimesh", trimesh2::init_world),
|
||||||
("Joint motor position", joint_motor_position2::init_world),
|
("Joint motor position", joint_motor_position2::init_world),
|
||||||
("(Debug) box ball", debug_box_ball2::init_world),
|
("(Debug) box ball", debug_box_ball2::init_world),
|
||||||
|
("(Debug) compression", debug_compression2::init_world),
|
||||||
|
("(Debug) total overlap", debug_total_overlap2::init_world),
|
||||||
|
(
|
||||||
|
"(Debug) vertical column",
|
||||||
|
debug_vertical_column2::init_world,
|
||||||
|
),
|
||||||
|
("(s2d) high mass ratio 1", s2d_high_mass_ratio_1::init_world),
|
||||||
|
("(s2d) high mass ratio 2", s2d_high_mass_ratio_2::init_world),
|
||||||
|
("(s2d) high mass ratio 3", s2d_high_mass_ratio_3::init_world),
|
||||||
|
("(s2d) confined", s2d_confined::init_world),
|
||||||
|
("(s2d) pyramid", s2d_pyramid::init_world),
|
||||||
|
("(s2d) card house", s2d_card_house::init_world),
|
||||||
|
("(s2d) arch", s2d_arch::init_world),
|
||||||
|
("(s2d) bridge", s2d_bridge::init_world),
|
||||||
|
("(s2d) ball and chain", s2d_ball_and_chain::init_world),
|
||||||
|
("(s2d) joint grid", s2d_joint_grid::init_world),
|
||||||
|
("(s2d) far pyramid", s2d_far_pyramid::init_world),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Lexicographic sort, with stress tests moved at the end of the list.
|
// Lexicographic sort, with stress tests moved at the end of the list.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
*/
|
*/
|
||||||
let rigid_body = RigidBodyBuilder::kinematic_position_based().translation(vector![-3.0, 5.0]);
|
let rigid_body = RigidBodyBuilder::kinematic_position_based().translation(vector![-3.0, 5.0]);
|
||||||
let character_handle = bodies.insert(rigid_body);
|
let character_handle = bodies.insert(rigid_body);
|
||||||
let collider = ColliderBuilder::cuboid(0.15, 0.3);
|
let collider = ColliderBuilder::capsule_y(0.3, 0.15);
|
||||||
colliders.insert_with_parent(collider, character_handle, &mut bodies);
|
colliders.insert_with_parent(collider, character_handle, &mut bodies);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -94,14 +94,18 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
*/
|
*/
|
||||||
let wall_angle = PI / 2.;
|
let wall_angle = PI / 2.;
|
||||||
let wall_size = 2.0;
|
let wall_size = 2.0;
|
||||||
|
let wall_pos = vector![
|
||||||
|
ground_size + slope_size * 2.0 + impossible_slope_size + 0.35,
|
||||||
|
-ground_height + 2.5 * 2.3
|
||||||
|
];
|
||||||
let collider = ColliderBuilder::cuboid(wall_size, ground_height)
|
let collider = ColliderBuilder::cuboid(wall_size, ground_height)
|
||||||
.translation(vector![
|
.translation(wall_pos)
|
||||||
ground_size + slope_size * 2.0 + impossible_slope_size + 0.35,
|
|
||||||
-ground_height + 2.5 * 2.3
|
|
||||||
])
|
|
||||||
.rotation(wall_angle);
|
.rotation(wall_angle);
|
||||||
colliders.insert(collider);
|
colliders.insert(collider);
|
||||||
|
|
||||||
|
let collider = ColliderBuilder::cuboid(wall_size, ground_height).translation(wall_pos);
|
||||||
|
colliders.insert(collider);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a moving platform.
|
* Create a moving platform.
|
||||||
*/
|
*/
|
||||||
|
|||||||
75
examples2d/debug_compression2.rs
Normal file
75
examples2d/debug_compression2.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let width = 75.0;
|
||||||
|
let thickness = 2.0;
|
||||||
|
let ys = [-30.0 - thickness, 30.0 + thickness];
|
||||||
|
|
||||||
|
for y in ys {
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, y]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(width, thickness);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build two compression boxes rigid body.
|
||||||
|
let half_height = (ys[1] - ys[0]) / 2.0 - thickness;
|
||||||
|
let xs = [-width + thickness, width - thickness];
|
||||||
|
let mut handles = [RigidBodyHandle::invalid(); 2];
|
||||||
|
|
||||||
|
for i in 0..2 {
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![xs[i], 0.0]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(thickness, half_height);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
handles[i] = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the balls.
|
||||||
|
let num = 8;
|
||||||
|
let rad = half_height / (num as f32);
|
||||||
|
for i in 0..num {
|
||||||
|
for j in 0..num {
|
||||||
|
let x = i as f32 * rad * 2.0 - num as f32 * rad;
|
||||||
|
let y = j as f32 * rad * 2.0 - num as f32 * rad + rad;
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::ball(rad);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut force = vector![0.0, 0.0];
|
||||||
|
|
||||||
|
testbed.add_callback(move |_, physics, _, _| {
|
||||||
|
let left_plank = &mut physics.bodies[handles[0]];
|
||||||
|
left_plank.reset_forces(true);
|
||||||
|
left_plank.add_force(force, true);
|
||||||
|
|
||||||
|
let right_plank = &mut physics.bodies[handles[1]];
|
||||||
|
right_plank.reset_forces(true);
|
||||||
|
right_plank.add_force(-force, true);
|
||||||
|
|
||||||
|
force.x += 10000.0;
|
||||||
|
|
||||||
|
println!("force: {}", force.x);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 0.0], 50.0);
|
||||||
|
}
|
||||||
28
examples2d/debug_total_overlap2.rs
Normal file
28
examples2d/debug_total_overlap2.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
// Build many balls, all spawned at the same point.
|
||||||
|
let rad = 0.5;
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic();
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(rad, rad);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 0.0], 50.0);
|
||||||
|
}
|
||||||
47
examples2d/debug_vertical_column2.rs
Normal file
47
examples2d/debug_vertical_column2.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let num = 80;
|
||||||
|
let rad = 0.5;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_size = 1.0;
|
||||||
|
let ground_thickness = 1.0;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed();
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(ground_size, ground_thickness).friction(0.3);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
|
||||||
|
for i in 0..num {
|
||||||
|
let y = i as f32 * rad * 2.0 + ground_thickness + rad;
|
||||||
|
|
||||||
|
// Build the rigid body.
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, y]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(rad, rad).friction(0.3);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
// testbed.harness_mut().physics.gravity.y = -981.0;
|
||||||
|
testbed.look_at(point![0.0, 2.5], 5.0);
|
||||||
|
}
|
||||||
96
examples2d/inverse_kinematics2.rs
Normal file
96
examples2d/inverse_kinematics2.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let mut multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_size = 1.0;
|
||||||
|
let ground_height = 0.01;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height]);
|
||||||
|
let floor_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(ground_size, ground_height);
|
||||||
|
colliders.insert_with_parent(collider, floor_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup groups
|
||||||
|
*/
|
||||||
|
let num_segments = 10;
|
||||||
|
let body = RigidBodyBuilder::fixed();
|
||||||
|
let mut last_body = bodies.insert(body);
|
||||||
|
let mut last_link = MultibodyJointHandle::invalid();
|
||||||
|
|
||||||
|
for i in 0..num_segments {
|
||||||
|
let size = 1.0 / num_segments as f32;
|
||||||
|
let body = RigidBodyBuilder::dynamic().can_sleep(false);
|
||||||
|
let new_body = bodies.insert(body);
|
||||||
|
// NOTE: we add a sensor collider just to make the destbed draw a rectangle to make
|
||||||
|
// the demo look nicer. IK could be used without cuboid.
|
||||||
|
let collider = ColliderBuilder::cuboid(size / 8.0, size / 2.0)
|
||||||
|
.density(0.0)
|
||||||
|
.sensor(true);
|
||||||
|
colliders.insert_with_parent(collider, new_body, &mut bodies);
|
||||||
|
|
||||||
|
let link_ab = RevoluteJointBuilder::new()
|
||||||
|
.local_anchor1(point![0.0, size / 2.0 * (i != 0) as usize as f32])
|
||||||
|
.local_anchor2(point![0.0, -size / 2.0])
|
||||||
|
.build()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
last_link = multibody_joints
|
||||||
|
.insert(last_body, new_body, link_ab, true)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
last_body = new_body;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut displacements = DVector::zeros(0);
|
||||||
|
|
||||||
|
testbed.add_callback(move |graphics, physics, _, _| {
|
||||||
|
let Some(graphics) = graphics else { return };
|
||||||
|
if let Some((multibody, link_id)) = physics.multibody_joints.get_mut(last_link) {
|
||||||
|
// Ensure our displacement vector has the right number of elements.
|
||||||
|
if displacements.nrows() < multibody.ndofs() {
|
||||||
|
displacements = DVector::zeros(multibody.ndofs());
|
||||||
|
} else {
|
||||||
|
displacements.fill(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(mouse_point) = graphics.mouse().point else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We will have the endpoint track the mouse position.
|
||||||
|
let target_point = mouse_point.coords;
|
||||||
|
|
||||||
|
let options = InverseKinematicsOption {
|
||||||
|
constrained_axes: JointAxesMask::LIN_AXES,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
multibody.inverse_kinematics(
|
||||||
|
&physics.bodies,
|
||||||
|
link_id,
|
||||||
|
&options,
|
||||||
|
&Isometry::from(target_point),
|
||||||
|
&mut displacements,
|
||||||
|
);
|
||||||
|
multibody.apply_displacements(displacements.as_slice());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 0.0], 300.0);
|
||||||
|
}
|
||||||
109
examples2d/s2d_arch.rs
Normal file
109
examples2d/s2d_arch.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
#[allow(clippy::excessive_precision)]
|
||||||
|
let mut ps1 = [
|
||||||
|
Point::new(16.0, 0.0),
|
||||||
|
Point::new(14.93803712795643, 5.133601056842984),
|
||||||
|
Point::new(13.79871746027416, 10.24928069555078),
|
||||||
|
Point::new(12.56252963284711, 15.34107019122473),
|
||||||
|
Point::new(11.20040987372525, 20.39856541571217),
|
||||||
|
Point::new(9.66521217819836, 25.40369899225096),
|
||||||
|
Point::new(7.87179930638133, 30.3179337000085),
|
||||||
|
Point::new(5.635199558196225, 35.03820717801641),
|
||||||
|
Point::new(2.405937953536585, 39.09554102558315),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[allow(clippy::excessive_precision)]
|
||||||
|
let mut ps2 = [
|
||||||
|
Point::new(24.0, 0.0),
|
||||||
|
Point::new(22.33619528222415, 6.02299846205841),
|
||||||
|
Point::new(20.54936888969905, 12.00964361211476),
|
||||||
|
Point::new(18.60854610798073, 17.9470321677465),
|
||||||
|
Point::new(16.46769273811807, 23.81367936585418),
|
||||||
|
Point::new(14.05325025774858, 29.57079353071012),
|
||||||
|
Point::new(11.23551045834022, 35.13775818285372),
|
||||||
|
Point::new(7.752568160730571, 40.30450679009583),
|
||||||
|
Point::new(3.016931552701656, 44.28891593799322),
|
||||||
|
];
|
||||||
|
|
||||||
|
let scale = 0.25;
|
||||||
|
let friction = 0.6;
|
||||||
|
|
||||||
|
for i in 0..9 {
|
||||||
|
ps1[i] *= scale;
|
||||||
|
ps2[i] *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let collider = ColliderBuilder::segment(point![-100.0, 0.0], point![100.0, 0.0]).friction(0.6);
|
||||||
|
colliders.insert(collider);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the arch
|
||||||
|
*/
|
||||||
|
for i in 0..8 {
|
||||||
|
let ps = [ps1[i], ps2[i], ps2[i + 1], ps1[i + 1]];
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic();
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::convex_hull(&ps)
|
||||||
|
.unwrap()
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
let ps = [
|
||||||
|
point![-ps2[i].x, ps2[i].y],
|
||||||
|
point![-ps1[i].x, ps1[i].y],
|
||||||
|
point![-ps1[i + 1].x, ps1[i + 1].y],
|
||||||
|
point![-ps2[i + 1].x, ps2[i + 1].y],
|
||||||
|
];
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic();
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::convex_hull(&ps)
|
||||||
|
.unwrap()
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let ps = [
|
||||||
|
ps1[8],
|
||||||
|
ps2[8],
|
||||||
|
point![-ps1[8].x, ps1[8].y],
|
||||||
|
point![-ps2[8].x, ps2[8].y],
|
||||||
|
];
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic();
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::convex_hull(&ps)
|
||||||
|
.unwrap()
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
let rigid_body =
|
||||||
|
RigidBodyBuilder::dynamic().translation(vector![0.0, 0.5 + ps2[8].y + 1.0 * i as f32]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(2.0, 0.5).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
73
examples2d/s2d_ball_and_chain.rs
Normal file
73
examples2d/s2d_ball_and_chain.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let mut impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground = bodies.insert(RigidBodyBuilder::fixed());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the bridge.
|
||||||
|
*/
|
||||||
|
let count = 40;
|
||||||
|
let hx = 0.5;
|
||||||
|
let density = 20.0;
|
||||||
|
let friction = 0.6;
|
||||||
|
let capsule = ColliderBuilder::capsule_x(hx, 0.125)
|
||||||
|
.friction(friction)
|
||||||
|
.density(density);
|
||||||
|
|
||||||
|
let mut prev = ground;
|
||||||
|
for i in 0..count {
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
.linear_damping(0.1)
|
||||||
|
.angular_damping(0.1)
|
||||||
|
.translation(vector![(1.0 + 2.0 * i as f32) * hx, count as f32 * hx]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
colliders.insert_with_parent(capsule.clone(), handle, &mut bodies);
|
||||||
|
|
||||||
|
let pivot = point![(2.0 * i as f32) * hx, count as f32 * hx];
|
||||||
|
let joint = RevoluteJointBuilder::new()
|
||||||
|
.local_anchor1(bodies[prev].position().inverse_transform_point(&pivot))
|
||||||
|
.local_anchor2(bodies[handle].position().inverse_transform_point(&pivot))
|
||||||
|
.contacts_enabled(false);
|
||||||
|
impulse_joints.insert(prev, handle, joint, true);
|
||||||
|
prev = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
let radius = 8.0;
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
.linear_damping(0.1)
|
||||||
|
.angular_damping(0.1)
|
||||||
|
.translation(vector![
|
||||||
|
(1.0 + 2.0 * count as f32) * hx + radius - hx,
|
||||||
|
count as f32 * hx
|
||||||
|
]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::ball(radius)
|
||||||
|
.friction(friction)
|
||||||
|
.density(density);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
|
||||||
|
let pivot = point![(2.0 * count as f32) * hx, count as f32 * hx];
|
||||||
|
let joint = RevoluteJointBuilder::new()
|
||||||
|
.local_anchor1(bodies[prev].position().inverse_transform_point(&pivot))
|
||||||
|
.local_anchor2(bodies[handle].position().inverse_transform_point(&pivot))
|
||||||
|
.contacts_enabled(false);
|
||||||
|
impulse_joints.insert(prev, handle, joint, true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
56
examples2d/s2d_bridge.rs
Normal file
56
examples2d/s2d_bridge.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let mut impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground = bodies.insert(RigidBodyBuilder::fixed());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the bridge.
|
||||||
|
*/
|
||||||
|
let density = 20.0;
|
||||||
|
let x_base = -80.0;
|
||||||
|
let count = 160;
|
||||||
|
let mut prev = ground;
|
||||||
|
|
||||||
|
for i in 0..count {
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
.linear_damping(0.1)
|
||||||
|
.angular_damping(0.1)
|
||||||
|
.translation(vector![x_base + 0.5 + 1.0 * i as f32, 20.0]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(0.5, 0.125).density(density);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
|
||||||
|
let pivot = point![x_base + 1.0 * i as f32, 20.0];
|
||||||
|
let joint = RevoluteJointBuilder::new()
|
||||||
|
.local_anchor1(bodies[prev].position().inverse_transform_point(&pivot))
|
||||||
|
.local_anchor2(bodies[handle].position().inverse_transform_point(&pivot))
|
||||||
|
.contacts_enabled(false);
|
||||||
|
impulse_joints.insert(prev, handle, joint, true);
|
||||||
|
prev = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pivot = point![x_base + 1.0 * count as f32, 20.0];
|
||||||
|
let joint = RevoluteJointBuilder::new()
|
||||||
|
.local_anchor1(bodies[prev].position().inverse_transform_point(&pivot))
|
||||||
|
.local_anchor2(bodies[ground].position().inverse_transform_point(&pivot))
|
||||||
|
.contacts_enabled(false);
|
||||||
|
impulse_joints.insert(prev, ground, joint, true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
78
examples2d/s2d_card_house.rs
Normal file
78
examples2d/s2d_card_house.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let friction = 0.7;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -2.0]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(40.0, 2.0).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
let scale = 10.0;
|
||||||
|
let card_height = 0.2 * scale;
|
||||||
|
let card_thickness = 0.001 * scale;
|
||||||
|
let angle0 = 25.0 * std::f32::consts::PI / 180.0;
|
||||||
|
let angle1 = -25.0 * std::f32::consts::PI / 180.0;
|
||||||
|
let angle2 = 0.5 * std::f32::consts::PI;
|
||||||
|
|
||||||
|
let card_box = ColliderBuilder::cuboid(card_thickness, card_height).friction(friction);
|
||||||
|
|
||||||
|
let mut nb = 5;
|
||||||
|
let mut z0 = 0.0;
|
||||||
|
let mut y = card_height - 0.02 * scale;
|
||||||
|
|
||||||
|
while nb != 0 {
|
||||||
|
let mut z = z0;
|
||||||
|
|
||||||
|
for i in 0..nb {
|
||||||
|
if i != nb - 1 {
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
.translation(vector![z + 0.25 * scale, y + card_height - 0.015 * scale])
|
||||||
|
.rotation(angle2);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
colliders.insert_with_parent(card_box.clone(), ground_handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
.translation(vector![z, y])
|
||||||
|
.rotation(angle1);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
colliders.insert_with_parent(card_box.clone(), ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
z += 0.175 * scale;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
.translation(vector![z, y])
|
||||||
|
.rotation(angle0);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
colliders.insert_with_parent(card_box.clone(), ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
z += 0.175 * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
y += card_height * 2.0 - 0.03 * scale;
|
||||||
|
z0 += 0.175 * scale;
|
||||||
|
nb -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
69
examples2d/s2d_confined.rs
Normal file
69
examples2d/s2d_confined.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let radius = 0.5;
|
||||||
|
let grid_count = 25;
|
||||||
|
let friction = 0.6;
|
||||||
|
let max_count = grid_count * grid_count;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let collider =
|
||||||
|
ColliderBuilder::capsule_from_endpoints(point![-10.5, 0.0], point![10.5, 0.0], radius)
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert(collider);
|
||||||
|
let collider =
|
||||||
|
ColliderBuilder::capsule_from_endpoints(point![-10.5, 0.0], point![-10.5, 20.5], radius)
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert(collider);
|
||||||
|
let collider =
|
||||||
|
ColliderBuilder::capsule_from_endpoints(point![10.5, 0.0], point![10.5, 20.5], radius)
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert(collider);
|
||||||
|
let collider =
|
||||||
|
ColliderBuilder::capsule_from_endpoints(point![-10.5, 20.5], point![10.5, 20.5], radius)
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert(collider);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the spheres
|
||||||
|
*/
|
||||||
|
let mut row;
|
||||||
|
let mut count = 0;
|
||||||
|
let mut column = 0;
|
||||||
|
|
||||||
|
while count < max_count {
|
||||||
|
row = 0;
|
||||||
|
for _ in 0..grid_count {
|
||||||
|
let x = -8.75 + column as f32 * 18.0 / (grid_count as f32);
|
||||||
|
let y = 1.5 + row as f32 * 18.0 / (grid_count as f32);
|
||||||
|
let body = RigidBodyBuilder::dynamic()
|
||||||
|
.translation(vector![x, y])
|
||||||
|
.gravity_scale(0.0);
|
||||||
|
let body_handle = bodies.insert(body);
|
||||||
|
let ball = ColliderBuilder::ball(radius).friction(friction);
|
||||||
|
colliders.insert_with_parent(ball, body_handle, &mut bodies);
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
row += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
column += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
50
examples2d/s2d_far_pyramid.rs
Normal file
50
examples2d/s2d_far_pyramid.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let origin = vector![100_000.0, -80_000.0];
|
||||||
|
let friction = 0.6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -1.0] + origin);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(100.0, 1.0).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
let base_count = 10;
|
||||||
|
|
||||||
|
let h = 0.5;
|
||||||
|
let shift = 1.25 * h;
|
||||||
|
|
||||||
|
for i in 0..base_count {
|
||||||
|
let y = (2.0 * i as f32 + 1.0) * shift + 0.5;
|
||||||
|
|
||||||
|
for j in i..base_count {
|
||||||
|
let x = (i as f32 + 1.0) * shift + 2.0 * (j as f32 - i as f32) * shift
|
||||||
|
- h * base_count as f32;
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y] + origin);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(h, h).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5] + origin, 20.0);
|
||||||
|
}
|
||||||
66
examples2d/s2d_high_mass_ratio_1.rs
Normal file
66
examples2d/s2d_high_mass_ratio_1.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let extent = 1.0;
|
||||||
|
let friction = 0.5;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_width = 66.0 * extent;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed();
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::segment(
|
||||||
|
point![-0.5 * 2.0 * ground_width, 0.0],
|
||||||
|
point![0.5 * 2.0 * ground_width, 0.0],
|
||||||
|
)
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
|
||||||
|
for j in 0..3 {
|
||||||
|
let mut count = 10;
|
||||||
|
let offset = -20.0 * extent + 2.0 * (count as f32 + 1.0) * extent * j as f32;
|
||||||
|
let mut y = extent;
|
||||||
|
|
||||||
|
while count > 0 {
|
||||||
|
for i in 0..count {
|
||||||
|
let coeff = i as f32 - 0.5 * count as f32;
|
||||||
|
let yy = if count == 1 { y + 2.0 } else { y };
|
||||||
|
let position = vector![2.0 * coeff * extent + offset, yy];
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(position);
|
||||||
|
let parent = bodies.insert(rigid_body);
|
||||||
|
|
||||||
|
let collider = ColliderBuilder::cuboid(extent, extent)
|
||||||
|
.density(if count == 1 {
|
||||||
|
(j as f32 + 1.0) * 100.0
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
})
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, parent, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
count -= 1;
|
||||||
|
y += 2.0 * extent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
53
examples2d/s2d_high_mass_ratio_2.rs
Normal file
53
examples2d/s2d_high_mass_ratio_2.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let extent = 1.0;
|
||||||
|
let friction = 0.6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_width = 66.0 * extent;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed();
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::segment(
|
||||||
|
point![-0.5 * 2.0 * ground_width, 0.0],
|
||||||
|
point![0.5 * 2.0 * ground_width, 0.0],
|
||||||
|
)
|
||||||
|
.friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![-9.0 * extent, 0.5 * extent]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(0.5 * extent, 0.5 * extent).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![9.0 * extent, 0.5 * extent]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(0.5 * extent, 0.5 * extent).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, (10.0 + 16.0) * extent]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(10.0 * extent, 10.0 * extent).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
47
examples2d/s2d_high_mass_ratio_3.rs
Normal file
47
examples2d/s2d_high_mass_ratio_3.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let extent = 1.0;
|
||||||
|
let friction = 0.6;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -2.0]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(40.0, 2.0).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![-9.0 * extent, 0.5 * extent]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(0.5 * extent, 0.5 * extent).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![9.0 * extent, 0.5 * extent]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(0.5 * extent, 0.5 * extent).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, (10.0 + 16.0) * extent]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(10.0 * extent, 10.0 * extent).friction(friction);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
63
examples2d/s2d_joint_grid.rs
Normal file
63
examples2d/s2d_joint_grid.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let mut impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the joint grid.
|
||||||
|
*/
|
||||||
|
let rad = 0.4;
|
||||||
|
let numi = 100;
|
||||||
|
let numk = 100;
|
||||||
|
let shift = 1.0;
|
||||||
|
let mut index = 0;
|
||||||
|
let mut handles = vec![RigidBodyHandle::invalid(); numi * numk];
|
||||||
|
|
||||||
|
for k in 0..numk {
|
||||||
|
for i in 0..numi {
|
||||||
|
let body_type = if k >= numk / 2 - 3 && k <= numk / 2 + 3 && i == 0 {
|
||||||
|
RigidBodyType::Fixed
|
||||||
|
} else {
|
||||||
|
RigidBodyType::Dynamic
|
||||||
|
};
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::new(body_type)
|
||||||
|
.translation(vector![k as f32 * shift, -(i as f32) * shift]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::ball(rad);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
|
let joint = RevoluteJointBuilder::new()
|
||||||
|
.local_anchor1(point![0.0, -0.5 * shift])
|
||||||
|
.local_anchor2(point![0.0, 0.5 * shift])
|
||||||
|
.contacts_enabled(false);
|
||||||
|
impulse_joints.insert(handles[index - 1], handle, joint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if k > 0 {
|
||||||
|
let joint = RevoluteJointBuilder::new()
|
||||||
|
.local_anchor1(point![0.5 * shift, 0.0])
|
||||||
|
.local_anchor2(point![-0.5 * shift, 0.0])
|
||||||
|
.contacts_enabled(false);
|
||||||
|
impulse_joints.insert(handles[index - numi], handle, joint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handles[index] = handle;
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
47
examples2d/s2d_pyramid.rs
Normal file
47
examples2d/s2d_pyramid.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use rapier2d::prelude::*;
|
||||||
|
use rapier_testbed2d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -1.0]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(100.0, 1.0).friction(0.6);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
let base_count = 100;
|
||||||
|
|
||||||
|
let h = 0.5;
|
||||||
|
let shift = 1.0 * h;
|
||||||
|
|
||||||
|
for i in 0..base_count {
|
||||||
|
let y = (2.0 * i as f32 + 1.0) * shift;
|
||||||
|
|
||||||
|
for j in i..base_count {
|
||||||
|
let x = (i as f32 + 1.0) * shift + 2.0 * (j as f32 - i as f32) * shift
|
||||||
|
- h * base_count as f32;
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y]);
|
||||||
|
let ground_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(h, h).friction(0.6);
|
||||||
|
colliders.insert_with_parent(collider, ground_handle, &mut bodies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 2.5], 20.0);
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
colliders.insert_with_parent(collider, handle, &mut bodies);
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the trimeshes from a tesselated SVG.
|
* Create the trimeshes from a tessellated SVG.
|
||||||
*/
|
*/
|
||||||
let mut fill_tess = FillTessellator::new();
|
let mut fill_tess = FillTessellator::new();
|
||||||
let opt = usvg::Options::default();
|
let opt = usvg::Options::default();
|
||||||
@@ -67,7 +67,7 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
&FillOptions::tolerance(0.01),
|
&FillOptions::tolerance(0.01),
|
||||||
&mut BuffersBuilder::new(&mut mesh, VertexCtor { prim_id: 0 }),
|
&mut BuffersBuilder::new(&mut mesh, VertexCtor { prim_id: 0 }),
|
||||||
)
|
)
|
||||||
.expect("Tesselation failed.");
|
.expect("Tessellation failed.");
|
||||||
|
|
||||||
let angle = transform.get_rotate() as f32;
|
let angle = transform.get_rotate() as f32;
|
||||||
|
|
||||||
@@ -84,7 +84,8 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for k in 0..5 {
|
for k in 0..5 {
|
||||||
let collider = ColliderBuilder::trimesh(vertices.clone(), indices.clone());
|
let collider = ColliderBuilder::trimesh(vertices.clone(), indices.clone())
|
||||||
|
.contact_skin(0.2);
|
||||||
let rigid_body = RigidBodyBuilder::dynamic()
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
.translation(vector![ith as f32 * 8.0 - 20.0, 20.0 + k as f32 * 11.0])
|
.translation(vector![ith as f32 * 8.0 - 20.0, 20.0 + k as f32 * 11.0])
|
||||||
.rotation(angle);
|
.rotation(angle);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use rapier_testbed3d::Testbed;
|
|||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct State {
|
struct State {
|
||||||
pub islands: IslandManager,
|
pub islands: IslandManager,
|
||||||
pub broad_phase: BroadPhase,
|
pub broad_phase: DefaultBroadPhase,
|
||||||
pub narrow_phase: NarrowPhase,
|
pub narrow_phase: NarrowPhase,
|
||||||
pub bodies: RigidBodySet,
|
pub bodies: RigidBodySet,
|
||||||
pub colliders: ColliderSet,
|
pub colliders: ColliderSet,
|
||||||
|
|||||||
@@ -23,12 +23,15 @@ mod debug_deserialize3;
|
|||||||
mod debug_dynamic_collider_add3;
|
mod debug_dynamic_collider_add3;
|
||||||
mod debug_friction3;
|
mod debug_friction3;
|
||||||
mod debug_infinite_fall3;
|
mod debug_infinite_fall3;
|
||||||
|
mod debug_pop3;
|
||||||
mod debug_prismatic3;
|
mod debug_prismatic3;
|
||||||
mod debug_rollback3;
|
mod debug_rollback3;
|
||||||
mod debug_shape_modification3;
|
mod debug_shape_modification3;
|
||||||
|
mod debug_thin_cube_on_mesh3;
|
||||||
mod debug_triangle3;
|
mod debug_triangle3;
|
||||||
mod debug_trimesh3;
|
mod debug_trimesh3;
|
||||||
mod domino3;
|
mod domino3;
|
||||||
|
mod dynamic_trimesh3;
|
||||||
mod fountain3;
|
mod fountain3;
|
||||||
mod heightfield3;
|
mod heightfield3;
|
||||||
mod joints3;
|
mod joints3;
|
||||||
@@ -39,6 +42,7 @@ mod debug_cube_high_mass_ratio3;
|
|||||||
mod debug_internal_edges3;
|
mod debug_internal_edges3;
|
||||||
mod debug_long_chain3;
|
mod debug_long_chain3;
|
||||||
mod debug_multibody_ang_motor_pos3;
|
mod debug_multibody_ang_motor_pos3;
|
||||||
|
mod inverse_kinematics3;
|
||||||
mod joint_motor_position3;
|
mod joint_motor_position3;
|
||||||
mod keva3;
|
mod keva3;
|
||||||
mod locked_rotations3;
|
mod locked_rotations3;
|
||||||
@@ -102,8 +106,10 @@ pub fn main() {
|
|||||||
("Convex polyhedron", convex_polyhedron3::init_world),
|
("Convex polyhedron", convex_polyhedron3::init_world),
|
||||||
("Damping", damping3::init_world),
|
("Damping", damping3::init_world),
|
||||||
("Domino", domino3::init_world),
|
("Domino", domino3::init_world),
|
||||||
|
("Dynamic trimeshes", dynamic_trimesh3::init_world),
|
||||||
("Heightfield", heightfield3::init_world),
|
("Heightfield", heightfield3::init_world),
|
||||||
("Impulse Joints", joints3::init_world_with_joints),
|
("Impulse Joints", joints3::init_world_with_joints),
|
||||||
|
("Inverse kinematics", inverse_kinematics3::init_world),
|
||||||
("Joint Motor Position", joint_motor_position3::init_world),
|
("Joint Motor Position", joint_motor_position3::init_world),
|
||||||
("Locked rotations", locked_rotations3::init_world),
|
("Locked rotations", locked_rotations3::init_world),
|
||||||
("One-way platforms", one_way_platforms3::init_world),
|
("One-way platforms", one_way_platforms3::init_world),
|
||||||
@@ -124,6 +130,7 @@ pub fn main() {
|
|||||||
),
|
),
|
||||||
("(Debug) big colliders", debug_big_colliders3::init_world),
|
("(Debug) big colliders", debug_big_colliders3::init_world),
|
||||||
("(Debug) boxes", debug_boxes3::init_world),
|
("(Debug) boxes", debug_boxes3::init_world),
|
||||||
|
("(Debug) pop", debug_pop3::init_world),
|
||||||
(
|
(
|
||||||
"(Debug) dyn. coll. add",
|
"(Debug) dyn. coll. add",
|
||||||
debug_dynamic_collider_add3::init_world,
|
debug_dynamic_collider_add3::init_world,
|
||||||
@@ -141,6 +148,7 @@ pub fn main() {
|
|||||||
),
|
),
|
||||||
("(Debug) triangle", debug_triangle3::init_world),
|
("(Debug) triangle", debug_triangle3::init_world),
|
||||||
("(Debug) trimesh", debug_trimesh3::init_world),
|
("(Debug) trimesh", debug_trimesh3::init_world),
|
||||||
|
("(Debug) thin cube", debug_thin_cube_on_mesh3::init_world),
|
||||||
("(Debug) cylinder", debug_cylinder3::init_world),
|
("(Debug) cylinder", debug_cylinder3::init_world),
|
||||||
("(Debug) infinite fall", debug_infinite_fall3::init_world),
|
("(Debug) infinite fall", debug_infinite_fall3::init_world),
|
||||||
("(Debug) prismatic", debug_prismatic3::init_world),
|
("(Debug) prismatic", debug_prismatic3::init_world),
|
||||||
|
|||||||
@@ -13,21 +13,37 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
/*
|
/*
|
||||||
* Ground
|
* Ground
|
||||||
*/
|
*/
|
||||||
|
let scale = 1.0; // Set to a larger value to check if it still works with larger units.
|
||||||
let ground_size = 5.0;
|
let ground_size = 5.0;
|
||||||
let ground_height = 0.1;
|
let ground_height = 0.1;
|
||||||
|
|
||||||
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
|
let rigid_body =
|
||||||
|
RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0] * scale);
|
||||||
let floor_handle = bodies.insert(rigid_body);
|
let floor_handle = bodies.insert(rigid_body);
|
||||||
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
|
let collider = ColliderBuilder::cuboid(
|
||||||
|
ground_size * scale,
|
||||||
|
ground_height * scale,
|
||||||
|
ground_size * scale,
|
||||||
|
);
|
||||||
|
colliders.insert_with_parent(collider, floor_handle, &mut bodies);
|
||||||
|
|
||||||
|
let rigid_body =
|
||||||
|
RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, -ground_size] * scale); //.rotation(vector![-0.1, 0.0, 0.0]);
|
||||||
|
let floor_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(
|
||||||
|
ground_size * scale,
|
||||||
|
ground_size * scale,
|
||||||
|
ground_height * scale,
|
||||||
|
);
|
||||||
colliders.insert_with_parent(collider, floor_handle, &mut bodies);
|
colliders.insert_with_parent(collider, floor_handle, &mut bodies);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Character we will control manually.
|
* Character we will control manually.
|
||||||
*/
|
*/
|
||||||
let rigid_body =
|
let rigid_body =
|
||||||
RigidBodyBuilder::kinematic_position_based().translation(vector![-3.0, 5.0, 0.0]);
|
RigidBodyBuilder::kinematic_position_based().translation(vector![-3.0, 5.0, 0.0] * scale);
|
||||||
let character_handle = bodies.insert(rigid_body);
|
let character_handle = bodies.insert(rigid_body);
|
||||||
let collider = ColliderBuilder::capsule_y(0.3, 0.15); // 0.15, 0.3, 0.15);
|
let collider = ColliderBuilder::capsule_y(0.3 * scale, 0.15 * scale); // 0.15, 0.3, 0.15);
|
||||||
colliders.insert_with_parent(collider, character_handle, &mut bodies);
|
colliders.insert_with_parent(collider, character_handle, &mut bodies);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -47,9 +63,9 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
let y = j as f32 * shift + centery;
|
let y = j as f32 * shift + centery;
|
||||||
let z = k as f32 * shift + centerx;
|
let z = k as f32 * shift + centerx;
|
||||||
|
|
||||||
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y, z]);
|
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y, z] * scale);
|
||||||
let handle = bodies.insert(rigid_body);
|
let handle = bodies.insert(rigid_body);
|
||||||
let collider = ColliderBuilder::cuboid(rad, rad, rad);
|
let collider = ColliderBuilder::cuboid(rad * scale, rad * scale, rad * scale);
|
||||||
colliders.insert_with_parent(collider, handle, &mut bodies);
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,8 +80,12 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
let x = i as f32 * stair_width / 2.0;
|
let x = i as f32 * stair_width / 2.0;
|
||||||
let y = i as f32 * stair_height * 1.5 + 3.0;
|
let y = i as f32 * stair_height * 1.5 + 3.0;
|
||||||
|
|
||||||
let collider = ColliderBuilder::cuboid(stair_width / 2.0, stair_height / 2.0, stair_width)
|
let collider = ColliderBuilder::cuboid(
|
||||||
.translation(vector![x, y, 0.0]);
|
stair_width / 2.0 * scale,
|
||||||
|
stair_height / 2.0 * scale,
|
||||||
|
stair_width * scale,
|
||||||
|
)
|
||||||
|
.translation(vector![x * scale, y * scale, 0.0]);
|
||||||
colliders.insert(collider);
|
colliders.insert(collider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +94,13 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
*/
|
*/
|
||||||
let slope_angle = 0.2;
|
let slope_angle = 0.2;
|
||||||
let slope_size = 2.0;
|
let slope_size = 2.0;
|
||||||
let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size)
|
let collider = ColliderBuilder::cuboid(
|
||||||
.translation(vector![ground_size + slope_size, -ground_height + 0.4, 0.0])
|
slope_size * scale,
|
||||||
.rotation(Vector::z() * slope_angle);
|
ground_height * scale,
|
||||||
|
slope_size * scale,
|
||||||
|
)
|
||||||
|
.translation(vector![ground_size + slope_size, -ground_height + 0.4, 0.0] * scale)
|
||||||
|
.rotation(Vector::z() * slope_angle);
|
||||||
colliders.insert(collider);
|
colliders.insert(collider);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -84,22 +108,29 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
*/
|
*/
|
||||||
let impossible_slope_angle = 0.9;
|
let impossible_slope_angle = 0.9;
|
||||||
let impossible_slope_size = 2.0;
|
let impossible_slope_size = 2.0;
|
||||||
let collider = ColliderBuilder::cuboid(slope_size, ground_height, ground_size)
|
let collider = ColliderBuilder::cuboid(
|
||||||
.translation(vector![
|
slope_size * scale,
|
||||||
|
ground_height * scale,
|
||||||
|
ground_size * scale,
|
||||||
|
)
|
||||||
|
.translation(
|
||||||
|
vector![
|
||||||
ground_size + slope_size * 2.0 + impossible_slope_size - 0.9,
|
ground_size + slope_size * 2.0 + impossible_slope_size - 0.9,
|
||||||
-ground_height + 2.3,
|
-ground_height + 2.3,
|
||||||
0.0
|
0.0
|
||||||
])
|
] * scale,
|
||||||
.rotation(Vector::z() * impossible_slope_angle);
|
)
|
||||||
|
.rotation(Vector::z() * impossible_slope_angle);
|
||||||
colliders.insert(collider);
|
colliders.insert(collider);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a moving platform.
|
* Create a moving platform.
|
||||||
*/
|
*/
|
||||||
let body = RigidBodyBuilder::kinematic_velocity_based().translation(vector![-8.0, 1.5, 0.0]);
|
let body =
|
||||||
|
RigidBodyBuilder::kinematic_velocity_based().translation(vector![-8.0, 1.5, 0.0] * scale);
|
||||||
// .rotation(-0.3);
|
// .rotation(-0.3);
|
||||||
let platform_handle = bodies.insert(body);
|
let platform_handle = bodies.insert(body);
|
||||||
let collider = ColliderBuilder::cuboid(2.0, ground_height, 2.0);
|
let collider = ColliderBuilder::cuboid(2.0 * scale, ground_height * scale, 2.0 * scale);
|
||||||
colliders.insert_with_parent(collider, platform_handle, &mut bodies);
|
colliders.insert_with_parent(collider, platform_handle, &mut bodies);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -113,18 +144,18 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
+ (j as f32 * ground_size.z / (nsubdivs as f32) / 2.0).cos()
|
+ (j as f32 * ground_size.z / (nsubdivs as f32) / 2.0).cos()
|
||||||
});
|
});
|
||||||
|
|
||||||
let collider =
|
let collider = ColliderBuilder::heightfield(heights, ground_size * scale)
|
||||||
ColliderBuilder::heightfield(heights, ground_size).translation(vector![-8.0, 5.0, 0.0]);
|
.translation(vector![-8.0, 5.0, 0.0] * scale);
|
||||||
colliders.insert(collider);
|
colliders.insert(collider);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A tilting dynamic body with a limited joint.
|
* A tilting dynamic body with a limited joint.
|
||||||
*/
|
*/
|
||||||
let ground = RigidBodyBuilder::fixed().translation(vector![0.0, 5.0, 0.0]);
|
let ground = RigidBodyBuilder::fixed().translation(vector![0.0, 5.0, 0.0] * scale);
|
||||||
let ground_handle = bodies.insert(ground);
|
let ground_handle = bodies.insert(ground);
|
||||||
let body = RigidBodyBuilder::dynamic().translation(vector![0.0, 5.0, 0.0]);
|
let body = RigidBodyBuilder::dynamic().translation(vector![0.0, 5.0, 0.0] * scale);
|
||||||
let handle = bodies.insert(body);
|
let handle = bodies.insert(body);
|
||||||
let collider = ColliderBuilder::cuboid(1.0, 0.1, 2.0);
|
let collider = ColliderBuilder::cuboid(1.0 * scale, 0.1 * scale, 2.0 * scale);
|
||||||
colliders.insert_with_parent(collider, handle, &mut bodies);
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
let joint = RevoluteJointBuilder::new(Vector::z_axis()).limits([-0.3, 0.3]);
|
let joint = RevoluteJointBuilder::new(Vector::z_axis()).limits([-0.3, 0.3]);
|
||||||
impulse_joints.insert(ground_handle, handle, joint, true);
|
impulse_joints.insert(ground_handle, handle, joint, true);
|
||||||
@@ -137,7 +168,7 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
(run_state.time * 2.0).sin() * 2.0,
|
(run_state.time * 2.0).sin() * 2.0,
|
||||||
(run_state.time * 5.0).sin() * 1.5,
|
(run_state.time * 5.0).sin() * 1.5,
|
||||||
0.0
|
0.0
|
||||||
];
|
] * scale;
|
||||||
// let angvel = run_state.time.sin() * 0.5;
|
// let angvel = run_state.time.sin() * 0.5;
|
||||||
|
|
||||||
// Update the velocity-based kinematic body by setting its velocity.
|
// Update the velocity-based kinematic body by setting its velocity.
|
||||||
|
|||||||
@@ -1,128 +1,5 @@
|
|||||||
use obj::raw::object::Polygon;
|
|
||||||
use rapier3d::parry::bounding_volume;
|
|
||||||
use rapier3d::prelude::*;
|
|
||||||
use rapier_testbed3d::Testbed;
|
use rapier_testbed3d::Testbed;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::BufReader;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* NOTE: The `r` macro is only here to convert from f64 to the `N` scalar type.
|
|
||||||
* This simplifies experimentation with various scalar types (f32, fixed-point numbers, etc.)
|
|
||||||
*/
|
|
||||||
pub fn init_world(testbed: &mut Testbed) {
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
/*
|
crate::dynamic_trimesh3::do_init_world(testbed, true);
|
||||||
* World
|
|
||||||
*/
|
|
||||||
let mut bodies = RigidBodySet::new();
|
|
||||||
let mut colliders = ColliderSet::new();
|
|
||||||
let impulse_joints = ImpulseJointSet::new();
|
|
||||||
let multibody_joints = MultibodyJointSet::new();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ground
|
|
||||||
*/
|
|
||||||
let ground_size = 50.0;
|
|
||||||
let ground_height = 0.1;
|
|
||||||
|
|
||||||
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
|
|
||||||
let handle = bodies.insert(rigid_body);
|
|
||||||
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
|
|
||||||
colliders.insert_with_parent(collider, handle, &mut bodies);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Create the convex decompositions.
|
|
||||||
*/
|
|
||||||
let geoms = models();
|
|
||||||
let ngeoms = geoms.len();
|
|
||||||
let width = (ngeoms as f32).sqrt() as usize;
|
|
||||||
let num_duplications = 4;
|
|
||||||
let shift = 5.0f32;
|
|
||||||
|
|
||||||
for (igeom, obj_path) in geoms.into_iter().enumerate() {
|
|
||||||
let deltas = Isometry::identity();
|
|
||||||
|
|
||||||
let mut shapes = Vec::new();
|
|
||||||
println!("Parsing and decomposing: {}", obj_path);
|
|
||||||
let input = BufReader::new(File::open(obj_path).unwrap());
|
|
||||||
|
|
||||||
if let Ok(model) = obj::raw::parse_obj(input) {
|
|
||||||
let mut vertices: Vec<_> = model
|
|
||||||
.positions
|
|
||||||
.iter()
|
|
||||||
.map(|v| point![v.0, v.1, v.2])
|
|
||||||
.collect();
|
|
||||||
let indices: Vec<_> = model
|
|
||||||
.polygons
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|p| match p {
|
|
||||||
Polygon::P(idx) => idx.into_iter(),
|
|
||||||
Polygon::PT(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
|
|
||||||
Polygon::PN(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
|
|
||||||
Polygon::PTN(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Compute the size of the model, to scale it and have similar size for everything.
|
|
||||||
let aabb = bounding_volume::details::point_cloud_aabb(&deltas, &vertices);
|
|
||||||
let center = aabb.center();
|
|
||||||
let diag = (aabb.maxs - aabb.mins).norm();
|
|
||||||
|
|
||||||
vertices
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|p| *p = (*p - center.coords) * 6.0 / diag);
|
|
||||||
|
|
||||||
let indices: Vec<_> = indices
|
|
||||||
.chunks(3)
|
|
||||||
.map(|idx| [idx[0] as u32, idx[1] as u32, idx[2] as u32])
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let decomposed_shape = SharedShape::convex_decomposition(&vertices, &indices);
|
|
||||||
shapes.push(decomposed_shape);
|
|
||||||
|
|
||||||
// let compound = SharedShape::compound(compound_parts);
|
|
||||||
|
|
||||||
for k in 1..num_duplications + 1 {
|
|
||||||
let x = (igeom % width) as f32 * shift;
|
|
||||||
let y = (igeom / width) as f32 * shift + 4.0;
|
|
||||||
let z = k as f32 * shift;
|
|
||||||
|
|
||||||
let body = RigidBodyBuilder::dynamic().translation(vector![x, y, z]);
|
|
||||||
let handle = bodies.insert(body);
|
|
||||||
|
|
||||||
for shape in &shapes {
|
|
||||||
let collider = ColliderBuilder::new(shape.clone());
|
|
||||||
colliders.insert_with_parent(collider, handle, &mut bodies);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set up the testbed.
|
|
||||||
*/
|
|
||||||
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
|
||||||
testbed.look_at(point![100.0, 100.0, 100.0], Point::origin());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn models() -> Vec<String> {
|
|
||||||
vec![
|
|
||||||
"assets/3d/camel_decimated.obj".to_string(),
|
|
||||||
"assets/3d/chair.obj".to_string(),
|
|
||||||
"assets/3d/cup_decimated.obj".to_string(),
|
|
||||||
"assets/3d/dilo_decimated.obj".to_string(),
|
|
||||||
"assets/3d/feline_decimated.obj".to_string(),
|
|
||||||
"assets/3d/genus3_decimated.obj".to_string(),
|
|
||||||
"assets/3d/hand2_decimated.obj".to_string(),
|
|
||||||
"assets/3d/hand_decimated.obj".to_string(),
|
|
||||||
"assets/3d/hornbug.obj".to_string(),
|
|
||||||
"assets/3d/octopus_decimated.obj".to_string(),
|
|
||||||
"assets/3d/rabbit_decimated.obj".to_string(),
|
|
||||||
// "assets/3d/rust_logo.obj".to_string(),
|
|
||||||
"assets/3d/rust_logo_simplified.obj".to_string(),
|
|
||||||
"assets/3d/screwdriver_decimated.obj".to_string(),
|
|
||||||
"assets/3d/table.obj".to_string(),
|
|
||||||
"assets/3d/tstTorusModel.obj".to_string(),
|
|
||||||
// "assets/3d/tstTorusModel2.obj".to_string(),
|
|
||||||
// "assets/3d/tstTorusModel3.obj".to_string(),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ struct PhysicsState {
|
|||||||
pub gravity: Vector<f32>,
|
pub gravity: Vector<f32>,
|
||||||
pub integration_parameters: IntegrationParameters,
|
pub integration_parameters: IntegrationParameters,
|
||||||
pub islands: IslandManager,
|
pub islands: IslandManager,
|
||||||
pub broad_phase: BroadPhase,
|
pub broad_phase: DefaultBroadPhase,
|
||||||
pub narrow_phase: NarrowPhase,
|
pub narrow_phase: NarrowPhase,
|
||||||
pub bodies: RigidBodySet,
|
pub bodies: RigidBodySet,
|
||||||
pub colliders: ColliderSet,
|
pub colliders: ColliderSet,
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
let multibody_joints = MultibodyJointSet::new();
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
let heights = DMatrix::zeros(100, 100);
|
let heights = DMatrix::zeros(100, 100);
|
||||||
let heightfield = HeightField::new(heights, vector![60.0, 1.0, 60.0]);
|
let heightfield =
|
||||||
|
HeightField::with_flags(heights, vector![60.0, 1.0, 60.0], HeightFieldFlags::all());
|
||||||
let rotation = vector![0.0, 0.0, 0.0]; // vector![-0.1, 0.0, 0.0];
|
let rotation = vector![0.0, 0.0, 0.0]; // vector![-0.1, 0.0, 0.0];
|
||||||
colliders
|
colliders
|
||||||
.insert(ColliderBuilder::new(SharedShape::new(heightfield.clone())).rotation(rotation));
|
.insert(ColliderBuilder::new(SharedShape::new(heightfield.clone())).rotation(rotation));
|
||||||
|
|||||||
42
examples3d/debug_pop3.rs
Normal file
42
examples3d/debug_pop3.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use rapier3d::prelude::*;
|
||||||
|
use rapier_testbed3d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_size = 10.0;
|
||||||
|
let ground_height = 10.0;
|
||||||
|
|
||||||
|
for _ in 0..1 {
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the dynamic box rigid body.
|
||||||
|
for _ in 0..1 {
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
// .translation(vector![0.0, 0.1, 0.0])
|
||||||
|
// .rotation(vector![0.8, 0.2, 0.1])
|
||||||
|
.can_sleep(false);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(1.0, 1.0, 1.0);
|
||||||
|
colliders.insert_with_parent(collider.clone(), handle, &mut bodies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![10.0, 10.0, 10.0], Point::origin());
|
||||||
|
}
|
||||||
56
examples3d/debug_thin_cube_on_mesh3.rs
Normal file
56
examples3d/debug_thin_cube_on_mesh3.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use rapier3d::prelude::*;
|
||||||
|
use rapier_testbed3d::Testbed;
|
||||||
|
|
||||||
|
// This shows a bug when a cylinder is in contact with a very large
|
||||||
|
// but very thin cuboid. In this case the EPA returns an incorrect
|
||||||
|
// contact normal, resulting in the cylinder falling through the floor.
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
// let vertices = vec![
|
||||||
|
// point![-50.0, 0.0, -50.0],
|
||||||
|
// point![-50.0, 0.0, 50.0],
|
||||||
|
// point![50.0, 0.0, 50.0],
|
||||||
|
// point![50.0, 0.0, -50.0],
|
||||||
|
// ];
|
||||||
|
// let indices = vec![[0, 1, 2], [0, 2, 3]];
|
||||||
|
//
|
||||||
|
// let collider = ColliderBuilder::trimesh_with_flags(vertices, indices, TriMeshFlags::all());
|
||||||
|
// colliders.insert(collider);
|
||||||
|
|
||||||
|
let heights = DMatrix::repeat(2, 2, 0.0);
|
||||||
|
let collider = ColliderBuilder::heightfield_with_flags(
|
||||||
|
heights,
|
||||||
|
Vector::new(50.0, 1.0, 50.0),
|
||||||
|
HeightFieldFlags::FIX_INTERNAL_EDGES,
|
||||||
|
);
|
||||||
|
colliders.insert(collider);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the cubes
|
||||||
|
*/
|
||||||
|
// Build the rigid body.
|
||||||
|
let rigid_body = RigidBodyBuilder::dynamic()
|
||||||
|
.translation(vector![0.0, 5.0, 0.0])
|
||||||
|
.rotation(vector![0.5, 0.0, 0.5])
|
||||||
|
.linvel(vector![0.0, -100.0, 0.0])
|
||||||
|
.soft_ccd_prediction(10.0);
|
||||||
|
let handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(5.0, 0.015, 5.0);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![100.0, 100.0, 100.0], Point::origin());
|
||||||
|
}
|
||||||
145
examples3d/dynamic_trimesh3.rs
Normal file
145
examples3d/dynamic_trimesh3.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
use obj::raw::object::Polygon;
|
||||||
|
use rapier3d::parry::bounding_volume;
|
||||||
|
use rapier3d::prelude::*;
|
||||||
|
use rapier_testbed3d::Testbed;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
do_init_world(testbed, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_init_world(testbed: &mut Testbed, use_convex_decomposition: bool) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
//// OPTION 1: floor made of a single big box.
|
||||||
|
// let ground_size = 50.0;
|
||||||
|
// let ground_height = 0.1;
|
||||||
|
// let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
|
||||||
|
// let handle = bodies.insert(rigid_body);
|
||||||
|
// let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
|
||||||
|
// colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
|
||||||
|
//// OPTION 2: floor made of a wavy mesh.
|
||||||
|
let nsubdivs = 100;
|
||||||
|
let heights = DMatrix::from_fn(nsubdivs + 1, nsubdivs + 1, |i, j| {
|
||||||
|
-(i as f32 * 40.0 / (nsubdivs as f32) / 2.0).cos()
|
||||||
|
- (j as f32 * 40.0 / (nsubdivs as f32) / 2.0).cos()
|
||||||
|
});
|
||||||
|
let heightfield = HeightField::new(heights, vector![100.0, 2.0, 100.0]);
|
||||||
|
let mut trimesh = TriMesh::from(heightfield);
|
||||||
|
let _ = trimesh.set_flags(TriMeshFlags::FIX_INTERNAL_EDGES);
|
||||||
|
colliders.insert(ColliderBuilder::new(SharedShape::new(trimesh.clone())));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the convex decompositions.
|
||||||
|
*/
|
||||||
|
let geoms = models();
|
||||||
|
let ngeoms = geoms.len();
|
||||||
|
let width = (ngeoms as f32).sqrt() as usize;
|
||||||
|
let num_duplications = 4;
|
||||||
|
let shift_y = 8.0f32;
|
||||||
|
let shift_xz = 9.0f32;
|
||||||
|
|
||||||
|
for (igeom, obj_path) in geoms.into_iter().enumerate() {
|
||||||
|
let deltas = Isometry::identity();
|
||||||
|
|
||||||
|
let mut shapes = Vec::new();
|
||||||
|
println!("Parsing and decomposing: {}", obj_path);
|
||||||
|
let input = BufReader::new(File::open(obj_path).unwrap());
|
||||||
|
|
||||||
|
if let Ok(model) = obj::raw::parse_obj(input) {
|
||||||
|
let mut vertices: Vec<_> = model
|
||||||
|
.positions
|
||||||
|
.iter()
|
||||||
|
.map(|v| point![v.0, v.1, v.2])
|
||||||
|
.collect();
|
||||||
|
let indices: Vec<_> = model
|
||||||
|
.polygons
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|p| match p {
|
||||||
|
Polygon::P(idx) => idx.into_iter(),
|
||||||
|
Polygon::PT(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
|
||||||
|
Polygon::PN(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
|
||||||
|
Polygon::PTN(idx) => Vec::from_iter(idx.into_iter().map(|i| i.0)).into_iter(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Compute the size of the model, to scale it and have similar size for everything.
|
||||||
|
let aabb = bounding_volume::details::point_cloud_aabb(&deltas, &vertices);
|
||||||
|
let center = aabb.center();
|
||||||
|
let diag = (aabb.maxs - aabb.mins).norm();
|
||||||
|
|
||||||
|
vertices
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|p| *p = (*p - center.coords) * 10.0 / diag);
|
||||||
|
|
||||||
|
let indices: Vec<_> = indices
|
||||||
|
.chunks(3)
|
||||||
|
.map(|idx| [idx[0] as u32, idx[1] as u32, idx[2] as u32])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let decomposed_shape = if use_convex_decomposition {
|
||||||
|
SharedShape::convex_decomposition(&vertices, &indices)
|
||||||
|
} else {
|
||||||
|
// SharedShape::trimesh(vertices, indices)
|
||||||
|
SharedShape::trimesh_with_flags(vertices, indices, TriMeshFlags::FIX_INTERNAL_EDGES)
|
||||||
|
};
|
||||||
|
shapes.push(decomposed_shape);
|
||||||
|
|
||||||
|
// let compound = SharedShape::compound(compound_parts);
|
||||||
|
|
||||||
|
for k in 1..num_duplications + 1 {
|
||||||
|
let x =
|
||||||
|
(igeom % width) as f32 * shift_xz - num_duplications as f32 * shift_xz / 2.0;
|
||||||
|
let y = (igeom / width) as f32 * shift_y + 7.0;
|
||||||
|
let z = k as f32 * shift_xz - num_duplications as f32 * shift_xz / 2.0;
|
||||||
|
|
||||||
|
let body = RigidBodyBuilder::dynamic().translation(vector![x, y, z]);
|
||||||
|
let handle = bodies.insert(body);
|
||||||
|
|
||||||
|
for shape in &shapes {
|
||||||
|
let collider = ColliderBuilder::new(shape.clone()).contact_skin(0.1);
|
||||||
|
colliders.insert_with_parent(collider, handle, &mut bodies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![100.0, 100.0, 100.0], Point::origin());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn models() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
"assets/3d/camel_decimated.obj".to_string(),
|
||||||
|
"assets/3d/chair.obj".to_string(),
|
||||||
|
"assets/3d/cup_decimated.obj".to_string(),
|
||||||
|
"assets/3d/dilo_decimated.obj".to_string(),
|
||||||
|
"assets/3d/tstTorusModel2.obj".to_string(),
|
||||||
|
"assets/3d/feline_decimated.obj".to_string(),
|
||||||
|
"assets/3d/genus3_decimated.obj".to_string(),
|
||||||
|
// "assets/3d/hand2_decimated.obj".to_string(),
|
||||||
|
// "assets/3d/hand_decimated.obj".to_string(),
|
||||||
|
"assets/3d/hornbug.obj".to_string(),
|
||||||
|
"assets/3d/tstTorusModel.obj".to_string(),
|
||||||
|
"assets/3d/octopus_decimated.obj".to_string(),
|
||||||
|
"assets/3d/rabbit_decimated.obj".to_string(),
|
||||||
|
"assets/3d/rust_logo_simplified.obj".to_string(),
|
||||||
|
"assets/3d/screwdriver_decimated.obj".to_string(),
|
||||||
|
"assets/3d/table.obj".to_string(),
|
||||||
|
"assets/3d/tstTorusModel3.obj".to_string(),
|
||||||
|
]
|
||||||
|
}
|
||||||
103
examples3d/inverse_kinematics3.rs
Normal file
103
examples3d/inverse_kinematics3.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
use rapier3d::prelude::*;
|
||||||
|
use rapier_testbed3d::Testbed;
|
||||||
|
|
||||||
|
pub fn init_world(testbed: &mut Testbed) {
|
||||||
|
/*
|
||||||
|
* World
|
||||||
|
*/
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut colliders = ColliderSet::new();
|
||||||
|
let impulse_joints = ImpulseJointSet::new();
|
||||||
|
let mut multibody_joints = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ground
|
||||||
|
*/
|
||||||
|
let ground_size = 0.2;
|
||||||
|
let ground_height = 0.01;
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
|
||||||
|
let floor_handle = bodies.insert(rigid_body);
|
||||||
|
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
|
||||||
|
colliders.insert_with_parent(collider, floor_handle, &mut bodies);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup groups
|
||||||
|
*/
|
||||||
|
let num_segments = 10;
|
||||||
|
let body = RigidBodyBuilder::fixed();
|
||||||
|
let mut last_body = bodies.insert(body);
|
||||||
|
let mut last_link = MultibodyJointHandle::invalid();
|
||||||
|
|
||||||
|
for i in 0..num_segments {
|
||||||
|
let size = 1.0 / num_segments as f32;
|
||||||
|
let body = RigidBodyBuilder::dynamic().can_sleep(false);
|
||||||
|
let new_body = bodies.insert(body);
|
||||||
|
// NOTE: we add a sensor collider just to make the destbed draw a rectangle to make
|
||||||
|
// the demo look nicer. IK could be used without cuboid.
|
||||||
|
let collider = ColliderBuilder::cuboid(size / 8.0, size / 2.0, size / 8.0)
|
||||||
|
.density(0.0)
|
||||||
|
.sensor(true);
|
||||||
|
colliders.insert_with_parent(collider, new_body, &mut bodies);
|
||||||
|
|
||||||
|
let link_ab = SphericalJointBuilder::new()
|
||||||
|
.local_anchor1(point![0.0, size / 2.0 * (i != 0) as usize as f32, 0.0])
|
||||||
|
.local_anchor2(point![0.0, -size / 2.0, 0.0])
|
||||||
|
.build()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
last_link = multibody_joints
|
||||||
|
.insert(last_body, new_body, link_ab, true)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
last_body = new_body;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut displacements = DVector::zeros(0);
|
||||||
|
|
||||||
|
testbed.add_callback(move |graphics, physics, _, _| {
|
||||||
|
let Some(graphics) = graphics else { return };
|
||||||
|
if let Some((multibody, link_id)) = physics.multibody_joints.get_mut(last_link) {
|
||||||
|
// Ensure our displacement vector has the right number of elements.
|
||||||
|
if displacements.nrows() < multibody.ndofs() {
|
||||||
|
displacements = DVector::zeros(multibody.ndofs());
|
||||||
|
} else {
|
||||||
|
displacements.fill(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(mouse_ray) = graphics.mouse().ray else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cast a ray on a plane aligned with the camera passing through the origin.
|
||||||
|
let halfspace = HalfSpace {
|
||||||
|
normal: -UnitVector::new_normalize(graphics.camera_fwd_dir()),
|
||||||
|
};
|
||||||
|
let mouse_ray = Ray::new(mouse_ray.0, mouse_ray.1);
|
||||||
|
let Some(hit) = halfspace.cast_local_ray(&mouse_ray, f32::MAX, false) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let target_point = mouse_ray.point_at(hit);
|
||||||
|
|
||||||
|
let options = InverseKinematicsOption {
|
||||||
|
constrained_axes: JointAxesMask::LIN_AXES,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
multibody.inverse_kinematics(
|
||||||
|
&physics.bodies,
|
||||||
|
link_id,
|
||||||
|
&options,
|
||||||
|
&Isometry::from(target_point),
|
||||||
|
&mut displacements,
|
||||||
|
);
|
||||||
|
multibody.apply_displacements(displacements.as_slice());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up the testbed.
|
||||||
|
*/
|
||||||
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
||||||
|
testbed.look_at(point![0.0, 0.5, 2.5], point![0.0, 0.5, 0.0]);
|
||||||
|
}
|
||||||
@@ -138,16 +138,16 @@ pub fn init_world(testbed: &mut Testbed) {
|
|||||||
|
|
||||||
for key in gfx.keys().get_pressed() {
|
for key in gfx.keys().get_pressed() {
|
||||||
match *key {
|
match *key {
|
||||||
KeyCode::Right => {
|
KeyCode::ArrowRight => {
|
||||||
steering = -1.0;
|
steering = -1.0;
|
||||||
}
|
}
|
||||||
KeyCode::Left => {
|
KeyCode::ArrowLeft => {
|
||||||
steering = 1.0;
|
steering = 1.0;
|
||||||
}
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::ArrowUp => {
|
||||||
thrust = -drive_strength;
|
thrust = -drive_strength;
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::ArrowDown => {
|
||||||
thrust = drive_strength;
|
thrust = drive_strength;
|
||||||
}
|
}
|
||||||
KeyCode::ShiftRight => {
|
KeyCode::ShiftRight => {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::dynamics::RigidBodySet;
|
use crate::dynamics::RigidBodySet;
|
||||||
use crate::geometry::{ColliderHandle, ColliderSet, ContactManifold, Shape, TOI};
|
use crate::geometry::{ColliderHandle, ColliderSet, ContactManifold, Shape, ShapeCastHit};
|
||||||
use crate::math::{Isometry, Point, Real, UnitVector, Vector};
|
use crate::math::{Isometry, Point, Real, UnitVector, Vector};
|
||||||
use crate::pipeline::{QueryFilter, QueryFilterFlags, QueryPipeline};
|
use crate::pipeline::{QueryFilter, QueryFilterFlags, QueryPipeline};
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use na::{RealField, Vector2};
|
use na::{RealField, Vector2};
|
||||||
use parry::bounding_volume::BoundingVolume;
|
use parry::bounding_volume::BoundingVolume;
|
||||||
use parry::math::Translation;
|
use parry::math::Translation;
|
||||||
|
use parry::query::details::ShapeCastOptions;
|
||||||
use parry::query::{DefaultQueryDispatcher, PersistentQueryDispatcher};
|
use parry::query::{DefaultQueryDispatcher, PersistentQueryDispatcher};
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
@@ -15,13 +16,13 @@ pub enum CharacterLength {
|
|||||||
/// The length is specified relative to some of the character shape’s size.
|
/// The length is specified relative to some of the character shape’s size.
|
||||||
///
|
///
|
||||||
/// For example setting `CharacterAutostep::max_height` to `CharacterLength::Relative(0.1)`
|
/// For example setting `CharacterAutostep::max_height` to `CharacterLength::Relative(0.1)`
|
||||||
/// for a shape with an height equal to 20.0 will result in a maximum step height
|
/// for a shape with a height equal to 20.0 will result in a maximum step height
|
||||||
/// of `0.1 * 20.0 = 2.0`.
|
/// of `0.1 * 20.0 = 2.0`.
|
||||||
Relative(Real),
|
Relative(Real),
|
||||||
/// The length is specified as an aboslute value, independent from the character shape’s size.
|
/// The length is specified as an absolute value, independent from the character shape’s size.
|
||||||
///
|
///
|
||||||
/// For example setting `CharacterAutostep::max_height` to `CharacterLength::Relative(0.1)`
|
/// For example setting `CharacterAutostep::max_height` to `CharacterLength::Relative(0.1)`
|
||||||
/// for a shape with an height equal to 20.0 will result in a maximum step height
|
/// for a shape with a height equal to 20.0 will result in a maximum step height
|
||||||
/// of `0.1` (the shape height is ignored in for this value).
|
/// of `0.1` (the shape height is ignored in for this value).
|
||||||
Absolute(Real),
|
Absolute(Real),
|
||||||
}
|
}
|
||||||
@@ -55,6 +56,13 @@ impl CharacterLength {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct HitInfo {
|
||||||
|
toi: ShapeCastHit,
|
||||||
|
is_wall: bool,
|
||||||
|
is_nonslip_slope: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration for the auto-stepping character controller feature.
|
/// Configuration for the auto-stepping character controller feature.
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
@@ -77,6 +85,21 @@ impl Default for CharacterAutostep {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct HitDecomposition {
|
||||||
|
normal_part: Vector<Real>,
|
||||||
|
horizontal_tangent: Vector<Real>,
|
||||||
|
vertical_tangent: Vector<Real>,
|
||||||
|
// NOTE: we don’t store the penetration part since we don’t really need it
|
||||||
|
// for anything.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HitDecomposition {
|
||||||
|
pub fn unconstrained_slide_part(&self) -> Vector<Real> {
|
||||||
|
self.normal_part + self.horizontal_tangent + self.vertical_tangent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A collision between the character and its environment during its movement.
|
/// A collision between the character and its environment during its movement.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct CharacterCollision {
|
pub struct CharacterCollision {
|
||||||
@@ -89,7 +112,7 @@ pub struct CharacterCollision {
|
|||||||
/// The translations that was still waiting to be applied to the character when the hit happens.
|
/// The translations that was still waiting to be applied to the character when the hit happens.
|
||||||
pub translation_remaining: Vector<Real>,
|
pub translation_remaining: Vector<Real>,
|
||||||
/// Geometric information about the hit.
|
/// Geometric information about the hit.
|
||||||
pub toi: TOI,
|
pub hit: ShapeCastHit,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A character controller for kinematic bodies.
|
/// A character controller for kinematic bodies.
|
||||||
@@ -105,7 +128,10 @@ pub struct KinematicCharacterController {
|
|||||||
pub offset: CharacterLength,
|
pub offset: CharacterLength,
|
||||||
/// Should the character try to slide against the floor if it hits it?
|
/// Should the character try to slide against the floor if it hits it?
|
||||||
pub slide: bool,
|
pub slide: bool,
|
||||||
/// Should the character automatically step over small obstacles?
|
/// Should the character automatically step over small obstacles? (disabled by default)
|
||||||
|
///
|
||||||
|
/// Note that autostepping is currently a very computationally expensive feature, so it
|
||||||
|
/// is disabled by default.
|
||||||
pub autostep: Option<CharacterAutostep>,
|
pub autostep: Option<CharacterAutostep>,
|
||||||
/// The maximum angle (radians) between the floor’s normal and the `up` vector that the
|
/// The maximum angle (radians) between the floor’s normal and the `up` vector that the
|
||||||
/// character is able to climb.
|
/// character is able to climb.
|
||||||
@@ -116,6 +142,15 @@ pub struct KinematicCharacterController {
|
|||||||
/// Should the character be automatically snapped to the ground if the distance between
|
/// Should the character be automatically snapped to the ground if the distance between
|
||||||
/// the ground and its feed are smaller than the specified threshold?
|
/// the ground and its feed are smaller than the specified threshold?
|
||||||
pub snap_to_ground: Option<CharacterLength>,
|
pub snap_to_ground: Option<CharacterLength>,
|
||||||
|
/// 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.
|
||||||
|
pub normal_nudge_factor: Real,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KinematicCharacterController {
|
impl Default for KinematicCharacterController {
|
||||||
@@ -124,10 +159,11 @@ impl Default for KinematicCharacterController {
|
|||||||
up: Vector::y_axis(),
|
up: Vector::y_axis(),
|
||||||
offset: CharacterLength::Relative(0.01),
|
offset: CharacterLength::Relative(0.01),
|
||||||
slide: true,
|
slide: true,
|
||||||
autostep: Some(CharacterAutostep::default()),
|
autostep: None,
|
||||||
max_slope_climb_angle: Real::frac_pi_4(),
|
max_slope_climb_angle: Real::frac_pi_4(),
|
||||||
min_slope_slide_angle: Real::frac_pi_4(),
|
min_slope_slide_angle: Real::frac_pi_4(),
|
||||||
snap_to_ground: Some(CharacterLength::Relative(0.2)),
|
snap_to_ground: Some(CharacterLength::Relative(0.2)),
|
||||||
|
normal_nudge_factor: 1.0e-4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,19 +261,22 @@ impl KinematicCharacterController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Cast towards the movement direction.
|
// 2. Cast towards the movement direction.
|
||||||
if let Some((handle, toi)) = queries.cast_shape(
|
if let Some((handle, hit)) = queries.cast_shape(
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
&(Translation::from(result.translation) * character_pos),
|
&(Translation::from(result.translation) * character_pos),
|
||||||
&translation_dir,
|
&translation_dir,
|
||||||
character_shape,
|
character_shape,
|
||||||
translation_dist + offset,
|
ShapeCastOptions {
|
||||||
false,
|
target_distance: offset,
|
||||||
|
stop_at_penetration: false,
|
||||||
|
max_time_of_impact: translation_dist,
|
||||||
|
compute_impact_geometry_on_penetration: true,
|
||||||
|
},
|
||||||
filter,
|
filter,
|
||||||
) {
|
) {
|
||||||
// We hit something, compute the allowed self.
|
// We hit something, compute and apply the allowed interference-free translation.
|
||||||
let allowed_dist =
|
let allowed_dist = hit.time_of_impact;
|
||||||
(toi.toi - (-toi.normal1.dot(&translation_dir)) * offset).max(0.0);
|
|
||||||
let allowed_translation = *translation_dir * allowed_dist;
|
let allowed_translation = *translation_dir * allowed_dist;
|
||||||
result.translation += allowed_translation;
|
result.translation += allowed_translation;
|
||||||
translation_remaining -= allowed_translation;
|
translation_remaining -= allowed_translation;
|
||||||
@@ -247,10 +286,12 @@ impl KinematicCharacterController {
|
|||||||
character_pos: Translation::from(result.translation) * character_pos,
|
character_pos: Translation::from(result.translation) * character_pos,
|
||||||
translation_applied: result.translation,
|
translation_applied: result.translation,
|
||||||
translation_remaining,
|
translation_remaining,
|
||||||
toi,
|
hit,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try to go up stairs.
|
let hit_info = self.compute_hit_info(hit);
|
||||||
|
|
||||||
|
// Try to go upstairs.
|
||||||
if !self.handle_stairs(
|
if !self.handle_stairs(
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
@@ -260,12 +301,18 @@ impl KinematicCharacterController {
|
|||||||
&dims,
|
&dims,
|
||||||
filter,
|
filter,
|
||||||
handle,
|
handle,
|
||||||
|
&hit_info,
|
||||||
&mut translation_remaining,
|
&mut translation_remaining,
|
||||||
&mut result,
|
&mut result,
|
||||||
) {
|
) {
|
||||||
// No stairs, try to move along slopes.
|
// No stairs, try to move along slopes.
|
||||||
translation_remaining =
|
translation_remaining = self.handle_slopes(
|
||||||
self.handle_slopes(&toi, &translation_remaining, &mut result);
|
&hit_info,
|
||||||
|
&desired_translation,
|
||||||
|
&translation_remaining,
|
||||||
|
self.normal_nudge_factor,
|
||||||
|
&mut result,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No interference along the path.
|
// No interference along the path.
|
||||||
@@ -319,7 +366,7 @@ impl KinematicCharacterController {
|
|||||||
dims: &Vector2<Real>,
|
dims: &Vector2<Real>,
|
||||||
filter: QueryFilter,
|
filter: QueryFilter,
|
||||||
result: &mut EffectiveCharacterMovement,
|
result: &mut EffectiveCharacterMovement,
|
||||||
) -> Option<(ColliderHandle, TOI)> {
|
) -> Option<(ColliderHandle, ShapeCastHit)> {
|
||||||
if let Some(snap_distance) = self.snap_to_ground {
|
if let Some(snap_distance) = self.snap_to_ground {
|
||||||
if result.translation.dot(&self.up) < -1.0e-5 {
|
if result.translation.dot(&self.up) < -1.0e-5 {
|
||||||
let snap_distance = snap_distance.eval(dims.y);
|
let snap_distance = snap_distance.eval(dims.y);
|
||||||
@@ -330,12 +377,16 @@ impl KinematicCharacterController {
|
|||||||
character_pos,
|
character_pos,
|
||||||
&-self.up,
|
&-self.up,
|
||||||
character_shape,
|
character_shape,
|
||||||
snap_distance + offset,
|
ShapeCastOptions {
|
||||||
false,
|
target_distance: offset,
|
||||||
|
stop_at_penetration: false,
|
||||||
|
max_time_of_impact: snap_distance,
|
||||||
|
compute_impact_geometry_on_penetration: true,
|
||||||
|
},
|
||||||
filter,
|
filter,
|
||||||
) {
|
) {
|
||||||
// Apply the snap.
|
// Apply the snap.
|
||||||
result.translation -= *self.up * (hit.toi - offset).max(0.0);
|
result.translation -= *self.up * hit.time_of_impact;
|
||||||
result.grounded = true;
|
result.grounded = true;
|
||||||
return Some((hit_handle, hit));
|
return Some((hit_handle, hit));
|
||||||
}
|
}
|
||||||
@@ -481,36 +532,40 @@ impl KinematicCharacterController {
|
|||||||
|
|
||||||
fn handle_slopes(
|
fn handle_slopes(
|
||||||
&self,
|
&self,
|
||||||
hit: &TOI,
|
hit: &HitInfo,
|
||||||
|
movement_input: &Vector<Real>,
|
||||||
translation_remaining: &Vector<Real>,
|
translation_remaining: &Vector<Real>,
|
||||||
|
normal_nudge_factor: Real,
|
||||||
result: &mut EffectiveCharacterMovement,
|
result: &mut EffectiveCharacterMovement,
|
||||||
) -> Vector<Real> {
|
) -> Vector<Real> {
|
||||||
let [vertical_translation, horizontal_translation] =
|
let [_vertical_input, horizontal_input] = self.split_into_components(movement_input);
|
||||||
self.split_into_components(translation_remaining);
|
let horiz_input_decomp = self.decompose_hit(&horizontal_input, &hit.toi);
|
||||||
let slope_translation = subtract_hit(*translation_remaining, hit);
|
let input_decomp = self.decompose_hit(movement_input, &hit.toi);
|
||||||
|
|
||||||
// Check if there is a slope to climb.
|
let decomp = self.decompose_hit(translation_remaining, &hit.toi);
|
||||||
let angle_with_floor = self.up.angle(&hit.normal1);
|
|
||||||
|
|
||||||
// We are climbing if the movement along the slope goes upward, and the angle with the
|
// An object is trying to slip if the tangential movement induced by its vertical movement
|
||||||
// floor is smaller than pi/2 (in which case we hit some some sort of ceiling).
|
// points downward.
|
||||||
//
|
let slipping_intent = self.up.dot(&horiz_input_decomp.vertical_tangent) < 0.0;
|
||||||
// NOTE: part of the slope will already be handled by auto-stepping if it was enabled.
|
let slipping = self.up.dot(&decomp.vertical_tangent) < 0.0;
|
||||||
// Therefore, `climbing` may not always be `true` when climbing on a slope at
|
|
||||||
// slow speed.
|
|
||||||
let climbing = self.up.dot(&slope_translation) >= 0.0 && self.up.dot(&hit.normal1) > 0.0;
|
|
||||||
|
|
||||||
if climbing && angle_with_floor >= self.max_slope_climb_angle {
|
// An object is trying to climb if its indirect vertical motion points upward.
|
||||||
// Prevent horizontal movement from pushing through the slope.
|
let climbing_intent = self.up.dot(&input_decomp.vertical_tangent) > 0.0;
|
||||||
vertical_translation
|
let climbing = self.up.dot(&decomp.vertical_tangent) > 0.0;
|
||||||
} else if !climbing && angle_with_floor <= self.min_slope_slide_angle {
|
|
||||||
|
let allowed_movement = if hit.is_wall && climbing && !climbing_intent {
|
||||||
|
// Can’t climb the slope, remove the vertical tangent motion induced by the forward motion.
|
||||||
|
decomp.horizontal_tangent + decomp.normal_part
|
||||||
|
} else if hit.is_nonslip_slope && slipping && !slipping_intent {
|
||||||
// Prevent the vertical movement from sliding down.
|
// Prevent the vertical movement from sliding down.
|
||||||
horizontal_translation
|
decomp.horizontal_tangent + decomp.normal_part
|
||||||
} else {
|
} else {
|
||||||
// Let it slide
|
// Let it slide (including climbing the slope).
|
||||||
result.is_sliding_down_slope = true;
|
result.is_sliding_down_slope = true;
|
||||||
slope_translation
|
decomp.unconstrained_slide_part()
|
||||||
}
|
};
|
||||||
|
|
||||||
|
allowed_movement + *hit.toi.normal1 * normal_nudge_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_into_components(&self, translation: &Vector<Real>) -> [Vector<Real>; 2] {
|
fn split_into_components(&self, translation: &Vector<Real>) -> [Vector<Real>; 2] {
|
||||||
@@ -519,6 +574,51 @@ impl KinematicCharacterController {
|
|||||||
[vertical_translation, horizontal_translation]
|
[vertical_translation, horizontal_translation]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_hit_info(&self, toi: ShapeCastHit) -> HitInfo {
|
||||||
|
let angle_with_floor = self.up.angle(&toi.normal1);
|
||||||
|
let is_ceiling = self.up.dot(&toi.normal1) < 0.0;
|
||||||
|
let is_wall = angle_with_floor >= self.max_slope_climb_angle && !is_ceiling;
|
||||||
|
let is_nonslip_slope = angle_with_floor <= self.min_slope_slide_angle;
|
||||||
|
|
||||||
|
HitInfo {
|
||||||
|
toi,
|
||||||
|
is_wall,
|
||||||
|
is_nonslip_slope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompose_hit(&self, translation: &Vector<Real>, hit: &ShapeCastHit) -> HitDecomposition {
|
||||||
|
let dist_to_surface = translation.dot(&hit.normal1);
|
||||||
|
let normal_part;
|
||||||
|
let penetration_part;
|
||||||
|
|
||||||
|
if dist_to_surface < 0.0 {
|
||||||
|
normal_part = Vector::zeros();
|
||||||
|
penetration_part = dist_to_surface * *hit.normal1;
|
||||||
|
} else {
|
||||||
|
penetration_part = Vector::zeros();
|
||||||
|
normal_part = dist_to_surface * *hit.normal1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tangent = translation - normal_part - penetration_part;
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let horizontal_tangent_dir = hit.normal1.cross(&self.up);
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
let horizontal_tangent_dir = Vector::zeros();
|
||||||
|
|
||||||
|
let horizontal_tangent_dir = horizontal_tangent_dir
|
||||||
|
.try_normalize(1.0e-5)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let horizontal_tangent = tangent.dot(&horizontal_tangent_dir) * horizontal_tangent_dir;
|
||||||
|
let vertical_tangent = tangent - horizontal_tangent;
|
||||||
|
|
||||||
|
HitDecomposition {
|
||||||
|
normal_part,
|
||||||
|
horizontal_tangent,
|
||||||
|
vertical_tangent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_dims(&self, character_shape: &dyn Shape) -> Vector2<Real> {
|
fn compute_dims(&self, character_shape: &dyn Shape) -> Vector2<Real> {
|
||||||
let extents = character_shape.compute_local_aabb().extents();
|
let extents = character_shape.compute_local_aabb().extents();
|
||||||
let up_extent = extents.dot(&self.up.abs());
|
let up_extent = extents.dot(&self.up.abs());
|
||||||
@@ -536,14 +636,19 @@ impl KinematicCharacterController {
|
|||||||
dims: &Vector2<Real>,
|
dims: &Vector2<Real>,
|
||||||
mut filter: QueryFilter,
|
mut filter: QueryFilter,
|
||||||
stair_handle: ColliderHandle,
|
stair_handle: ColliderHandle,
|
||||||
|
hit: &HitInfo,
|
||||||
translation_remaining: &mut Vector<Real>,
|
translation_remaining: &mut Vector<Real>,
|
||||||
result: &mut EffectiveCharacterMovement,
|
result: &mut EffectiveCharacterMovement,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let autostep = match self.autostep {
|
let Some(autostep) = self.autostep else {
|
||||||
Some(autostep) => autostep,
|
return false;
|
||||||
None => return false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only try to autostep on walls.
|
||||||
|
if !hit.is_wall {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let offset = self.offset.eval(dims.y);
|
let offset = self.offset.eval(dims.y);
|
||||||
let min_width = autostep.min_width.eval(dims.x) + offset;
|
let min_width = autostep.min_width.eval(dims.x) + offset;
|
||||||
let max_height = autostep.max_height.eval(dims.y) + offset;
|
let max_height = autostep.max_height.eval(dims.y) + offset;
|
||||||
@@ -565,12 +670,10 @@ impl KinematicCharacterController {
|
|||||||
|
|
||||||
let shifted_character_pos = Translation::from(*self.up * max_height) * character_pos;
|
let shifted_character_pos = Translation::from(*self.up * max_height) * character_pos;
|
||||||
|
|
||||||
let horizontal_dir = match (*translation_remaining
|
let Some(horizontal_dir) = (*translation_remaining
|
||||||
- *self.up * translation_remaining.dot(&self.up))
|
- *self.up * translation_remaining.dot(&self.up))
|
||||||
.try_normalize(1.0e-5)
|
.try_normalize(1.0e-5) else {
|
||||||
{
|
return false;
|
||||||
Some(dir) => dir,
|
|
||||||
None => return false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if queries
|
if queries
|
||||||
@@ -580,8 +683,12 @@ impl KinematicCharacterController {
|
|||||||
character_pos,
|
character_pos,
|
||||||
&self.up,
|
&self.up,
|
||||||
character_shape,
|
character_shape,
|
||||||
max_height,
|
ShapeCastOptions {
|
||||||
false,
|
target_distance: offset,
|
||||||
|
stop_at_penetration: false,
|
||||||
|
max_time_of_impact: max_height,
|
||||||
|
compute_impact_geometry_on_penetration: true,
|
||||||
|
},
|
||||||
filter,
|
filter,
|
||||||
)
|
)
|
||||||
.is_some()
|
.is_some()
|
||||||
@@ -597,8 +704,12 @@ impl KinematicCharacterController {
|
|||||||
&shifted_character_pos,
|
&shifted_character_pos,
|
||||||
&horizontal_dir,
|
&horizontal_dir,
|
||||||
character_shape,
|
character_shape,
|
||||||
min_width,
|
ShapeCastOptions {
|
||||||
false,
|
target_distance: offset,
|
||||||
|
stop_at_penetration: false,
|
||||||
|
max_time_of_impact: min_width,
|
||||||
|
compute_impact_geometry_on_penetration: true,
|
||||||
|
},
|
||||||
filter,
|
filter,
|
||||||
)
|
)
|
||||||
.is_some()
|
.is_some()
|
||||||
@@ -615,8 +726,12 @@ impl KinematicCharacterController {
|
|||||||
&(Translation::from(horizontal_dir * min_width) * shifted_character_pos),
|
&(Translation::from(horizontal_dir * min_width) * shifted_character_pos),
|
||||||
&-self.up,
|
&-self.up,
|
||||||
character_shape,
|
character_shape,
|
||||||
max_height,
|
ShapeCastOptions {
|
||||||
false,
|
target_distance: offset,
|
||||||
|
stop_at_penetration: false,
|
||||||
|
max_time_of_impact: max_height,
|
||||||
|
compute_impact_geometry_on_penetration: true,
|
||||||
|
},
|
||||||
filter,
|
filter,
|
||||||
) {
|
) {
|
||||||
let [vertical_slope_translation, horizontal_slope_translation] = self
|
let [vertical_slope_translation, horizontal_slope_translation] = self
|
||||||
@@ -642,11 +757,15 @@ impl KinematicCharacterController {
|
|||||||
&(Translation::from(horizontal_dir * min_width) * shifted_character_pos),
|
&(Translation::from(horizontal_dir * min_width) * shifted_character_pos),
|
||||||
&-self.up,
|
&-self.up,
|
||||||
character_shape,
|
character_shape,
|
||||||
max_height,
|
ShapeCastOptions {
|
||||||
false,
|
target_distance: offset,
|
||||||
|
stop_at_penetration: false,
|
||||||
|
max_time_of_impact: max_height,
|
||||||
|
compute_impact_geometry_on_penetration: true,
|
||||||
|
},
|
||||||
filter,
|
filter,
|
||||||
)
|
)
|
||||||
.map(|hit| hit.1.toi)
|
.map(|hit| hit.1.time_of_impact)
|
||||||
.unwrap_or(max_height);
|
.unwrap_or(max_height);
|
||||||
|
|
||||||
// Remove the step height from the vertical part of the self.
|
// Remove the step height from the vertical part of the self.
|
||||||
@@ -681,7 +800,7 @@ impl KinematicCharacterController {
|
|||||||
let extents = character_shape.compute_local_aabb().extents();
|
let extents = character_shape.compute_local_aabb().extents();
|
||||||
let up_extent = extents.dot(&self.up.abs());
|
let up_extent = extents.dot(&self.up.abs());
|
||||||
let movement_to_transfer =
|
let movement_to_transfer =
|
||||||
*collision.toi.normal1 * collision.translation_remaining.dot(&collision.toi.normal1);
|
*collision.hit.normal1 * collision.translation_remaining.dot(&collision.hit.normal1);
|
||||||
let prediction = self.predict_ground(up_extent);
|
let prediction = self.predict_ground(up_extent);
|
||||||
|
|
||||||
// TODO: allow custom dispatchers.
|
// TODO: allow custom dispatchers.
|
||||||
@@ -748,7 +867,7 @@ impl KinematicCharacterController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subtract_hit(translation: Vector<Real>, hit: &TOI) -> Vector<Real> {
|
fn subtract_hit(translation: Vector<Real>, hit: &ShapeCastHit) -> Vector<Real> {
|
||||||
let surface_correction = (-translation).dot(&hit.normal1).max(0.0);
|
let surface_correction = (-translation).dot(&hit.normal1).max(0.0);
|
||||||
// This fixes some instances of moving through walls
|
// This fixes some instances of moving through walls
|
||||||
let surface_correction = surface_correction * (1.0 + 1.0e-5);
|
let surface_correction = surface_correction * (1.0 + 1.0e-5);
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ impl DynamicRayCastVehicleController {
|
|||||||
wheel.raycast_info.ground_object = None;
|
wheel.raycast_info.ground_object = None;
|
||||||
|
|
||||||
if let Some((collider_hit, mut hit)) = hit {
|
if let Some((collider_hit, mut hit)) = hit {
|
||||||
if hit.toi == 0.0 {
|
if hit.time_of_impact == 0.0 {
|
||||||
let collider = &colliders[collider_hit];
|
let collider = &colliders[collider_hit];
|
||||||
let up_ray = Ray::new(source + rayvector, -rayvector);
|
let up_ray = Ray::new(source + rayvector, -rayvector);
|
||||||
if let Some(hit2) =
|
if let Some(hit2) =
|
||||||
@@ -362,7 +362,7 @@ impl DynamicRayCastVehicleController {
|
|||||||
wheel.raycast_info.is_in_contact = true;
|
wheel.raycast_info.is_in_contact = true;
|
||||||
wheel.raycast_info.ground_object = Some(collider_hit);
|
wheel.raycast_info.ground_object = Some(collider_hit);
|
||||||
|
|
||||||
let hit_distance = hit.toi * raylen;
|
let hit_distance = hit.time_of_impact * raylen;
|
||||||
wheel.raycast_info.suspension_length = hit_distance - wheel.radius;
|
wheel.raycast_info.suspension_length = hit_distance - wheel.radius;
|
||||||
|
|
||||||
// clamp on max suspension travel
|
// clamp on max suspension travel
|
||||||
@@ -372,7 +372,7 @@ impl DynamicRayCastVehicleController {
|
|||||||
.raycast_info
|
.raycast_info
|
||||||
.suspension_length
|
.suspension_length
|
||||||
.clamp(min_suspension_length, max_suspension_length);
|
.clamp(min_suspension_length, max_suspension_length);
|
||||||
wheel.raycast_info.contact_point_ws = ray.point_at(hit.toi);
|
wheel.raycast_info.contact_point_ws = ray.point_at(hit.time_of_impact);
|
||||||
|
|
||||||
let denominator = wheel
|
let denominator = wheel
|
||||||
.raycast_info
|
.raycast_info
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ mod timer;
|
|||||||
/// Aggregation of all the performances counters tracked by rapier.
|
/// Aggregation of all the performances counters tracked by rapier.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Counters {
|
pub struct Counters {
|
||||||
/// Whether thi counter is enabled or not.
|
/// Whether this counter is enabled or not.
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
/// Timer for a whole timestep.
|
/// Timer for a whole timestep.
|
||||||
pub step_time: Timer,
|
pub step_time: Timer,
|
||||||
@@ -69,7 +69,7 @@ impl Counters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notfy that the time-step has finished.
|
/// Notify that the time-step has finished.
|
||||||
pub fn step_completed(&mut self) {
|
pub fn step_completed(&mut self) {
|
||||||
if self.enabled {
|
if self.enabled {
|
||||||
self.step_time.pause();
|
self.step_time.pause();
|
||||||
@@ -88,7 +88,7 @@ impl Counters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notfy that the custom operation has finished.
|
/// Notify that the custom operation has finished.
|
||||||
pub fn custom_completed(&mut self) {
|
pub fn custom_completed(&mut self) {
|
||||||
if self.enabled {
|
if self.enabled {
|
||||||
self.custom.pause();
|
self.custom.pause();
|
||||||
@@ -182,6 +182,12 @@ measure_method!(
|
|||||||
stages.solver_time
|
stages.solver_time
|
||||||
);
|
);
|
||||||
measure_method!(ccd_started, ccd_completed, ccd_time, stages.ccd_time);
|
measure_method!(ccd_started, ccd_completed, ccd_time, stages.ccd_time);
|
||||||
|
measure_method!(
|
||||||
|
query_pipeline_update_started,
|
||||||
|
query_pipeline_update_completed,
|
||||||
|
query_pipeline_update_time,
|
||||||
|
stages.query_pipeline_time
|
||||||
|
);
|
||||||
|
|
||||||
measure_method!(
|
measure_method!(
|
||||||
assembly_started,
|
assembly_started,
|
||||||
@@ -201,12 +207,6 @@ measure_method!(
|
|||||||
velocity_update_time,
|
velocity_update_time,
|
||||||
solver.velocity_update_time
|
solver.velocity_update_time
|
||||||
);
|
);
|
||||||
measure_method!(
|
|
||||||
position_resolution_started,
|
|
||||||
position_resolution_completed,
|
|
||||||
position_resolution_time,
|
|
||||||
solver.position_resolution_time
|
|
||||||
);
|
|
||||||
measure_method!(
|
measure_method!(
|
||||||
broad_phase_started,
|
broad_phase_started,
|
||||||
broad_phase_completed,
|
broad_phase_completed,
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ pub struct SolverCounters {
|
|||||||
pub velocity_assembly_time: Timer,
|
pub velocity_assembly_time: Timer,
|
||||||
/// Time spent for the update of the velocity of the bodies.
|
/// Time spent for the update of the velocity of the bodies.
|
||||||
pub velocity_update_time: Timer,
|
pub velocity_update_time: Timer,
|
||||||
/// Time spent for the assembly of all the position constraints.
|
/// Time spent to write force back to user-accessible data.
|
||||||
pub position_assembly_time: Timer,
|
pub velocity_writeback_time: Timer,
|
||||||
/// Time spent for the update of the position of the bodies.
|
|
||||||
pub position_resolution_time: Timer,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SolverCounters {
|
impl SolverCounters {
|
||||||
@@ -29,8 +27,7 @@ impl SolverCounters {
|
|||||||
velocity_assembly_time: Timer::new(),
|
velocity_assembly_time: Timer::new(),
|
||||||
velocity_resolution_time: Timer::new(),
|
velocity_resolution_time: Timer::new(),
|
||||||
velocity_update_time: Timer::new(),
|
velocity_update_time: Timer::new(),
|
||||||
position_assembly_time: Timer::new(),
|
velocity_writeback_time: Timer::new(),
|
||||||
position_resolution_time: Timer::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +38,7 @@ impl SolverCounters {
|
|||||||
self.velocity_resolution_time.reset();
|
self.velocity_resolution_time.reset();
|
||||||
self.velocity_assembly_time.reset();
|
self.velocity_assembly_time.reset();
|
||||||
self.velocity_update_time.reset();
|
self.velocity_update_time.reset();
|
||||||
self.position_assembly_time.reset();
|
self.velocity_writeback_time.reset();
|
||||||
self.position_resolution_time.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,11 +53,10 @@ impl Display for SolverCounters {
|
|||||||
self.velocity_resolution_time
|
self.velocity_resolution_time
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, "Velocity update time: {}", self.velocity_update_time)?;
|
writeln!(f, "Velocity update time: {}", self.velocity_update_time)?;
|
||||||
writeln!(f, "Position assembly time: {}", self.position_assembly_time)?;
|
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"Position resolution time: {}",
|
"Velocity writeback time: {}",
|
||||||
self.position_resolution_time
|
self.velocity_writeback_time
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ pub struct StagesCounters {
|
|||||||
pub solver_time: Timer,
|
pub solver_time: Timer,
|
||||||
/// Total time spent for CCD and CCD resolution.
|
/// Total time spent for CCD and CCD resolution.
|
||||||
pub ccd_time: Timer,
|
pub ccd_time: Timer,
|
||||||
|
/// Total time spent updating the query pipeline (if provided to `PhysicsPipeline::step`).
|
||||||
|
pub query_pipeline_time: Timer,
|
||||||
|
/// Total time spent propagating user changes.
|
||||||
|
pub user_changes: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StagesCounters {
|
impl StagesCounters {
|
||||||
/// Create a new counter intialized to zero.
|
/// Create a new counter initialized to zero.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
StagesCounters {
|
StagesCounters {
|
||||||
update_time: Timer::new(),
|
update_time: Timer::new(),
|
||||||
@@ -25,6 +29,8 @@ impl StagesCounters {
|
|||||||
island_construction_time: Timer::new(),
|
island_construction_time: Timer::new(),
|
||||||
solver_time: Timer::new(),
|
solver_time: Timer::new(),
|
||||||
ccd_time: Timer::new(),
|
ccd_time: Timer::new(),
|
||||||
|
query_pipeline_time: Timer::new(),
|
||||||
|
user_changes: Timer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +41,8 @@ impl StagesCounters {
|
|||||||
self.island_construction_time.reset();
|
self.island_construction_time.reset();
|
||||||
self.solver_time.reset();
|
self.solver_time.reset();
|
||||||
self.ccd_time.reset();
|
self.ccd_time.reset();
|
||||||
|
self.query_pipeline_time.reset();
|
||||||
|
self.user_changes.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +60,8 @@ impl Display for StagesCounters {
|
|||||||
self.island_construction_time
|
self.island_construction_time
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, "Solver time: {}", self.solver_time)?;
|
writeln!(f, "Solver time: {}", self.solver_time)?;
|
||||||
writeln!(f, "CCD time: {}", self.ccd_time)
|
writeln!(f, "CCD time: {}", self.ccd_time)?;
|
||||||
|
writeln!(f, "Query pipeline time: {}", self.query_pipeline_time)?;
|
||||||
|
writeln!(f, "User changes time: {}", self.user_changes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ impl<N, E> Graph<N, E> {
|
|||||||
// indices.
|
// indices.
|
||||||
let edge = self.edges.swap_remove(e.index());
|
let edge = self.edges.swap_remove(e.index());
|
||||||
let swap = match self.edges.get(e.index()) {
|
let swap = match self.edges.get(e.index()) {
|
||||||
// no elment needed to be swapped.
|
// no element needed to be swapped.
|
||||||
None => return Some(edge.weight),
|
None => return Some(edge.weight),
|
||||||
Some(ed) => ed.node,
|
Some(ed) => ed.node,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ impl<T> PubSub<T> {
|
|||||||
subscription
|
subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the i-th message not yet read by the given subsciber.
|
/// Read the i-th message not yet read by the given subscriber.
|
||||||
pub fn read_ith(&self, sub: &Subscription<T>, i: usize) -> Option<&T> {
|
pub fn read_ith(&self, sub: &Subscription<T>, i: usize) -> Option<&T> {
|
||||||
let cursor = &self.cursors[sub.id as usize];
|
let cursor = &self.cursors[sub.id as usize];
|
||||||
self.messages.get(cursor.next(self.deleted_messages) + i)
|
self.messages.get(cursor.next(self.deleted_messages) + i)
|
||||||
@@ -114,11 +114,7 @@ impl<T> PubSub<T> {
|
|||||||
let cursor = &self.cursors[sub.id as usize];
|
let cursor = &self.cursors[sub.id as usize];
|
||||||
let next = cursor.next(self.deleted_messages);
|
let next = cursor.next(self.deleted_messages);
|
||||||
|
|
||||||
// TODO: use self.queue.range(next..) once it is stabilised.
|
self.messages.range(next..)
|
||||||
MessageRange {
|
|
||||||
queue: &self.messages,
|
|
||||||
next,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes the given subscribe acknowledge all the messages in the queue.
|
/// Makes the given subscribe acknowledge all the messages in the queue.
|
||||||
@@ -159,19 +155,3 @@ impl<T> PubSub<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MessageRange<'a, T> {
|
|
||||||
queue: &'a VecDeque<T>,
|
|
||||||
next: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Iterator for MessageRange<'a, T> {
|
|
||||||
type Item = &'a T;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn next(&mut self) -> Option<&'a T> {
|
|
||||||
let result = self.queue.get(self.next);
|
|
||||||
self.next += 1;
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ impl TOIEntry {
|
|||||||
// .ok();
|
// .ok();
|
||||||
|
|
||||||
let res_toi = query_dispatcher
|
let res_toi = query_dispatcher
|
||||||
.nonlinear_time_of_impact(
|
.cast_shapes_nonlinear(
|
||||||
&motion_c1,
|
&motion_c1,
|
||||||
co1.shape.as_ref(),
|
co1.shape.as_ref(),
|
||||||
&motion_c2,
|
&motion_c2,
|
||||||
@@ -143,7 +143,7 @@ impl TOIEntry {
|
|||||||
let toi = res_toi??;
|
let toi = res_toi??;
|
||||||
|
|
||||||
Some(Self::new(
|
Some(Self::new(
|
||||||
toi.toi,
|
toi.time_of_impact,
|
||||||
ch1,
|
ch1,
|
||||||
co1.parent.map(|p| p.handle),
|
co1.parent.map(|p| p.handle),
|
||||||
ch2,
|
ch2,
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
use crate::math::Real;
|
use crate::math::Real;
|
||||||
|
use na::RealField;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
// TODO: enabling the block solver in 3d introduces a lot of jitters in
|
||||||
|
// the 3D domino demo. So for now we dont enable it in 3D.
|
||||||
|
pub(crate) static BLOCK_SOLVER_ENABLED: bool = cfg!(feature = "dim2");
|
||||||
|
|
||||||
/// Parameters for a time-step of the physics engine.
|
/// Parameters for a time-step of the physics engine.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
pub struct IntegrationParameters {
|
pub struct IntegrationParameters {
|
||||||
/// The timestep length (default: `1.0 / 60.0`)
|
/// The timestep length (default: `1.0 / 60.0`).
|
||||||
pub dt: Real,
|
pub dt: Real,
|
||||||
/// Minimum timestep size when using CCD with multiple substeps (default `1.0 / 60.0 / 100.0`)
|
/// Minimum timestep size when using CCD with multiple substeps (default: `1.0 / 60.0 / 100.0`).
|
||||||
///
|
///
|
||||||
/// When CCD with multiple substeps is enabled, the timestep is subdivided
|
/// When CCD with multiple substeps is enabled, the timestep is subdivided
|
||||||
/// into smaller pieces. This timestep subdivision won't generate timestep
|
/// into smaller pieces. This timestep subdivision won't generate timestep
|
||||||
@@ -19,37 +24,78 @@ pub struct IntegrationParameters {
|
|||||||
/// to numerical instabilities.
|
/// to numerical instabilities.
|
||||||
pub min_ccd_dt: Real,
|
pub min_ccd_dt: Real,
|
||||||
|
|
||||||
/// 0-1: multiplier for how much of the constraint violation (e.g. contact penetration)
|
/// > 0: the damping ratio used by the springs for contact constraint stabilization.
|
||||||
/// will be compensated for during the velocity solve.
|
///
|
||||||
/// (default `0.8`).
|
/// Larger values make the constraints more compliant (allowing more visible
|
||||||
pub erp: Real,
|
/// penetrations before stabilization).
|
||||||
/// 0-1: the damping ratio used by the springs for Baumgarte constraints stabilization.
|
/// (default `5.0`).
|
||||||
/// Lower values make the constraints more compliant (more "springy", allowing more visible penetrations
|
pub contact_damping_ratio: Real,
|
||||||
/// before stabilization).
|
|
||||||
/// (default `0.25`).
|
|
||||||
pub damping_ratio: Real,
|
|
||||||
|
|
||||||
/// 0-1: multiplier for how much of the joint violation
|
/// > 0: the natural frequency used by the springs for contact constraint regularization.
|
||||||
/// will be compensated for during the velocity solve.
|
///
|
||||||
/// (default `1.0`).
|
/// Increasing this value will make it so that penetrations get fixed more quickly at the
|
||||||
pub joint_erp: Real,
|
/// expense of potential jitter effects due to overshooting. In order to make the simulation
|
||||||
|
/// look stiffer, it is recommended to increase the [`Self::contact_damping_ratio`] instead of this
|
||||||
|
/// value.
|
||||||
|
/// (default: `30.0`).
|
||||||
|
pub contact_natural_frequency: Real,
|
||||||
|
|
||||||
|
/// > 0: the natural frequency used by the springs for joint constraint regularization.
|
||||||
|
///
|
||||||
|
/// Increasing this value will make it so that penetrations get fixed more quickly.
|
||||||
|
/// (default: `1.0e6`).
|
||||||
|
pub joint_natural_frequency: Real,
|
||||||
|
|
||||||
/// The fraction of critical damping applied to the joint for constraints regularization.
|
/// The fraction of critical damping applied to the joint for constraints regularization.
|
||||||
/// (default `0.25`).
|
///
|
||||||
|
/// Larger values make the constraints more compliant (allowing more joint
|
||||||
|
/// drift before stabilization).
|
||||||
|
/// (default `1.0`).
|
||||||
pub joint_damping_ratio: Real,
|
pub joint_damping_ratio: Real,
|
||||||
|
|
||||||
/// Amount of penetration the engine wont attempt to correct (default: `0.001m`).
|
/// The coefficient in `[0, 1]` applied to warmstart impulses, i.e., impulses that are used as the
|
||||||
pub allowed_linear_error: Real,
|
/// initial solution (instead of 0) at the next simulation step.
|
||||||
/// Maximum amount of penetration the solver will attempt to resolve in one timestep.
|
///
|
||||||
pub max_penetration_correction: Real,
|
/// This should generally be set to 1.
|
||||||
/// The maximal distance separating two objects that will generate predictive contacts (default: `0.002`).
|
///
|
||||||
pub prediction_distance: Real,
|
/// (default `1.0`).
|
||||||
|
pub warmstart_coefficient: Real,
|
||||||
|
|
||||||
|
/// The approximate size of most dynamic objects in the scene.
|
||||||
|
///
|
||||||
|
/// This value is used internally to estimate some length-based tolerance. In particular, the
|
||||||
|
/// values [`IntegrationParameters::allowed_linear_error`],
|
||||||
|
/// [`IntegrationParameters::max_corrective_velocity`],
|
||||||
|
/// [`IntegrationParameters::prediction_distance`], [`RigidBodyActivation::linear_threshold`]
|
||||||
|
/// are scaled by this value implicitly.
|
||||||
|
///
|
||||||
|
/// This value can be understood as the number of units-per-meter in your physical world compared
|
||||||
|
/// to a human-sized world in meter. For example, in a 2d game, if your typical object size is 100
|
||||||
|
/// pixels, set the [`Self::length_unit`] parameter to 100.0. The physics engine will interpret
|
||||||
|
/// it as if 100 pixels is equivalent to 1 meter in its various internal threshold.
|
||||||
|
/// (default `1.0`).
|
||||||
|
pub length_unit: Real,
|
||||||
|
|
||||||
|
/// Amount of penetration the engine won’t attempt to correct (default: `0.001m`).
|
||||||
|
///
|
||||||
|
/// This value is implicitly scaled by [`IntegrationParameters::length_unit`].
|
||||||
|
pub normalized_allowed_linear_error: Real,
|
||||||
|
/// Maximum amount of penetration the solver will attempt to resolve in one timestep (default: `10.0`).
|
||||||
|
///
|
||||||
|
/// This value is implicitly scaled by [`IntegrationParameters::length_unit`].
|
||||||
|
pub normalized_max_corrective_velocity: Real,
|
||||||
|
/// The maximal distance separating two objects that will generate predictive contacts (default: `0.002m`).
|
||||||
|
///
|
||||||
|
/// This value is implicitly scaled by [`IntegrationParameters::length_unit`].
|
||||||
|
pub normalized_prediction_distance: Real,
|
||||||
/// The number of solver iterations run by the constraints solver for calculating forces (default: `4`).
|
/// The number of solver iterations run by the constraints solver for calculating forces (default: `4`).
|
||||||
pub num_solver_iterations: NonZeroUsize,
|
pub num_solver_iterations: NonZeroUsize,
|
||||||
/// Number of addition friction resolution iteration run during the last solver sub-step (default: `4`).
|
/// Number of addition friction resolution iteration run during the last solver sub-step (default: `0`).
|
||||||
pub num_additional_friction_iterations: usize,
|
pub num_additional_friction_iterations: usize,
|
||||||
/// Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
|
/// Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`).
|
||||||
pub num_internal_pgs_iterations: usize,
|
pub num_internal_pgs_iterations: usize,
|
||||||
|
/// The number of stabilization iterations run at each solver iterations (default: `2`).
|
||||||
|
pub num_internal_stabilization_iterations: usize,
|
||||||
/// Minimum number of dynamic bodies in each active island (default: `128`).
|
/// Minimum number of dynamic bodies in each active island (default: `128`).
|
||||||
pub min_island_size: usize,
|
pub min_island_size: usize,
|
||||||
/// Maximum number of substeps performed by the solver (default: `1`).
|
/// Maximum number of substeps performed by the solver (default: `1`).
|
||||||
@@ -57,51 +103,6 @@ pub struct IntegrationParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IntegrationParameters {
|
impl IntegrationParameters {
|
||||||
/// Configures the integration parameters to match the old PGS solver
|
|
||||||
/// from Rapier version <= 0.17.
|
|
||||||
///
|
|
||||||
/// This solver was slightly faster than the new one but resulted
|
|
||||||
/// in less stable joints and worse convergence rates.
|
|
||||||
///
|
|
||||||
/// This should only be used for comparison purpose or if you are
|
|
||||||
/// experiencing problems with the new solver.
|
|
||||||
///
|
|
||||||
/// NOTE: this does not affect any [`RigidBody::additional_solver_iterations`] that will
|
|
||||||
/// still create solver iterations based on the new "small-steps" PGS solver.
|
|
||||||
/// NOTE: this resets [`Self::erp`], [`Self::damping_ratio`], [`Self::joint_erp`],
|
|
||||||
/// [`Self::joint_damping_ratio`] to their former default values.
|
|
||||||
pub fn switch_to_standard_pgs_solver(&mut self) {
|
|
||||||
self.num_internal_pgs_iterations *= self.num_solver_iterations.get();
|
|
||||||
self.num_solver_iterations = NonZeroUsize::new(1).unwrap();
|
|
||||||
self.erp = 0.8;
|
|
||||||
self.damping_ratio = 0.25;
|
|
||||||
self.joint_erp = 1.0;
|
|
||||||
self.joint_damping_ratio = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the integration parameters to match the new "small-steps" PGS solver
|
|
||||||
/// from Rapier version >= 0.18.
|
|
||||||
///
|
|
||||||
/// The "small-steps" PGS solver is the default one given by [`Self::default()`] so
|
|
||||||
/// calling this function is generally not needed unless
|
|
||||||
/// [`Self::switch_to_standard_pgs_solver()`] was called.
|
|
||||||
///
|
|
||||||
/// This solver results in more stable joints and significantly better convergence
|
|
||||||
/// rates but is slightly slower in its default settings.
|
|
||||||
///
|
|
||||||
/// NOTE: this resets [`Self::erp`], [`Self::damping_ratio`], [`Self::joint_erp`],
|
|
||||||
/// [`Self::joint_damping_ratio`] to their default values.
|
|
||||||
pub fn switch_to_small_steps_pgs_solver(&mut self) {
|
|
||||||
self.num_solver_iterations = NonZeroUsize::new(self.num_internal_pgs_iterations).unwrap();
|
|
||||||
self.num_internal_pgs_iterations = 1;
|
|
||||||
|
|
||||||
let default = Self::default();
|
|
||||||
self.erp = default.erp;
|
|
||||||
self.damping_ratio = default.damping_ratio;
|
|
||||||
self.joint_erp = default.joint_erp;
|
|
||||||
self.joint_damping_ratio = default.joint_damping_ratio;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The inverse of the time-stepping length, i.e. the steps per seconds (Hz).
|
/// The inverse of the time-stepping length, i.e. the steps per seconds (Hz).
|
||||||
///
|
///
|
||||||
/// This is zero if `self.dt` is zero.
|
/// This is zero if `self.dt` is zero.
|
||||||
@@ -134,29 +135,65 @@ impl IntegrationParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ERP coefficient, multiplied by the inverse timestep length.
|
/// The contact’s spring angular frequency for constraints regularization.
|
||||||
pub fn erp_inv_dt(&self) -> Real {
|
pub fn contact_angular_frequency(&self) -> Real {
|
||||||
self.erp * self.inv_dt()
|
self.contact_natural_frequency * Real::two_pi()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The joint ERP coefficient, multiplied by the inverse timestep length.
|
/// The [`Self::contact_erp`] coefficient, multiplied by the inverse timestep length.
|
||||||
|
pub fn contact_erp_inv_dt(&self) -> Real {
|
||||||
|
let ang_freq = self.contact_angular_frequency();
|
||||||
|
ang_freq / (self.dt * ang_freq + 2.0 * self.contact_damping_ratio)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The effective Error Reduction Parameter applied for calculating regularization forces
|
||||||
|
/// on contacts.
|
||||||
|
///
|
||||||
|
/// This parameter is computed automatically from [`Self::contact_natural_frequency`],
|
||||||
|
/// [`Self::contact_damping_ratio`] and the substep length.
|
||||||
|
pub fn contact_erp(&self) -> Real {
|
||||||
|
self.dt * self.contact_erp_inv_dt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The joint’s spring angular frequency for constraint regularization.
|
||||||
|
pub fn joint_angular_frequency(&self) -> Real {
|
||||||
|
self.joint_natural_frequency * Real::two_pi()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`Self::joint_erp`] coefficient, multiplied by the inverse timestep length.
|
||||||
pub fn joint_erp_inv_dt(&self) -> Real {
|
pub fn joint_erp_inv_dt(&self) -> Real {
|
||||||
self.joint_erp * self.inv_dt()
|
let ang_freq = self.joint_angular_frequency();
|
||||||
|
ang_freq / (self.dt * ang_freq + 2.0 * self.joint_damping_ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The CFM factor to be used in the constraints resolution.
|
/// The effective Error Reduction Parameter applied for calculating regularization forces
|
||||||
pub fn cfm_factor(&self) -> Real {
|
/// on joints.
|
||||||
|
///
|
||||||
|
/// This parameter is computed automatically from [`Self::joint_natural_frequency`],
|
||||||
|
/// [`Self::joint_damping_ratio`] and the substep length.
|
||||||
|
pub fn joint_erp(&self) -> Real {
|
||||||
|
self.dt * self.joint_erp_inv_dt()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The CFM factor to be used in the constraint resolution.
|
||||||
|
///
|
||||||
|
/// This parameter is computed automatically from [`Self::contact_natural_frequency`],
|
||||||
|
/// [`Self::contact_damping_ratio`] and the substep length.
|
||||||
|
pub fn contact_cfm_factor(&self) -> Real {
|
||||||
// Compute CFM assuming a critically damped spring multiplied by the damping ratio.
|
// Compute CFM assuming a critically damped spring multiplied by the damping ratio.
|
||||||
let inv_erp_minus_one = 1.0 / self.erp - 1.0;
|
let inv_erp_minus_one = 1.0 / self.contact_erp() - 1.0;
|
||||||
|
|
||||||
// let stiffness = 4.0 * damping_ratio * damping_ratio * projected_mass
|
// let stiffness = 4.0 * damping_ratio * damping_ratio * projected_mass
|
||||||
// / (dt * dt * inv_erp_minus_one * inv_erp_minus_one);
|
// / (dt * dt * inv_erp_minus_one * inv_erp_minus_one);
|
||||||
// let damping = 4.0 * damping_ratio * damping_ratio * projected_mass
|
// let damping = 4.0 * damping_ratio * damping_ratio * projected_mass
|
||||||
// / (dt * inv_erp_minus_one);
|
// / (dt * inv_erp_minus_one);
|
||||||
// let cfm = 1.0 / (dt * dt * stiffness + dt * damping);
|
// let cfm = 1.0 / (dt * dt * stiffness + dt * damping);
|
||||||
// NOTE: This simplies to cfm = cfm_coefff / projected_mass:
|
// NOTE: This simplifies to cfm = cfm_coeff / projected_mass:
|
||||||
let cfm_coeff = inv_erp_minus_one * inv_erp_minus_one
|
let cfm_coeff = inv_erp_minus_one * inv_erp_minus_one
|
||||||
/ ((1.0 + inv_erp_minus_one) * 4.0 * self.damping_ratio * self.damping_ratio);
|
/ ((1.0 + inv_erp_minus_one)
|
||||||
|
* 4.0
|
||||||
|
* self.contact_damping_ratio
|
||||||
|
* self.contact_damping_ratio);
|
||||||
|
|
||||||
// Furthermore, we use this coefficient inside of the impulse resolution.
|
// Furthermore, we use this coefficient inside of the impulse resolution.
|
||||||
// Surprisingly, several simplifications happen there.
|
// Surprisingly, several simplifications happen there.
|
||||||
@@ -173,36 +210,65 @@ impl IntegrationParameters {
|
|||||||
// new_impulse = cfm_factor * (old_impulse - m * delta_vel)
|
// new_impulse = cfm_factor * (old_impulse - m * delta_vel)
|
||||||
//
|
//
|
||||||
// The value returned by this function is this cfm_factor that can be used directly
|
// The value returned by this function is this cfm_factor that can be used directly
|
||||||
// in the constraints solver.
|
// in the constraint solver.
|
||||||
1.0 / (1.0 + cfm_coeff)
|
1.0 / (1.0 + cfm_coeff)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The CFM (constraints force mixing) coefficient applied to all joints for constraints regularization
|
/// The CFM (constraints force mixing) coefficient applied to all joints for constraints regularization.
|
||||||
|
///
|
||||||
|
/// This parameter is computed automatically from [`Self::joint_natural_frequency`],
|
||||||
|
/// [`Self::joint_damping_ratio`] and the substep length.
|
||||||
pub fn joint_cfm_coeff(&self) -> Real {
|
pub fn joint_cfm_coeff(&self) -> Real {
|
||||||
// Compute CFM assuming a critically damped spring multiplied by the damping ratio.
|
// Compute CFM assuming a critically damped spring multiplied by the damping ratio.
|
||||||
let inv_erp_minus_one = 1.0 / self.joint_erp - 1.0;
|
// The logic is similar to `Self::cfm_factor`.
|
||||||
|
let inv_erp_minus_one = 1.0 / self.joint_erp() - 1.0;
|
||||||
inv_erp_minus_one * inv_erp_minus_one
|
inv_erp_minus_one * inv_erp_minus_one
|
||||||
/ ((1.0 + inv_erp_minus_one)
|
/ ((1.0 + inv_erp_minus_one)
|
||||||
* 4.0
|
* 4.0
|
||||||
* self.joint_damping_ratio
|
* self.joint_damping_ratio
|
||||||
* self.joint_damping_ratio)
|
* self.joint_damping_ratio)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for IntegrationParameters {
|
/// Amount of penetration the engine won’t attempt to correct (default: `0.001` multiplied by
|
||||||
fn default() -> Self {
|
/// [`Self::length_unit`]).
|
||||||
|
pub fn allowed_linear_error(&self) -> Real {
|
||||||
|
self.normalized_allowed_linear_error * self.length_unit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximum amount of penetration the solver will attempt to resolve in one timestep.
|
||||||
|
///
|
||||||
|
/// This is equal to [`Self::normalized_max_corrective_velocity`] multiplied by
|
||||||
|
/// [`Self::length_unit`].
|
||||||
|
pub fn max_corrective_velocity(&self) -> Real {
|
||||||
|
if self.normalized_max_corrective_velocity != Real::MAX {
|
||||||
|
self.normalized_max_corrective_velocity * self.length_unit
|
||||||
|
} else {
|
||||||
|
Real::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximal distance separating two objects that will generate predictive contacts
|
||||||
|
/// (default: `0.002m` multiped by [`Self::length_unit`]).
|
||||||
|
pub fn prediction_distance(&self) -> Real {
|
||||||
|
self.normalized_prediction_distance * self.length_unit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the simulation parameters with settings matching the TGS-soft solver
|
||||||
|
/// with warmstarting.
|
||||||
|
///
|
||||||
|
/// This is the default configuration, equivalent to [`IntegrationParameters::default()`].
|
||||||
|
pub fn tgs_soft() -> Self {
|
||||||
Self {
|
Self {
|
||||||
dt: 1.0 / 60.0,
|
dt: 1.0 / 60.0,
|
||||||
min_ccd_dt: 1.0 / 60.0 / 100.0,
|
min_ccd_dt: 1.0 / 60.0 / 100.0,
|
||||||
erp: 0.6,
|
contact_natural_frequency: 30.0,
|
||||||
damping_ratio: 1.0,
|
contact_damping_ratio: 5.0,
|
||||||
joint_erp: 1.0,
|
joint_natural_frequency: 1.0e6,
|
||||||
joint_damping_ratio: 1.0,
|
joint_damping_ratio: 1.0,
|
||||||
allowed_linear_error: 0.001,
|
warmstart_coefficient: 1.0,
|
||||||
max_penetration_correction: Real::MAX,
|
|
||||||
prediction_distance: 0.002,
|
|
||||||
num_internal_pgs_iterations: 1,
|
num_internal_pgs_iterations: 1,
|
||||||
num_additional_friction_iterations: 4,
|
num_internal_stabilization_iterations: 2,
|
||||||
|
num_additional_friction_iterations: 0,
|
||||||
num_solver_iterations: NonZeroUsize::new(4).unwrap(),
|
num_solver_iterations: NonZeroUsize::new(4).unwrap(),
|
||||||
// TODO: what is the optimal value for min_island_size?
|
// TODO: what is the optimal value for min_island_size?
|
||||||
// It should not be too big so that we don't end up with
|
// It should not be too big so that we don't end up with
|
||||||
@@ -210,7 +276,42 @@ impl Default for IntegrationParameters {
|
|||||||
// However we don't want it to be too small and end up with
|
// However we don't want it to be too small and end up with
|
||||||
// tons of islands, reducing SIMD parallelism opportunities.
|
// tons of islands, reducing SIMD parallelism opportunities.
|
||||||
min_island_size: 128,
|
min_island_size: 128,
|
||||||
|
normalized_allowed_linear_error: 0.001,
|
||||||
|
normalized_max_corrective_velocity: 10.0,
|
||||||
|
normalized_prediction_distance: 0.002,
|
||||||
max_ccd_substeps: 1,
|
max_ccd_substeps: 1,
|
||||||
|
length_unit: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the simulation parameters with settings matching the TGS-soft solver
|
||||||
|
/// **without** warmstarting.
|
||||||
|
///
|
||||||
|
/// The [`IntegrationParameters::tgs_soft()`] configuration should be preferred unless
|
||||||
|
/// warmstarting proves to be undesirable for your use-case.
|
||||||
|
pub fn tgs_soft_without_warmstart() -> Self {
|
||||||
|
Self {
|
||||||
|
contact_damping_ratio: 0.25,
|
||||||
|
warmstart_coefficient: 0.0,
|
||||||
|
num_additional_friction_iterations: 4,
|
||||||
|
..Self::tgs_soft()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the integration parameters to match the legacy PGS solver from Rapier version <= 0.17.
|
||||||
|
///
|
||||||
|
/// This exists mainly for testing and comparison purpose.
|
||||||
|
pub fn pgs_legacy() -> Self {
|
||||||
|
Self {
|
||||||
|
num_solver_iterations: NonZeroUsize::new(1).unwrap(),
|
||||||
|
num_internal_pgs_iterations: 4,
|
||||||
|
..Self::tgs_soft_without_warmstart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for IntegrationParameters {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::tgs_soft()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ impl IslandManager {
|
|||||||
pub(crate) fn update_active_set_with_contacts(
|
pub(crate) fn update_active_set_with_contacts(
|
||||||
&mut self,
|
&mut self,
|
||||||
dt: Real,
|
dt: Real,
|
||||||
|
length_unit: Real,
|
||||||
bodies: &mut RigidBodySet,
|
bodies: &mut RigidBodySet,
|
||||||
colliders: &ColliderSet,
|
colliders: &ColliderSet,
|
||||||
narrow_phase: &NarrowPhase,
|
narrow_phase: &NarrowPhase,
|
||||||
@@ -181,7 +182,7 @@ impl IslandManager {
|
|||||||
let sq_linvel = rb.vels.linvel.norm_squared();
|
let sq_linvel = rb.vels.linvel.norm_squared();
|
||||||
let sq_angvel = rb.vels.angvel.gdot(rb.vels.angvel);
|
let sq_angvel = rb.vels.angvel.gdot(rb.vels.angvel);
|
||||||
|
|
||||||
update_energy(&mut rb.activation, sq_linvel, sq_angvel, dt);
|
update_energy(length_unit, &mut rb.activation, sq_linvel, sq_angvel, dt);
|
||||||
|
|
||||||
if rb.activation.time_since_can_sleep >= rb.activation.time_until_sleep {
|
if rb.activation.time_since_can_sleep >= rb.activation.time_until_sleep {
|
||||||
// Mark them as sleeping for now. This will
|
// Mark them as sleeping for now. This will
|
||||||
@@ -324,8 +325,15 @@ impl IslandManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_energy(activation: &mut RigidBodyActivation, sq_linvel: Real, sq_angvel: Real, dt: Real) {
|
fn update_energy(
|
||||||
if sq_linvel < activation.linear_threshold * activation.linear_threshold.abs()
|
length_unit: Real,
|
||||||
|
activation: &mut RigidBodyActivation,
|
||||||
|
sq_linvel: Real,
|
||||||
|
sq_angvel: Real,
|
||||||
|
dt: Real,
|
||||||
|
) {
|
||||||
|
let linear_threshold = activation.normalized_linear_threshold * length_unit;
|
||||||
|
if sq_linvel < linear_threshold * linear_threshold.abs()
|
||||||
&& sq_angvel < activation.angular_threshold * activation.angular_threshold.abs()
|
&& sq_angvel < activation.angular_threshold * activation.angular_threshold.abs()
|
||||||
{
|
{
|
||||||
activation.time_since_can_sleep += dt;
|
activation.time_since_can_sleep += dt;
|
||||||
|
|||||||
@@ -496,6 +496,24 @@ impl GenericJoint {
|
|||||||
self.motors[i].damping = damping;
|
self.motors[i].damping = damping;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flips the orientation of the joint, including limits and motors.
|
||||||
|
pub fn flip(&mut self) {
|
||||||
|
std::mem::swap(&mut self.local_frame1, &mut self.local_frame2);
|
||||||
|
|
||||||
|
let coupled_bits = self.coupled_axes.bits();
|
||||||
|
|
||||||
|
for dim in 0..SPATIAL_DIM {
|
||||||
|
if coupled_bits & (1 << dim) == 0 {
|
||||||
|
let limit = self.limits[dim];
|
||||||
|
self.limits[dim].min = -limit.max;
|
||||||
|
self.limits[dim].max = -limit.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.motors[dim].target_vel = -self.motors[dim].target_vel;
|
||||||
|
self.motors[dim].target_pos = -self.motors[dim].target_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! joint_conversion_methods(
|
macro_rules! joint_conversion_methods(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! MultibodyJoints using the reduced-coordinates formalism or using constraints.
|
//! MultibodyJoints using the reduced-coordinates formalism or using constraints.
|
||||||
|
|
||||||
pub use self::multibody::Multibody;
|
pub use self::multibody::Multibody;
|
||||||
|
pub use self::multibody_ik::InverseKinematicsOption;
|
||||||
pub use self::multibody_joint::MultibodyJoint;
|
pub use self::multibody_joint::MultibodyJoint;
|
||||||
pub use self::multibody_joint_set::{
|
pub use self::multibody_joint_set::{
|
||||||
MultibodyIndex, MultibodyJointHandle, MultibodyJointSet, MultibodyLinkId,
|
MultibodyIndex, MultibodyJointHandle, MultibodyJointSet, MultibodyLinkId,
|
||||||
@@ -13,5 +14,6 @@ mod multibody_joint_set;
|
|||||||
mod multibody_link;
|
mod multibody_link;
|
||||||
mod multibody_workspace;
|
mod multibody_workspace;
|
||||||
|
|
||||||
|
mod multibody_ik;
|
||||||
mod multibody_joint;
|
mod multibody_joint;
|
||||||
mod unit_multibody_joint;
|
mod unit_multibody_joint;
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ impl Default for Multibody {
|
|||||||
Multibody::new()
|
Multibody::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Multibody {
|
impl Multibody {
|
||||||
/// Creates a new multibody with no link.
|
/// Creates a new multibody with no link.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -115,6 +116,8 @@ impl Multibody {
|
|||||||
|
|
||||||
pub(crate) fn with_root(handle: RigidBodyHandle) -> Self {
|
pub(crate) fn with_root(handle: RigidBodyHandle) -> Self {
|
||||||
let mut mb = Multibody::new();
|
let mut mb = Multibody::new();
|
||||||
|
// NOTE: we have no way of knowing if the root in fixed at this point, so
|
||||||
|
// we mark it as dynamic and will fixe later with `Self::update_root_type`.
|
||||||
mb.root_is_dynamic = true;
|
mb.root_is_dynamic = true;
|
||||||
let joint = MultibodyJoint::free(Isometry::identity());
|
let joint = MultibodyJoint::free(Isometry::identity());
|
||||||
mb.add_link(None, joint, handle);
|
mb.add_link(None, joint, handle);
|
||||||
@@ -747,6 +750,12 @@ impl Multibody {
|
|||||||
self.velocities.rows(0, self.ndofs)
|
self.velocities.rows(0, self.ndofs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The body jacobian for link `link_id` calculated by the last call to [`Multibody::forward_kinematics`].
|
||||||
|
#[inline]
|
||||||
|
pub fn body_jacobian(&self, link_id: usize) -> &Jacobian<Real> {
|
||||||
|
&self.body_jacobians[link_id]
|
||||||
|
}
|
||||||
|
|
||||||
/// The mutable generalized velocities of this multibodies.
|
/// The mutable generalized velocities of this multibodies.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generalized_velocity_mut(&mut self) -> DVectorViewMut<Real> {
|
pub fn generalized_velocity_mut(&mut self) -> DVectorViewMut<Real> {
|
||||||
@@ -762,17 +771,27 @@ impl Multibody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply displacements, in generalized coordinates, to this multibody.
|
/// Apply displacements, in generalized coordinates, to this multibody.
|
||||||
|
///
|
||||||
|
/// Note this does **not** updates the link poses, only their generalized coordinates.
|
||||||
|
/// To update the link poses and associated rigid-bodies, call [`Self::forward_kinematics`]
|
||||||
|
/// or [`Self::finalize`].
|
||||||
pub fn apply_displacements(&mut self, disp: &[Real]) {
|
pub fn apply_displacements(&mut self, disp: &[Real]) {
|
||||||
for link in self.links.iter_mut() {
|
for link in self.links.iter_mut() {
|
||||||
link.joint.apply_displacement(&disp[link.assembly_id..])
|
link.joint.apply_displacement(&disp[link.assembly_id..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_root_type(&mut self, bodies: &mut RigidBodySet) {
|
pub(crate) fn update_root_type(&mut self, bodies: &RigidBodySet, take_body_pose: bool) {
|
||||||
if let Some(rb) = bodies.get(self.links[0].rigid_body) {
|
if let Some(rb) = bodies.get(self.links[0].rigid_body) {
|
||||||
if rb.is_dynamic() != self.root_is_dynamic {
|
if rb.is_dynamic() != self.root_is_dynamic {
|
||||||
|
let root_pose = if take_body_pose {
|
||||||
|
*rb.position()
|
||||||
|
} else {
|
||||||
|
self.links[0].local_to_world
|
||||||
|
};
|
||||||
|
|
||||||
if rb.is_dynamic() {
|
if rb.is_dynamic() {
|
||||||
let free_joint = MultibodyJoint::free(*rb.position());
|
let free_joint = MultibodyJoint::free(root_pose);
|
||||||
let prev_root_ndofs = self.links[0].joint().ndofs();
|
let prev_root_ndofs = self.links[0].joint().ndofs();
|
||||||
self.links[0].joint = free_joint;
|
self.links[0].joint = free_joint;
|
||||||
self.links[0].assembly_id = 0;
|
self.links[0].assembly_id = 0;
|
||||||
@@ -791,7 +810,7 @@ impl Multibody {
|
|||||||
assert!(self.damping.len() >= SPATIAL_DIM);
|
assert!(self.damping.len() >= SPATIAL_DIM);
|
||||||
assert!(self.accelerations.len() >= SPATIAL_DIM);
|
assert!(self.accelerations.len() >= SPATIAL_DIM);
|
||||||
|
|
||||||
let fixed_joint = MultibodyJoint::fixed(*rb.position());
|
let fixed_joint = MultibodyJoint::fixed(root_pose);
|
||||||
let prev_root_ndofs = self.links[0].joint().ndofs();
|
let prev_root_ndofs = self.links[0].joint().ndofs();
|
||||||
self.links[0].joint = fixed_joint;
|
self.links[0].joint = fixed_joint;
|
||||||
self.links[0].assembly_id = 0;
|
self.links[0].assembly_id = 0;
|
||||||
@@ -820,30 +839,86 @@ impl Multibody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the positions are properly set to match the rigid-body’s.
|
// Make sure the positions are properly set to match the rigid-body’s.
|
||||||
if self.links[0].joint.data.locked_axes.is_empty() {
|
if take_body_pose {
|
||||||
self.links[0].joint.set_free_pos(*rb.position());
|
if self.links[0].joint.data.locked_axes.is_empty() {
|
||||||
|
self.links[0].joint.set_free_pos(*rb.position());
|
||||||
|
} else {
|
||||||
|
self.links[0].joint.data.local_frame1 = *rb.position();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the rigid-body poses based on this multibody joint poses.
|
||||||
|
///
|
||||||
|
/// This is typically called after [`Self::forward_kinematics`] to apply the new joint poses
|
||||||
|
/// to the rigid-bodies.
|
||||||
|
pub fn update_rigid_bodies(&self, bodies: &mut RigidBodySet, update_mass_properties: bool) {
|
||||||
|
self.update_rigid_bodies_internal(bodies, update_mass_properties, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_rigid_bodies_internal(
|
||||||
|
&self,
|
||||||
|
bodies: &mut RigidBodySet,
|
||||||
|
update_mass_properties: bool,
|
||||||
|
update_next_positions_only: bool,
|
||||||
|
change_tracking: bool,
|
||||||
|
) {
|
||||||
|
// Handle the children. They all have a parent within this multibody.
|
||||||
|
for link in self.links.iter() {
|
||||||
|
let rb = if change_tracking {
|
||||||
|
bodies.get_mut_internal_with_modification_tracking(link.rigid_body)
|
||||||
} else {
|
} else {
|
||||||
self.links[0].joint.data.local_frame1 = *rb.position();
|
bodies.get_mut_internal(link.rigid_body)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rb) = rb {
|
||||||
|
rb.pos.next_position = link.local_to_world;
|
||||||
|
|
||||||
|
if !update_next_positions_only {
|
||||||
|
rb.pos.position = link.local_to_world;
|
||||||
|
}
|
||||||
|
|
||||||
|
if update_mass_properties {
|
||||||
|
rb.mprops.update_world_mass_properties(&link.local_to_world);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make a version that doesn’t write back to bodies and doesn’t update the jacobians
|
// TODO: make a version that doesn’t write back to bodies and doesn’t update the jacobians
|
||||||
// (i.e. just something used by the velocity solver’s small steps).
|
// (i.e. just something used by the velocity solver’s small steps).
|
||||||
/// Apply forward-kinematics to this multibody and its related rigid-bodies.
|
/// Apply forward-kinematics to this multibody.
|
||||||
pub fn forward_kinematics(&mut self, bodies: &mut RigidBodySet, update_rb_mass_props: bool) {
|
///
|
||||||
|
/// This will update the [`MultibodyLink`] pose information as wall as the body jacobians.
|
||||||
|
/// This will also ensure that the multibody has the proper number of degrees of freedom if
|
||||||
|
/// its root node changed between dynamic and non-dynamic.
|
||||||
|
///
|
||||||
|
/// Note that this does **not** update the poses of the [`RigidBody`] attached to the joints.
|
||||||
|
/// Run [`Self::update_rigid_bodies`] to trigger that update.
|
||||||
|
///
|
||||||
|
/// This method updates `self` with the result of the forward-kinematics operation.
|
||||||
|
/// For a non-mutable version running forward kinematics on a single link, see
|
||||||
|
/// [`Self::forward_kinematics_single_link`].
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - `bodies`: the set of rigid-bodies.
|
||||||
|
/// - `read_root_pose_from_rigid_body`: if set to `true`, the root joint (either a fixed joint,
|
||||||
|
/// or a free joint) will have its pose set to its associated-rigid-body pose. Set this to `true`
|
||||||
|
/// when the root rigid-body pose has been modified and needs to affect the multibody.
|
||||||
|
pub fn forward_kinematics(
|
||||||
|
&mut self,
|
||||||
|
bodies: &RigidBodySet,
|
||||||
|
read_root_pose_from_rigid_body: bool,
|
||||||
|
) {
|
||||||
|
// Be sure the degrees of freedom match and take the root position if needed.
|
||||||
|
self.update_root_type(bodies, read_root_pose_from_rigid_body);
|
||||||
|
|
||||||
// Special case for the root, which has no parent.
|
// Special case for the root, which has no parent.
|
||||||
{
|
{
|
||||||
let link = &mut self.links[0];
|
let link = &mut self.links[0];
|
||||||
link.local_to_parent = link.joint.body_to_parent();
|
link.local_to_parent = link.joint.body_to_parent();
|
||||||
link.local_to_world = link.local_to_parent;
|
link.local_to_world = link.local_to_parent;
|
||||||
|
|
||||||
if let Some(rb) = bodies.get_mut_internal(link.rigid_body) {
|
|
||||||
rb.pos.next_position = link.local_to_world;
|
|
||||||
if update_rb_mass_props {
|
|
||||||
rb.mprops.update_world_mass_properties(&link.local_to_world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the children. They all have a parent within this multibody.
|
// Handle the children. They all have a parent within this multibody.
|
||||||
@@ -865,20 +940,11 @@ impl Multibody {
|
|||||||
link.shift23 = c3 - c2;
|
link.shift23 = c3 - c2;
|
||||||
}
|
}
|
||||||
|
|
||||||
let link_rb = bodies.index_mut_internal(link.rigid_body);
|
|
||||||
link_rb.pos.next_position = link.local_to_world;
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
link_rb.body_type,
|
bodies[link.rigid_body].body_type,
|
||||||
RigidBodyType::Dynamic,
|
RigidBodyType::Dynamic,
|
||||||
"A rigid-body that is not at the root of a multibody must be dynamic."
|
"A rigid-body that is not at the root of a multibody must be dynamic."
|
||||||
);
|
);
|
||||||
|
|
||||||
if update_rb_mass_props {
|
|
||||||
link_rb
|
|
||||||
.mprops
|
|
||||||
.update_world_mass_properties(&link.local_to_world);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -887,6 +953,107 @@ impl Multibody {
|
|||||||
self.update_body_jacobians();
|
self.update_body_jacobians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply forward-kinematics to compute the position of a single link of this multibody.
|
||||||
|
///
|
||||||
|
/// If `out_jacobian` is `Some`, this will simultaneously compute the new jacobian of this link.
|
||||||
|
/// If `displacement` is `Some`, the generalized position considered during transform propagation
|
||||||
|
/// is the sum of the current position of `self` and this `displacement`.
|
||||||
|
// TODO: this shares a lot of code with `forward_kinematics` and `update_body_jacobians`, except
|
||||||
|
// that we are only traversing a single kinematic chain. Could this be refactored?
|
||||||
|
pub fn forward_kinematics_single_link(
|
||||||
|
&self,
|
||||||
|
bodies: &RigidBodySet,
|
||||||
|
link_id: usize,
|
||||||
|
displacement: Option<&[Real]>,
|
||||||
|
mut out_jacobian: Option<&mut Jacobian<Real>>,
|
||||||
|
) -> Isometry<Real> {
|
||||||
|
let mut branch = vec![]; // Perf: avoid allocation.
|
||||||
|
let mut curr_id = Some(link_id);
|
||||||
|
|
||||||
|
while let Some(id) = curr_id {
|
||||||
|
branch.push(id);
|
||||||
|
curr_id = self.links[id].parent_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
branch.reverse();
|
||||||
|
|
||||||
|
if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
|
||||||
|
if out_jacobian.ncols() != self.ndofs {
|
||||||
|
*out_jacobian = Jacobian::zeros(self.ndofs);
|
||||||
|
} else {
|
||||||
|
out_jacobian.fill(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parent_link: Option<MultibodyLink> = None;
|
||||||
|
|
||||||
|
for i in branch {
|
||||||
|
let mut link = self.links[i];
|
||||||
|
|
||||||
|
if let Some(displacement) = displacement {
|
||||||
|
link.joint
|
||||||
|
.apply_displacement(&displacement[link.assembly_id..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent_to_world;
|
||||||
|
|
||||||
|
if let Some(parent_link) = parent_link {
|
||||||
|
link.local_to_parent = link.joint.body_to_parent();
|
||||||
|
link.local_to_world = parent_link.local_to_world * link.local_to_parent;
|
||||||
|
|
||||||
|
{
|
||||||
|
let parent_rb = &bodies[parent_link.rigid_body];
|
||||||
|
let link_rb = &bodies[link.rigid_body];
|
||||||
|
let c0 = parent_link.local_to_world * parent_rb.mprops.local_mprops.local_com;
|
||||||
|
let c2 = link.local_to_world
|
||||||
|
* Point::from(link.joint.data.local_frame2.translation.vector);
|
||||||
|
let c3 = link.local_to_world * link_rb.mprops.local_mprops.local_com;
|
||||||
|
|
||||||
|
link.shift02 = c2 - c0;
|
||||||
|
link.shift23 = c3 - c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent_to_world = parent_link.local_to_world;
|
||||||
|
|
||||||
|
if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
|
||||||
|
let (mut link_j_v, parent_j_w) =
|
||||||
|
out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
|
||||||
|
let shift_tr = (link.shift02).gcross_matrix_tr();
|
||||||
|
link_j_v.gemm(1.0, &shift_tr, &parent_j_w, 1.0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
link.local_to_parent = link.joint.body_to_parent();
|
||||||
|
link.local_to_world = link.local_to_parent;
|
||||||
|
parent_to_world = Isometry::identity();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(out_jacobian) = out_jacobian.as_deref_mut() {
|
||||||
|
let ndofs = link.joint.ndofs();
|
||||||
|
let mut tmp = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::zeros();
|
||||||
|
let mut link_joint_j = tmp.columns_mut(0, ndofs);
|
||||||
|
let mut link_j_part = out_jacobian.columns_mut(link.assembly_id, ndofs);
|
||||||
|
link.joint.jacobian(
|
||||||
|
&(parent_to_world.rotation * link.joint.data.local_frame1.rotation),
|
||||||
|
&mut link_joint_j,
|
||||||
|
);
|
||||||
|
link_j_part += link_joint_j;
|
||||||
|
|
||||||
|
{
|
||||||
|
let (mut link_j_v, link_j_w) =
|
||||||
|
out_jacobian.rows_range_pair_mut(0..DIM, DIM..DIM + ANG_DIM);
|
||||||
|
let shift_tr = link.shift23.gcross_matrix_tr();
|
||||||
|
link_j_v.gemm(1.0, &shift_tr, &link_j_w, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent_link = Some(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent_link
|
||||||
|
.map(|link| link.local_to_world)
|
||||||
|
.unwrap_or_else(Isometry::identity)
|
||||||
|
}
|
||||||
|
|
||||||
/// The total number of freedoms of this multibody.
|
/// The total number of freedoms of this multibody.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ndofs(&self) -> usize {
|
pub fn ndofs(&self) -> usize {
|
||||||
|
|||||||
238
src/dynamics/joint/multibody_joint/multibody_ik.rs
Normal file
238
src/dynamics/joint/multibody_joint/multibody_ik.rs
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
use crate::dynamics::{JointAxesMask, Multibody, RigidBodySet};
|
||||||
|
use crate::math::{Isometry, Jacobian, Real, ANG_DIM, DIM, SPATIAL_DIM};
|
||||||
|
use na::{self, DVector, SMatrix};
|
||||||
|
use parry::math::SpacialVector;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
/// Options for the jacobian-based Inverse Kinematics solver for multibodies.
|
||||||
|
pub struct InverseKinematicsOption {
|
||||||
|
/// A damping coefficient.
|
||||||
|
///
|
||||||
|
/// Small value can lead to overshooting preventing convergence. Large
|
||||||
|
/// values can slown down convergence, requiring more iterations to converge.
|
||||||
|
pub damping: Real,
|
||||||
|
/// The maximum number of iterations the iterative IK solver can take.
|
||||||
|
pub max_iters: usize,
|
||||||
|
/// The axes the IK solver will solve for.
|
||||||
|
pub constrained_axes: JointAxesMask,
|
||||||
|
/// The error threshold on the linear error.
|
||||||
|
///
|
||||||
|
/// If errors on both linear and angular parts fall below this
|
||||||
|
/// threshold, the iterative resolution will stop.
|
||||||
|
pub epsilon_linear: Real,
|
||||||
|
/// The error threshold on the angular error.
|
||||||
|
///
|
||||||
|
/// If errors on both linear and angular parts fall below this
|
||||||
|
/// threshold, the iterative resolution will stop.
|
||||||
|
pub epsilon_angular: Real,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InverseKinematicsOption {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
damping: 1.0,
|
||||||
|
max_iters: 10,
|
||||||
|
constrained_axes: JointAxesMask::all(),
|
||||||
|
epsilon_linear: 1.0e-3,
|
||||||
|
epsilon_angular: 1.0e-3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Multibody {
|
||||||
|
/// Computes the displacement needed to have the link identified by `link_id` move by the
|
||||||
|
/// desired transform.
|
||||||
|
///
|
||||||
|
/// The displacement calculated by this function is added to the `displacement` vector.
|
||||||
|
pub fn inverse_kinematics_delta(
|
||||||
|
&self,
|
||||||
|
link_id: usize,
|
||||||
|
desired_movement: &SpacialVector<Real>,
|
||||||
|
damping: Real,
|
||||||
|
displacements: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let body_jacobian = self.body_jacobian(link_id);
|
||||||
|
Self::inverse_kinematics_delta_with_jacobian(
|
||||||
|
body_jacobian,
|
||||||
|
desired_movement,
|
||||||
|
damping,
|
||||||
|
displacements,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the displacement needed to have a link with the given jacobian move by the
|
||||||
|
/// desired transform.
|
||||||
|
///
|
||||||
|
/// The displacement calculated by this function is added to the `displacement` vector.
|
||||||
|
pub fn inverse_kinematics_delta_with_jacobian(
|
||||||
|
jacobian: &Jacobian<Real>,
|
||||||
|
desired_movement: &SpacialVector<Real>,
|
||||||
|
damping: Real,
|
||||||
|
displacements: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let identity = SMatrix::<Real, SPATIAL_DIM, SPATIAL_DIM>::identity();
|
||||||
|
let jj = jacobian * &jacobian.transpose() + identity * (damping * damping);
|
||||||
|
let inv_jj = jj.pseudo_inverse(1.0e-5).unwrap_or(identity);
|
||||||
|
displacements.gemv_tr(1.0, jacobian, &(inv_jj * desired_movement), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the displacement needed to have the link identified by `link_id` have a pose
|
||||||
|
/// equal (or as close as possible) to `target_pose`.
|
||||||
|
///
|
||||||
|
/// If `displacement` is given non-zero, the current pose of the rigid-body is considered to be
|
||||||
|
/// obtained from its current generalized coordinates summed with the `displacement` vector.
|
||||||
|
///
|
||||||
|
/// The `displacements` vector is overwritten with the new displacement.
|
||||||
|
pub fn inverse_kinematics(
|
||||||
|
&self,
|
||||||
|
bodies: &RigidBodySet,
|
||||||
|
link_id: usize,
|
||||||
|
options: &InverseKinematicsOption,
|
||||||
|
target_pose: &Isometry<Real>,
|
||||||
|
displacements: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let mut jacobian = Jacobian::zeros(0);
|
||||||
|
|
||||||
|
for _ in 0..options.max_iters {
|
||||||
|
let pose = self.forward_kinematics_single_link(
|
||||||
|
bodies,
|
||||||
|
link_id,
|
||||||
|
Some(displacements.as_slice()),
|
||||||
|
Some(&mut jacobian),
|
||||||
|
);
|
||||||
|
|
||||||
|
let delta_lin = target_pose.translation.vector - pose.translation.vector;
|
||||||
|
let delta_ang = (target_pose.rotation * pose.rotation.inverse()).scaled_axis();
|
||||||
|
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
let mut delta = na::vector![delta_lin.x, delta_lin.y, delta_ang.x];
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let mut delta = na::vector![
|
||||||
|
delta_lin.x,
|
||||||
|
delta_lin.y,
|
||||||
|
delta_lin.z,
|
||||||
|
delta_ang.x,
|
||||||
|
delta_ang.y,
|
||||||
|
delta_ang.z
|
||||||
|
];
|
||||||
|
|
||||||
|
if !options.constrained_axes.contains(JointAxesMask::X) {
|
||||||
|
delta[0] = 0.0;
|
||||||
|
}
|
||||||
|
if !options.constrained_axes.contains(JointAxesMask::Y) {
|
||||||
|
delta[1] = 0.0;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
if !options.constrained_axes.contains(JointAxesMask::Z) {
|
||||||
|
delta[2] = 0.0;
|
||||||
|
}
|
||||||
|
if !options.constrained_axes.contains(JointAxesMask::ANG_X) {
|
||||||
|
delta[DIM] = 0.0;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
if !options.constrained_axes.contains(JointAxesMask::ANG_Y) {
|
||||||
|
delta[DIM + 1] = 0.0;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
if !options.constrained_axes.contains(JointAxesMask::ANG_Z) {
|
||||||
|
delta[DIM + 2] = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: measure convergence on the error variation instead?
|
||||||
|
if delta.rows(0, DIM).norm() <= options.epsilon_linear
|
||||||
|
&& delta.rows(DIM, ANG_DIM).norm() <= options.epsilon_angular
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::inverse_kinematics_delta_with_jacobian(
|
||||||
|
&jacobian,
|
||||||
|
&delta,
|
||||||
|
options.damping,
|
||||||
|
displacements,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::dynamics::{
|
||||||
|
MultibodyJointHandle, MultibodyJointSet, RevoluteJointBuilder, RigidBodyBuilder,
|
||||||
|
RigidBodySet,
|
||||||
|
};
|
||||||
|
use crate::math::{Jacobian, Real, Vector};
|
||||||
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_link_fwd_kinematics() {
|
||||||
|
let mut bodies = RigidBodySet::new();
|
||||||
|
let mut multibodies = MultibodyJointSet::new();
|
||||||
|
|
||||||
|
let num_segments = 10;
|
||||||
|
let body = RigidBodyBuilder::fixed();
|
||||||
|
let mut last_body = bodies.insert(body);
|
||||||
|
let mut last_link = MultibodyJointHandle::invalid();
|
||||||
|
|
||||||
|
for _ in 0..num_segments {
|
||||||
|
let body = RigidBodyBuilder::dynamic().can_sleep(false);
|
||||||
|
let new_body = bodies.insert(body);
|
||||||
|
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
let builder = RevoluteJointBuilder::new();
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let builder = RevoluteJointBuilder::new(Vector::z_axis());
|
||||||
|
let link_ab = builder
|
||||||
|
.local_anchor1((Vector::y() * (0.5 / num_segments as Real)).into())
|
||||||
|
.local_anchor2((Vector::y() * (-0.5 / num_segments as Real)).into());
|
||||||
|
last_link = multibodies
|
||||||
|
.insert(last_body, new_body, link_ab, true)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
last_body = new_body;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (multibody, last_id) = multibodies.get_mut(last_link).unwrap();
|
||||||
|
multibody.forward_kinematics(&bodies, true); // Be sure all the dofs are up to date.
|
||||||
|
assert_eq!(multibody.ndofs(), num_segments);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* No displacement.
|
||||||
|
*/
|
||||||
|
let mut jacobian2 = Jacobian::zeros(0);
|
||||||
|
let link_pose1 = *multibody.link(last_id).unwrap().local_to_world();
|
||||||
|
let jacobian1 = multibody.body_jacobian(last_id);
|
||||||
|
let link_pose2 =
|
||||||
|
multibody.forward_kinematics_single_link(&bodies, last_id, None, Some(&mut jacobian2));
|
||||||
|
assert_eq!(link_pose1, link_pose2);
|
||||||
|
assert_eq!(jacobian1, &jacobian2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Arbitrary displacement.
|
||||||
|
*/
|
||||||
|
let niter = 100;
|
||||||
|
let displacement_part: Vec<_> = (0..multibody.ndofs())
|
||||||
|
.map(|i| i as Real * -0.1 / niter as Real)
|
||||||
|
.collect();
|
||||||
|
let displacement_total: Vec<_> = displacement_part
|
||||||
|
.iter()
|
||||||
|
.map(|d| *d * niter as Real)
|
||||||
|
.collect();
|
||||||
|
let link_pose2 = multibody.forward_kinematics_single_link(
|
||||||
|
&bodies,
|
||||||
|
last_id,
|
||||||
|
Some(&displacement_total),
|
||||||
|
Some(&mut jacobian2),
|
||||||
|
);
|
||||||
|
|
||||||
|
for _ in 0..niter {
|
||||||
|
multibody.apply_displacements(&displacement_part);
|
||||||
|
multibody.forward_kinematics(&bodies, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let link_pose1 = *multibody.link(last_id).unwrap().local_to_world();
|
||||||
|
let jacobian1 = multibody.body_jacobian(last_id);
|
||||||
|
assert_relative_eq!(link_pose1, link_pose2, epsilon = 1.0e-5);
|
||||||
|
assert_relative_eq!(jacobian1, &jacobian2, epsilon = 1.0e-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -286,6 +286,13 @@ impl MultibodyJointSet {
|
|||||||
self.multibodies.get(index.0)
|
self.multibodies.get(index.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to a multibody, based on its temporary index.
|
||||||
|
/// `MultibodyJointSet`.
|
||||||
|
pub fn get_multibody_mut(&mut self, index: MultibodyIndex) -> Option<&mut Multibody> {
|
||||||
|
// TODO: modification tracking.
|
||||||
|
self.multibodies.get_mut(index.0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a mutable reference to a multibody, based on its temporary index.
|
/// Gets a mutable reference to a multibody, based on its temporary index.
|
||||||
///
|
///
|
||||||
/// This method will bypass any modification-detection automatically done by the
|
/// This method will bypass any modification-detection automatically done by the
|
||||||
@@ -363,13 +370,13 @@ impl MultibodyJointSet {
|
|||||||
let parent1 = link1.parent_id();
|
let parent1 = link1.parent_id();
|
||||||
|
|
||||||
if parent1 == Some(id2.id) {
|
if parent1 == Some(id2.id) {
|
||||||
Some((MultibodyJointHandle(rb1.0), mb, &link1))
|
Some((MultibodyJointHandle(rb1.0), mb, link1))
|
||||||
} else {
|
} else {
|
||||||
let link2 = mb.link(id2.id)?;
|
let link2 = mb.link(id2.id)?;
|
||||||
let parent2 = link2.parent_id();
|
let parent2 = link2.parent_id();
|
||||||
|
|
||||||
if parent2 == Some(id1.id) {
|
if parent2 == Some(id1.id) {
|
||||||
Some((MultibodyJointHandle(rb2.0), mb, &link2))
|
Some((MultibodyJointHandle(rb2.0), mb, link2))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::prelude::RigidBodyVelocity;
|
|||||||
|
|
||||||
/// One link of a multibody.
|
/// One link of a multibody.
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct MultibodyLink {
|
pub struct MultibodyLink {
|
||||||
// FIXME: make all those private.
|
// FIXME: make all those private.
|
||||||
pub(crate) internal_id: usize,
|
pub(crate) internal_id: usize,
|
||||||
|
|||||||
@@ -74,6 +74,61 @@ impl RigidBody {
|
|||||||
self.ids = Default::default();
|
self.ids = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copy all the characteristics from `other` to `self`.
|
||||||
|
///
|
||||||
|
/// If you have a mutable reference to a rigid-body `rigid_body: &mut RigidBody`, attempting to
|
||||||
|
/// assign it a whole new rigid-body instance, e.g., `*rigid_body = RigidBodyBuilder::dynamic().build()`,
|
||||||
|
/// will crash due to some internal indices being overwritten. Instead, use
|
||||||
|
/// `rigid_body.copy_from(&RigidBodyBuilder::dynamic().build())`.
|
||||||
|
///
|
||||||
|
/// This method will allow you to set most characteristics of this rigid-body from another
|
||||||
|
/// rigid-body instance without causing any breakage.
|
||||||
|
///
|
||||||
|
/// This method **cannot** be used for editing the list of colliders attached to this rigid-body.
|
||||||
|
/// Therefore, the list of colliders attached to `self` won’t be replaced by the one attached
|
||||||
|
/// to `other`.
|
||||||
|
///
|
||||||
|
/// The pose of `other` will only copied into `self` if `self` doesn’t have a parent (if it has
|
||||||
|
/// a parent, its position is directly controlled by the parent rigid-body).
|
||||||
|
pub fn copy_from(&mut self, other: &RigidBody) {
|
||||||
|
// NOTE: we deconstruct the rigid-body struct to be sure we don’t forget to
|
||||||
|
// add some copies here if we add more field to RigidBody in the future.
|
||||||
|
let RigidBody {
|
||||||
|
pos,
|
||||||
|
mprops,
|
||||||
|
integrated_vels,
|
||||||
|
vels,
|
||||||
|
damping,
|
||||||
|
forces,
|
||||||
|
ccd,
|
||||||
|
ids: _ids, // Internal ids must not be overwritten.
|
||||||
|
colliders: _colliders, // This function cannot be used to edit collider sets.
|
||||||
|
activation,
|
||||||
|
changes: _changes, // Will be set to ALL.
|
||||||
|
body_type,
|
||||||
|
dominance,
|
||||||
|
enabled,
|
||||||
|
additional_solver_iterations,
|
||||||
|
user_data,
|
||||||
|
} = other;
|
||||||
|
|
||||||
|
self.pos = *pos;
|
||||||
|
self.mprops = mprops.clone();
|
||||||
|
self.integrated_vels = *integrated_vels;
|
||||||
|
self.vels = *vels;
|
||||||
|
self.damping = *damping;
|
||||||
|
self.forces = *forces;
|
||||||
|
self.ccd = *ccd;
|
||||||
|
self.activation = *activation;
|
||||||
|
self.body_type = *body_type;
|
||||||
|
self.dominance = *dominance;
|
||||||
|
self.enabled = *enabled;
|
||||||
|
self.additional_solver_iterations = *additional_solver_iterations;
|
||||||
|
self.user_data = *user_data;
|
||||||
|
|
||||||
|
self.changes = RigidBodyChanges::all();
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the additional number of solver iterations run for this rigid-body and
|
/// Set the additional number of solver iterations run for this rigid-body and
|
||||||
/// everything interacting with it.
|
/// everything interacting with it.
|
||||||
///
|
///
|
||||||
@@ -237,9 +292,9 @@ impl RigidBody {
|
|||||||
allow_rotations_z: bool,
|
allow_rotations_z: bool,
|
||||||
wake_up: bool,
|
wake_up: bool,
|
||||||
) {
|
) {
|
||||||
if self.mprops.flags.contains(LockedAxes::ROTATION_LOCKED_X) != !allow_rotations_x
|
if self.mprops.flags.contains(LockedAxes::ROTATION_LOCKED_X) == allow_rotations_x
|
||||||
|| self.mprops.flags.contains(LockedAxes::ROTATION_LOCKED_Y) != !allow_rotations_y
|
|| self.mprops.flags.contains(LockedAxes::ROTATION_LOCKED_Y) == allow_rotations_y
|
||||||
|| self.mprops.flags.contains(LockedAxes::ROTATION_LOCKED_Z) != !allow_rotations_z
|
|| self.mprops.flags.contains(LockedAxes::ROTATION_LOCKED_Z) == allow_rotations_z
|
||||||
{
|
{
|
||||||
if self.is_dynamic() && wake_up {
|
if self.is_dynamic() && wake_up {
|
||||||
self.wake_up(true);
|
self.wake_up(true);
|
||||||
@@ -381,18 +436,40 @@ impl RigidBody {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables of disable CCD (continuous collision-detection) for this rigid-body.
|
/// Enables of disable CCD (Continuous Collision-Detection) for this rigid-body.
|
||||||
///
|
///
|
||||||
/// CCD prevents tunneling, but may still allow limited interpenetration of colliders.
|
/// CCD prevents tunneling, but may still allow limited interpenetration of colliders.
|
||||||
pub fn enable_ccd(&mut self, enabled: bool) {
|
pub fn enable_ccd(&mut self, enabled: bool) {
|
||||||
self.ccd.ccd_enabled = enabled;
|
self.ccd.ccd_enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is CCD (continous collision-detection) enabled for this rigid-body?
|
/// Is CCD (continuous collision-detection) enabled for this rigid-body?
|
||||||
pub fn is_ccd_enabled(&self) -> bool {
|
pub fn is_ccd_enabled(&self) -> bool {
|
||||||
self.ccd.ccd_enabled
|
self.ccd.ccd_enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the maximum prediction distance Soft Continuous Collision-Detection.
|
||||||
|
///
|
||||||
|
/// When set to 0, soft-CCD is disabled. Soft-CCD helps prevent tunneling especially of
|
||||||
|
/// slow-but-thin to moderately fast objects. The soft CCD prediction distance indicates how
|
||||||
|
/// far in the object’s path the CCD algorithm is allowed to inspect. Large values can impact
|
||||||
|
/// performance badly by increasing the work needed from the broad-phase.
|
||||||
|
///
|
||||||
|
/// It is a generally cheaper variant of regular CCD (that can be enabled with
|
||||||
|
/// [`RigidBody::enable_ccd`] since it relies on predictive constraints instead of
|
||||||
|
/// shape-cast and substeps.
|
||||||
|
pub fn set_soft_ccd_prediction(&mut self, prediction_distance: Real) {
|
||||||
|
self.ccd.soft_ccd_prediction = prediction_distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The soft-CCD prediction distance for this rigid-body.
|
||||||
|
///
|
||||||
|
/// See the documentation of [`RigidBody::set_soft_ccd_prediction`] for additional details on
|
||||||
|
/// soft-CCD.
|
||||||
|
pub fn soft_ccd_prediction(&self) -> Real {
|
||||||
|
self.ccd.soft_ccd_prediction
|
||||||
|
}
|
||||||
|
|
||||||
// This is different from `is_ccd_enabled`. This checks that CCD
|
// This is different from `is_ccd_enabled`. This checks that CCD
|
||||||
// is active for this rigid-body, i.e., if it was seen to move fast
|
// is active for this rigid-body, i.e., if it was seen to move fast
|
||||||
// enough to justify a CCD run.
|
// enough to justify a CCD run.
|
||||||
@@ -810,6 +887,25 @@ impl RigidBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Predicts the next position of this rigid-body, by integrating its velocity and forces
|
||||||
|
/// by a time of `dt`.
|
||||||
|
pub(crate) fn predict_position_using_velocity_and_forces_with_max_dist(
|
||||||
|
&self,
|
||||||
|
dt: Real,
|
||||||
|
max_dist: Real,
|
||||||
|
) -> Isometry<Real> {
|
||||||
|
let new_vels = self.forces.integrate(dt, &self.vels, &self.mprops);
|
||||||
|
// Compute the clamped dt such that the body doesn’t travel more than `max_dist`.
|
||||||
|
let linvel_norm = new_vels.linvel.norm();
|
||||||
|
let clamped_linvel = linvel_norm.min(max_dist * crate::utils::inv(dt));
|
||||||
|
let clamped_dt = dt * clamped_linvel * crate::utils::inv(linvel_norm);
|
||||||
|
new_vels.integrate(
|
||||||
|
clamped_dt,
|
||||||
|
&self.pos.position,
|
||||||
|
&self.mprops.local_mprops.local_com,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Predicts the next position of this rigid-body, by integrating its velocity and forces
|
/// Predicts the next position of this rigid-body, by integrating its velocity and forces
|
||||||
/// by a time of `dt`.
|
/// by a time of `dt`.
|
||||||
pub fn predict_position_using_velocity_and_forces(&self, dt: Real) -> Isometry<Real> {
|
pub fn predict_position_using_velocity_and_forces(&self, dt: Real) -> Isometry<Real> {
|
||||||
@@ -817,6 +913,17 @@ impl RigidBody {
|
|||||||
.integrate_forces_and_velocities(dt, &self.forces, &self.vels, &self.mprops)
|
.integrate_forces_and_velocities(dt, &self.forces, &self.vels, &self.mprops)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Predicts the next position of this rigid-body, by integrating only its velocity
|
||||||
|
/// by a time of `dt`.
|
||||||
|
///
|
||||||
|
/// The forces that were applied to this rigid-body since the last physics step will
|
||||||
|
/// be ignored by this function. Use [`Self::predict_position_using_velocity_and_forces`]
|
||||||
|
/// instead to take forces into account.
|
||||||
|
pub fn predict_position_using_velocity(&self, dt: Real) -> Isometry<Real> {
|
||||||
|
self.vels
|
||||||
|
.integrate(dt, &self.pos.position, &self.mprops.local_mprops.local_com)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn update_world_mass_properties(&mut self) {
|
pub(crate) fn update_world_mass_properties(&mut self) {
|
||||||
self.mprops.update_world_mass_properties(&self.pos.position);
|
self.mprops.update_world_mass_properties(&self.pos.position);
|
||||||
}
|
}
|
||||||
@@ -1031,14 +1138,25 @@ pub struct RigidBodyBuilder {
|
|||||||
mprops_flags: LockedAxes,
|
mprops_flags: LockedAxes,
|
||||||
/// The additional mass-properties of the rigid-body being built. See [`RigidBodyBuilder::additional_mass_properties`] for more information.
|
/// The additional mass-properties of the rigid-body being built. See [`RigidBodyBuilder::additional_mass_properties`] for more information.
|
||||||
additional_mass_properties: RigidBodyAdditionalMassProps,
|
additional_mass_properties: RigidBodyAdditionalMassProps,
|
||||||
/// Whether or not the rigid-body to be created can sleep if it reaches a dynamic equilibrium.
|
/// Whether the rigid-body to be created can sleep if it reaches a dynamic equilibrium.
|
||||||
pub can_sleep: bool,
|
pub can_sleep: bool,
|
||||||
/// Whether or not the rigid-body is to be created asleep.
|
/// Whether the rigid-body is to be created asleep.
|
||||||
pub sleeping: bool,
|
pub sleeping: bool,
|
||||||
/// Whether continuous collision-detection is enabled for the rigid-body to be built.
|
/// Whether Continuous Collision-Detection is enabled for the rigid-body to be built.
|
||||||
///
|
///
|
||||||
/// CCD prevents tunneling, but may still allow limited interpenetration of colliders.
|
/// CCD prevents tunneling, but may still allow limited interpenetration of colliders.
|
||||||
pub ccd_enabled: bool,
|
pub ccd_enabled: bool,
|
||||||
|
/// The maximum prediction distance Soft Continuous Collision-Detection.
|
||||||
|
///
|
||||||
|
/// When set to 0, soft CCD is disabled. Soft-CCD helps prevent tunneling especially of
|
||||||
|
/// slow-but-thin to moderately fast objects. The soft CCD prediction distance indicates how
|
||||||
|
/// far in the object’s path the CCD algorithm is allowed to inspect. Large values can impact
|
||||||
|
/// performance badly by increasing the work needed from the broad-phase.
|
||||||
|
///
|
||||||
|
/// It is a generally cheaper variant of regular CCD (that can be enabled with
|
||||||
|
/// [`RigidBodyBuilder::ccd_enabled`] since it relies on predictive constraints instead of
|
||||||
|
/// shape-cast and substeps.
|
||||||
|
pub soft_ccd_prediction: Real,
|
||||||
/// The dominance group of the rigid-body to be built.
|
/// The dominance group of the rigid-body to be built.
|
||||||
pub dominance_group: i8,
|
pub dominance_group: i8,
|
||||||
/// Will the rigid-body being built be enabled?
|
/// Will the rigid-body being built be enabled?
|
||||||
@@ -1068,6 +1186,7 @@ impl RigidBodyBuilder {
|
|||||||
can_sleep: true,
|
can_sleep: true,
|
||||||
sleeping: false,
|
sleeping: false,
|
||||||
ccd_enabled: false,
|
ccd_enabled: false,
|
||||||
|
soft_ccd_prediction: 0.0,
|
||||||
dominance_group: 0,
|
dominance_group: 0,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
user_data: 0,
|
user_data: 0,
|
||||||
@@ -1306,13 +1425,13 @@ impl RigidBodyBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether or not the rigid-body to be created can sleep if it reaches a dynamic equilibrium.
|
/// Sets whether the rigid-body to be created can sleep if it reaches a dynamic equilibrium.
|
||||||
pub fn can_sleep(mut self, can_sleep: bool) -> Self {
|
pub fn can_sleep(mut self, can_sleep: bool) -> Self {
|
||||||
self.can_sleep = can_sleep;
|
self.can_sleep = can_sleep;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether or not continuous collision-detection is enabled for this rigid-body.
|
/// Sets whether Continuous Collision-Detection is enabled for this rigid-body.
|
||||||
///
|
///
|
||||||
/// CCD prevents tunneling, but may still allow limited interpenetration of colliders.
|
/// CCD prevents tunneling, but may still allow limited interpenetration of colliders.
|
||||||
pub fn ccd_enabled(mut self, enabled: bool) -> Self {
|
pub fn ccd_enabled(mut self, enabled: bool) -> Self {
|
||||||
@@ -1320,7 +1439,22 @@ impl RigidBodyBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets whether or not the rigid-body is to be created asleep.
|
/// Sets the maximum prediction distance Soft Continuous Collision-Detection.
|
||||||
|
///
|
||||||
|
/// When set to 0, soft-CCD is disabled. Soft-CCD helps prevent tunneling especially of
|
||||||
|
/// slow-but-thin to moderately fast objects. The soft CCD prediction distance indicates how
|
||||||
|
/// far in the object’s path the CCD algorithm is allowed to inspect. Large values can impact
|
||||||
|
/// performance badly by increasing the work needed from the broad-phase.
|
||||||
|
///
|
||||||
|
/// It is a generally cheaper variant of regular CCD (that can be enabled with
|
||||||
|
/// [`RigidBodyBuilder::ccd_enabled`] since it relies on predictive constraints instead of
|
||||||
|
/// shape-cast and substeps.
|
||||||
|
pub fn soft_ccd_prediction(mut self, prediction_distance: Real) -> Self {
|
||||||
|
self.soft_ccd_prediction = prediction_distance;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether the rigid-body is to be created asleep.
|
||||||
pub fn sleeping(mut self, sleeping: bool) -> Self {
|
pub fn sleeping(mut self, sleeping: bool) -> Self {
|
||||||
self.sleeping = sleeping;
|
self.sleeping = sleeping;
|
||||||
self
|
self
|
||||||
@@ -1357,13 +1491,14 @@ impl RigidBodyBuilder {
|
|||||||
rb.dominance = RigidBodyDominance(self.dominance_group);
|
rb.dominance = RigidBodyDominance(self.dominance_group);
|
||||||
rb.enabled = self.enabled;
|
rb.enabled = self.enabled;
|
||||||
rb.enable_ccd(self.ccd_enabled);
|
rb.enable_ccd(self.ccd_enabled);
|
||||||
|
rb.set_soft_ccd_prediction(self.soft_ccd_prediction);
|
||||||
|
|
||||||
if self.can_sleep && self.sleeping {
|
if self.can_sleep && self.sleeping {
|
||||||
rb.sleep();
|
rb.sleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.can_sleep {
|
if !self.can_sleep {
|
||||||
rb.activation.linear_threshold = -1.0;
|
rb.activation.normalized_linear_threshold = -1.0;
|
||||||
rb.activation.angular_threshold = -1.0;
|
rb.activation.angular_threshold = -1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -571,10 +571,11 @@ impl RigidBodyVelocity {
|
|||||||
/// The approximate kinetic energy of this rigid-body.
|
/// The approximate kinetic energy of this rigid-body.
|
||||||
///
|
///
|
||||||
/// This approximation does not take the rigid-body's mass and angular inertia
|
/// This approximation does not take the rigid-body's mass and angular inertia
|
||||||
/// into account.
|
/// into account. Some physics engines call this the "mass-normalized kinetic
|
||||||
|
/// energy".
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn pseudo_kinetic_energy(&self) -> Real {
|
pub fn pseudo_kinetic_energy(&self) -> Real {
|
||||||
self.linvel.norm_squared() + self.angvel.gdot(self.angvel)
|
0.5 * (self.linvel.norm_squared() + self.angvel.gdot(self.angvel))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the update velocities after applying the given damping.
|
/// Returns the update velocities after applying the given damping.
|
||||||
@@ -594,7 +595,7 @@ impl RigidBodyVelocity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Integrate the velocities in `self` to compute obtain new positions when moving from the given
|
/// Integrate the velocities in `self` to compute obtain new positions when moving from the given
|
||||||
/// inital position `init_pos`.
|
/// initial position `init_pos`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn integrate(
|
pub fn integrate(
|
||||||
&self,
|
&self,
|
||||||
@@ -821,6 +822,8 @@ pub struct RigidBodyCcd {
|
|||||||
pub ccd_active: bool,
|
pub ccd_active: bool,
|
||||||
/// Is CCD enabled for this rigid-body?
|
/// Is CCD enabled for this rigid-body?
|
||||||
pub ccd_enabled: bool,
|
pub ccd_enabled: bool,
|
||||||
|
/// The soft-CCD prediction distance for this rigid-body.
|
||||||
|
pub soft_ccd_prediction: Real,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RigidBodyCcd {
|
impl Default for RigidBodyCcd {
|
||||||
@@ -830,6 +833,7 @@ impl Default for RigidBodyCcd {
|
|||||||
ccd_max_dist: 0.0,
|
ccd_max_dist: 0.0,
|
||||||
ccd_active: false,
|
ccd_active: false,
|
||||||
ccd_enabled: false,
|
ccd_enabled: false,
|
||||||
|
soft_ccd_prediction: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -992,7 +996,10 @@ impl RigidBodyDominance {
|
|||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
pub struct RigidBodyActivation {
|
pub struct RigidBodyActivation {
|
||||||
/// The threshold linear velocity bellow which the body can fall asleep.
|
/// The threshold linear velocity bellow which the body can fall asleep.
|
||||||
pub linear_threshold: Real,
|
///
|
||||||
|
/// The value is "normalized", i.e., the actual threshold applied by the physics engine
|
||||||
|
/// is equal to this value multiplied by [`IntegrationParameters::length_unit`].
|
||||||
|
pub normalized_linear_threshold: Real,
|
||||||
/// The angular linear velocity bellow which the body can fall asleep.
|
/// The angular linear velocity bellow which the body can fall asleep.
|
||||||
pub angular_threshold: Real,
|
pub angular_threshold: Real,
|
||||||
/// The amount of time the rigid-body must remain below the thresholds to be put to sleep.
|
/// The amount of time the rigid-body must remain below the thresholds to be put to sleep.
|
||||||
@@ -1011,7 +1018,7 @@ impl Default for RigidBodyActivation {
|
|||||||
|
|
||||||
impl RigidBodyActivation {
|
impl RigidBodyActivation {
|
||||||
/// The default linear velocity bellow which a body can be put to sleep.
|
/// The default linear velocity bellow which a body can be put to sleep.
|
||||||
pub fn default_linear_threshold() -> Real {
|
pub fn default_normalized_linear_threshold() -> Real {
|
||||||
0.4
|
0.4
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1029,7 +1036,7 @@ impl RigidBodyActivation {
|
|||||||
/// Create a new rb_activation status initialised with the default rb_activation threshold and is active.
|
/// Create a new rb_activation status initialised with the default rb_activation threshold and is active.
|
||||||
pub fn active() -> Self {
|
pub fn active() -> Self {
|
||||||
RigidBodyActivation {
|
RigidBodyActivation {
|
||||||
linear_threshold: Self::default_linear_threshold(),
|
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
|
||||||
angular_threshold: Self::default_angular_threshold(),
|
angular_threshold: Self::default_angular_threshold(),
|
||||||
time_until_sleep: Self::default_time_until_sleep(),
|
time_until_sleep: Self::default_time_until_sleep(),
|
||||||
time_since_can_sleep: 0.0,
|
time_since_can_sleep: 0.0,
|
||||||
@@ -1040,7 +1047,7 @@ impl RigidBodyActivation {
|
|||||||
/// Create a new rb_activation status initialised with the default rb_activation threshold and is inactive.
|
/// Create a new rb_activation status initialised with the default rb_activation threshold and is inactive.
|
||||||
pub fn inactive() -> Self {
|
pub fn inactive() -> Self {
|
||||||
RigidBodyActivation {
|
RigidBodyActivation {
|
||||||
linear_threshold: Self::default_linear_threshold(),
|
normalized_linear_threshold: Self::default_normalized_linear_threshold(),
|
||||||
angular_threshold: Self::default_angular_threshold(),
|
angular_threshold: Self::default_angular_threshold(),
|
||||||
time_until_sleep: Self::default_time_until_sleep(),
|
time_until_sleep: Self::default_time_until_sleep(),
|
||||||
time_since_can_sleep: Self::default_time_until_sleep(),
|
time_since_can_sleep: Self::default_time_until_sleep(),
|
||||||
@@ -1051,7 +1058,7 @@ impl RigidBodyActivation {
|
|||||||
/// Create a new activation status that prevents the rigid-body from sleeping.
|
/// Create a new activation status that prevents the rigid-body from sleeping.
|
||||||
pub fn cannot_sleep() -> Self {
|
pub fn cannot_sleep() -> Self {
|
||||||
RigidBodyActivation {
|
RigidBodyActivation {
|
||||||
linear_threshold: -1.0,
|
normalized_linear_threshold: -1.0,
|
||||||
angular_threshold: -1.0,
|
angular_threshold: -1.0,
|
||||||
..Self::active()
|
..Self::active()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ impl RigidBodySet {
|
|||||||
/// Update colliders positions after rigid-bodies moved.
|
/// Update colliders positions after rigid-bodies moved.
|
||||||
///
|
///
|
||||||
/// When a rigid-body moves, the positions of the colliders attached to it need to be updated.
|
/// When a rigid-body moves, the positions of the colliders attached to it need to be updated.
|
||||||
/// This update is generally automatically done at the beggining and the end of each simulation
|
/// This update is generally automatically done at the beginning and the end of each simulation
|
||||||
/// step with `PhysicsPipeline::step`. If the positions need to be updated without running a
|
/// step with `PhysicsPipeline::step`. If the positions need to be updated without running a
|
||||||
/// simulation step (for example when using the `QueryPipeline` alone), this method can be called
|
/// simulation step (for example when using the `QueryPipeline` alone), this method can be called
|
||||||
/// manually.
|
/// manually.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use {
|
|||||||
crate::math::SIMD_WIDTH,
|
crate::math::SIMD_WIDTH,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ConstraintsCounts {
|
pub struct ConstraintsCounts {
|
||||||
pub num_constraints: usize,
|
pub num_constraints: usize,
|
||||||
pub num_jacobian_lines: usize,
|
pub num_jacobian_lines: usize,
|
||||||
@@ -441,6 +442,17 @@ impl ContactConstraintsSet {
|
|||||||
assert_eq!(curr_start, total_num_constraints);
|
assert_eq!(curr_start, total_num_constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn warmstart(
|
||||||
|
&mut self,
|
||||||
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
|
generic_solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let (jac, constraints) = self.iter_constraints_mut();
|
||||||
|
for mut c in constraints {
|
||||||
|
c.warmstart(jac, solver_vels, generic_solver_vels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve_restitution(
|
pub fn solve_restitution(
|
||||||
&mut self,
|
&mut self,
|
||||||
solver_vels: &mut [SolverVel<Real>],
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
|
|||||||
@@ -153,8 +153,9 @@ impl GenericOneBodyConstraintBuilder {
|
|||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
impulse: na::zero(),
|
impulse: na::zero(),
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
r,
|
r,
|
||||||
|
r_mat_elts: [0.0; 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,13 +231,8 @@ impl GenericOneBodyConstraintBuilder {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.local_to_world;
|
.local_to_world;
|
||||||
|
|
||||||
self.inner.update_with_positions(
|
self.inner
|
||||||
params,
|
.update_with_positions(params, solved_dt, pos2, &mut constraint.inner);
|
||||||
solved_dt,
|
|
||||||
pos2,
|
|
||||||
self.ccd_thickness,
|
|
||||||
&mut constraint.inner,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +254,24 @@ impl GenericOneBodyConstraint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn warmstart(
|
||||||
|
&mut self,
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
generic_solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let solver_vel2 = self.inner.solver_vel2;
|
||||||
|
|
||||||
|
let elements = &mut self.inner.elements[..self.inner.num_contacts as usize];
|
||||||
|
OneBodyConstraintElement::generic_warmstart_group(
|
||||||
|
elements,
|
||||||
|
jacobians,
|
||||||
|
self.ndofs2,
|
||||||
|
self.j_id,
|
||||||
|
solver_vel2,
|
||||||
|
generic_solver_vels,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
jacobians: &DVector<Real>,
|
jacobians: &DVector<Real>,
|
||||||
|
|||||||
@@ -7,6 +7,40 @@ use na::DVector;
|
|||||||
use na::SimdPartialOrd;
|
use na::SimdPartialOrd;
|
||||||
|
|
||||||
impl OneBodyConstraintTangentPart<Real> {
|
impl OneBodyConstraintTangentPart<Real> {
|
||||||
|
#[inline]
|
||||||
|
pub fn generic_warmstart(
|
||||||
|
&mut self,
|
||||||
|
j_id2: usize,
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
ndofs2: usize,
|
||||||
|
solver_vel2: usize,
|
||||||
|
solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
{
|
||||||
|
solver_vels.rows_mut(solver_vel2, ndofs2).axpy(
|
||||||
|
self.impulse[0],
|
||||||
|
&jacobians.rows(j_id2 + ndofs2, ndofs2),
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
{
|
||||||
|
let j_step = ndofs2 * 2;
|
||||||
|
solver_vels.rows_mut(solver_vel2, ndofs2).axpy(
|
||||||
|
self.impulse[0],
|
||||||
|
&jacobians.rows(j_id2 + ndofs2, ndofs2),
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
solver_vels.rows_mut(solver_vel2, ndofs2).axpy(
|
||||||
|
self.impulse[1],
|
||||||
|
&jacobians.rows(j_id2 + j_step + ndofs2, ndofs2),
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generic_solve(
|
pub fn generic_solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -71,6 +105,22 @@ impl OneBodyConstraintTangentPart<Real> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OneBodyConstraintNormalPart<Real> {
|
impl OneBodyConstraintNormalPart<Real> {
|
||||||
|
#[inline]
|
||||||
|
pub fn generic_warmstart(
|
||||||
|
&mut self,
|
||||||
|
j_id2: usize,
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
ndofs2: usize,
|
||||||
|
solver_vel2: usize,
|
||||||
|
solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
solver_vels.rows_mut(solver_vel2, ndofs2).axpy(
|
||||||
|
self.impulse,
|
||||||
|
&jacobians.rows(j_id2 + ndofs2, ndofs2),
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generic_solve(
|
pub fn generic_solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -99,6 +149,42 @@ impl OneBodyConstraintNormalPart<Real> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OneBodyConstraintElement<Real> {
|
impl OneBodyConstraintElement<Real> {
|
||||||
|
#[inline]
|
||||||
|
pub fn generic_warmstart_group(
|
||||||
|
elements: &mut [Self],
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
ndofs2: usize,
|
||||||
|
// Jacobian index of the first constraint.
|
||||||
|
j_id: usize,
|
||||||
|
solver_vel2: usize,
|
||||||
|
solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let j_step = ndofs2 * 2 * DIM;
|
||||||
|
|
||||||
|
// Solve penetration.
|
||||||
|
let mut nrm_j_id = j_id;
|
||||||
|
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
element.normal_part.generic_warmstart(
|
||||||
|
nrm_j_id,
|
||||||
|
jacobians,
|
||||||
|
ndofs2,
|
||||||
|
solver_vel2,
|
||||||
|
solver_vels,
|
||||||
|
);
|
||||||
|
nrm_j_id += j_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve friction.
|
||||||
|
let mut tng_j_id = j_id + ndofs2 * 2;
|
||||||
|
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
let part = &mut element.tangent_part;
|
||||||
|
part.generic_warmstart(tng_j_id, jacobians, ndofs2, solver_vel2, solver_vels);
|
||||||
|
tng_j_id += j_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generic_solve_group(
|
pub fn generic_solve_group(
|
||||||
cfm_factor: Real,
|
cfm_factor: Real,
|
||||||
|
|||||||
@@ -201,15 +201,17 @@ impl GenericTwoBodyConstraintBuilder {
|
|||||||
gcross2,
|
gcross2,
|
||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
impulse: na::zero(),
|
impulse: manifold_point.warmstart_impulse,
|
||||||
r,
|
r,
|
||||||
|
r_mat_elts: [0.0; 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tangent parts.
|
// Tangent parts.
|
||||||
{
|
{
|
||||||
constraint.inner.elements[k].tangent_part.impulse = na::zero();
|
constraint.inner.elements[k].tangent_part.impulse =
|
||||||
|
manifold_point.warmstart_tangent_impulse;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let torque_dir1 = dp1.gcross(tangents1[j]);
|
let torque_dir1 = dp1.gcross(tangents1[j]);
|
||||||
@@ -340,14 +342,8 @@ impl GenericTwoBodyConstraintBuilder {
|
|||||||
.map(|m| &multibodies[m.multibody].link(m.id).unwrap().local_to_world)
|
.map(|m| &multibodies[m.multibody].link(m.id).unwrap().local_to_world)
|
||||||
.unwrap_or_else(|| &bodies[constraint.inner.solver_vel2].position);
|
.unwrap_or_else(|| &bodies[constraint.inner.solver_vel2].position);
|
||||||
|
|
||||||
self.inner.update_with_positions(
|
self.inner
|
||||||
params,
|
.update_with_positions(params, solved_dt, pos1, pos2, &mut constraint.inner);
|
||||||
solved_dt,
|
|
||||||
pos1,
|
|
||||||
pos2,
|
|
||||||
self.ccd_thickness,
|
|
||||||
&mut constraint.inner,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,6 +369,50 @@ impl GenericTwoBodyConstraint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn warmstart(
|
||||||
|
&mut self,
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
|
generic_solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let mut solver_vel1 = if self.generic_constraint_mask & 0b01 == 0 {
|
||||||
|
GenericRhs::SolverVel(solver_vels[self.inner.solver_vel1])
|
||||||
|
} else {
|
||||||
|
GenericRhs::GenericId(self.inner.solver_vel1)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut solver_vel2 = if self.generic_constraint_mask & 0b10 == 0 {
|
||||||
|
GenericRhs::SolverVel(solver_vels[self.inner.solver_vel2])
|
||||||
|
} else {
|
||||||
|
GenericRhs::GenericId(self.inner.solver_vel2)
|
||||||
|
};
|
||||||
|
|
||||||
|
let elements = &mut self.inner.elements[..self.inner.num_contacts as usize];
|
||||||
|
TwoBodyConstraintElement::generic_warmstart_group(
|
||||||
|
elements,
|
||||||
|
jacobians,
|
||||||
|
&self.inner.dir1,
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
&self.inner.tangent1,
|
||||||
|
&self.inner.im1,
|
||||||
|
&self.inner.im2,
|
||||||
|
self.ndofs1,
|
||||||
|
self.ndofs2,
|
||||||
|
self.j_id,
|
||||||
|
&mut solver_vel1,
|
||||||
|
&mut solver_vel2,
|
||||||
|
generic_solver_vels,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let GenericRhs::SolverVel(solver_vel1) = solver_vel1 {
|
||||||
|
solver_vels[self.inner.solver_vel1] = solver_vel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let GenericRhs::SolverVel(solver_vel2) = solver_vel2 {
|
||||||
|
solver_vels[self.inner.solver_vel2] = solver_vel2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
jacobians: &DVector<Real>,
|
jacobians: &DVector<Real>,
|
||||||
|
|||||||
@@ -88,6 +88,95 @@ impl GenericRhs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TwoBodyConstraintTangentPart<Real> {
|
impl TwoBodyConstraintTangentPart<Real> {
|
||||||
|
#[inline]
|
||||||
|
pub fn generic_warmstart(
|
||||||
|
&mut self,
|
||||||
|
j_id: usize,
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
tangents1: [&Vector<Real>; DIM - 1],
|
||||||
|
im1: &Vector<Real>,
|
||||||
|
im2: &Vector<Real>,
|
||||||
|
ndofs1: usize,
|
||||||
|
ndofs2: usize,
|
||||||
|
solver_vel1: &mut GenericRhs,
|
||||||
|
solver_vel2: &mut GenericRhs,
|
||||||
|
solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let j_id1 = j_id1(j_id, ndofs1, ndofs2);
|
||||||
|
let j_id2 = j_id2(j_id, ndofs1, ndofs2);
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let j_step = j_step(ndofs1, ndofs2);
|
||||||
|
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
{
|
||||||
|
solver_vel1.apply_impulse(
|
||||||
|
j_id1,
|
||||||
|
ndofs1,
|
||||||
|
self.impulse[0],
|
||||||
|
jacobians,
|
||||||
|
tangents1[0],
|
||||||
|
&self.gcross1[0],
|
||||||
|
solver_vels,
|
||||||
|
im1,
|
||||||
|
);
|
||||||
|
solver_vel2.apply_impulse(
|
||||||
|
j_id2,
|
||||||
|
ndofs2,
|
||||||
|
self.impulse[0],
|
||||||
|
jacobians,
|
||||||
|
&-tangents1[0],
|
||||||
|
&self.gcross2[0],
|
||||||
|
solver_vels,
|
||||||
|
im2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
{
|
||||||
|
solver_vel1.apply_impulse(
|
||||||
|
j_id1,
|
||||||
|
ndofs1,
|
||||||
|
self.impulse[0],
|
||||||
|
jacobians,
|
||||||
|
tangents1[0],
|
||||||
|
&self.gcross1[0],
|
||||||
|
solver_vels,
|
||||||
|
im1,
|
||||||
|
);
|
||||||
|
solver_vel1.apply_impulse(
|
||||||
|
j_id1 + j_step,
|
||||||
|
ndofs1,
|
||||||
|
self.impulse[1],
|
||||||
|
jacobians,
|
||||||
|
tangents1[1],
|
||||||
|
&self.gcross1[1],
|
||||||
|
solver_vels,
|
||||||
|
im1,
|
||||||
|
);
|
||||||
|
|
||||||
|
solver_vel2.apply_impulse(
|
||||||
|
j_id2,
|
||||||
|
ndofs2,
|
||||||
|
self.impulse[0],
|
||||||
|
jacobians,
|
||||||
|
&-tangents1[0],
|
||||||
|
&self.gcross2[0],
|
||||||
|
solver_vels,
|
||||||
|
im2,
|
||||||
|
);
|
||||||
|
solver_vel2.apply_impulse(
|
||||||
|
j_id2 + j_step,
|
||||||
|
ndofs2,
|
||||||
|
self.impulse[1],
|
||||||
|
jacobians,
|
||||||
|
&-tangents1[1],
|
||||||
|
&self.gcross2[1],
|
||||||
|
solver_vels,
|
||||||
|
im2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generic_solve(
|
pub fn generic_solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -240,6 +329,45 @@ impl TwoBodyConstraintTangentPart<Real> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TwoBodyConstraintNormalPart<Real> {
|
impl TwoBodyConstraintNormalPart<Real> {
|
||||||
|
#[inline]
|
||||||
|
pub fn generic_warmstart(
|
||||||
|
&mut self,
|
||||||
|
j_id: usize,
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
dir1: &Vector<Real>,
|
||||||
|
im1: &Vector<Real>,
|
||||||
|
im2: &Vector<Real>,
|
||||||
|
ndofs1: usize,
|
||||||
|
ndofs2: usize,
|
||||||
|
solver_vel1: &mut GenericRhs,
|
||||||
|
solver_vel2: &mut GenericRhs,
|
||||||
|
solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let j_id1 = j_id1(j_id, ndofs1, ndofs2);
|
||||||
|
let j_id2 = j_id2(j_id, ndofs1, ndofs2);
|
||||||
|
|
||||||
|
solver_vel1.apply_impulse(
|
||||||
|
j_id1,
|
||||||
|
ndofs1,
|
||||||
|
self.impulse,
|
||||||
|
jacobians,
|
||||||
|
dir1,
|
||||||
|
&self.gcross1,
|
||||||
|
solver_vels,
|
||||||
|
im1,
|
||||||
|
);
|
||||||
|
solver_vel2.apply_impulse(
|
||||||
|
j_id2,
|
||||||
|
ndofs2,
|
||||||
|
self.impulse,
|
||||||
|
jacobians,
|
||||||
|
&-dir1,
|
||||||
|
&self.gcross2,
|
||||||
|
solver_vels,
|
||||||
|
im2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generic_solve(
|
pub fn generic_solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -290,6 +418,74 @@ impl TwoBodyConstraintNormalPart<Real> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TwoBodyConstraintElement<Real> {
|
impl TwoBodyConstraintElement<Real> {
|
||||||
|
#[inline]
|
||||||
|
pub fn generic_warmstart_group(
|
||||||
|
elements: &mut [Self],
|
||||||
|
jacobians: &DVector<Real>,
|
||||||
|
dir1: &Vector<Real>,
|
||||||
|
#[cfg(feature = "dim3")] tangent1: &Vector<Real>,
|
||||||
|
im1: &Vector<Real>,
|
||||||
|
im2: &Vector<Real>,
|
||||||
|
// ndofs is 0 for a non-multibody body, or a multibody with zero
|
||||||
|
// degrees of freedom.
|
||||||
|
ndofs1: usize,
|
||||||
|
ndofs2: usize,
|
||||||
|
// Jacobian index of the first constraint.
|
||||||
|
j_id: usize,
|
||||||
|
solver_vel1: &mut GenericRhs,
|
||||||
|
solver_vel2: &mut GenericRhs,
|
||||||
|
solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
let j_step = j_step(ndofs1, ndofs2) * DIM;
|
||||||
|
|
||||||
|
// Solve penetration.
|
||||||
|
{
|
||||||
|
let mut nrm_j_id = normal_j_id(j_id, ndofs1, ndofs2);
|
||||||
|
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
element.normal_part.generic_warmstart(
|
||||||
|
nrm_j_id,
|
||||||
|
jacobians,
|
||||||
|
dir1,
|
||||||
|
im1,
|
||||||
|
im2,
|
||||||
|
ndofs1,
|
||||||
|
ndofs2,
|
||||||
|
solver_vel1,
|
||||||
|
solver_vel2,
|
||||||
|
solver_vels,
|
||||||
|
);
|
||||||
|
nrm_j_id += j_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve friction.
|
||||||
|
{
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let tangents1 = [tangent1, &dir1.cross(tangent1)];
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
let tangents1 = [&dir1.orthonormal_vector()];
|
||||||
|
let mut tng_j_id = tangent_j_id(j_id, ndofs1, ndofs2);
|
||||||
|
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
let part = &mut element.tangent_part;
|
||||||
|
part.generic_warmstart(
|
||||||
|
tng_j_id,
|
||||||
|
jacobians,
|
||||||
|
tangents1,
|
||||||
|
im1,
|
||||||
|
im2,
|
||||||
|
ndofs1,
|
||||||
|
ndofs2,
|
||||||
|
solver_vel1,
|
||||||
|
solver_vel2,
|
||||||
|
solver_vels,
|
||||||
|
);
|
||||||
|
tng_j_id += j_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn generic_solve_group(
|
pub fn generic_solve_group(
|
||||||
cfm_factor: Real,
|
cfm_factor: Real,
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ use crate::math::{Point, Real, Vector, DIM, MAX_MANIFOLD_POINTS};
|
|||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
use crate::utils::SimdBasis;
|
use crate::utils::SimdBasis;
|
||||||
use crate::utils::{self, SimdAngularInertia, SimdCross, SimdDot, SimdRealCopy};
|
use crate::utils::{self, SimdAngularInertia, SimdCross, SimdDot, SimdRealCopy};
|
||||||
|
use na::Matrix2;
|
||||||
use parry::math::Isometry;
|
use parry::math::Isometry;
|
||||||
|
|
||||||
|
use crate::dynamics::integration_parameters::BLOCK_SOLVER_ENABLED;
|
||||||
use crate::dynamics::solver::solver_body::SolverBody;
|
use crate::dynamics::solver::solver_body::SolverBody;
|
||||||
use crate::dynamics::solver::SolverVel;
|
use crate::dynamics::solver::SolverVel;
|
||||||
use crate::dynamics::{IntegrationParameters, MultibodyJointSet, RigidBodySet, RigidBodyVelocity};
|
use crate::dynamics::{IntegrationParameters, MultibodyJointSet, RigidBodySet, RigidBodyVelocity};
|
||||||
@@ -130,10 +132,11 @@ impl OneBodyConstraintBuilder {
|
|||||||
.effective_world_inv_inertia_sqrt
|
.effective_world_inv_inertia_sqrt
|
||||||
.transform_vector(dp2.gcross(-force_dir1));
|
.transform_vector(dp2.gcross(-force_dir1));
|
||||||
|
|
||||||
let projected_mass = utils::inv(
|
let projected_lin_mass =
|
||||||
force_dir1.dot(&mprops2.effective_inv_mass.component_mul(&force_dir1))
|
force_dir1.dot(&mprops2.effective_inv_mass.component_mul(&force_dir1));
|
||||||
+ gcross2.gdot(gcross2),
|
let projected_ang_mass = gcross2.gdot(gcross2);
|
||||||
);
|
|
||||||
|
let projected_mass = utils::inv(projected_lin_mass + projected_ang_mass);
|
||||||
|
|
||||||
let is_bouncy = manifold_point.is_bouncy() as u32 as Real;
|
let is_bouncy = manifold_point.is_bouncy() as u32 as Real;
|
||||||
|
|
||||||
@@ -148,15 +151,17 @@ impl OneBodyConstraintBuilder {
|
|||||||
gcross2,
|
gcross2,
|
||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
impulse: na::zero(),
|
impulse: manifold_point.warmstart_impulse,
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
r: projected_mass,
|
r: projected_mass,
|
||||||
|
r_mat_elts: [0.0; 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tangent parts.
|
// Tangent parts.
|
||||||
{
|
{
|
||||||
constraint.elements[k].tangent_part.impulse = na::zero();
|
constraint.elements[k].tangent_part.impulse =
|
||||||
|
manifold_point.warmstart_tangent_impulse;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let gcross2 = mprops2
|
let gcross2 = mprops2
|
||||||
@@ -205,6 +210,44 @@ impl OneBodyConstraintBuilder {
|
|||||||
builder.infos[k] = infos;
|
builder.infos[k] = infos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if BLOCK_SOLVER_ENABLED {
|
||||||
|
// Coupling between consecutive pairs.
|
||||||
|
for k in 0..manifold_points.len() / 2 {
|
||||||
|
let k0 = k * 2;
|
||||||
|
let k1 = k * 2 + 1;
|
||||||
|
|
||||||
|
let mut r_mat = Matrix2::zeros();
|
||||||
|
let r0 = constraint.elements[k0].normal_part.r;
|
||||||
|
let r1 = constraint.elements[k1].normal_part.r;
|
||||||
|
r_mat.m12 = force_dir1
|
||||||
|
.dot(&mprops2.effective_inv_mass.component_mul(&force_dir1))
|
||||||
|
+ constraint.elements[k0]
|
||||||
|
.normal_part
|
||||||
|
.gcross2
|
||||||
|
.gdot(constraint.elements[k1].normal_part.gcross2);
|
||||||
|
r_mat.m21 = r_mat.m12;
|
||||||
|
r_mat.m11 = utils::inv(r0);
|
||||||
|
r_mat.m22 = utils::inv(r1);
|
||||||
|
|
||||||
|
if let Some(inv) = r_mat.try_inverse() {
|
||||||
|
constraint.elements[k0].normal_part.r_mat_elts = [inv.m11, inv.m22];
|
||||||
|
constraint.elements[k1].normal_part.r_mat_elts = [inv.m12, r_mat.m12];
|
||||||
|
} else {
|
||||||
|
// If inversion failed, the contacts are redundant.
|
||||||
|
// Ignore the one with the smallest depth (it is too late to
|
||||||
|
// have the constraint removed from the constraint set, so just
|
||||||
|
// set the mass (r) matrix elements to 0.
|
||||||
|
constraint.elements[k0].normal_part.r_mat_elts =
|
||||||
|
if manifold_points[k0].dist <= manifold_points[k1].dist {
|
||||||
|
[r0, 0.0]
|
||||||
|
} else {
|
||||||
|
[0.0, r1]
|
||||||
|
};
|
||||||
|
constraint.elements[k1].normal_part.r_mat_elts = [0.0; 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,13 +260,7 @@ impl OneBodyConstraintBuilder {
|
|||||||
constraint: &mut OneBodyConstraint,
|
constraint: &mut OneBodyConstraint,
|
||||||
) {
|
) {
|
||||||
let rb2 = &bodies[constraint.solver_vel2];
|
let rb2 = &bodies[constraint.solver_vel2];
|
||||||
self.update_with_positions(
|
self.update_with_positions(params, solved_dt, &rb2.position, constraint)
|
||||||
params,
|
|
||||||
solved_dt,
|
|
||||||
&rb2.position,
|
|
||||||
rb2.ccd_thickness,
|
|
||||||
constraint,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this code is SOOOO similar to TwoBodyConstraint::update.
|
// TODO: this code is SOOOO similar to TwoBodyConstraint::update.
|
||||||
@@ -233,12 +270,11 @@ impl OneBodyConstraintBuilder {
|
|||||||
params: &IntegrationParameters,
|
params: &IntegrationParameters,
|
||||||
solved_dt: Real,
|
solved_dt: Real,
|
||||||
rb2_pos: &Isometry<Real>,
|
rb2_pos: &Isometry<Real>,
|
||||||
ccd_thickness: Real,
|
|
||||||
constraint: &mut OneBodyConstraint,
|
constraint: &mut OneBodyConstraint,
|
||||||
) {
|
) {
|
||||||
let cfm_factor = params.cfm_factor();
|
let cfm_factor = params.contact_cfm_factor();
|
||||||
let inv_dt = params.inv_dt();
|
let inv_dt = params.inv_dt();
|
||||||
let erp_inv_dt = params.erp_inv_dt();
|
let erp_inv_dt = params.contact_erp_inv_dt();
|
||||||
|
|
||||||
let all_infos = &self.infos[..constraint.num_contacts as usize];
|
let all_infos = &self.infos[..constraint.num_contacts as usize];
|
||||||
let all_elements = &mut constraint.elements[..constraint.num_contacts as usize];
|
let all_elements = &mut constraint.elements[..constraint.num_contacts as usize];
|
||||||
@@ -247,7 +283,6 @@ impl OneBodyConstraintBuilder {
|
|||||||
let new_pos1 = self
|
let new_pos1 = self
|
||||||
.vels1
|
.vels1
|
||||||
.integrate(solved_dt, &rb1.position, &rb1.local_com);
|
.integrate(solved_dt, &rb1.position, &rb1.local_com);
|
||||||
let mut is_fast_contact = false;
|
|
||||||
|
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
let tangents1 = constraint.dir1.orthonormal_basis();
|
let tangents1 = constraint.dir1.orthonormal_basis();
|
||||||
@@ -266,23 +301,20 @@ impl OneBodyConstraintBuilder {
|
|||||||
// Normal part.
|
// Normal part.
|
||||||
{
|
{
|
||||||
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
||||||
let rhs_bias = erp_inv_dt
|
let rhs_bias = (erp_inv_dt * (dist + params.allowed_linear_error()))
|
||||||
* (dist + params.allowed_linear_error)
|
.clamp(-params.max_corrective_velocity(), 0.0);
|
||||||
.clamp(-params.max_penetration_correction, 0.0);
|
|
||||||
let new_rhs = rhs_wo_bias + rhs_bias;
|
let new_rhs = rhs_wo_bias + rhs_bias;
|
||||||
let total_impulse = element.normal_part.total_impulse + element.normal_part.impulse;
|
|
||||||
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);
|
|
||||||
|
|
||||||
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
||||||
element.normal_part.rhs = new_rhs;
|
element.normal_part.rhs = new_rhs;
|
||||||
element.normal_part.total_impulse = total_impulse;
|
element.normal_part.impulse_accumulator += element.normal_part.impulse;
|
||||||
element.normal_part.impulse = na::zero();
|
element.normal_part.impulse *= params.warmstart_coefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tangent part.
|
// Tangent part.
|
||||||
{
|
{
|
||||||
element.tangent_part.total_impulse += element.tangent_part.impulse;
|
element.tangent_part.impulse_accumulator += element.tangent_part.impulse;
|
||||||
element.tangent_part.impulse = na::zero();
|
element.tangent_part.impulse *= params.warmstart_coefficient;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
||||||
@@ -291,7 +323,7 @@ impl OneBodyConstraintBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constraint.cfm_factor = if is_fast_contact { 1.0 } else { cfm_factor };
|
constraint.cfm_factor = cfm_factor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,6 +360,21 @@ impl OneBodyConstraint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn warmstart(&mut self, solver_vels: &mut [SolverVel<Real>]) {
|
||||||
|
let mut solver_vel2 = solver_vels[self.solver_vel2];
|
||||||
|
|
||||||
|
OneBodyConstraintElement::warmstart_group(
|
||||||
|
&mut self.elements[..self.num_contacts as usize],
|
||||||
|
&self.dir1,
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
&self.tangent1,
|
||||||
|
&self.im2,
|
||||||
|
&mut solver_vel2,
|
||||||
|
);
|
||||||
|
|
||||||
|
solver_vels[self.solver_vel2] = solver_vel2;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
solver_vels: &mut [SolverVel<Real>],
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
@@ -359,16 +406,11 @@ impl OneBodyConstraint {
|
|||||||
for k in 0..self.num_contacts as usize {
|
for k in 0..self.num_contacts as usize {
|
||||||
let contact_id = self.manifold_contact_id[k];
|
let contact_id = self.manifold_contact_id[k];
|
||||||
let active_contact = &mut manifold.points[contact_id as usize];
|
let active_contact = &mut manifold.points[contact_id as usize];
|
||||||
active_contact.data.impulse = self.elements[k].normal_part.impulse;
|
|
||||||
|
|
||||||
#[cfg(feature = "dim2")]
|
active_contact.data.warmstart_impulse = self.elements[k].normal_part.impulse;
|
||||||
{
|
active_contact.data.warmstart_tangent_impulse = self.elements[k].tangent_part.impulse;
|
||||||
active_contact.data.tangent_impulse = self.elements[k].tangent_part.impulse[0];
|
active_contact.data.impulse = self.elements[k].normal_part.total_impulse();
|
||||||
}
|
active_contact.data.tangent_impulse = self.elements[k].tangent_part.total_impulse();
|
||||||
#[cfg(feature = "dim3")]
|
|
||||||
{
|
|
||||||
active_contact.data.tangent_impulse = self.elements[k].tangent_part.impulse;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
|
use crate::dynamics::integration_parameters::BLOCK_SOLVER_ENABLED;
|
||||||
|
use crate::dynamics::solver::contact_constraint::TwoBodyConstraintNormalPart;
|
||||||
use crate::dynamics::solver::SolverVel;
|
use crate::dynamics::solver::SolverVel;
|
||||||
use crate::math::{AngVector, Vector, DIM};
|
use crate::math::{AngVector, TangentImpulse, Vector, DIM};
|
||||||
use crate::utils::{SimdBasis, SimdDot, SimdRealCopy};
|
use crate::utils::{SimdBasis, SimdDot, SimdRealCopy};
|
||||||
|
use na::Vector2;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub(crate) struct OneBodyConstraintTangentPart<N: SimdRealCopy> {
|
pub(crate) struct OneBodyConstraintTangentPart<N: SimdRealCopy> {
|
||||||
pub gcross2: [AngVector<N>; DIM - 1],
|
pub gcross2: [AngVector<N>; DIM - 1],
|
||||||
pub rhs: [N; DIM - 1],
|
pub rhs: [N; DIM - 1],
|
||||||
pub rhs_wo_bias: [N; DIM - 1],
|
pub rhs_wo_bias: [N; DIM - 1],
|
||||||
#[cfg(feature = "dim2")]
|
pub impulse: TangentImpulse<N>,
|
||||||
pub impulse: na::Vector1<N>,
|
pub impulse_accumulator: TangentImpulse<N>,
|
||||||
#[cfg(feature = "dim3")]
|
|
||||||
pub impulse: na::Vector2<N>,
|
|
||||||
#[cfg(feature = "dim2")]
|
|
||||||
pub total_impulse: na::Vector1<N>,
|
|
||||||
#[cfg(feature = "dim3")]
|
|
||||||
pub total_impulse: na::Vector2<N>,
|
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
pub r: [N; 1],
|
pub r: [N; 1],
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
@@ -28,7 +25,7 @@ impl<N: SimdRealCopy> OneBodyConstraintTangentPart<N> {
|
|||||||
rhs: [na::zero(); DIM - 1],
|
rhs: [na::zero(); DIM - 1],
|
||||||
rhs_wo_bias: [na::zero(); DIM - 1],
|
rhs_wo_bias: [na::zero(); DIM - 1],
|
||||||
impulse: na::zero(),
|
impulse: na::zero(),
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
r: [na::zero(); 1],
|
r: [na::zero(); 1],
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
@@ -36,41 +33,31 @@ impl<N: SimdRealCopy> OneBodyConstraintTangentPart<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Total impulse applied across all the solver substeps.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn apply_limit(
|
pub fn total_impulse(&self) -> TangentImpulse<N> {
|
||||||
|
self.impulse_accumulator + self.impulse
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn warmstart(
|
||||||
&mut self,
|
&mut self,
|
||||||
tangents1: [&Vector<N>; DIM - 1],
|
tangents1: [&Vector<N>; DIM - 1],
|
||||||
im2: &Vector<N>,
|
im2: &Vector<N>,
|
||||||
limit: N,
|
|
||||||
solver_vel2: &mut SolverVel<N>,
|
solver_vel2: &mut SolverVel<N>,
|
||||||
) where
|
) {
|
||||||
AngVector<N>: SimdDot<AngVector<N>, Result = N>,
|
|
||||||
{
|
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
{
|
{
|
||||||
let new_impulse = self.impulse[0].simd_clamp(-limit, limit);
|
solver_vel2.linear += tangents1[0].component_mul(im2) * -self.impulse[0];
|
||||||
let dlambda = new_impulse - self.impulse[0];
|
solver_vel2.angular += self.gcross2[0] * self.impulse[0];
|
||||||
self.impulse[0] = new_impulse;
|
|
||||||
|
|
||||||
solver_vel2.linear += tangents1[0].component_mul(im2) * -dlambda;
|
|
||||||
solver_vel2.angular += self.gcross2[0] * dlambda;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
{
|
{
|
||||||
let new_impulse = self.impulse;
|
solver_vel2.linear += tangents1[0].component_mul(im2) * -self.impulse[0]
|
||||||
let new_impulse = {
|
+ tangents1[1].component_mul(im2) * -self.impulse[1];
|
||||||
let _disable_fe_except =
|
solver_vel2.angular +=
|
||||||
crate::utils::DisableFloatingPointExceptionsFlags::
|
self.gcross2[0] * self.impulse[0] + self.gcross2[1] * self.impulse[1];
|
||||||
disable_floating_point_exceptions();
|
|
||||||
new_impulse.simd_cap_magnitude(limit)
|
|
||||||
};
|
|
||||||
let dlambda = new_impulse - self.impulse;
|
|
||||||
self.impulse = new_impulse;
|
|
||||||
|
|
||||||
solver_vel2.linear += tangents1[0].component_mul(im2) * -dlambda[0]
|
|
||||||
+ tangents1[1].component_mul(im2) * -dlambda[1];
|
|
||||||
solver_vel2.angular += self.gcross2[0] * dlambda[0] + self.gcross2[1] * dlambda[1];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +124,9 @@ pub(crate) struct OneBodyConstraintNormalPart<N: SimdRealCopy> {
|
|||||||
pub rhs: N,
|
pub rhs: N,
|
||||||
pub rhs_wo_bias: N,
|
pub rhs_wo_bias: N,
|
||||||
pub impulse: N,
|
pub impulse: N,
|
||||||
pub total_impulse: N,
|
pub impulse_accumulator: N,
|
||||||
pub r: N,
|
pub r: N,
|
||||||
|
pub r_mat_elts: [N; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: SimdRealCopy> OneBodyConstraintNormalPart<N> {
|
impl<N: SimdRealCopy> OneBodyConstraintNormalPart<N> {
|
||||||
@@ -148,11 +136,24 @@ impl<N: SimdRealCopy> OneBodyConstraintNormalPart<N> {
|
|||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
impulse: na::zero(),
|
impulse: na::zero(),
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
r: na::zero(),
|
r: na::zero(),
|
||||||
|
r_mat_elts: [N::zero(); 2],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Total impulse applied across all the solver substeps.
|
||||||
|
#[inline]
|
||||||
|
pub fn total_impulse(&self) -> N {
|
||||||
|
self.impulse_accumulator + self.impulse
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn warmstart(&mut self, dir1: &Vector<N>, im2: &Vector<N>, solver_vel2: &mut SolverVel<N>) {
|
||||||
|
solver_vel2.linear += dir1.component_mul(im2) * -self.impulse;
|
||||||
|
solver_vel2.angular += self.gcross2 * self.impulse;
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -172,6 +173,44 @@ impl<N: SimdRealCopy> OneBodyConstraintNormalPart<N> {
|
|||||||
solver_vel2.linear += dir1.component_mul(im2) * -dlambda;
|
solver_vel2.linear += dir1.component_mul(im2) * -dlambda;
|
||||||
solver_vel2.angular += self.gcross2 * dlambda;
|
solver_vel2.angular += self.gcross2 * dlambda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn solve_pair(
|
||||||
|
constraint_a: &mut Self,
|
||||||
|
constraint_b: &mut Self,
|
||||||
|
cfm_factor: N,
|
||||||
|
dir1: &Vector<N>,
|
||||||
|
im2: &Vector<N>,
|
||||||
|
solver_vel2: &mut SolverVel<N>,
|
||||||
|
) where
|
||||||
|
AngVector<N>: SimdDot<AngVector<N>, Result = N>,
|
||||||
|
{
|
||||||
|
let dvel_a = -dir1.dot(&solver_vel2.linear)
|
||||||
|
+ constraint_a.gcross2.gdot(solver_vel2.angular)
|
||||||
|
+ constraint_a.rhs;
|
||||||
|
let dvel_b = -dir1.dot(&solver_vel2.linear)
|
||||||
|
+ constraint_b.gcross2.gdot(solver_vel2.angular)
|
||||||
|
+ constraint_b.rhs;
|
||||||
|
|
||||||
|
let prev_impulse = Vector2::new(constraint_a.impulse, constraint_b.impulse);
|
||||||
|
let new_impulse = TwoBodyConstraintNormalPart::solve_mlcp_two_constraints(
|
||||||
|
Vector2::new(dvel_a, dvel_b),
|
||||||
|
prev_impulse,
|
||||||
|
constraint_a.r,
|
||||||
|
constraint_b.r,
|
||||||
|
constraint_a.r_mat_elts,
|
||||||
|
constraint_b.r_mat_elts,
|
||||||
|
cfm_factor,
|
||||||
|
);
|
||||||
|
|
||||||
|
let dlambda = new_impulse - prev_impulse;
|
||||||
|
|
||||||
|
constraint_a.impulse = new_impulse.x;
|
||||||
|
constraint_b.impulse = new_impulse.y;
|
||||||
|
|
||||||
|
solver_vel2.linear += dir1.component_mul(im2) * (-dlambda.x - dlambda.y);
|
||||||
|
solver_vel2.angular += constraint_a.gcross2 * dlambda.x + constraint_b.gcross2 * dlambda.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@@ -188,6 +227,25 @@ impl<N: SimdRealCopy> OneBodyConstraintElement<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn warmstart_group(
|
||||||
|
elements: &mut [Self],
|
||||||
|
dir1: &Vector<N>,
|
||||||
|
#[cfg(feature = "dim3")] tangent1: &Vector<N>,
|
||||||
|
im2: &Vector<N>,
|
||||||
|
solver_vel2: &mut SolverVel<N>,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let tangents1 = [tangent1, &dir1.cross(tangent1)];
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
let tangents1 = [&dir1.orthonormal_vector()];
|
||||||
|
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
element.normal_part.warmstart(dir1, im2, solver_vel2);
|
||||||
|
element.tangent_part.warmstart(tangents1, im2, solver_vel2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn solve_group(
|
pub fn solve_group(
|
||||||
cfm_factor: N,
|
cfm_factor: N,
|
||||||
@@ -210,13 +268,34 @@ impl<N: SimdRealCopy> OneBodyConstraintElement<N> {
|
|||||||
|
|
||||||
// Solve penetration.
|
// Solve penetration.
|
||||||
if solve_normal {
|
if solve_normal {
|
||||||
for element in elements.iter_mut() {
|
if BLOCK_SOLVER_ENABLED {
|
||||||
element
|
for elements in elements.chunks_exact_mut(2) {
|
||||||
.normal_part
|
let [element_a, element_b] = elements else {
|
||||||
.solve(cfm_factor, dir1, im2, solver_vel2);
|
unreachable!()
|
||||||
let limit = limit * element.normal_part.impulse;
|
};
|
||||||
let part = &mut element.tangent_part;
|
|
||||||
part.apply_limit(tangents1, im2, limit, solver_vel2);
|
OneBodyConstraintNormalPart::solve_pair(
|
||||||
|
&mut element_a.normal_part,
|
||||||
|
&mut element_b.normal_part,
|
||||||
|
cfm_factor,
|
||||||
|
dir1,
|
||||||
|
im2,
|
||||||
|
solver_vel2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if elements.len() % 2 == 1 {
|
||||||
|
let element = elements.last_mut().unwrap();
|
||||||
|
element
|
||||||
|
.normal_part
|
||||||
|
.solve(cfm_factor, dir1, im2, solver_vel2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
element
|
||||||
|
.normal_part
|
||||||
|
.solve(cfm_factor, dir1, im2, solver_vel2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::{OneBodyConstraintElement, OneBodyConstraintNormalPart};
|
use super::{OneBodyConstraintElement, OneBodyConstraintNormalPart};
|
||||||
|
use crate::dynamics::integration_parameters::BLOCK_SOLVER_ENABLED;
|
||||||
use crate::dynamics::solver::solver_body::SolverBody;
|
use crate::dynamics::solver::solver_body::SolverBody;
|
||||||
use crate::dynamics::solver::{ContactPointInfos, SolverVel};
|
use crate::dynamics::solver::{ContactPointInfos, SolverVel};
|
||||||
use crate::dynamics::{
|
use crate::dynamics::{
|
||||||
@@ -7,14 +8,14 @@ use crate::dynamics::{
|
|||||||
};
|
};
|
||||||
use crate::geometry::{ContactManifold, ContactManifoldIndex};
|
use crate::geometry::{ContactManifold, ContactManifoldIndex};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
AngVector, AngularInertia, Isometry, Point, Real, SimdReal, Vector, DIM, MAX_MANIFOLD_POINTS,
|
AngVector, AngularInertia, Isometry, Point, Real, SimdReal, TangentImpulse, Vector, DIM,
|
||||||
SIMD_WIDTH,
|
MAX_MANIFOLD_POINTS, SIMD_WIDTH,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
use crate::utils::SimdBasis;
|
use crate::utils::SimdBasis;
|
||||||
use crate::utils::{self, SimdAngularInertia, SimdCross, SimdDot};
|
use crate::utils::{self, SimdAngularInertia, SimdCross, SimdDot};
|
||||||
use num::Zero;
|
use num::Zero;
|
||||||
use parry::math::SimdBool;
|
use parry::utils::SdpMatrix2;
|
||||||
use simba::simd::{SimdPartialOrd, SimdValue};
|
use simba::simd::{SimdPartialOrd, SimdValue};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@@ -118,6 +119,11 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
let is_bouncy = SimdReal::from(gather![
|
let is_bouncy = SimdReal::from(gather![
|
||||||
|ii| manifold_points[ii][k].is_bouncy() as u32 as Real
|
|ii| manifold_points[ii][k].is_bouncy() as u32 as Real
|
||||||
]);
|
]);
|
||||||
|
let warmstart_impulse =
|
||||||
|
SimdReal::from(gather![|ii| manifold_points[ii][k].warmstart_impulse]);
|
||||||
|
let warmstart_tangent_impulse = TangentImpulse::from(gather![|ii| manifold_points
|
||||||
|
[ii][k]
|
||||||
|
.warmstart_tangent_impulse]);
|
||||||
|
|
||||||
let dist = SimdReal::from(gather![|ii| manifold_points[ii][k].dist]);
|
let dist = SimdReal::from(gather![|ii| manifold_points[ii][k].dist]);
|
||||||
let point = Point::from(gather![|ii| manifold_points[ii][k].point]);
|
let point = Point::from(gather![|ii| manifold_points[ii][k].point]);
|
||||||
@@ -153,14 +159,15 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
gcross2,
|
gcross2,
|
||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
impulse: na::zero(),
|
impulse: warmstart_impulse,
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
r: projected_mass,
|
r: projected_mass,
|
||||||
|
r_mat_elts: [SimdReal::zero(); 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// tangent parts.
|
// tangent parts.
|
||||||
constraint.elements[k].tangent_part.impulse = na::zero();
|
constraint.elements[k].tangent_part.impulse = warmstart_tangent_impulse;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let gcross2 = ii2.transform_vector(dp2.gcross(-tangents1[j]));
|
let gcross2 = ii2.transform_vector(dp2.gcross(-tangents1[j]));
|
||||||
@@ -200,6 +207,47 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
builder.infos[k] = infos;
|
builder.infos[k] = infos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if BLOCK_SOLVER_ENABLED {
|
||||||
|
// Coupling between consecutive pairs.
|
||||||
|
for k in 0..num_points / 2 {
|
||||||
|
let k0 = k * 2;
|
||||||
|
let k1 = k * 2 + 1;
|
||||||
|
|
||||||
|
let r0 = constraint.elements[k0].normal_part.r;
|
||||||
|
let r1 = constraint.elements[k1].normal_part.r;
|
||||||
|
|
||||||
|
let mut r_mat = SdpMatrix2::zero();
|
||||||
|
r_mat.m12 = force_dir1.dot(&im2.component_mul(&force_dir1))
|
||||||
|
+ constraint.elements[k0]
|
||||||
|
.normal_part
|
||||||
|
.gcross2
|
||||||
|
.gdot(constraint.elements[k1].normal_part.gcross2);
|
||||||
|
r_mat.m11 = utils::simd_inv(r0);
|
||||||
|
r_mat.m22 = utils::simd_inv(r1);
|
||||||
|
|
||||||
|
let (inv, det) = {
|
||||||
|
let _disable_fe_except =
|
||||||
|
crate::utils::DisableFloatingPointExceptionsFlags::
|
||||||
|
disable_floating_point_exceptions();
|
||||||
|
r_mat.inverse_and_get_determinant_unchecked()
|
||||||
|
};
|
||||||
|
let is_invertible = det.simd_gt(SimdReal::zero());
|
||||||
|
|
||||||
|
// If inversion failed, the contacts are redundant.
|
||||||
|
// Ignore the one with the smallest depth (it is too late to
|
||||||
|
// have the constraint removed from the constraint set, so just
|
||||||
|
// set the mass (r) matrix elements to 0.
|
||||||
|
constraint.elements[k0].normal_part.r_mat_elts = [
|
||||||
|
inv.m11.select(is_invertible, r0),
|
||||||
|
inv.m22.select(is_invertible, SimdReal::zero()),
|
||||||
|
];
|
||||||
|
constraint.elements[k1].normal_part.r_mat_elts = [
|
||||||
|
inv.m12.select(is_invertible, SimdReal::zero()),
|
||||||
|
r_mat.m12.select(is_invertible, SimdReal::zero()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,15 +261,14 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
_multibodies: &MultibodyJointSet,
|
_multibodies: &MultibodyJointSet,
|
||||||
constraint: &mut OneBodyConstraintSimd,
|
constraint: &mut OneBodyConstraintSimd,
|
||||||
) {
|
) {
|
||||||
let cfm_factor = SimdReal::splat(params.cfm_factor());
|
let cfm_factor = SimdReal::splat(params.contact_cfm_factor());
|
||||||
let dt = SimdReal::splat(params.dt);
|
|
||||||
let inv_dt = SimdReal::splat(params.inv_dt());
|
let inv_dt = SimdReal::splat(params.inv_dt());
|
||||||
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error);
|
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error());
|
||||||
let erp_inv_dt = SimdReal::splat(params.erp_inv_dt());
|
let erp_inv_dt = SimdReal::splat(params.contact_erp_inv_dt());
|
||||||
let max_penetration_correction = SimdReal::splat(params.max_penetration_correction);
|
let max_corrective_velocity = SimdReal::splat(params.max_corrective_velocity());
|
||||||
|
let warmstart_coeff = SimdReal::splat(params.warmstart_coefficient);
|
||||||
|
|
||||||
let rb2 = gather![|ii| &bodies[constraint.solver_vel2[ii]]];
|
let rb2 = gather![|ii| &bodies[constraint.solver_vel2[ii]]];
|
||||||
let ccd_thickness = SimdReal::from(gather![|ii| rb2[ii].ccd_thickness]);
|
|
||||||
let poss2 = Isometry::from(gather![|ii| rb2[ii].position]);
|
let poss2 = Isometry::from(gather![|ii| rb2[ii].position]);
|
||||||
|
|
||||||
let all_infos = &self.infos[..constraint.num_contacts as usize];
|
let all_infos = &self.infos[..constraint.num_contacts as usize];
|
||||||
@@ -242,7 +289,6 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
constraint.dir1.cross(&constraint.tangent1),
|
constraint.dir1.cross(&constraint.tangent1),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut is_fast_contact = SimdBool::splat(false);
|
|
||||||
let solved_dt = SimdReal::splat(solved_dt);
|
let solved_dt = SimdReal::splat(solved_dt);
|
||||||
|
|
||||||
for (info, element) in all_infos.iter().zip(all_elements.iter_mut()) {
|
for (info, element) in all_infos.iter().zip(all_elements.iter_mut()) {
|
||||||
@@ -255,24 +301,20 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
{
|
{
|
||||||
let rhs_wo_bias =
|
let rhs_wo_bias =
|
||||||
info.normal_rhs_wo_bias + dist.simd_max(SimdReal::zero()) * inv_dt;
|
info.normal_rhs_wo_bias + dist.simd_max(SimdReal::zero()) * inv_dt;
|
||||||
let rhs_bias = (dist + allowed_lin_err)
|
let rhs_bias = ((dist + allowed_lin_err) * erp_inv_dt)
|
||||||
.simd_clamp(-max_penetration_correction, SimdReal::zero())
|
.simd_clamp(-max_corrective_velocity, SimdReal::zero());
|
||||||
* erp_inv_dt;
|
|
||||||
let new_rhs = rhs_wo_bias + rhs_bias;
|
let new_rhs = rhs_wo_bias + rhs_bias;
|
||||||
let total_impulse = element.normal_part.total_impulse + element.normal_part.impulse;
|
|
||||||
is_fast_contact =
|
|
||||||
is_fast_contact | (-new_rhs * dt).simd_gt(ccd_thickness * SimdReal::splat(0.5));
|
|
||||||
|
|
||||||
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
||||||
element.normal_part.rhs = new_rhs;
|
element.normal_part.rhs = new_rhs;
|
||||||
element.normal_part.total_impulse = total_impulse;
|
element.normal_part.impulse_accumulator += element.normal_part.impulse;
|
||||||
element.normal_part.impulse = na::zero();
|
element.normal_part.impulse *= warmstart_coeff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tangent parts.
|
// tangent parts.
|
||||||
{
|
{
|
||||||
element.tangent_part.total_impulse += element.tangent_part.impulse;
|
element.tangent_part.impulse_accumulator += element.tangent_part.impulse;
|
||||||
element.tangent_part.impulse = na::zero();
|
element.tangent_part.impulse *= warmstart_coeff;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
||||||
@@ -281,7 +323,7 @@ impl SimdOneBodyConstraintBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constraint.cfm_factor = SimdReal::splat(1.0).select(is_fast_contact, cfm_factor);
|
constraint.cfm_factor = cfm_factor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +343,27 @@ pub(crate) struct OneBodyConstraintSimd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OneBodyConstraintSimd {
|
impl OneBodyConstraintSimd {
|
||||||
|
pub fn warmstart(&mut self, solver_vels: &mut [SolverVel<Real>]) {
|
||||||
|
let mut solver_vel2 = SolverVel {
|
||||||
|
linear: Vector::from(gather![|ii| solver_vels[self.solver_vel2[ii]].linear]),
|
||||||
|
angular: AngVector::from(gather![|ii| solver_vels[self.solver_vel2[ii]].angular]),
|
||||||
|
};
|
||||||
|
|
||||||
|
OneBodyConstraintElement::warmstart_group(
|
||||||
|
&mut self.elements[..self.num_contacts as usize],
|
||||||
|
&self.dir1,
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
&self.tangent1,
|
||||||
|
&self.im2,
|
||||||
|
&mut solver_vel2,
|
||||||
|
);
|
||||||
|
|
||||||
|
for ii in 0..SIMD_WIDTH {
|
||||||
|
solver_vels[self.solver_vel2[ii]].linear = solver_vel2.linear.extract(ii);
|
||||||
|
solver_vels[self.solver_vel2[ii]].angular = solver_vel2.angular.extract(ii);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
solver_vels: &mut [SolverVel<Real>],
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
@@ -334,26 +397,21 @@ impl OneBodyConstraintSimd {
|
|||||||
// FIXME: duplicated code. This is exactly the same as in the two-body velocity constraint.
|
// FIXME: duplicated code. This is exactly the same as in the two-body velocity constraint.
|
||||||
pub fn writeback_impulses(&self, manifolds_all: &mut [&mut ContactManifold]) {
|
pub fn writeback_impulses(&self, manifolds_all: &mut [&mut ContactManifold]) {
|
||||||
for k in 0..self.num_contacts as usize {
|
for k in 0..self.num_contacts as usize {
|
||||||
let impulses: [_; SIMD_WIDTH] = self.elements[k].normal_part.impulse.into();
|
let warmstart_impulses: [_; SIMD_WIDTH] = self.elements[k].normal_part.impulse.into();
|
||||||
#[cfg(feature = "dim2")]
|
let warmstart_tangent_impulses = self.elements[k].tangent_part.impulse;
|
||||||
let tangent_impulses: [_; SIMD_WIDTH] = self.elements[k].tangent_part.impulse[0].into();
|
let impulses: [_; SIMD_WIDTH] = self.elements[k].normal_part.total_impulse().into();
|
||||||
#[cfg(feature = "dim3")]
|
let tangent_impulses = self.elements[k].tangent_part.total_impulse();
|
||||||
let tangent_impulses = self.elements[k].tangent_part.impulse;
|
|
||||||
|
|
||||||
for ii in 0..SIMD_WIDTH {
|
for ii in 0..SIMD_WIDTH {
|
||||||
let manifold = &mut manifolds_all[self.manifold_id[ii]];
|
let manifold = &mut manifolds_all[self.manifold_id[ii]];
|
||||||
let contact_id = self.manifold_contact_id[k][ii];
|
let contact_id = self.manifold_contact_id[k][ii];
|
||||||
let active_contact = &mut manifold.points[contact_id as usize];
|
let active_contact = &mut manifold.points[contact_id as usize];
|
||||||
active_contact.data.impulse = impulses[ii];
|
|
||||||
|
|
||||||
#[cfg(feature = "dim2")]
|
active_contact.data.warmstart_impulse = warmstart_impulses[ii];
|
||||||
{
|
active_contact.data.warmstart_tangent_impulse =
|
||||||
active_contact.data.tangent_impulse = tangent_impulses[ii];
|
warmstart_tangent_impulses.extract(ii);
|
||||||
}
|
active_contact.data.impulse = impulses[ii];
|
||||||
#[cfg(feature = "dim3")]
|
active_contact.data.tangent_impulse = tangent_impulses.extract(ii);
|
||||||
{
|
|
||||||
active_contact.data.tangent_impulse = tangent_impulses.extract(ii);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ use super::{ContactConstraintTypes, ContactPointInfos};
|
|||||||
use crate::dynamics::solver::SolverVel;
|
use crate::dynamics::solver::SolverVel;
|
||||||
use crate::dynamics::solver::{AnyConstraintMut, SolverBody};
|
use crate::dynamics::solver::{AnyConstraintMut, SolverBody};
|
||||||
|
|
||||||
|
use crate::dynamics::integration_parameters::BLOCK_SOLVER_ENABLED;
|
||||||
use crate::dynamics::{IntegrationParameters, MultibodyJointSet, RigidBodySet};
|
use crate::dynamics::{IntegrationParameters, MultibodyJointSet, RigidBodySet};
|
||||||
use crate::geometry::{ContactManifold, ContactManifoldIndex};
|
use crate::geometry::{ContactManifold, ContactManifoldIndex};
|
||||||
use crate::math::{Isometry, Real, Vector, DIM, MAX_MANIFOLD_POINTS};
|
use crate::math::{Isometry, Real, Vector, DIM, MAX_MANIFOLD_POINTS};
|
||||||
use crate::utils::{self, SimdAngularInertia, SimdBasis, SimdCross, SimdDot};
|
use crate::utils::{self, SimdAngularInertia, SimdBasis, SimdCross, SimdDot};
|
||||||
use na::DVector;
|
use na::{DVector, Matrix2};
|
||||||
|
|
||||||
use super::{TwoBodyConstraintElement, TwoBodyConstraintNormalPart};
|
use super::{TwoBodyConstraintElement, TwoBodyConstraintNormalPart};
|
||||||
|
|
||||||
@@ -23,6 +24,25 @@ impl<'a> AnyConstraintMut<'a, ContactConstraintTypes> {
|
|||||||
Self::SimdTwoBodies(c) => c.remove_cfm_and_bias_from_rhs(),
|
Self::SimdTwoBodies(c) => c.remove_cfm_and_bias_from_rhs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn warmstart(
|
||||||
|
&mut self,
|
||||||
|
generic_jacobians: &DVector<Real>,
|
||||||
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
|
generic_solver_vels: &mut DVector<Real>,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Self::OneBody(c) => c.warmstart(solver_vels),
|
||||||
|
Self::TwoBodies(c) => c.warmstart(solver_vels),
|
||||||
|
Self::GenericOneBody(c) => c.warmstart(generic_jacobians, generic_solver_vels),
|
||||||
|
Self::GenericTwoBodies(c) => {
|
||||||
|
c.warmstart(generic_jacobians, solver_vels, generic_solver_vels)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "simd-is-enabled")]
|
||||||
|
Self::SimdOneBody(c) => c.warmstart(solver_vels),
|
||||||
|
#[cfg(feature = "simd-is-enabled")]
|
||||||
|
Self::SimdTwoBodies(c) => c.warmstart(solver_vels),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve_restitution(
|
pub fn solve_restitution(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -222,15 +242,17 @@ impl TwoBodyConstraintBuilder {
|
|||||||
gcross2,
|
gcross2,
|
||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
impulse: na::zero(),
|
impulse: manifold_point.warmstart_impulse,
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
r: projected_mass,
|
r: projected_mass,
|
||||||
|
r_mat_elts: [0.0; 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tangent parts.
|
// Tangent parts.
|
||||||
{
|
{
|
||||||
constraint.elements[k].tangent_part.impulse = na::zero();
|
constraint.elements[k].tangent_part.impulse =
|
||||||
|
manifold_point.warmstart_tangent_impulse;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let gcross1 = mprops1
|
let gcross1 = mprops1
|
||||||
@@ -284,6 +306,48 @@ impl TwoBodyConstraintBuilder {
|
|||||||
builder.infos[k] = infos;
|
builder.infos[k] = infos;
|
||||||
constraint.manifold_contact_id[k] = manifold_point.contact_id;
|
constraint.manifold_contact_id[k] = manifold_point.contact_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if BLOCK_SOLVER_ENABLED {
|
||||||
|
// Coupling between consecutive pairs.
|
||||||
|
for k in 0..manifold_points.len() / 2 {
|
||||||
|
let k0 = k * 2;
|
||||||
|
let k1 = k * 2 + 1;
|
||||||
|
|
||||||
|
let mut r_mat = Matrix2::zeros();
|
||||||
|
let imsum = mprops1.effective_inv_mass + mprops2.effective_inv_mass;
|
||||||
|
let r0 = constraint.elements[k0].normal_part.r;
|
||||||
|
let r1 = constraint.elements[k1].normal_part.r;
|
||||||
|
r_mat.m12 = force_dir1.dot(&imsum.component_mul(&force_dir1))
|
||||||
|
+ constraint.elements[k0]
|
||||||
|
.normal_part
|
||||||
|
.gcross1
|
||||||
|
.gdot(constraint.elements[k1].normal_part.gcross1)
|
||||||
|
+ constraint.elements[k0]
|
||||||
|
.normal_part
|
||||||
|
.gcross2
|
||||||
|
.gdot(constraint.elements[k1].normal_part.gcross2);
|
||||||
|
r_mat.m21 = r_mat.m12;
|
||||||
|
r_mat.m11 = utils::inv(r0);
|
||||||
|
r_mat.m22 = utils::inv(r1);
|
||||||
|
|
||||||
|
if let Some(inv) = r_mat.try_inverse() {
|
||||||
|
constraint.elements[k0].normal_part.r_mat_elts = [inv.m11, inv.m22];
|
||||||
|
constraint.elements[k1].normal_part.r_mat_elts = [inv.m12, r_mat.m12];
|
||||||
|
} else {
|
||||||
|
// If inversion failed, the contacts are redundant.
|
||||||
|
// Ignore the one with the smallest depth (it is too late to
|
||||||
|
// have the constraint removed from the constraint set, so just
|
||||||
|
// set the mass (r) matrix elements to 0.
|
||||||
|
constraint.elements[k0].normal_part.r_mat_elts =
|
||||||
|
if manifold_points[k0].dist <= manifold_points[k1].dist {
|
||||||
|
[r0, 0.0]
|
||||||
|
} else {
|
||||||
|
[0.0, r1]
|
||||||
|
};
|
||||||
|
constraint.elements[k1].normal_part.r_mat_elts = [0.0; 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,15 +361,7 @@ impl TwoBodyConstraintBuilder {
|
|||||||
) {
|
) {
|
||||||
let rb1 = &bodies[constraint.solver_vel1];
|
let rb1 = &bodies[constraint.solver_vel1];
|
||||||
let rb2 = &bodies[constraint.solver_vel2];
|
let rb2 = &bodies[constraint.solver_vel2];
|
||||||
let ccd_thickness = rb1.ccd_thickness + rb2.ccd_thickness;
|
self.update_with_positions(params, solved_dt, &rb1.position, &rb2.position, constraint)
|
||||||
self.update_with_positions(
|
|
||||||
params,
|
|
||||||
solved_dt,
|
|
||||||
&rb1.position,
|
|
||||||
&rb2.position,
|
|
||||||
ccd_thickness,
|
|
||||||
constraint,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by both generic and non-generic builders..
|
// Used by both generic and non-generic builders..
|
||||||
@@ -315,18 +371,15 @@ impl TwoBodyConstraintBuilder {
|
|||||||
solved_dt: Real,
|
solved_dt: Real,
|
||||||
rb1_pos: &Isometry<Real>,
|
rb1_pos: &Isometry<Real>,
|
||||||
rb2_pos: &Isometry<Real>,
|
rb2_pos: &Isometry<Real>,
|
||||||
ccd_thickness: Real,
|
|
||||||
constraint: &mut TwoBodyConstraint,
|
constraint: &mut TwoBodyConstraint,
|
||||||
) {
|
) {
|
||||||
let cfm_factor = params.cfm_factor();
|
let cfm_factor = params.contact_cfm_factor();
|
||||||
let inv_dt = params.inv_dt();
|
let inv_dt = params.inv_dt();
|
||||||
let erp_inv_dt = params.erp_inv_dt();
|
let erp_inv_dt = params.contact_erp_inv_dt();
|
||||||
|
|
||||||
let all_infos = &self.infos[..constraint.num_contacts as usize];
|
let all_infos = &self.infos[..constraint.num_contacts as usize];
|
||||||
let all_elements = &mut constraint.elements[..constraint.num_contacts as usize];
|
let all_elements = &mut constraint.elements[..constraint.num_contacts as usize];
|
||||||
|
|
||||||
let mut is_fast_contact = false;
|
|
||||||
|
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
let tangents1 = constraint.dir1.orthonormal_basis();
|
let tangents1 = constraint.dir1.orthonormal_basis();
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
@@ -344,23 +397,20 @@ impl TwoBodyConstraintBuilder {
|
|||||||
// Normal part.
|
// Normal part.
|
||||||
{
|
{
|
||||||
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
let rhs_wo_bias = info.normal_rhs_wo_bias + dist.max(0.0) * inv_dt;
|
||||||
let rhs_bias = erp_inv_dt
|
let rhs_bias = (erp_inv_dt * (dist + params.allowed_linear_error()))
|
||||||
* (dist + params.allowed_linear_error)
|
.clamp(-params.max_corrective_velocity(), 0.0);
|
||||||
.clamp(-params.max_penetration_correction, 0.0);
|
|
||||||
let new_rhs = rhs_wo_bias + rhs_bias;
|
let new_rhs = rhs_wo_bias + rhs_bias;
|
||||||
let total_impulse = element.normal_part.total_impulse + element.normal_part.impulse;
|
|
||||||
is_fast_contact = is_fast_contact || (-new_rhs * params.dt > ccd_thickness * 0.5);
|
|
||||||
|
|
||||||
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
||||||
element.normal_part.rhs = new_rhs;
|
element.normal_part.rhs = new_rhs;
|
||||||
element.normal_part.total_impulse = total_impulse;
|
element.normal_part.impulse_accumulator += element.normal_part.impulse;
|
||||||
element.normal_part.impulse = na::zero();
|
element.normal_part.impulse *= params.warmstart_coefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tangent part.
|
// Tangent part.
|
||||||
{
|
{
|
||||||
element.tangent_part.total_impulse += element.tangent_part.impulse;
|
element.tangent_part.impulse_accumulator += element.tangent_part.impulse;
|
||||||
element.tangent_part.impulse = na::zero();
|
element.tangent_part.impulse *= params.warmstart_coefficient;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
||||||
@@ -369,11 +419,30 @@ impl TwoBodyConstraintBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constraint.cfm_factor = if is_fast_contact { 1.0 } else { cfm_factor };
|
constraint.cfm_factor = cfm_factor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TwoBodyConstraint {
|
impl TwoBodyConstraint {
|
||||||
|
pub fn warmstart(&mut self, solver_vels: &mut [SolverVel<Real>]) {
|
||||||
|
let mut solver_vel1 = solver_vels[self.solver_vel1];
|
||||||
|
let mut solver_vel2 = solver_vels[self.solver_vel2];
|
||||||
|
|
||||||
|
TwoBodyConstraintElement::warmstart_group(
|
||||||
|
&mut self.elements[..self.num_contacts as usize],
|
||||||
|
&self.dir1,
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
&self.tangent1,
|
||||||
|
&self.im1,
|
||||||
|
&self.im2,
|
||||||
|
&mut solver_vel1,
|
||||||
|
&mut solver_vel2,
|
||||||
|
);
|
||||||
|
|
||||||
|
solver_vels[self.solver_vel1] = solver_vel1;
|
||||||
|
solver_vels[self.solver_vel2] = solver_vel2;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
solver_vels: &mut [SolverVel<Real>],
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
@@ -408,16 +477,10 @@ impl TwoBodyConstraint {
|
|||||||
for k in 0..self.num_contacts as usize {
|
for k in 0..self.num_contacts as usize {
|
||||||
let contact_id = self.manifold_contact_id[k];
|
let contact_id = self.manifold_contact_id[k];
|
||||||
let active_contact = &mut manifold.points[contact_id as usize];
|
let active_contact = &mut manifold.points[contact_id as usize];
|
||||||
active_contact.data.impulse = self.elements[k].normal_part.impulse;
|
active_contact.data.warmstart_impulse = self.elements[k].normal_part.impulse;
|
||||||
|
active_contact.data.warmstart_tangent_impulse = self.elements[k].tangent_part.impulse;
|
||||||
#[cfg(feature = "dim2")]
|
active_contact.data.impulse = self.elements[k].normal_part.total_impulse();
|
||||||
{
|
active_contact.data.tangent_impulse = self.elements[k].tangent_part.total_impulse();
|
||||||
active_contact.data.tangent_impulse = self.elements[k].tangent_part.impulse[0];
|
|
||||||
}
|
|
||||||
#[cfg(feature = "dim3")]
|
|
||||||
{
|
|
||||||
active_contact.data.tangent_impulse = self.elements[k].tangent_part.impulse;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
use crate::dynamics::integration_parameters::BLOCK_SOLVER_ENABLED;
|
||||||
use crate::dynamics::solver::SolverVel;
|
use crate::dynamics::solver::SolverVel;
|
||||||
use crate::math::{AngVector, Vector, DIM};
|
use crate::math::{AngVector, TangentImpulse, Vector, DIM};
|
||||||
use crate::utils::{SimdBasis, SimdDot, SimdRealCopy};
|
use crate::utils::{SimdBasis, SimdDot, SimdRealCopy};
|
||||||
|
use na::Vector2;
|
||||||
|
use simba::simd::SimdValue;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub(crate) struct TwoBodyConstraintTangentPart<N: SimdRealCopy> {
|
pub(crate) struct TwoBodyConstraintTangentPart<N: SimdRealCopy> {
|
||||||
@@ -13,9 +16,9 @@ pub(crate) struct TwoBodyConstraintTangentPart<N: SimdRealCopy> {
|
|||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
pub impulse: na::Vector2<N>,
|
pub impulse: na::Vector2<N>,
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
pub total_impulse: na::Vector1<N>,
|
pub impulse_accumulator: na::Vector1<N>,
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
pub total_impulse: na::Vector2<N>,
|
pub impulse_accumulator: na::Vector2<N>,
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
pub r: [N; 1],
|
pub r: [N; 1],
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
@@ -30,7 +33,7 @@ impl<N: SimdRealCopy> TwoBodyConstraintTangentPart<N> {
|
|||||||
rhs: [na::zero(); DIM - 1],
|
rhs: [na::zero(); DIM - 1],
|
||||||
rhs_wo_bias: [na::zero(); DIM - 1],
|
rhs_wo_bias: [na::zero(); DIM - 1],
|
||||||
impulse: na::zero(),
|
impulse: na::zero(),
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
r: [na::zero(); 1],
|
r: [na::zero(); 1],
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
@@ -38,13 +41,18 @@ impl<N: SimdRealCopy> TwoBodyConstraintTangentPart<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Total impulse applied across all the solver substeps.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn apply_limit(
|
pub fn total_impulse(&self) -> TangentImpulse<N> {
|
||||||
|
self.impulse_accumulator + self.impulse
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn warmstart(
|
||||||
&mut self,
|
&mut self,
|
||||||
tangents1: [&Vector<N>; DIM - 1],
|
tangents1: [&Vector<N>; DIM - 1],
|
||||||
im1: &Vector<N>,
|
im1: &Vector<N>,
|
||||||
im2: &Vector<N>,
|
im2: &Vector<N>,
|
||||||
limit: N,
|
|
||||||
solver_vel1: &mut SolverVel<N>,
|
solver_vel1: &mut SolverVel<N>,
|
||||||
solver_vel2: &mut SolverVel<N>,
|
solver_vel2: &mut SolverVel<N>,
|
||||||
) where
|
) where
|
||||||
@@ -52,37 +60,24 @@ impl<N: SimdRealCopy> TwoBodyConstraintTangentPart<N> {
|
|||||||
{
|
{
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
{
|
{
|
||||||
let new_impulse = self.impulse[0].simd_clamp(-limit, limit);
|
solver_vel1.linear += tangents1[0].component_mul(im1) * self.impulse[0];
|
||||||
let dlambda = new_impulse - self.impulse[0];
|
solver_vel1.angular += self.gcross1[0] * self.impulse[0];
|
||||||
self.impulse[0] = new_impulse;
|
|
||||||
|
|
||||||
solver_vel1.linear += tangents1[0].component_mul(im1) * dlambda;
|
solver_vel2.linear += tangents1[0].component_mul(im2) * -self.impulse[0];
|
||||||
solver_vel1.angular += self.gcross1[0] * dlambda;
|
solver_vel2.angular += self.gcross2[0] * self.impulse[0];
|
||||||
|
|
||||||
solver_vel2.linear += tangents1[0].component_mul(im2) * -dlambda;
|
|
||||||
solver_vel2.angular += self.gcross2[0] * dlambda;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
{
|
{
|
||||||
let new_impulse = self.impulse;
|
solver_vel1.linear += tangents1[0].component_mul(im1) * self.impulse[0]
|
||||||
let new_impulse = {
|
+ tangents1[1].component_mul(im1) * self.impulse[1];
|
||||||
let _disable_fe_except =
|
solver_vel1.angular +=
|
||||||
crate::utils::DisableFloatingPointExceptionsFlags::
|
self.gcross1[0] * self.impulse[0] + self.gcross1[1] * self.impulse[1];
|
||||||
disable_floating_point_exceptions();
|
|
||||||
new_impulse.simd_cap_magnitude(limit)
|
|
||||||
};
|
|
||||||
|
|
||||||
let dlambda = new_impulse - self.impulse;
|
solver_vel2.linear += tangents1[0].component_mul(im2) * -self.impulse[0]
|
||||||
self.impulse = new_impulse;
|
+ tangents1[1].component_mul(im2) * -self.impulse[1];
|
||||||
|
solver_vel2.angular +=
|
||||||
solver_vel1.linear += tangents1[0].component_mul(im1) * dlambda[0]
|
self.gcross2[0] * self.impulse[0] + self.gcross2[1] * self.impulse[1];
|
||||||
+ tangents1[1].component_mul(im1) * dlambda[1];
|
|
||||||
solver_vel1.angular += self.gcross1[0] * dlambda[0] + self.gcross1[1] * dlambda[1];
|
|
||||||
|
|
||||||
solver_vel2.linear += tangents1[0].component_mul(im2) * -dlambda[0]
|
|
||||||
+ tangents1[1].component_mul(im2) * -dlambda[1];
|
|
||||||
solver_vel2.angular += self.gcross2[0] * dlambda[0] + self.gcross2[1] * dlambda[1];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +161,13 @@ pub(crate) struct TwoBodyConstraintNormalPart<N: SimdRealCopy> {
|
|||||||
pub rhs: N,
|
pub rhs: N,
|
||||||
pub rhs_wo_bias: N,
|
pub rhs_wo_bias: N,
|
||||||
pub impulse: N,
|
pub impulse: N,
|
||||||
pub total_impulse: N,
|
pub impulse_accumulator: N,
|
||||||
pub r: N,
|
pub r: N,
|
||||||
|
// For coupled constraint pairs, even constraints store the
|
||||||
|
// diagonal of the projected mass matrix. Odd constraints
|
||||||
|
// store the off-diagonal element of the projected mass matrix,
|
||||||
|
// as well as the off-diagonal element of the inverse projected mass matrix.
|
||||||
|
pub r_mat_elts: [N; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: SimdRealCopy> TwoBodyConstraintNormalPart<N> {
|
impl<N: SimdRealCopy> TwoBodyConstraintNormalPart<N> {
|
||||||
@@ -178,11 +178,34 @@ impl<N: SimdRealCopy> TwoBodyConstraintNormalPart<N> {
|
|||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
impulse: na::zero(),
|
impulse: na::zero(),
|
||||||
total_impulse: na::zero(),
|
impulse_accumulator: na::zero(),
|
||||||
r: na::zero(),
|
r: na::zero(),
|
||||||
|
r_mat_elts: [N::zero(); 2],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Total impulse applied across all the solver substeps.
|
||||||
|
#[inline]
|
||||||
|
pub fn total_impulse(&self) -> N {
|
||||||
|
self.impulse_accumulator + self.impulse
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn warmstart(
|
||||||
|
&mut self,
|
||||||
|
dir1: &Vector<N>,
|
||||||
|
im1: &Vector<N>,
|
||||||
|
im2: &Vector<N>,
|
||||||
|
solver_vel1: &mut SolverVel<N>,
|
||||||
|
solver_vel2: &mut SolverVel<N>,
|
||||||
|
) {
|
||||||
|
solver_vel1.linear += dir1.component_mul(im1) * self.impulse;
|
||||||
|
solver_vel1.angular += self.gcross1 * self.impulse;
|
||||||
|
|
||||||
|
solver_vel2.linear += dir1.component_mul(im2) * -self.impulse;
|
||||||
|
solver_vel2.angular += self.gcross2 * self.impulse;
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -209,6 +232,83 @@ impl<N: SimdRealCopy> TwoBodyConstraintNormalPart<N> {
|
|||||||
solver_vel2.linear += dir1.component_mul(im2) * -dlambda;
|
solver_vel2.linear += dir1.component_mul(im2) * -dlambda;
|
||||||
solver_vel2.angular += self.gcross2 * dlambda;
|
solver_vel2.angular += self.gcross2 * dlambda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn solve_mlcp_two_constraints(
|
||||||
|
dvel: Vector2<N>,
|
||||||
|
prev_impulse: Vector2<N>,
|
||||||
|
r_a: N,
|
||||||
|
r_b: N,
|
||||||
|
[r_mat11, r_mat22]: [N; 2],
|
||||||
|
[r_mat12, r_mat_inv12]: [N; 2],
|
||||||
|
cfm_factor: N,
|
||||||
|
) -> Vector2<N> {
|
||||||
|
let r_dvel = Vector2::new(
|
||||||
|
r_mat11 * dvel.x + r_mat12 * dvel.y,
|
||||||
|
r_mat12 * dvel.x + r_mat22 * dvel.y,
|
||||||
|
);
|
||||||
|
let new_impulse0 = prev_impulse - r_dvel;
|
||||||
|
let new_impulse1 = Vector2::new(prev_impulse.x - r_a * dvel.x, N::zero());
|
||||||
|
let new_impulse2 = Vector2::new(N::zero(), prev_impulse.y - r_b * dvel.y);
|
||||||
|
let new_impulse3 = Vector2::new(N::zero(), N::zero());
|
||||||
|
|
||||||
|
let keep0 = new_impulse0.x.simd_ge(N::zero()) & new_impulse0.y.simd_ge(N::zero());
|
||||||
|
let keep1 = new_impulse1.x.simd_ge(N::zero())
|
||||||
|
& (dvel.y + r_mat_inv12 * new_impulse1.x).simd_ge(N::zero());
|
||||||
|
let keep2 = new_impulse2.y.simd_ge(N::zero())
|
||||||
|
& (dvel.x + r_mat_inv12 * new_impulse2.y).simd_ge(N::zero());
|
||||||
|
let keep3 = dvel.x.simd_ge(N::zero()) & dvel.y.simd_ge(N::zero());
|
||||||
|
|
||||||
|
let selected3 = (new_impulse3 * cfm_factor).select(keep3, prev_impulse);
|
||||||
|
let selected2 = (new_impulse2 * cfm_factor).select(keep2, selected3);
|
||||||
|
let selected1 = (new_impulse1 * cfm_factor).select(keep1, selected2);
|
||||||
|
(new_impulse0 * cfm_factor).select(keep0, selected1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn solve_pair(
|
||||||
|
constraint_a: &mut Self,
|
||||||
|
constraint_b: &mut Self,
|
||||||
|
cfm_factor: N,
|
||||||
|
dir1: &Vector<N>,
|
||||||
|
im1: &Vector<N>,
|
||||||
|
im2: &Vector<N>,
|
||||||
|
solver_vel1: &mut SolverVel<N>,
|
||||||
|
solver_vel2: &mut SolverVel<N>,
|
||||||
|
) where
|
||||||
|
AngVector<N>: SimdDot<AngVector<N>, Result = N>,
|
||||||
|
{
|
||||||
|
let dvel_lin = dir1.dot(&solver_vel1.linear) - dir1.dot(&solver_vel2.linear);
|
||||||
|
let dvel_a = dvel_lin
|
||||||
|
+ constraint_a.gcross1.gdot(solver_vel1.angular)
|
||||||
|
+ constraint_a.gcross2.gdot(solver_vel2.angular)
|
||||||
|
+ constraint_a.rhs;
|
||||||
|
let dvel_b = dvel_lin
|
||||||
|
+ constraint_b.gcross1.gdot(solver_vel1.angular)
|
||||||
|
+ constraint_b.gcross2.gdot(solver_vel2.angular)
|
||||||
|
+ constraint_b.rhs;
|
||||||
|
|
||||||
|
let prev_impulse = Vector2::new(constraint_a.impulse, constraint_b.impulse);
|
||||||
|
let new_impulse = Self::solve_mlcp_two_constraints(
|
||||||
|
Vector2::new(dvel_a, dvel_b),
|
||||||
|
prev_impulse,
|
||||||
|
constraint_a.r,
|
||||||
|
constraint_b.r,
|
||||||
|
constraint_a.r_mat_elts,
|
||||||
|
constraint_b.r_mat_elts,
|
||||||
|
cfm_factor,
|
||||||
|
);
|
||||||
|
|
||||||
|
let dlambda = new_impulse - prev_impulse;
|
||||||
|
|
||||||
|
constraint_a.impulse = new_impulse.x;
|
||||||
|
constraint_b.impulse = new_impulse.y;
|
||||||
|
|
||||||
|
solver_vel1.linear += dir1.component_mul(im1) * (dlambda.x + dlambda.y);
|
||||||
|
solver_vel1.angular += constraint_a.gcross1 * dlambda.x + constraint_b.gcross1 * dlambda.y;
|
||||||
|
solver_vel2.linear += dir1.component_mul(im2) * (-dlambda.x - dlambda.y);
|
||||||
|
solver_vel2.angular += constraint_a.gcross2 * dlambda.x + constraint_b.gcross2 * dlambda.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@@ -225,6 +325,31 @@ impl<N: SimdRealCopy> TwoBodyConstraintElement<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn warmstart_group(
|
||||||
|
elements: &mut [Self],
|
||||||
|
dir1: &Vector<N>,
|
||||||
|
#[cfg(feature = "dim3")] tangent1: &Vector<N>,
|
||||||
|
im1: &Vector<N>,
|
||||||
|
im2: &Vector<N>,
|
||||||
|
solver_vel1: &mut SolverVel<N>,
|
||||||
|
solver_vel2: &mut SolverVel<N>,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let tangents1 = [tangent1, &dir1.cross(tangent1)];
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
let tangents1 = [&dir1.orthonormal_vector()];
|
||||||
|
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
element
|
||||||
|
.normal_part
|
||||||
|
.warmstart(dir1, im1, im2, solver_vel1, solver_vel2);
|
||||||
|
element
|
||||||
|
.tangent_part
|
||||||
|
.warmstart(tangents1, im1, im2, solver_vel1, solver_vel2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn solve_group(
|
pub fn solve_group(
|
||||||
cfm_factor: N,
|
cfm_factor: N,
|
||||||
@@ -236,31 +361,53 @@ impl<N: SimdRealCopy> TwoBodyConstraintElement<N> {
|
|||||||
limit: N,
|
limit: N,
|
||||||
solver_vel1: &mut SolverVel<N>,
|
solver_vel1: &mut SolverVel<N>,
|
||||||
solver_vel2: &mut SolverVel<N>,
|
solver_vel2: &mut SolverVel<N>,
|
||||||
solve_normal: bool,
|
solve_restitution: bool,
|
||||||
solve_friction: bool,
|
solve_friction: bool,
|
||||||
) where
|
) where
|
||||||
Vector<N>: SimdBasis,
|
Vector<N>: SimdBasis,
|
||||||
AngVector<N>: SimdDot<AngVector<N>, Result = N>,
|
AngVector<N>: SimdDot<AngVector<N>, Result = N>,
|
||||||
{
|
{
|
||||||
#[cfg(feature = "dim3")]
|
if solve_restitution {
|
||||||
let tangents1 = [tangent1, &dir1.cross(tangent1)];
|
if BLOCK_SOLVER_ENABLED {
|
||||||
#[cfg(feature = "dim2")]
|
for elements in elements.chunks_exact_mut(2) {
|
||||||
let tangents1 = [&dir1.orthonormal_vector()];
|
let [element_a, element_b] = elements else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
// Solve penetration.
|
TwoBodyConstraintNormalPart::solve_pair(
|
||||||
if solve_normal {
|
&mut element_a.normal_part,
|
||||||
for element in elements.iter_mut() {
|
&mut element_b.normal_part,
|
||||||
element
|
cfm_factor,
|
||||||
.normal_part
|
dir1,
|
||||||
.solve(cfm_factor, dir1, im1, im2, solver_vel1, solver_vel2);
|
im1,
|
||||||
let limit = limit * element.normal_part.impulse;
|
im2,
|
||||||
let part = &mut element.tangent_part;
|
solver_vel1,
|
||||||
part.apply_limit(tangents1, im1, im2, limit, solver_vel1, solver_vel2);
|
solver_vel2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is one constraint left to solve if there isn’t an even number.
|
||||||
|
if elements.len() % 2 == 1 {
|
||||||
|
let element = elements.last_mut().unwrap();
|
||||||
|
element
|
||||||
|
.normal_part
|
||||||
|
.solve(cfm_factor, dir1, im1, im2, solver_vel1, solver_vel2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for element in elements.iter_mut() {
|
||||||
|
element
|
||||||
|
.normal_part
|
||||||
|
.solve(cfm_factor, dir1, im1, im2, solver_vel1, solver_vel2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve friction.
|
|
||||||
if solve_friction {
|
if solve_friction {
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
let tangents1 = [tangent1, &dir1.cross(tangent1)];
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
let tangents1 = [&dir1.orthonormal_vector()];
|
||||||
|
|
||||||
for element in elements.iter_mut() {
|
for element in elements.iter_mut() {
|
||||||
let limit = limit * element.normal_part.impulse;
|
let limit = limit * element.normal_part.impulse;
|
||||||
let part = &mut element.tangent_part;
|
let part = &mut element.tangent_part;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::{TwoBodyConstraintElement, TwoBodyConstraintNormalPart};
|
use super::{TwoBodyConstraintElement, TwoBodyConstraintNormalPart};
|
||||||
|
use crate::dynamics::integration_parameters::BLOCK_SOLVER_ENABLED;
|
||||||
use crate::dynamics::solver::solver_body::SolverBody;
|
use crate::dynamics::solver::solver_body::SolverBody;
|
||||||
use crate::dynamics::solver::{ContactPointInfos, SolverVel};
|
use crate::dynamics::solver::{ContactPointInfos, SolverVel};
|
||||||
use crate::dynamics::{
|
use crate::dynamics::{
|
||||||
@@ -7,14 +8,14 @@ use crate::dynamics::{
|
|||||||
};
|
};
|
||||||
use crate::geometry::{ContactManifold, ContactManifoldIndex};
|
use crate::geometry::{ContactManifold, ContactManifoldIndex};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
AngVector, AngularInertia, Isometry, Point, Real, SimdReal, Vector, DIM, MAX_MANIFOLD_POINTS,
|
AngVector, AngularInertia, Isometry, Point, Real, SimdReal, TangentImpulse, Vector, DIM,
|
||||||
SIMD_WIDTH,
|
MAX_MANIFOLD_POINTS, SIMD_WIDTH,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
use crate::utils::SimdBasis;
|
use crate::utils::SimdBasis;
|
||||||
use crate::utils::{self, SimdAngularInertia, SimdCross, SimdDot};
|
use crate::utils::{self, SimdAngularInertia, SimdCross, SimdDot};
|
||||||
use num::Zero;
|
use num::Zero;
|
||||||
use parry::math::SimdBool;
|
use parry::utils::SdpMatrix2;
|
||||||
use simba::simd::{SimdPartialOrd, SimdValue};
|
use simba::simd::{SimdPartialOrd, SimdValue};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@@ -101,6 +102,11 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
let is_bouncy = SimdReal::from(gather![
|
let is_bouncy = SimdReal::from(gather![
|
||||||
|ii| manifold_points[ii][k].is_bouncy() as u32 as Real
|
|ii| manifold_points[ii][k].is_bouncy() as u32 as Real
|
||||||
]);
|
]);
|
||||||
|
let warmstart_impulse =
|
||||||
|
SimdReal::from(gather![|ii| manifold_points[ii][k].warmstart_impulse]);
|
||||||
|
let warmstart_tangent_impulse = TangentImpulse::from(gather![|ii| manifold_points
|
||||||
|
[ii][k]
|
||||||
|
.warmstart_tangent_impulse]);
|
||||||
|
|
||||||
let dist = SimdReal::from(gather![|ii| manifold_points[ii][k].dist]);
|
let dist = SimdReal::from(gather![|ii| manifold_points[ii][k].dist]);
|
||||||
let point = Point::from(gather![|ii| manifold_points[ii][k].point]);
|
let point = Point::from(gather![|ii| manifold_points[ii][k].point]);
|
||||||
@@ -137,14 +143,15 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
gcross2,
|
gcross2,
|
||||||
rhs: na::zero(),
|
rhs: na::zero(),
|
||||||
rhs_wo_bias: na::zero(),
|
rhs_wo_bias: na::zero(),
|
||||||
impulse: SimdReal::splat(0.0),
|
impulse: warmstart_impulse,
|
||||||
total_impulse: SimdReal::splat(0.0),
|
impulse_accumulator: SimdReal::splat(0.0),
|
||||||
r: projected_mass,
|
r: projected_mass,
|
||||||
|
r_mat_elts: [SimdReal::zero(); 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// tangent parts.
|
// tangent parts.
|
||||||
constraint.elements[k].tangent_part.impulse = na::zero();
|
constraint.elements[k].tangent_part.impulse = warmstart_tangent_impulse;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let gcross1 = ii1.transform_vector(dp1.gcross(tangents1[j]));
|
let gcross1 = ii1.transform_vector(dp1.gcross(tangents1[j]));
|
||||||
@@ -186,6 +193,52 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
|
|
||||||
builder.infos[k] = infos;
|
builder.infos[k] = infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if BLOCK_SOLVER_ENABLED {
|
||||||
|
// Coupling between consecutive pairs.
|
||||||
|
for k in 0..num_points / 2 {
|
||||||
|
let k0 = k * 2;
|
||||||
|
let k1 = k * 2 + 1;
|
||||||
|
|
||||||
|
let imsum = im1 + im2;
|
||||||
|
let r0 = constraint.elements[k0].normal_part.r;
|
||||||
|
let r1 = constraint.elements[k1].normal_part.r;
|
||||||
|
|
||||||
|
let mut r_mat = SdpMatrix2::zero();
|
||||||
|
r_mat.m12 = force_dir1.dot(&imsum.component_mul(&force_dir1))
|
||||||
|
+ constraint.elements[k0]
|
||||||
|
.normal_part
|
||||||
|
.gcross1
|
||||||
|
.gdot(constraint.elements[k1].normal_part.gcross1)
|
||||||
|
+ constraint.elements[k0]
|
||||||
|
.normal_part
|
||||||
|
.gcross2
|
||||||
|
.gdot(constraint.elements[k1].normal_part.gcross2);
|
||||||
|
r_mat.m11 = utils::simd_inv(r0);
|
||||||
|
r_mat.m22 = utils::simd_inv(r1);
|
||||||
|
|
||||||
|
let (inv, det) = {
|
||||||
|
let _disable_fe_except =
|
||||||
|
crate::utils::DisableFloatingPointExceptionsFlags::
|
||||||
|
disable_floating_point_exceptions();
|
||||||
|
r_mat.inverse_and_get_determinant_unchecked()
|
||||||
|
};
|
||||||
|
let is_invertible = det.simd_gt(SimdReal::zero());
|
||||||
|
|
||||||
|
// If inversion failed, the contacts are redundant.
|
||||||
|
// Ignore the one with the smallest depth (it is too late to
|
||||||
|
// have the constraint removed from the constraint set, so just
|
||||||
|
// set the mass (r) matrix elements to 0.
|
||||||
|
constraint.elements[k0].normal_part.r_mat_elts = [
|
||||||
|
inv.m11.select(is_invertible, r0),
|
||||||
|
inv.m22.select(is_invertible, SimdReal::zero()),
|
||||||
|
];
|
||||||
|
constraint.elements[k1].normal_part.r_mat_elts = [
|
||||||
|
inv.m12.select(is_invertible, SimdReal::zero()),
|
||||||
|
r_mat.m12.select(is_invertible, SimdReal::zero()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,19 +250,16 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
_multibodies: &MultibodyJointSet,
|
_multibodies: &MultibodyJointSet,
|
||||||
constraint: &mut TwoBodyConstraintSimd,
|
constraint: &mut TwoBodyConstraintSimd,
|
||||||
) {
|
) {
|
||||||
let cfm_factor = SimdReal::splat(params.cfm_factor());
|
let cfm_factor = SimdReal::splat(params.contact_cfm_factor());
|
||||||
let dt = SimdReal::splat(params.dt);
|
|
||||||
let inv_dt = SimdReal::splat(params.inv_dt());
|
let inv_dt = SimdReal::splat(params.inv_dt());
|
||||||
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error);
|
let allowed_lin_err = SimdReal::splat(params.allowed_linear_error());
|
||||||
let erp_inv_dt = SimdReal::splat(params.erp_inv_dt());
|
let erp_inv_dt = SimdReal::splat(params.contact_erp_inv_dt());
|
||||||
let max_penetration_correction = SimdReal::splat(params.max_penetration_correction);
|
let max_corrective_velocity = SimdReal::splat(params.max_corrective_velocity());
|
||||||
|
let warmstart_coeff = SimdReal::splat(params.warmstart_coefficient);
|
||||||
|
|
||||||
let rb1 = gather![|ii| &bodies[constraint.solver_vel1[ii]]];
|
let rb1 = gather![|ii| &bodies[constraint.solver_vel1[ii]]];
|
||||||
let rb2 = gather![|ii| &bodies[constraint.solver_vel2[ii]]];
|
let rb2 = gather![|ii| &bodies[constraint.solver_vel2[ii]]];
|
||||||
|
|
||||||
let ccd_thickness = SimdReal::from(gather![|ii| rb1[ii].ccd_thickness])
|
|
||||||
+ SimdReal::from(gather![|ii| rb2[ii].ccd_thickness]);
|
|
||||||
|
|
||||||
let poss1 = Isometry::from(gather![|ii| rb1[ii].position]);
|
let poss1 = Isometry::from(gather![|ii| rb1[ii].position]);
|
||||||
let poss2 = Isometry::from(gather![|ii| rb2[ii].position]);
|
let poss2 = Isometry::from(gather![|ii| rb2[ii].position]);
|
||||||
|
|
||||||
@@ -224,7 +274,6 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
constraint.dir1.cross(&constraint.tangent1),
|
constraint.dir1.cross(&constraint.tangent1),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut is_fast_contact = SimdBool::splat(false);
|
|
||||||
let solved_dt = SimdReal::splat(solved_dt);
|
let solved_dt = SimdReal::splat(solved_dt);
|
||||||
|
|
||||||
for (info, element) in all_infos.iter().zip(all_elements.iter_mut()) {
|
for (info, element) in all_infos.iter().zip(all_elements.iter_mut()) {
|
||||||
@@ -237,24 +286,20 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
{
|
{
|
||||||
let rhs_wo_bias =
|
let rhs_wo_bias =
|
||||||
info.normal_rhs_wo_bias + dist.simd_max(SimdReal::zero()) * inv_dt;
|
info.normal_rhs_wo_bias + dist.simd_max(SimdReal::zero()) * inv_dt;
|
||||||
let rhs_bias = (dist + allowed_lin_err)
|
let rhs_bias = ((dist + allowed_lin_err) * erp_inv_dt)
|
||||||
.simd_clamp(-max_penetration_correction, SimdReal::zero())
|
.simd_clamp(-max_corrective_velocity, SimdReal::zero());
|
||||||
* erp_inv_dt;
|
|
||||||
let new_rhs = rhs_wo_bias + rhs_bias;
|
let new_rhs = rhs_wo_bias + rhs_bias;
|
||||||
let total_impulse = element.normal_part.total_impulse + element.normal_part.impulse;
|
|
||||||
is_fast_contact =
|
|
||||||
is_fast_contact | (-new_rhs * dt).simd_gt(ccd_thickness * SimdReal::splat(0.5));
|
|
||||||
|
|
||||||
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
element.normal_part.rhs_wo_bias = rhs_wo_bias;
|
||||||
element.normal_part.rhs = new_rhs;
|
element.normal_part.rhs = new_rhs;
|
||||||
element.normal_part.total_impulse = total_impulse;
|
element.normal_part.impulse_accumulator += element.normal_part.impulse;
|
||||||
element.normal_part.impulse = na::zero();
|
element.normal_part.impulse *= warmstart_coeff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tangent parts.
|
// tangent parts.
|
||||||
{
|
{
|
||||||
element.tangent_part.total_impulse += element.tangent_part.impulse;
|
element.tangent_part.impulse_accumulator += element.tangent_part.impulse;
|
||||||
element.tangent_part.impulse = na::zero();
|
element.tangent_part.impulse *= warmstart_coeff;
|
||||||
|
|
||||||
for j in 0..DIM - 1 {
|
for j in 0..DIM - 1 {
|
||||||
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
let bias = (p1 - p2).dot(&tangents1[j]) * inv_dt;
|
||||||
@@ -263,7 +308,7 @@ impl TwoBodyConstraintBuilderSimd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constraint.cfm_factor = SimdReal::splat(1.0).select(is_fast_contact, cfm_factor);
|
constraint.cfm_factor = cfm_factor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +330,38 @@ pub(crate) struct TwoBodyConstraintSimd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TwoBodyConstraintSimd {
|
impl TwoBodyConstraintSimd {
|
||||||
|
pub fn warmstart(&mut self, solver_vels: &mut [SolverVel<Real>]) {
|
||||||
|
let mut solver_vel1 = SolverVel {
|
||||||
|
linear: Vector::from(gather![|ii| solver_vels[self.solver_vel1[ii]].linear]),
|
||||||
|
angular: AngVector::from(gather![|ii| solver_vels[self.solver_vel1[ii]].angular]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut solver_vel2 = SolverVel {
|
||||||
|
linear: Vector::from(gather![|ii| solver_vels[self.solver_vel2[ii]].linear]),
|
||||||
|
angular: AngVector::from(gather![|ii| solver_vels[self.solver_vel2[ii]].angular]),
|
||||||
|
};
|
||||||
|
|
||||||
|
TwoBodyConstraintElement::warmstart_group(
|
||||||
|
&mut self.elements[..self.num_contacts as usize],
|
||||||
|
&self.dir1,
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
&self.tangent1,
|
||||||
|
&self.im1,
|
||||||
|
&self.im2,
|
||||||
|
&mut solver_vel1,
|
||||||
|
&mut solver_vel2,
|
||||||
|
);
|
||||||
|
|
||||||
|
for ii in 0..SIMD_WIDTH {
|
||||||
|
solver_vels[self.solver_vel1[ii]].linear = solver_vel1.linear.extract(ii);
|
||||||
|
solver_vels[self.solver_vel1[ii]].angular = solver_vel1.angular.extract(ii);
|
||||||
|
}
|
||||||
|
for ii in 0..SIMD_WIDTH {
|
||||||
|
solver_vels[self.solver_vel2[ii]].linear = solver_vel2.linear.extract(ii);
|
||||||
|
solver_vels[self.solver_vel2[ii]].angular = solver_vel2.angular.extract(ii);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn solve(
|
pub fn solve(
|
||||||
&mut self,
|
&mut self,
|
||||||
solver_vels: &mut [SolverVel<Real>],
|
solver_vels: &mut [SolverVel<Real>],
|
||||||
@@ -328,26 +405,20 @@ impl TwoBodyConstraintSimd {
|
|||||||
|
|
||||||
pub fn writeback_impulses(&self, manifolds_all: &mut [&mut ContactManifold]) {
|
pub fn writeback_impulses(&self, manifolds_all: &mut [&mut ContactManifold]) {
|
||||||
for k in 0..self.num_contacts as usize {
|
for k in 0..self.num_contacts as usize {
|
||||||
let impulses: [_; SIMD_WIDTH] = self.elements[k].normal_part.impulse.into();
|
let warmstart_impulses: [_; SIMD_WIDTH] = self.elements[k].normal_part.impulse.into();
|
||||||
#[cfg(feature = "dim2")]
|
let warmstart_tangent_impulses = self.elements[k].tangent_part.impulse;
|
||||||
let tangent_impulses: [_; SIMD_WIDTH] = self.elements[k].tangent_part.impulse[0].into();
|
let impulses: [_; SIMD_WIDTH] = self.elements[k].normal_part.total_impulse().into();
|
||||||
#[cfg(feature = "dim3")]
|
let tangent_impulses = self.elements[k].tangent_part.total_impulse();
|
||||||
let tangent_impulses = self.elements[k].tangent_part.impulse;
|
|
||||||
|
|
||||||
for ii in 0..SIMD_WIDTH {
|
for ii in 0..SIMD_WIDTH {
|
||||||
let manifold = &mut manifolds_all[self.manifold_id[ii]];
|
let manifold = &mut manifolds_all[self.manifold_id[ii]];
|
||||||
let contact_id = self.manifold_contact_id[k][ii];
|
let contact_id = self.manifold_contact_id[k][ii];
|
||||||
let active_contact = &mut manifold.points[contact_id as usize];
|
let active_contact = &mut manifold.points[contact_id as usize];
|
||||||
|
active_contact.data.warmstart_impulse = warmstart_impulses[ii];
|
||||||
|
active_contact.data.warmstart_tangent_impulse =
|
||||||
|
warmstart_tangent_impulses.extract(ii);
|
||||||
active_contact.data.impulse = impulses[ii];
|
active_contact.data.impulse = impulses[ii];
|
||||||
|
active_contact.data.tangent_impulse = tangent_impulses.extract(ii);
|
||||||
#[cfg(feature = "dim2")]
|
|
||||||
{
|
|
||||||
active_contact.data.tangent_impulse = tangent_impulses[ii];
|
|
||||||
}
|
|
||||||
#[cfg(feature = "dim3")]
|
|
||||||
{
|
|
||||||
active_contact.data.tangent_impulse = tangent_impulses.extract(ii);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,12 @@ impl IslandSolver {
|
|||||||
joint_indices: &[JointIndex],
|
joint_indices: &[JointIndex],
|
||||||
multibodies: &mut MultibodyJointSet,
|
multibodies: &mut MultibodyJointSet,
|
||||||
) {
|
) {
|
||||||
counters.solver.velocity_resolution_time.resume();
|
counters.solver.velocity_assembly_time.resume();
|
||||||
let num_solver_iterations = base_params.num_solver_iterations.get()
|
let num_solver_iterations = base_params.num_solver_iterations.get()
|
||||||
+ islands.active_island_additional_solver_iterations(island_id);
|
+ islands.active_island_additional_solver_iterations(island_id);
|
||||||
|
|
||||||
let mut params = *base_params;
|
let mut params = *base_params;
|
||||||
params.dt /= num_solver_iterations as Real;
|
params.dt /= num_solver_iterations as Real;
|
||||||
params.damping_ratio /= num_solver_iterations as Real;
|
|
||||||
// params.joint_damping_ratio /= num_solver_iterations as Real;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
@@ -76,8 +74,10 @@ impl IslandSolver {
|
|||||||
&mut self.contact_constraints,
|
&mut self.contact_constraints,
|
||||||
&mut self.joint_constraints,
|
&mut self.joint_constraints,
|
||||||
);
|
);
|
||||||
|
counters.solver.velocity_assembly_time.pause();
|
||||||
|
|
||||||
// SOLVE
|
// SOLVE
|
||||||
|
counters.solver.velocity_resolution_time.resume();
|
||||||
self.velocity_solver.solve_constraints(
|
self.velocity_solver.solve_constraints(
|
||||||
¶ms,
|
¶ms,
|
||||||
num_solver_iterations,
|
num_solver_iterations,
|
||||||
@@ -86,8 +86,10 @@ impl IslandSolver {
|
|||||||
&mut self.contact_constraints,
|
&mut self.contact_constraints,
|
||||||
&mut self.joint_constraints,
|
&mut self.joint_constraints,
|
||||||
);
|
);
|
||||||
|
counters.solver.velocity_resolution_time.pause();
|
||||||
|
|
||||||
// WRITEBACK
|
// WRITEBACK
|
||||||
|
counters.solver.velocity_writeback_time.resume();
|
||||||
self.joint_constraints.writeback_impulses(impulse_joints);
|
self.joint_constraints.writeback_impulses(impulse_joints);
|
||||||
self.contact_constraints.writeback_impulses(manifolds);
|
self.contact_constraints.writeback_impulses(manifolds);
|
||||||
self.velocity_solver.writeback_bodies(
|
self.velocity_solver.writeback_bodies(
|
||||||
@@ -98,6 +100,6 @@ impl IslandSolver {
|
|||||||
bodies,
|
bodies,
|
||||||
multibodies,
|
multibodies,
|
||||||
);
|
);
|
||||||
counters.solver.velocity_resolution_time.pause();
|
counters.solver.velocity_writeback_time.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ impl JointOneBodyConstraintBuilder {
|
|||||||
|
|
||||||
if flipped {
|
if flipped {
|
||||||
std::mem::swap(&mut handle1, &mut handle2);
|
std::mem::swap(&mut handle1, &mut handle2);
|
||||||
std::mem::swap(&mut joint_data.local_frame1, &mut joint_data.local_frame2);
|
joint_data.flip();
|
||||||
};
|
};
|
||||||
|
|
||||||
let rb1 = &bodies[handle1];
|
let rb1 = &bodies[handle1];
|
||||||
@@ -551,6 +551,82 @@ impl<N: SimdRealCopy> JointTwoBodyConstraintHelper<N> {
|
|||||||
constraint
|
constraint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn motor_linear_coupled<const LANES: usize>(
|
||||||
|
&self,
|
||||||
|
params: &IntegrationParameters,
|
||||||
|
joint_id: [JointIndex; LANES],
|
||||||
|
body1: &JointSolverBody<N, LANES>,
|
||||||
|
body2: &JointSolverBody<N, LANES>,
|
||||||
|
coupled_axes: u8,
|
||||||
|
motor_params: &MotorParameters<N>,
|
||||||
|
limits: Option<[N; 2]>,
|
||||||
|
writeback_id: WritebackId,
|
||||||
|
) -> JointTwoBodyConstraint<N, LANES> {
|
||||||
|
let inv_dt = N::splat(params.inv_dt());
|
||||||
|
|
||||||
|
let mut lin_jac = Vector::zeros();
|
||||||
|
let mut ang_jac1: AngVector<N> = na::zero();
|
||||||
|
let mut ang_jac2: AngVector<N> = na::zero();
|
||||||
|
|
||||||
|
for i in 0..DIM {
|
||||||
|
if coupled_axes & (1 << i) != 0 {
|
||||||
|
let coeff = self.basis.column(i).dot(&self.lin_err);
|
||||||
|
lin_jac += self.basis.column(i) * coeff;
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
{
|
||||||
|
ang_jac1 += self.cmat1_basis[i] * coeff;
|
||||||
|
ang_jac2 += self.cmat2_basis[i] * coeff;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
{
|
||||||
|
ang_jac1 += self.cmat1_basis.column(i) * coeff;
|
||||||
|
ang_jac2 += self.cmat2_basis.column(i) * coeff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dist = lin_jac.norm();
|
||||||
|
let inv_dist = crate::utils::simd_inv(dist);
|
||||||
|
lin_jac *= inv_dist;
|
||||||
|
ang_jac1 *= inv_dist;
|
||||||
|
ang_jac2 *= inv_dist;
|
||||||
|
|
||||||
|
let mut rhs_wo_bias = N::zero();
|
||||||
|
if motor_params.erp_inv_dt != N::zero() {
|
||||||
|
rhs_wo_bias += (dist - motor_params.target_pos) * motor_params.erp_inv_dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target_vel = motor_params.target_vel;
|
||||||
|
if let Some(limits) = limits {
|
||||||
|
target_vel =
|
||||||
|
target_vel.simd_clamp((limits[0] - dist) * inv_dt, (limits[1] - dist) * inv_dt);
|
||||||
|
};
|
||||||
|
|
||||||
|
rhs_wo_bias += -target_vel;
|
||||||
|
|
||||||
|
ang_jac1 = body1.sqrt_ii * ang_jac1;
|
||||||
|
ang_jac2 = body2.sqrt_ii * ang_jac2;
|
||||||
|
|
||||||
|
JointTwoBodyConstraint {
|
||||||
|
joint_id,
|
||||||
|
solver_vel1: body1.solver_vel,
|
||||||
|
solver_vel2: body2.solver_vel,
|
||||||
|
im1: body1.im,
|
||||||
|
im2: body2.im,
|
||||||
|
impulse: N::zero(),
|
||||||
|
impulse_bounds: [-motor_params.max_impulse, motor_params.max_impulse],
|
||||||
|
lin_jac,
|
||||||
|
ang_jac1,
|
||||||
|
ang_jac2,
|
||||||
|
inv_lhs: N::zero(), // Will be set during ortogonalization.
|
||||||
|
cfm_coeff: motor_params.cfm_coeff,
|
||||||
|
cfm_gain: motor_params.cfm_gain,
|
||||||
|
rhs: rhs_wo_bias,
|
||||||
|
rhs_wo_bias,
|
||||||
|
writeback_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lock_linear<const LANES: usize>(
|
pub fn lock_linear<const LANES: usize>(
|
||||||
&self,
|
&self,
|
||||||
params: &IntegrationParameters,
|
params: &IntegrationParameters,
|
||||||
|
|||||||
@@ -319,7 +319,6 @@ pub struct JointGenericVelocityOneBodyExternalConstraintBuilder {
|
|||||||
joint_id: JointIndex,
|
joint_id: JointIndex,
|
||||||
joint: GenericJoint,
|
joint: GenericJoint,
|
||||||
j_id: usize,
|
j_id: usize,
|
||||||
flipped: bool,
|
|
||||||
constraint_id: usize,
|
constraint_id: usize,
|
||||||
// These are solver body for both joints, except that
|
// These are solver body for both joints, except that
|
||||||
// the world_com is actually in local-space.
|
// the world_com is actually in local-space.
|
||||||
@@ -337,21 +336,20 @@ impl JointGenericVelocityOneBodyExternalConstraintBuilder {
|
|||||||
jacobians: &mut DVector<Real>,
|
jacobians: &mut DVector<Real>,
|
||||||
out_constraint_id: &mut usize,
|
out_constraint_id: &mut usize,
|
||||||
) {
|
) {
|
||||||
|
let mut joint_data = joint.data;
|
||||||
let mut handle1 = joint.body1;
|
let mut handle1 = joint.body1;
|
||||||
let mut handle2 = joint.body2;
|
let mut handle2 = joint.body2;
|
||||||
let flipped = !bodies[handle2].is_dynamic();
|
let flipped = !bodies[handle2].is_dynamic();
|
||||||
|
|
||||||
let local_frame1 = if flipped {
|
if flipped {
|
||||||
std::mem::swap(&mut handle1, &mut handle2);
|
std::mem::swap(&mut handle1, &mut handle2);
|
||||||
joint.data.local_frame2
|
joint_data.flip();
|
||||||
} else {
|
}
|
||||||
joint.data.local_frame1
|
|
||||||
};
|
|
||||||
|
|
||||||
let rb1 = &bodies[handle1];
|
let rb1 = &bodies[handle1];
|
||||||
let rb2 = &bodies[handle2];
|
let rb2 = &bodies[handle2];
|
||||||
|
|
||||||
let frame1 = rb1.pos.position * local_frame1;
|
let frame1 = rb1.pos.position * joint_data.local_frame1;
|
||||||
|
|
||||||
let starting_j_id = *j_id;
|
let starting_j_id = *j_id;
|
||||||
|
|
||||||
@@ -394,11 +392,10 @@ impl JointGenericVelocityOneBodyExternalConstraintBuilder {
|
|||||||
body1,
|
body1,
|
||||||
link2,
|
link2,
|
||||||
joint_id,
|
joint_id,
|
||||||
joint: joint.data,
|
joint: joint_data,
|
||||||
j_id: starting_j_id,
|
j_id: starting_j_id,
|
||||||
frame1,
|
frame1,
|
||||||
local_body2,
|
local_body2,
|
||||||
flipped,
|
|
||||||
constraint_id: *out_constraint_id,
|
constraint_id: *out_constraint_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -417,12 +414,7 @@ impl JointGenericVelocityOneBodyExternalConstraintBuilder {
|
|||||||
// constraints. Could we make this more incremental?
|
// constraints. Could we make this more incremental?
|
||||||
let mb2 = &multibodies[self.link2.multibody];
|
let mb2 = &multibodies[self.link2.multibody];
|
||||||
let pos2 = &mb2.link(self.link2.id).unwrap().local_to_world;
|
let pos2 = &mb2.link(self.link2.id).unwrap().local_to_world;
|
||||||
let frame2 = pos2
|
let frame2 = pos2 * self.joint.local_frame2;
|
||||||
* if self.flipped {
|
|
||||||
self.joint.local_frame1
|
|
||||||
} else {
|
|
||||||
self.joint.local_frame2
|
|
||||||
};
|
|
||||||
|
|
||||||
let joint_body2 = JointSolverBody {
|
let joint_body2 = JointSolverBody {
|
||||||
world_com: pos2 * self.local_body2.world_com, // the world_com was stored in local-space.
|
world_com: pos2 * self.local_body2.world_com, // the world_com was stored in local-space.
|
||||||
|
|||||||
@@ -217,28 +217,26 @@ impl JointTwoBodyConstraint<Real, 1> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (motor_axes & coupled_axes) & JointAxesMask::LIN_AXES.bits() != 0 {
|
if (motor_axes & coupled_axes) & JointAxesMask::LIN_AXES.bits() != 0 {
|
||||||
// if (motor_axes & !coupled_axes) & (1 << first_coupled_lin_axis_id) != 0 {
|
let limits = if (limit_axes & (1 << first_coupled_lin_axis_id)) != 0 {
|
||||||
// let limits = if limit_axes & (1 << first_coupled_lin_axis_id) != 0 {
|
Some([
|
||||||
// Some([
|
joint.limits[first_coupled_lin_axis_id].min,
|
||||||
// joint.limits[first_coupled_lin_axis_id].min,
|
joint.limits[first_coupled_lin_axis_id].max,
|
||||||
// joint.limits[first_coupled_lin_axis_id].max,
|
])
|
||||||
// ])
|
} else {
|
||||||
// } else {
|
None
|
||||||
// None
|
};
|
||||||
// };
|
|
||||||
//
|
out[len] = builder.motor_linear_coupled(
|
||||||
// out[len] = builder.motor_linear_coupled
|
params,
|
||||||
// params,
|
[joint_id],
|
||||||
// [joint_id],
|
body1,
|
||||||
// body1,
|
body2,
|
||||||
// body2,
|
coupled_axes,
|
||||||
// coupled_axes,
|
&joint.motors[first_coupled_lin_axis_id].motor_params(params.dt),
|
||||||
// &joint.motors[first_coupled_lin_axis_id].motor_params(params.dt),
|
limits,
|
||||||
// limits,
|
WritebackId::Motor(first_coupled_lin_axis_id),
|
||||||
// WritebackId::Motor(first_coupled_lin_axis_id),
|
);
|
||||||
// );
|
len += 1;
|
||||||
// len += 1;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JointTwoBodyConstraintHelper::finalize_constraints(&mut out[start..len]);
|
JointTwoBodyConstraintHelper::finalize_constraints(&mut out[start..len]);
|
||||||
@@ -350,6 +348,7 @@ impl JointTwoBodyConstraint<Real, 1> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "simd-is-enabled")]
|
#[cfg(feature = "simd-is-enabled")]
|
||||||
impl JointTwoBodyConstraint<SimdReal, SIMD_WIDTH> {
|
impl JointTwoBodyConstraint<SimdReal, SIMD_WIDTH> {
|
||||||
pub fn lock_axes(
|
pub fn lock_axes(
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pub(crate) trait ConstraintTypes {
|
|||||||
type SimdBuilderTwoBodies;
|
type SimdBuilderTwoBodies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum AnyConstraintMut<'a, Constraints: ConstraintTypes> {
|
pub enum AnyConstraintMut<'a, Constraints: ConstraintTypes> {
|
||||||
OneBody(&'a mut Constraints::OneBody),
|
OneBody(&'a mut Constraints::OneBody),
|
||||||
TwoBodies(&'a mut Constraints::TwoBodies),
|
TwoBodies(&'a mut Constraints::TwoBodies),
|
||||||
@@ -95,7 +96,7 @@ impl<Constraints: ConstraintTypes> SolverConstraintsSet<Constraints> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)] // Useful for debuging.
|
#[allow(dead_code)] // Useful for debugging.
|
||||||
pub fn print_counts(&self) {
|
pub fn print_counts(&self) {
|
||||||
println!("Solver constraints:");
|
println!("Solver constraints:");
|
||||||
println!(
|
println!(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::math::{AngVector, Vector, SPATIAL_DIM};
|
use crate::math::{AngVector, Vector, SPATIAL_DIM};
|
||||||
use crate::utils::SimdRealCopy;
|
use crate::utils::SimdRealCopy;
|
||||||
use na::{DVectorView, DVectorViewMut, Scalar};
|
use na::{DVectorView, DVectorViewMut, Scalar};
|
||||||
use std::ops::{AddAssign, Sub};
|
use std::ops::{AddAssign, Sub, SubAssign};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -47,6 +47,13 @@ impl<N: SimdRealCopy> AddAssign for SolverVel<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<N: SimdRealCopy> SubAssign for SolverVel<N> {
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
self.linear -= rhs.linear;
|
||||||
|
self.angular -= rhs.angular;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<N: SimdRealCopy> Sub for SolverVel<N> {
|
impl<N: SimdRealCopy> Sub for SolverVel<N> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ impl VelocitySolver {
|
|||||||
joint_constraints.update(params, multibodies, &self.solver_bodies);
|
joint_constraints.update(params, multibodies, &self.solver_bodies);
|
||||||
contact_constraints.update(params, substep_id, multibodies, &self.solver_bodies);
|
contact_constraints.update(params, substep_id, multibodies, &self.solver_bodies);
|
||||||
|
|
||||||
|
if params.warmstart_coefficient != 0.0 {
|
||||||
|
contact_constraints.warmstart(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
|
}
|
||||||
|
|
||||||
for _ in 0..params.num_internal_pgs_iterations {
|
for _ in 0..params.num_internal_pgs_iterations {
|
||||||
joint_constraints.solve(&mut self.solver_vels, &mut self.generic_solver_vels);
|
joint_constraints.solve(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
contact_constraints
|
contact_constraints
|
||||||
@@ -201,9 +205,19 @@ impl VelocitySolver {
|
|||||||
/*
|
/*
|
||||||
* Resolution without bias.
|
* Resolution without bias.
|
||||||
*/
|
*/
|
||||||
joint_constraints.solve_wo_bias(&mut self.solver_vels, &mut self.generic_solver_vels);
|
if params.num_internal_stabilization_iterations > 0 {
|
||||||
contact_constraints
|
for _ in 0..params.num_internal_stabilization_iterations {
|
||||||
.solve_restitution_wo_bias(&mut self.solver_vels, &mut self.generic_solver_vels);
|
joint_constraints
|
||||||
|
.solve_wo_bias(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
|
contact_constraints.solve_restitution_wo_bias(
|
||||||
|
&mut self.solver_vels,
|
||||||
|
&mut self.generic_solver_vels,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
contact_constraints
|
||||||
|
.solve_friction(&mut self.solver_vels, &mut self.generic_solver_vels);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,8 +253,9 @@ impl VelocitySolver {
|
|||||||
.rows(multibody.solver_id, multibody.ndofs());
|
.rows(multibody.solver_id, multibody.ndofs());
|
||||||
multibody.velocities.copy_from(&solver_vels);
|
multibody.velocities.copy_from(&solver_vels);
|
||||||
multibody.integrate(params.dt);
|
multibody.integrate(params.dt);
|
||||||
// PERF: we could have a mode where it doesn’t write back to the `bodies` yet.
|
// PERF: don’t write back to the rigid-body poses `bodies` before the last step?
|
||||||
multibody.forward_kinematics(bodies, !is_last_substep);
|
multibody.forward_kinematics(bodies, false);
|
||||||
|
multibody.update_rigid_bodies_internal(bodies, !is_last_substep, true, false);
|
||||||
|
|
||||||
if !is_last_substep {
|
if !is_last_substep {
|
||||||
// These are very expensive and not needed if we don’t
|
// These are very expensive and not needed if we don’t
|
||||||
|
|||||||
50
src/geometry/broad_phase.rs
Normal file
50
src/geometry/broad_phase.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use crate::dynamics::RigidBodySet;
|
||||||
|
use crate::geometry::{BroadPhasePairEvent, ColliderHandle, ColliderSet};
|
||||||
|
use parry::math::Real;
|
||||||
|
|
||||||
|
/// An internal index stored in colliders by some broad-phase algorithms.
|
||||||
|
pub type BroadPhaseProxyIndex = u32;
|
||||||
|
|
||||||
|
/// Trait implemented by broad-phase algorithms supported by Rapier.
|
||||||
|
///
|
||||||
|
/// The task of a broad-phase algorithm is to detect potential collision pairs, usually based on
|
||||||
|
/// bounding volumes. The pairs must be conservative: it is OK to create a collision pair if
|
||||||
|
/// two objects don’t actually touch, but it is incorrect to remove a pair between two objects
|
||||||
|
/// that are still touching. In other words, it can have false-positive (though these induce
|
||||||
|
/// some computational overhead on the narrow-phase), but cannot have false-negative.
|
||||||
|
pub trait BroadPhase: Send + Sync + 'static {
|
||||||
|
/// Updates the broad-phase.
|
||||||
|
///
|
||||||
|
/// The results must be output through the `events` struct. The broad-phase algorithm is only
|
||||||
|
/// required to generate new events (i.e. no need to re-send an `AddPair` event if it was already
|
||||||
|
/// sent previously and no `RemovePair` happened since then). Sending redundant events is allowed
|
||||||
|
/// but can result in a slight computational overhead.
|
||||||
|
///
|
||||||
|
/// The `colliders` set is mutable only to provide access to
|
||||||
|
/// [`collider.set_internal_broad_phase_proxy_index`]. Other properties of the collider should
|
||||||
|
/// **not** be modified during the broad-phase update.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `prediction_distance`: colliders that are not exactly touching, but closer to this
|
||||||
|
/// distance must form a collision pair.
|
||||||
|
/// - `colliders`: the set of colliders. Change detection with `collider.needs_broad_phase_update()`
|
||||||
|
/// can be relied on at this stage.
|
||||||
|
/// - `modified_colliders`: colliders that are know to be modified since the last update.
|
||||||
|
/// - `removed_colliders`: colliders that got removed since the last update. Any associated data
|
||||||
|
/// in the broad-phase should be removed by this call to `update`.
|
||||||
|
/// - `events`: the broad-phase’s output. They indicate what collision pairs need to be created
|
||||||
|
/// and what pairs need to be removed. It is OK to create pairs for colliders that don’t
|
||||||
|
/// actually collide (though this can increase computational overhead in the narrow-phase)
|
||||||
|
/// but it is important not to indicate removal of a collision pair if the underlying colliders
|
||||||
|
/// are still touching or closer than `prediction_distance`.
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
dt: Real,
|
||||||
|
prediction_distance: Real,
|
||||||
|
colliders: &mut ColliderSet,
|
||||||
|
bodies: &RigidBodySet,
|
||||||
|
modified_colliders: &[ColliderHandle],
|
||||||
|
removed_colliders: &[ColliderHandle],
|
||||||
|
events: &mut Vec<BroadPhasePairEvent>,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use super::{
|
use super::{
|
||||||
BroadPhasePairEvent, ColliderPair, SAPLayer, SAPProxies, SAPProxy, SAPProxyData, SAPRegionPool,
|
BroadPhasePairEvent, ColliderPair, SAPLayer, SAPProxies, SAPProxy, SAPProxyData, SAPRegionPool,
|
||||||
};
|
};
|
||||||
use crate::geometry::broad_phase_multi_sap::SAPProxyIndex;
|
|
||||||
use crate::geometry::{
|
use crate::geometry::{
|
||||||
ColliderBroadPhaseData, ColliderChanges, ColliderHandle, ColliderPosition, ColliderSet,
|
BroadPhaseProxyIndex, Collider, ColliderBroadPhaseData, ColliderChanges, ColliderHandle,
|
||||||
ColliderShape,
|
ColliderSet,
|
||||||
};
|
};
|
||||||
use crate::math::Real;
|
use crate::math::{Isometry, Real};
|
||||||
|
use crate::prelude::{BroadPhase, RigidBodySet};
|
||||||
use crate::utils::IndexMut2;
|
use crate::utils::IndexMut2;
|
||||||
use parry::bounding_volume::BoundingVolume;
|
use parry::bounding_volume::BoundingVolume;
|
||||||
use parry::utils::hashmap::HashMap;
|
use parry::utils::hashmap::HashMap;
|
||||||
@@ -74,7 +74,7 @@ use parry::utils::hashmap::HashMap;
|
|||||||
/// broad-phase, as well as the Aabbs of all the regions part of this broad-phase.
|
/// broad-phase, as well as the Aabbs of all the regions part of this broad-phase.
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BroadPhase {
|
pub struct BroadPhaseMultiSap {
|
||||||
proxies: SAPProxies,
|
proxies: SAPProxies,
|
||||||
layers: Vec<SAPLayer>,
|
layers: Vec<SAPLayer>,
|
||||||
smallest_layer: u8,
|
smallest_layer: u8,
|
||||||
@@ -90,7 +90,7 @@ pub struct BroadPhase {
|
|||||||
// Another alternative would be to remove ColliderProxyId and
|
// Another alternative would be to remove ColliderProxyId and
|
||||||
// just use a Coarena. But this seems like it could use too
|
// just use a Coarena. But this seems like it could use too
|
||||||
// much memory.
|
// much memory.
|
||||||
colliders_proxy_ids: HashMap<ColliderHandle, SAPProxyIndex>,
|
colliders_proxy_ids: HashMap<ColliderHandle, BroadPhaseProxyIndex>,
|
||||||
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
||||||
region_pool: SAPRegionPool, // To avoid repeated allocations.
|
region_pool: SAPRegionPool, // To avoid repeated allocations.
|
||||||
// We could think serializing this workspace is useless.
|
// We could think serializing this workspace is useless.
|
||||||
@@ -114,16 +114,16 @@ pub struct BroadPhase {
|
|||||||
reporting: HashMap<(u32, u32), bool>, // Workspace
|
reporting: HashMap<(u32, u32), bool>, // Workspace
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BroadPhase {
|
impl Default for BroadPhaseMultiSap {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BroadPhase {
|
impl BroadPhaseMultiSap {
|
||||||
/// Create a new empty broad-phase.
|
/// Create a new empty broad-phase.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
BroadPhase {
|
BroadPhaseMultiSap {
|
||||||
proxies: SAPProxies::new(),
|
proxies: SAPProxies::new(),
|
||||||
layers: Vec::new(),
|
layers: Vec::new(),
|
||||||
smallest_layer: 0,
|
smallest_layer: 0,
|
||||||
@@ -138,7 +138,7 @@ impl BroadPhase {
|
|||||||
///
|
///
|
||||||
/// For each colliders marked as removed, we make their containing layer mark
|
/// For each colliders marked as removed, we make their containing layer mark
|
||||||
/// its proxy as pre-deleted. The actual proxy removal will happen at the end
|
/// its proxy as pre-deleted. The actual proxy removal will happen at the end
|
||||||
/// of the `BroadPhase::update`.
|
/// of the `BroadPhaseMultiSap::update`.
|
||||||
fn handle_removed_colliders(&mut self, removed_colliders: &[ColliderHandle]) {
|
fn handle_removed_colliders(&mut self, removed_colliders: &[ColliderHandle]) {
|
||||||
// For each removed collider, remove the corresponding proxy.
|
// For each removed collider, remove the corresponding proxy.
|
||||||
for removed in removed_colliders {
|
for removed in removed_colliders {
|
||||||
@@ -156,7 +156,7 @@ impl BroadPhase {
|
|||||||
/// remove, the `complete_removal` method MUST be called to
|
/// remove, the `complete_removal` method MUST be called to
|
||||||
/// complete the removal of these proxies, by actually removing them
|
/// complete the removal of these proxies, by actually removing them
|
||||||
/// from all the relevant layers/regions/axes.
|
/// from all the relevant layers/regions/axes.
|
||||||
fn predelete_proxy(&mut self, proxy_index: SAPProxyIndex) {
|
fn predelete_proxy(&mut self, proxy_index: BroadPhaseProxyIndex) {
|
||||||
if proxy_index == crate::INVALID_U32 {
|
if proxy_index == crate::INVALID_U32 {
|
||||||
// This collider has not been added to the broad-phase yet.
|
// This collider has not been added to the broad-phase yet.
|
||||||
return;
|
return;
|
||||||
@@ -353,13 +353,18 @@ impl BroadPhase {
|
|||||||
prediction_distance: Real,
|
prediction_distance: Real,
|
||||||
handle: ColliderHandle,
|
handle: ColliderHandle,
|
||||||
proxy_index: &mut u32,
|
proxy_index: &mut u32,
|
||||||
collider: (&ColliderPosition, &ColliderShape, &ColliderChanges),
|
collider: &Collider,
|
||||||
|
next_position: Option<&Isometry<Real>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let (co_pos, co_shape, co_changes) = collider;
|
let mut aabb = collider.compute_collision_aabb(prediction_distance / 2.0);
|
||||||
|
|
||||||
let mut aabb = co_shape
|
if let Some(next_position) = next_position {
|
||||||
.compute_aabb(co_pos)
|
let next_aabb = collider
|
||||||
.loosened(prediction_distance / 2.0);
|
.shape
|
||||||
|
.compute_aabb(next_position)
|
||||||
|
.loosened(collider.contact_skin() + prediction_distance / 2.0);
|
||||||
|
aabb.merge(&next_aabb);
|
||||||
|
}
|
||||||
|
|
||||||
if aabb.mins.coords.iter().any(|e| !e.is_finite())
|
if aabb.mins.coords.iter().any(|e| !e.is_finite())
|
||||||
|| aabb.maxs.coords.iter().any(|e| !e.is_finite())
|
|| aabb.maxs.coords.iter().any(|e| !e.is_finite())
|
||||||
@@ -378,7 +383,7 @@ impl BroadPhase {
|
|||||||
prev_aabb = proxy.aabb;
|
prev_aabb = proxy.aabb;
|
||||||
proxy.aabb = aabb;
|
proxy.aabb = aabb;
|
||||||
|
|
||||||
if co_changes.contains(ColliderChanges::SHAPE) {
|
if collider.changes.contains(ColliderChanges::SHAPE) {
|
||||||
// If the shape was changed, then we need to see if this proxy should be
|
// If the shape was changed, then we need to see if this proxy should be
|
||||||
// migrated to a larger layer. Indeed, if the shape was replaced by
|
// migrated to a larger layer. Indeed, if the shape was replaced by
|
||||||
// a much larger shape, we need to promote the proxy to a bigger layer
|
// a much larger shape, we need to promote the proxy to a bigger layer
|
||||||
@@ -449,65 +454,6 @@ impl BroadPhase {
|
|||||||
!layer.created_regions.is_empty()
|
!layer.created_regions.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the broad-phase, taking into account the new collider positions.
|
|
||||||
pub fn update(
|
|
||||||
&mut self,
|
|
||||||
prediction_distance: Real,
|
|
||||||
colliders: &mut ColliderSet,
|
|
||||||
modified_colliders: &[ColliderHandle],
|
|
||||||
removed_colliders: &[ColliderHandle],
|
|
||||||
events: &mut Vec<BroadPhasePairEvent>,
|
|
||||||
) {
|
|
||||||
// Phase 1: pre-delete the collisions that have been deleted.
|
|
||||||
self.handle_removed_colliders(removed_colliders);
|
|
||||||
|
|
||||||
let mut need_region_propagation = false;
|
|
||||||
|
|
||||||
// Phase 2: pre-delete the collisions that have been deleted.
|
|
||||||
for handle in modified_colliders {
|
|
||||||
// NOTE: we use `get` because the collider may no longer
|
|
||||||
// exist if it has been removed.
|
|
||||||
if let Some(co) = colliders.get_mut_internal(*handle) {
|
|
||||||
if !co.is_enabled() || !co.changes.needs_broad_phase_update() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut new_proxy_id = co.bf_data.proxy_index;
|
|
||||||
|
|
||||||
if self.handle_modified_collider(
|
|
||||||
prediction_distance,
|
|
||||||
*handle,
|
|
||||||
&mut new_proxy_id,
|
|
||||||
(&co.pos, &co.shape, &co.changes),
|
|
||||||
) {
|
|
||||||
need_region_propagation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if co.bf_data.proxy_index != new_proxy_id {
|
|
||||||
self.colliders_proxy_ids.insert(*handle, new_proxy_id);
|
|
||||||
|
|
||||||
// Make sure we have the new proxy index in case
|
|
||||||
// the collider was added for the first time.
|
|
||||||
co.bf_data = ColliderBroadPhaseData {
|
|
||||||
proxy_index: new_proxy_id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3: bottom-up pass to propagate new regions from smaller layers to larger layers.
|
|
||||||
if need_region_propagation {
|
|
||||||
self.propagate_created_regions();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 4: top-down pass to propagate proxies from larger layers to smaller layers.
|
|
||||||
self.update_layers_and_find_pairs(events);
|
|
||||||
|
|
||||||
// Phase 5: bottom-up pass to remove proxies, and propagate region removed from smaller
|
|
||||||
// layers to possible remove regions from larger layers that would become empty that way.
|
|
||||||
self.complete_removals(colliders, removed_colliders);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Propagate regions from the smallest layers up to the larger layers.
|
/// Propagate regions from the smallest layers up to the larger layers.
|
||||||
///
|
///
|
||||||
/// Whenever a region is created on a layer `n`, then its Aabb must be
|
/// Whenever a region is created on a layer `n`, then its Aabb must be
|
||||||
@@ -618,16 +564,90 @@ impl BroadPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BroadPhase for BroadPhaseMultiSap {
|
||||||
|
/// Updates the broad-phase, taking into account the new collider positions.
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
dt: Real,
|
||||||
|
prediction_distance: Real,
|
||||||
|
colliders: &mut ColliderSet,
|
||||||
|
bodies: &RigidBodySet,
|
||||||
|
modified_colliders: &[ColliderHandle],
|
||||||
|
removed_colliders: &[ColliderHandle],
|
||||||
|
events: &mut Vec<BroadPhasePairEvent>,
|
||||||
|
) {
|
||||||
|
// Phase 1: pre-delete the collisions that have been deleted.
|
||||||
|
self.handle_removed_colliders(removed_colliders);
|
||||||
|
|
||||||
|
let mut need_region_propagation = false;
|
||||||
|
|
||||||
|
// Phase 2: pre-delete the collisions that have been deleted.
|
||||||
|
for handle in modified_colliders {
|
||||||
|
// NOTE: we use `get` because the collider may no longer
|
||||||
|
// exist if it has been removed.
|
||||||
|
if let Some(co) = colliders.get_mut_internal(*handle) {
|
||||||
|
if !co.is_enabled() || !co.changes.needs_broad_phase_update() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_proxy_id = co.bf_data.proxy_index;
|
||||||
|
|
||||||
|
let next_pos = co.parent.and_then(|p| {
|
||||||
|
let parent = bodies.get(p.handle)?;
|
||||||
|
(parent.soft_ccd_prediction() > 0.0).then(|| {
|
||||||
|
parent.predict_position_using_velocity_and_forces_with_max_dist(
|
||||||
|
dt,
|
||||||
|
parent.soft_ccd_prediction(),
|
||||||
|
) * p.pos_wrt_parent
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.handle_modified_collider(
|
||||||
|
prediction_distance,
|
||||||
|
*handle,
|
||||||
|
&mut new_proxy_id,
|
||||||
|
co,
|
||||||
|
next_pos.as_ref(),
|
||||||
|
) {
|
||||||
|
need_region_propagation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if co.bf_data.proxy_index != new_proxy_id {
|
||||||
|
self.colliders_proxy_ids.insert(*handle, new_proxy_id);
|
||||||
|
|
||||||
|
// Make sure we have the new proxy index in case
|
||||||
|
// the collider was added for the first time.
|
||||||
|
co.bf_data = ColliderBroadPhaseData {
|
||||||
|
proxy_index: new_proxy_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 3: bottom-up pass to propagate new regions from smaller layers to larger layers.
|
||||||
|
if need_region_propagation {
|
||||||
|
self.propagate_created_regions();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 4: top-down pass to propagate proxies from larger layers to smaller layers.
|
||||||
|
self.update_layers_and_find_pairs(events);
|
||||||
|
|
||||||
|
// Phase 5: bottom-up pass to remove proxies, and propagate region removed from smaller
|
||||||
|
// layers to possible remove regions from larger layers that would become empty that way.
|
||||||
|
self.complete_removals(colliders, removed_colliders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::dynamics::{
|
use crate::dynamics::{
|
||||||
ImpulseJointSet, IslandManager, MultibodyJointSet, RigidBodyBuilder, RigidBodySet,
|
ImpulseJointSet, IslandManager, MultibodyJointSet, RigidBodyBuilder, RigidBodySet,
|
||||||
};
|
};
|
||||||
use crate::geometry::{BroadPhase, ColliderBuilder, ColliderSet};
|
use crate::geometry::{BroadPhase, BroadPhaseMultiSap, ColliderBuilder, ColliderSet};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_update_remove() {
|
fn test_add_update_remove() {
|
||||||
let mut broad_phase = BroadPhase::new();
|
let mut broad_phase = BroadPhaseMultiSap::new();
|
||||||
let mut bodies = RigidBodySet::new();
|
let mut bodies = RigidBodySet::new();
|
||||||
let mut colliders = ColliderSet::new();
|
let mut colliders = ColliderSet::new();
|
||||||
let mut impulse_joints = ImpulseJointSet::new();
|
let mut impulse_joints = ImpulseJointSet::new();
|
||||||
@@ -640,7 +660,7 @@ mod test {
|
|||||||
let coh = colliders.insert_with_parent(co, hrb, &mut bodies);
|
let coh = colliders.insert_with_parent(co, hrb, &mut bodies);
|
||||||
|
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
broad_phase.update(0.0, &mut colliders, &[coh], &[], &mut events);
|
broad_phase.update(0.0, 0.0, &mut colliders, &bodies, &[coh], &[], &mut events);
|
||||||
|
|
||||||
bodies.remove(
|
bodies.remove(
|
||||||
hrb,
|
hrb,
|
||||||
@@ -650,7 +670,7 @@ mod test {
|
|||||||
&mut multibody_joints,
|
&mut multibody_joints,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
broad_phase.update(0.0, &mut colliders, &[], &[coh], &mut events);
|
broad_phase.update(0.0, 0.0, &mut colliders, &bodies, &[], &[coh], &mut events);
|
||||||
|
|
||||||
// Create another body.
|
// Create another body.
|
||||||
let rb = RigidBodyBuilder::dynamic().build();
|
let rb = RigidBodyBuilder::dynamic().build();
|
||||||
@@ -659,6 +679,6 @@ mod test {
|
|||||||
let coh = colliders.insert_with_parent(co, hrb, &mut bodies);
|
let coh = colliders.insert_with_parent(co, hrb, &mut bodies);
|
||||||
|
|
||||||
// Make sure the proxy handles is recycled properly.
|
// Make sure the proxy handles is recycled properly.
|
||||||
broad_phase.update(0.0, &mut colliders, &[coh], &[], &mut events);
|
broad_phase.update(0.0, 0.0, &mut colliders, &bodies, &[coh], &[], &mut events);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
pub use self::broad_phase::BroadPhase;
|
pub use self::broad_phase_multi_sap::BroadPhaseMultiSap;
|
||||||
pub use self::broad_phase_pair_event::{BroadPhasePairEvent, ColliderPair};
|
pub use self::broad_phase_pair_event::{BroadPhasePairEvent, ColliderPair};
|
||||||
pub use self::sap_proxy::SAPProxyIndex;
|
|
||||||
|
|
||||||
use self::sap_axis::*;
|
use self::sap_axis::*;
|
||||||
use self::sap_endpoint::*;
|
use self::sap_endpoint::*;
|
||||||
@@ -9,7 +8,7 @@ use self::sap_proxy::*;
|
|||||||
use self::sap_region::*;
|
use self::sap_region::*;
|
||||||
use self::sap_utils::*;
|
use self::sap_utils::*;
|
||||||
|
|
||||||
mod broad_phase;
|
mod broad_phase_multi_sap;
|
||||||
mod broad_phase_pair_event;
|
mod broad_phase_pair_event;
|
||||||
mod sap_axis;
|
mod sap_axis;
|
||||||
mod sap_endpoint;
|
mod sap_endpoint;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::{SAPEndpoint, SAPProxies, NUM_SENTINELS};
|
use super::{SAPEndpoint, SAPProxies, NUM_SENTINELS};
|
||||||
use crate::geometry::broad_phase_multi_sap::DELETED_AABB_VALUE;
|
use crate::geometry::broad_phase_multi_sap::DELETED_AABB_VALUE;
|
||||||
use crate::geometry::SAPProxyIndex;
|
use crate::geometry::BroadPhaseProxyIndex;
|
||||||
use crate::math::Real;
|
use crate::math::Real;
|
||||||
use bit_vec::BitVec;
|
use bit_vec::BitVec;
|
||||||
use parry::bounding_volume::BoundingVolume;
|
use parry::bounding_volume::BoundingVolume;
|
||||||
@@ -39,7 +39,7 @@ impl SAPAxis {
|
|||||||
pub fn batch_insert(
|
pub fn batch_insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
dim: usize,
|
dim: usize,
|
||||||
new_proxies: &[SAPProxyIndex],
|
new_proxies: &[BroadPhaseProxyIndex],
|
||||||
proxies: &SAPProxies,
|
proxies: &SAPProxies,
|
||||||
reporting: Option<&mut HashMap<(u32, u32), bool>>,
|
reporting: Option<&mut HashMap<(u32, u32), bool>>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::{SAPProxies, SAPProxy, SAPRegion, SAPRegionPool};
|
use super::{SAPProxies, SAPProxy, SAPRegion, SAPRegionPool};
|
||||||
use crate::geometry::broad_phase_multi_sap::DELETED_AABB_VALUE;
|
use crate::geometry::broad_phase_multi_sap::DELETED_AABB_VALUE;
|
||||||
use crate::geometry::{Aabb, SAPProxyIndex};
|
use crate::geometry::{Aabb, BroadPhaseProxyIndex};
|
||||||
use crate::math::{Point, Real};
|
use crate::math::{Point, Real};
|
||||||
use parry::bounding_volume::BoundingVolume;
|
use parry::bounding_volume::BoundingVolume;
|
||||||
use parry::utils::hashmap::{Entry, HashMap};
|
use parry::utils::hashmap::{Entry, HashMap};
|
||||||
@@ -13,11 +13,11 @@ pub(crate) struct SAPLayer {
|
|||||||
pub smaller_layer: Option<u8>,
|
pub smaller_layer: Option<u8>,
|
||||||
pub larger_layer: Option<u8>,
|
pub larger_layer: Option<u8>,
|
||||||
region_width: Real,
|
region_width: Real,
|
||||||
pub regions: HashMap<Point<i32>, SAPProxyIndex>,
|
pub regions: HashMap<Point<i32>, BroadPhaseProxyIndex>,
|
||||||
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
||||||
regions_to_potentially_remove: Vec<Point<i32>>, // Workspace
|
regions_to_potentially_remove: Vec<Point<i32>>, // Workspace
|
||||||
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
||||||
pub created_regions: Vec<SAPProxyIndex>,
|
pub created_regions: Vec<BroadPhaseProxyIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SAPLayer {
|
impl SAPLayer {
|
||||||
@@ -71,7 +71,7 @@ impl SAPLayer {
|
|||||||
///
|
///
|
||||||
/// This method must be called in a bottom-up loop, propagating new regions from the
|
/// This method must be called in a bottom-up loop, propagating new regions from the
|
||||||
/// smallest layer, up to the largest layer. That loop is done by the Phase 3 of the
|
/// smallest layer, up to the largest layer. That loop is done by the Phase 3 of the
|
||||||
/// BroadPhase::update.
|
/// BroadPhaseMultiSap::update.
|
||||||
pub fn propagate_created_regions(
|
pub fn propagate_created_regions(
|
||||||
&mut self,
|
&mut self,
|
||||||
larger_layer: &mut Self,
|
larger_layer: &mut Self,
|
||||||
@@ -103,7 +103,7 @@ impl SAPLayer {
|
|||||||
/// one region on its parent "larger" layer.
|
/// one region on its parent "larger" layer.
|
||||||
fn register_subregion(
|
fn register_subregion(
|
||||||
&mut self,
|
&mut self,
|
||||||
proxy_id: SAPProxyIndex,
|
proxy_id: BroadPhaseProxyIndex,
|
||||||
proxies: &mut SAPProxies,
|
proxies: &mut SAPProxies,
|
||||||
pool: &mut SAPRegionPool,
|
pool: &mut SAPRegionPool,
|
||||||
) {
|
) {
|
||||||
@@ -140,7 +140,7 @@ impl SAPLayer {
|
|||||||
|
|
||||||
fn unregister_subregion(
|
fn unregister_subregion(
|
||||||
&mut self,
|
&mut self,
|
||||||
proxy_id: SAPProxyIndex,
|
proxy_id: BroadPhaseProxyIndex,
|
||||||
proxy_region: &SAPRegion,
|
proxy_region: &SAPRegion,
|
||||||
proxies: &mut SAPProxies,
|
proxies: &mut SAPProxies,
|
||||||
) {
|
) {
|
||||||
@@ -182,7 +182,7 @@ impl SAPLayer {
|
|||||||
/// If the region with the given region key does not exist yet, it is created.
|
/// If the region with the given region key does not exist yet, it is created.
|
||||||
/// When a region is created, it creates a new proxy for that region, and its
|
/// When a region is created, it creates a new proxy for that region, and its
|
||||||
/// proxy ID is added to `self.created_region` so it can be propagated during
|
/// proxy ID is added to `self.created_region` so it can be propagated during
|
||||||
/// the Phase 3 of `BroadPhase::update`.
|
/// the Phase 3 of `BroadPhaseMultiSap::update`.
|
||||||
///
|
///
|
||||||
/// This returns the proxy ID of the already existing region if it existed, or
|
/// This returns the proxy ID of the already existing region if it existed, or
|
||||||
/// of the new region if it did not exist and has been created by this method.
|
/// of the new region if it did not exist and has been created by this method.
|
||||||
@@ -191,7 +191,7 @@ impl SAPLayer {
|
|||||||
region_key: Point<i32>,
|
region_key: Point<i32>,
|
||||||
proxies: &mut SAPProxies,
|
proxies: &mut SAPProxies,
|
||||||
pool: &mut SAPRegionPool,
|
pool: &mut SAPRegionPool,
|
||||||
) -> SAPProxyIndex {
|
) -> BroadPhaseProxyIndex {
|
||||||
match self.regions.entry(region_key) {
|
match self.regions.entry(region_key) {
|
||||||
// Yay, the region already exists!
|
// Yay, the region already exists!
|
||||||
Entry::Occupied(occupied) => *occupied.get(),
|
Entry::Occupied(occupied) => *occupied.get(),
|
||||||
@@ -266,7 +266,7 @@ impl SAPLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn predelete_proxy(&mut self, proxies: &mut SAPProxies, proxy_index: SAPProxyIndex) {
|
pub fn predelete_proxy(&mut self, proxies: &mut SAPProxies, proxy_index: BroadPhaseProxyIndex) {
|
||||||
// Discretize the Aabb to find the regions that need to be invalidated.
|
// Discretize the Aabb to find the regions that need to be invalidated.
|
||||||
let proxy_aabb = &mut proxies[proxy_index].aabb;
|
let proxy_aabb = &mut proxies[proxy_index].aabb;
|
||||||
let start = super::point_key(proxy_aabb.mins, self.region_width);
|
let start = super::point_key(proxy_aabb.mins, self.region_width);
|
||||||
@@ -379,7 +379,7 @@ impl SAPLayer {
|
|||||||
pub fn proper_proxy_moved_to_bigger_layer(
|
pub fn proper_proxy_moved_to_bigger_layer(
|
||||||
&mut self,
|
&mut self,
|
||||||
proxies: &mut SAPProxies,
|
proxies: &mut SAPProxies,
|
||||||
proxy_id: SAPProxyIndex,
|
proxy_id: BroadPhaseProxyIndex,
|
||||||
) {
|
) {
|
||||||
for (point, region_id) in &self.regions {
|
for (point, region_id) in &self.regions {
|
||||||
let region = &mut proxies[*region_id].data.as_region_mut();
|
let region = &mut proxies[*region_id].data.as_region_mut();
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use super::NEXT_FREE_SENTINEL;
|
use super::NEXT_FREE_SENTINEL;
|
||||||
use crate::geometry::broad_phase_multi_sap::SAPRegion;
|
use crate::geometry::broad_phase_multi_sap::SAPRegion;
|
||||||
use crate::geometry::ColliderHandle;
|
use crate::geometry::{BroadPhaseProxyIndex, ColliderHandle};
|
||||||
use parry::bounding_volume::Aabb;
|
use parry::bounding_volume::Aabb;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
pub type SAPProxyIndex = u32;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SAPProxyData {
|
pub enum SAPProxyData {
|
||||||
@@ -49,7 +47,7 @@ impl SAPProxyData {
|
|||||||
pub struct SAPProxy {
|
pub struct SAPProxy {
|
||||||
pub data: SAPProxyData,
|
pub data: SAPProxyData,
|
||||||
pub aabb: Aabb,
|
pub aabb: Aabb,
|
||||||
pub next_free: SAPProxyIndex,
|
pub next_free: BroadPhaseProxyIndex,
|
||||||
// TODO: pack the layer_id and layer_depth into a single u16?
|
// TODO: pack the layer_id and layer_depth into a single u16?
|
||||||
pub layer_id: u8,
|
pub layer_id: u8,
|
||||||
pub layer_depth: i8,
|
pub layer_depth: i8,
|
||||||
@@ -81,7 +79,7 @@ impl SAPProxy {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SAPProxies {
|
pub struct SAPProxies {
|
||||||
pub elements: Vec<SAPProxy>,
|
pub elements: Vec<SAPProxy>,
|
||||||
pub first_free: SAPProxyIndex,
|
pub first_free: BroadPhaseProxyIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SAPProxies {
|
impl Default for SAPProxies {
|
||||||
@@ -98,7 +96,7 @@ impl SAPProxies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, proxy: SAPProxy) -> SAPProxyIndex {
|
pub fn insert(&mut self, proxy: SAPProxy) -> BroadPhaseProxyIndex {
|
||||||
if self.first_free != NEXT_FREE_SENTINEL {
|
if self.first_free != NEXT_FREE_SENTINEL {
|
||||||
let proxy_id = self.first_free;
|
let proxy_id = self.first_free;
|
||||||
self.first_free = self.elements[proxy_id as usize].next_free;
|
self.first_free = self.elements[proxy_id as usize].next_free;
|
||||||
@@ -110,31 +108,31 @@ impl SAPProxies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, proxy_id: SAPProxyIndex) {
|
pub fn remove(&mut self, proxy_id: BroadPhaseProxyIndex) {
|
||||||
let proxy = &mut self.elements[proxy_id as usize];
|
let proxy = &mut self.elements[proxy_id as usize];
|
||||||
proxy.next_free = self.first_free;
|
proxy.next_free = self.first_free;
|
||||||
self.first_free = proxy_id;
|
self.first_free = proxy_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: this must not take holes into account.
|
// NOTE: this must not take holes into account.
|
||||||
pub fn get_mut(&mut self, i: SAPProxyIndex) -> Option<&mut SAPProxy> {
|
pub fn get_mut(&mut self, i: BroadPhaseProxyIndex) -> Option<&mut SAPProxy> {
|
||||||
self.elements.get_mut(i as usize)
|
self.elements.get_mut(i as usize)
|
||||||
}
|
}
|
||||||
// NOTE: this must not take holes into account.
|
// NOTE: this must not take holes into account.
|
||||||
pub fn get(&self, i: SAPProxyIndex) -> Option<&SAPProxy> {
|
pub fn get(&self, i: BroadPhaseProxyIndex) -> Option<&SAPProxy> {
|
||||||
self.elements.get(i as usize)
|
self.elements.get(i as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<SAPProxyIndex> for SAPProxies {
|
impl Index<BroadPhaseProxyIndex> for SAPProxies {
|
||||||
type Output = SAPProxy;
|
type Output = SAPProxy;
|
||||||
fn index(&self, i: SAPProxyIndex) -> &SAPProxy {
|
fn index(&self, i: BroadPhaseProxyIndex) -> &SAPProxy {
|
||||||
self.elements.index(i as usize)
|
self.elements.index(i as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexMut<SAPProxyIndex> for SAPProxies {
|
impl IndexMut<BroadPhaseProxyIndex> for SAPProxies {
|
||||||
fn index_mut(&mut self, i: SAPProxyIndex) -> &mut SAPProxy {
|
fn index_mut(&mut self, i: BroadPhaseProxyIndex) -> &mut SAPProxy {
|
||||||
self.elements.index_mut(i as usize)
|
self.elements.index_mut(i as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{SAPAxis, SAPProxies};
|
use super::{SAPAxis, SAPProxies};
|
||||||
use crate::geometry::SAPProxyIndex;
|
use crate::geometry::BroadPhaseProxyIndex;
|
||||||
use crate::math::DIM;
|
use crate::math::DIM;
|
||||||
use bit_vec::BitVec;
|
use bit_vec::BitVec;
|
||||||
use parry::bounding_volume::Aabb;
|
use parry::bounding_volume::Aabb;
|
||||||
@@ -13,8 +13,8 @@ pub struct SAPRegion {
|
|||||||
pub axes: [SAPAxis; DIM],
|
pub axes: [SAPAxis; DIM],
|
||||||
pub existing_proxies: BitVec,
|
pub existing_proxies: BitVec,
|
||||||
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
||||||
pub to_insert: Vec<SAPProxyIndex>, // Workspace
|
pub to_insert: Vec<BroadPhaseProxyIndex>, // Workspace
|
||||||
pub subregions: Vec<SAPProxyIndex>,
|
pub subregions: Vec<BroadPhaseProxyIndex>,
|
||||||
pub id_in_parent_subregion: u32,
|
pub id_in_parent_subregion: u32,
|
||||||
pub update_count: u8,
|
pub update_count: u8,
|
||||||
pub needs_update_after_subregion_removal: bool,
|
pub needs_update_after_subregion_removal: bool,
|
||||||
@@ -90,7 +90,7 @@ impl SAPRegion {
|
|||||||
/// If this region contains the given proxy, this will decrement this region's proxy count.
|
/// If this region contains the given proxy, this will decrement this region's proxy count.
|
||||||
///
|
///
|
||||||
/// Returns `true` if this region contained the proxy. Returns `false` otherwise.
|
/// Returns `true` if this region contained the proxy. Returns `false` otherwise.
|
||||||
pub fn proper_proxy_moved_to_a_bigger_layer(&mut self, proxy_id: SAPProxyIndex) -> bool {
|
pub fn proper_proxy_moved_to_a_bigger_layer(&mut self, proxy_id: BroadPhaseProxyIndex) -> bool {
|
||||||
if self.existing_proxies.get(proxy_id as usize) == Some(true) {
|
if self.existing_proxies.get(proxy_id as usize) == Some(true) {
|
||||||
// NOTE: we are just registering the fact that that proxy isn't a
|
// NOTE: we are just registering the fact that that proxy isn't a
|
||||||
// subproper proxy anymore. But it is still part of this region
|
// subproper proxy anymore. But it is still part of this region
|
||||||
@@ -142,7 +142,7 @@ impl SAPRegion {
|
|||||||
self.subproper_proxy_count -= num_deleted_subregion_endpoints[0] / 2;
|
self.subproper_proxy_count -= num_deleted_subregion_endpoints[0] / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn predelete_proxy(&mut self, _proxy_id: SAPProxyIndex) {
|
pub fn predelete_proxy(&mut self, _proxy_id: BroadPhaseProxyIndex) {
|
||||||
// We keep the proxy_id as argument for uniformity with the "preupdate"
|
// We keep the proxy_id as argument for uniformity with the "preupdate"
|
||||||
// method. However we don't actually need it because the deletion will be
|
// method. However we don't actually need it because the deletion will be
|
||||||
// handled transparently during the next update.
|
// handled transparently during the next update.
|
||||||
@@ -153,14 +153,18 @@ impl SAPRegion {
|
|||||||
self.update_count = self.update_count.max(1);
|
self.update_count = self.update_count.max(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_subregion(&mut self, proxy_id: SAPProxyIndex) -> usize {
|
pub fn register_subregion(&mut self, proxy_id: BroadPhaseProxyIndex) -> usize {
|
||||||
let subregion_index = self.subregions.len();
|
let subregion_index = self.subregions.len();
|
||||||
self.subregions.push(proxy_id);
|
self.subregions.push(proxy_id);
|
||||||
self.preupdate_proxy(proxy_id, true);
|
self.preupdate_proxy(proxy_id, true);
|
||||||
subregion_index
|
subregion_index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preupdate_proxy(&mut self, proxy_id: SAPProxyIndex, is_subproper_proxy: bool) -> bool {
|
pub fn preupdate_proxy(
|
||||||
|
&mut self,
|
||||||
|
proxy_id: BroadPhaseProxyIndex,
|
||||||
|
is_subproper_proxy: bool,
|
||||||
|
) -> bool {
|
||||||
let mask_len = self.existing_proxies.len();
|
let mask_len = self.existing_proxies.len();
|
||||||
if proxy_id as usize >= mask_len {
|
if proxy_id as usize >= mask_len {
|
||||||
self.existing_proxies
|
self.existing_proxies
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use crate::geometry::{BroadPhasePairEvent, ColliderHandle, ColliderPair, ColliderSet};
|
use crate::geometry::{BroadPhasePairEvent, ColliderHandle, ColliderPair, ColliderSet};
|
||||||
use parry::bounding_volume::BoundingVolume;
|
|
||||||
use parry::math::Real;
|
use parry::math::Real;
|
||||||
use parry::partitioning::Qbvh;
|
use parry::partitioning::Qbvh;
|
||||||
use parry::partitioning::QbvhUpdateWorkspace;
|
use parry::partitioning::QbvhUpdateWorkspace;
|
||||||
@@ -7,20 +6,20 @@ use parry::query::visitors::BoundingVolumeIntersectionsSimultaneousVisitor;
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BroadPhase {
|
pub struct BroadPhaseQbvh {
|
||||||
qbvh: Qbvh<ColliderHandle>,
|
qbvh: Qbvh<ColliderHandle>,
|
||||||
stack: Vec<(u32, u32)>,
|
stack: Vec<(u32, u32)>,
|
||||||
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
#[cfg_attr(feature = "serde-serialize", serde(skip))]
|
||||||
workspace: QbvhUpdateWorkspace,
|
workspace: QbvhUpdateWorkspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BroadPhase {
|
impl Default for BroadPhaseQbvh {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BroadPhase {
|
impl BroadPhaseQbvh {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
qbvh: Qbvh::new(),
|
qbvh: Qbvh::new(),
|
||||||
@@ -59,7 +58,7 @@ impl BroadPhase {
|
|||||||
colliders.iter().map(|(handle, collider)| {
|
colliders.iter().map(|(handle, collider)| {
|
||||||
(
|
(
|
||||||
handle,
|
handle,
|
||||||
collider.compute_aabb().loosened(prediction_distance / 2.0),
|
collider.compute_collision_aabb(prediction_distance / 2.0),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
margin,
|
margin,
|
||||||
@@ -76,9 +75,7 @@ impl BroadPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.qbvh.refit(margin, &mut self.workspace, |handle| {
|
let _ = self.qbvh.refit(margin, &mut self.workspace, |handle| {
|
||||||
colliders[*handle]
|
colliders[*handle].compute_collision_aabb(prediction_distance / 2.0)
|
||||||
.compute_aabb()
|
|
||||||
.loosened(prediction_distance / 2.0)
|
|
||||||
});
|
});
|
||||||
self.qbvh
|
self.qbvh
|
||||||
.traverse_modified_bvtt_with_stack(&self.qbvh, &mut visitor, &mut self.stack);
|
.traverse_modified_bvtt_with_stack(&self.qbvh, &mut visitor, &mut self.stack);
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
use crate::dynamics::{CoefficientCombineRule, MassProperties, RigidBodyHandle};
|
use crate::dynamics::{CoefficientCombineRule, MassProperties, RigidBodyHandle};
|
||||||
use crate::geometry::{
|
use crate::geometry::{
|
||||||
ActiveCollisionTypes, ColliderBroadPhaseData, ColliderChanges, ColliderFlags,
|
ActiveCollisionTypes, BroadPhaseProxyIndex, ColliderBroadPhaseData, ColliderChanges,
|
||||||
ColliderMassProps, ColliderMaterial, ColliderParent, ColliderPosition, ColliderShape,
|
ColliderFlags, ColliderMassProps, ColliderMaterial, ColliderParent, ColliderPosition,
|
||||||
ColliderType, InteractionGroups, SharedShape,
|
ColliderShape, ColliderType, InteractionGroups, SharedShape,
|
||||||
};
|
};
|
||||||
use crate::math::{AngVector, Isometry, Point, Real, Rotation, Vector, DIM};
|
use crate::math::{AngVector, Isometry, Point, Real, Rotation, Vector, DIM};
|
||||||
use crate::parry::transformation::vhacd::VHACDParameters;
|
use crate::parry::transformation::vhacd::VHACDParameters;
|
||||||
use crate::pipeline::{ActiveEvents, ActiveHooks};
|
use crate::pipeline::{ActiveEvents, ActiveHooks};
|
||||||
use crate::prelude::ColliderEnabled;
|
use crate::prelude::ColliderEnabled;
|
||||||
use na::Unit;
|
use na::Unit;
|
||||||
use parry::bounding_volume::Aabb;
|
use parry::bounding_volume::{Aabb, BoundingVolume};
|
||||||
use parry::shape::{Shape, TriMeshFlags};
|
use parry::shape::{Shape, TriMeshFlags};
|
||||||
|
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
use crate::geometry::HeightFieldFlags;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
/// A geometric entity that can be attached to a body so it can be affected by contacts and proximity queries.
|
/// A geometric entity that can be attached to a body so it can be affected by contacts and proximity queries.
|
||||||
@@ -27,6 +30,7 @@ pub struct Collider {
|
|||||||
pub(crate) material: ColliderMaterial,
|
pub(crate) material: ColliderMaterial,
|
||||||
pub(crate) flags: ColliderFlags,
|
pub(crate) flags: ColliderFlags,
|
||||||
pub(crate) bf_data: ColliderBroadPhaseData,
|
pub(crate) bf_data: ColliderBroadPhaseData,
|
||||||
|
contact_skin: Real,
|
||||||
contact_force_event_threshold: Real,
|
contact_force_event_threshold: Real,
|
||||||
/// User-defined data associated to this collider.
|
/// User-defined data associated to this collider.
|
||||||
pub user_data: u128,
|
pub user_data: u128,
|
||||||
@@ -50,6 +54,21 @@ impl Collider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An internal index associated to this collider by the broad-phase algorithm.
|
||||||
|
pub fn internal_broad_phase_proxy_index(&self) -> BroadPhaseProxyIndex {
|
||||||
|
self.bf_data.proxy_index
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the internal index associated to this collider by the broad-phase algorithm.
|
||||||
|
///
|
||||||
|
/// This must **not** be called, unless you are implementing your own custom broad-phase
|
||||||
|
/// that require storing an index in the collider struct.
|
||||||
|
/// Modifying that index outside of a custom broad-phase code will most certainly break
|
||||||
|
/// the physics engine.
|
||||||
|
pub fn set_internal_broad_phase_proxy_index(&mut self, id: BroadPhaseProxyIndex) {
|
||||||
|
self.bf_data.proxy_index = id;
|
||||||
|
}
|
||||||
|
|
||||||
/// The rigid body this collider is attached to.
|
/// The rigid body this collider is attached to.
|
||||||
pub fn parent(&self) -> Option<RigidBodyHandle> {
|
pub fn parent(&self) -> Option<RigidBodyHandle> {
|
||||||
self.parent.map(|parent| parent.handle)
|
self.parent.map(|parent| parent.handle)
|
||||||
@@ -60,6 +79,55 @@ impl Collider {
|
|||||||
self.coll_type.is_sensor()
|
self.coll_type.is_sensor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copy all the characteristics from `other` to `self`.
|
||||||
|
///
|
||||||
|
/// If you have a mutable reference to a collider `collider: &mut Collider`, attempting to
|
||||||
|
/// assign it a whole new collider instance, e.g., `*collider = ColliderBuilder::ball(0.5).build()`,
|
||||||
|
/// will crash due to some internal indices being overwritten. Instead, use
|
||||||
|
/// `collider.copy_from(&ColliderBuilder::ball(0.5).build())`.
|
||||||
|
///
|
||||||
|
/// This method will allow you to set most characteristics of this collider from another
|
||||||
|
/// collider instance without causing any breakage.
|
||||||
|
///
|
||||||
|
/// This method **cannot** be used for reparenting a collider. Therefore, the parent of the
|
||||||
|
/// `other` (if any), as well as its relative position to that parent will not be copied into
|
||||||
|
/// `self`.
|
||||||
|
///
|
||||||
|
/// The pose of `other` will only copied into `self` if `self` doesn’t have a parent (if it has
|
||||||
|
/// a parent, its position is directly controlled by the parent rigid-body).
|
||||||
|
pub fn copy_from(&mut self, other: &Collider) {
|
||||||
|
// NOTE: we deconstruct the collider struct to be sure we don’t forget to
|
||||||
|
// add some copies here if we add more field to Collider in the future.
|
||||||
|
let Collider {
|
||||||
|
coll_type,
|
||||||
|
shape,
|
||||||
|
mprops,
|
||||||
|
changes: _changes, // Will be set to ALL.
|
||||||
|
parent: _parent, // This function cannot be used to reparent the collider.
|
||||||
|
pos,
|
||||||
|
material,
|
||||||
|
flags,
|
||||||
|
bf_data: _bf_data, // Internal ids must not be overwritten.
|
||||||
|
contact_force_event_threshold,
|
||||||
|
user_data,
|
||||||
|
contact_skin,
|
||||||
|
} = other;
|
||||||
|
|
||||||
|
if self.parent.is_none() {
|
||||||
|
self.pos = *pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.coll_type = *coll_type;
|
||||||
|
self.shape = shape.clone();
|
||||||
|
self.mprops = mprops.clone();
|
||||||
|
self.material = *material;
|
||||||
|
self.contact_force_event_threshold = *contact_force_event_threshold;
|
||||||
|
self.user_data = *user_data;
|
||||||
|
self.flags = *flags;
|
||||||
|
self.changes = ColliderChanges::all();
|
||||||
|
self.contact_skin = *contact_skin;
|
||||||
|
}
|
||||||
|
|
||||||
/// The physics hooks enabled for this collider.
|
/// The physics hooks enabled for this collider.
|
||||||
pub fn active_hooks(&self) -> ActiveHooks {
|
pub fn active_hooks(&self) -> ActiveHooks {
|
||||||
self.flags.active_hooks
|
self.flags.active_hooks
|
||||||
@@ -90,6 +158,20 @@ impl Collider {
|
|||||||
self.flags.active_collision_types = active_collision_types;
|
self.flags.active_collision_types = active_collision_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The contact skin of this collider.
|
||||||
|
///
|
||||||
|
/// See the documentation of [`ColliderBuilder::contact_skin`] for details.
|
||||||
|
pub fn contact_skin(&self) -> Real {
|
||||||
|
self.contact_skin
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the contact skin of this collider.
|
||||||
|
///
|
||||||
|
/// See the documentation of [`ColliderBuilder::contact_skin`] for details.
|
||||||
|
pub fn set_contact_skin(&mut self, skin_thickness: Real) {
|
||||||
|
self.contact_skin = skin_thickness;
|
||||||
|
}
|
||||||
|
|
||||||
/// The friction coefficient of this collider.
|
/// The friction coefficient of this collider.
|
||||||
pub fn friction(&self) -> Real {
|
pub fn friction(&self) -> Real {
|
||||||
self.material.friction
|
self.material.friction
|
||||||
@@ -224,7 +306,7 @@ impl Collider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the rotational part of this collider's rotaiton relative to its parent rigid-body.
|
/// Sets the rotational part of this collider's rotation relative to its parent rigid-body.
|
||||||
pub fn set_rotation_wrt_parent(&mut self, rotation: AngVector<Real>) {
|
pub fn set_rotation_wrt_parent(&mut self, rotation: AngVector<Real>) {
|
||||||
if let Some(parent) = self.parent.as_mut() {
|
if let Some(parent) = self.parent.as_mut() {
|
||||||
self.changes.insert(ColliderChanges::PARENT);
|
self.changes.insert(ColliderChanges::PARENT);
|
||||||
@@ -372,10 +454,21 @@ impl Collider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the axis-aligned bounding box of this collider.
|
/// Compute the axis-aligned bounding box of this collider.
|
||||||
|
///
|
||||||
|
/// This AABB doesn’t take into account the collider’s contact skin.
|
||||||
|
/// [`Collider::contact_skin`].
|
||||||
pub fn compute_aabb(&self) -> Aabb {
|
pub fn compute_aabb(&self) -> Aabb {
|
||||||
self.shape.compute_aabb(&self.pos)
|
self.shape.compute_aabb(&self.pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute the axis-aligned bounding box of this collider, taking into account the
|
||||||
|
/// [`Collider::contact_skin`] and prediction distance.
|
||||||
|
pub fn compute_collision_aabb(&self, prediction: Real) -> Aabb {
|
||||||
|
self.shape
|
||||||
|
.compute_aabb(&self.pos)
|
||||||
|
.loosened(self.contact_skin + prediction)
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute the axis-aligned bounding box of this collider moving from its current position
|
/// Compute the axis-aligned bounding box of this collider moving from its current position
|
||||||
/// to the given `next_position`
|
/// to the given `next_position`
|
||||||
pub fn compute_swept_aabb(&self, next_position: &Isometry<Real>) -> Aabb {
|
pub fn compute_swept_aabb(&self, next_position: &Isometry<Real>) -> Aabb {
|
||||||
@@ -430,6 +523,8 @@ pub struct ColliderBuilder {
|
|||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
/// The total force magnitude beyond which a contact force event can be emitted.
|
/// The total force magnitude beyond which a contact force event can be emitted.
|
||||||
pub contact_force_event_threshold: Real,
|
pub contact_force_event_threshold: Real,
|
||||||
|
/// An extra thickness around the collider shape to keep them further apart when colliding.
|
||||||
|
pub contact_skin: Real,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColliderBuilder {
|
impl ColliderBuilder {
|
||||||
@@ -452,6 +547,7 @@ impl ColliderBuilder {
|
|||||||
active_events: ActiveEvents::empty(),
|
active_events: ActiveEvents::empty(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
contact_force_event_threshold: 0.0,
|
contact_force_event_threshold: 0.0,
|
||||||
|
contact_skin: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +614,15 @@ impl ColliderBuilder {
|
|||||||
Self::new(SharedShape::round_cuboid(hx, hy, border_radius))
|
Self::new(SharedShape::round_cuboid(hx, hy, border_radius))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize a new collider builder with a capsule defined from its endpoints.
|
||||||
|
///
|
||||||
|
/// See also [`ColliderBuilder::capsule_x`], [`ColliderBuilder::capsule_y`], and
|
||||||
|
/// [`ColliderBuilder::capsule_z`], for a simpler way to build capsules with common
|
||||||
|
/// orientations.
|
||||||
|
pub fn capsule_from_endpoints(a: Point<Real>, b: Point<Real>, radius: Real) -> Self {
|
||||||
|
Self::new(SharedShape::capsule(a, b, radius))
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize a new collider builder with a capsule shape aligned with the `x` axis.
|
/// Initialize a new collider builder with a capsule shape aligned with the `x` axis.
|
||||||
pub fn capsule_x(half_height: Real, radius: Real) -> Self {
|
pub fn capsule_x(half_height: Real, radius: Real) -> Self {
|
||||||
Self::new(SharedShape::capsule_x(half_height, radius))
|
Self::new(SharedShape::capsule_x(half_height, radius))
|
||||||
@@ -698,6 +803,17 @@ impl ColliderBuilder {
|
|||||||
Self::new(SharedShape::heightfield(heights, scale))
|
Self::new(SharedShape::heightfield(heights, scale))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes a collider builder with a heightfield shape defined by its set of height and a scale
|
||||||
|
/// factor along each coordinate axis.
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
pub fn heightfield_with_flags(
|
||||||
|
heights: na::DMatrix<Real>,
|
||||||
|
scale: Vector<Real>,
|
||||||
|
flags: HeightFieldFlags,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(SharedShape::heightfield_with_flags(heights, scale, flags))
|
||||||
|
}
|
||||||
|
|
||||||
/// The default friction coefficient used by the collider builder.
|
/// The default friction coefficient used by the collider builder.
|
||||||
pub fn default_friction() -> Real {
|
pub fn default_friction() -> Real {
|
||||||
0.5
|
0.5
|
||||||
@@ -705,7 +821,7 @@ impl ColliderBuilder {
|
|||||||
|
|
||||||
/// The default density used by the collider builder.
|
/// The default density used by the collider builder.
|
||||||
pub fn default_density() -> Real {
|
pub fn default_density() -> Real {
|
||||||
1.0
|
100.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets an arbitrary user-defined 128-bit integer associated to the colliders built by this builder.
|
/// Sets an arbitrary user-defined 128-bit integer associated to the colliders built by this builder.
|
||||||
@@ -861,6 +977,20 @@ impl ColliderBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the contact skin of the collider.
|
||||||
|
///
|
||||||
|
/// The contact skin acts as if the collider was enlarged with a skin of width `skin_thickness`
|
||||||
|
/// around it, keeping objects further apart when colliding.
|
||||||
|
///
|
||||||
|
/// A non-zero contact skin can increase performance, and in some cases, stability. However
|
||||||
|
/// it creates a small gap between colliding object (equal to the sum of their skin). If the
|
||||||
|
/// skin is sufficiently small, this might not be visually significant or can be hidden by the
|
||||||
|
/// rendering assets.
|
||||||
|
pub fn contact_skin(mut self, skin_thickness: Real) -> Self {
|
||||||
|
self.contact_skin = skin_thickness;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Enable or disable the collider after its creation.
|
/// Enable or disable the collider after its creation.
|
||||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||||
self.enabled = enabled;
|
self.enabled = enabled;
|
||||||
@@ -908,6 +1038,7 @@ impl ColliderBuilder {
|
|||||||
flags,
|
flags,
|
||||||
coll_type,
|
coll_type,
|
||||||
contact_force_event_threshold: self.contact_force_event_threshold,
|
contact_force_event_threshold: self.contact_force_event_threshold,
|
||||||
|
contact_skin: self.contact_skin,
|
||||||
user_data: self.user_data,
|
user_data: self.user_data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::dynamics::{CoefficientCombineRule, MassProperties, RigidBodyHandle, RigidBodyType};
|
use crate::dynamics::{CoefficientCombineRule, MassProperties, RigidBodyHandle, RigidBodyType};
|
||||||
use crate::geometry::{InteractionGroups, SAPProxyIndex, Shape, SharedShape};
|
use crate::geometry::{BroadPhaseProxyIndex, InteractionGroups, Shape, SharedShape};
|
||||||
use crate::math::{Isometry, Real};
|
use crate::math::{Isometry, Real};
|
||||||
use crate::parry::partitioning::IndexedData;
|
use crate::parry::partitioning::IndexedData;
|
||||||
use crate::pipeline::{ActiveEvents, ActiveHooks};
|
use crate::pipeline::{ActiveEvents, ActiveHooks};
|
||||||
@@ -118,7 +118,7 @@ impl ColliderType {
|
|||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
/// Data associated to a collider that takes part to a broad-phase algorithm.
|
/// Data associated to a collider that takes part to a broad-phase algorithm.
|
||||||
pub struct ColliderBroadPhaseData {
|
pub struct ColliderBroadPhaseData {
|
||||||
pub(crate) proxy_index: SAPProxyIndex,
|
pub(crate) proxy_index: BroadPhaseProxyIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ColliderBroadPhaseData {
|
impl Default for ColliderBroadPhaseData {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::dynamics::{RigidBodyHandle, RigidBodySet};
|
use crate::dynamics::{RigidBodyHandle, RigidBodySet};
|
||||||
use crate::geometry::{ColliderHandle, ColliderSet, Contact, ContactManifold};
|
use crate::geometry::{ColliderHandle, ColliderSet, Contact, ContactManifold};
|
||||||
use crate::math::{Point, Real, Vector};
|
use crate::math::{Point, Real, TangentImpulse, Vector};
|
||||||
use crate::pipeline::EventHandler;
|
use crate::pipeline::EventHandler;
|
||||||
use crate::prelude::CollisionEventFlags;
|
use crate::prelude::CollisionEventFlags;
|
||||||
use parry::query::ContactManifoldsWorkspace;
|
use parry::query::ContactManifoldsWorkspace;
|
||||||
@@ -33,12 +33,11 @@ pub struct ContactData {
|
|||||||
pub impulse: Real,
|
pub impulse: Real,
|
||||||
/// The friction impulse along the vector orthonormal to the contact normal, applied to the first
|
/// The friction impulse along the vector orthonormal to the contact normal, applied to the first
|
||||||
/// collider's rigid-body.
|
/// collider's rigid-body.
|
||||||
#[cfg(feature = "dim2")]
|
pub tangent_impulse: TangentImpulse<Real>,
|
||||||
pub tangent_impulse: Real,
|
/// The impulse retained for warmstarting the next simulation step.
|
||||||
/// The friction impulses along the basis orthonormal to the contact normal, applied to the first
|
pub warmstart_impulse: Real,
|
||||||
/// collider's rigid-body.
|
/// The friction impulse retained for warmstarting the next simulation step.
|
||||||
#[cfg(feature = "dim3")]
|
pub warmstart_tangent_impulse: TangentImpulse<Real>,
|
||||||
pub tangent_impulse: na::Vector2<Real>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ContactData {
|
impl Default for ContactData {
|
||||||
@@ -46,6 +45,8 @@ impl Default for ContactData {
|
|||||||
Self {
|
Self {
|
||||||
impulse: 0.0,
|
impulse: 0.0,
|
||||||
tangent_impulse: na::zero(),
|
tangent_impulse: na::zero(),
|
||||||
|
warmstart_impulse: 0.0,
|
||||||
|
warmstart_tangent_impulse: na::zero(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,14 +58,14 @@ pub struct IntersectionPair {
|
|||||||
/// Are the colliders intersecting?
|
/// Are the colliders intersecting?
|
||||||
pub intersecting: bool,
|
pub intersecting: bool,
|
||||||
/// Was a `CollisionEvent::Started` emitted for this collider?
|
/// Was a `CollisionEvent::Started` emitted for this collider?
|
||||||
pub(crate) start_event_emited: bool,
|
pub(crate) start_event_emitted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntersectionPair {
|
impl IntersectionPair {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
intersecting: false,
|
intersecting: false,
|
||||||
start_event_emited: false,
|
start_event_emitted: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ impl IntersectionPair {
|
|||||||
collider2: ColliderHandle,
|
collider2: ColliderHandle,
|
||||||
events: &dyn EventHandler,
|
events: &dyn EventHandler,
|
||||||
) {
|
) {
|
||||||
self.start_event_emited = true;
|
self.start_event_emitted = true;
|
||||||
events.handle_collision_event(
|
events.handle_collision_event(
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
@@ -93,7 +94,7 @@ impl IntersectionPair {
|
|||||||
collider2: ColliderHandle,
|
collider2: ColliderHandle,
|
||||||
events: &dyn EventHandler,
|
events: &dyn EventHandler,
|
||||||
) {
|
) {
|
||||||
self.start_event_emited = false;
|
self.start_event_emitted = false;
|
||||||
events.handle_collision_event(
|
events.handle_collision_event(
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
@@ -114,11 +115,14 @@ pub struct ContactPair {
|
|||||||
/// The set of contact manifolds between the two colliders.
|
/// The set of contact manifolds between the two colliders.
|
||||||
///
|
///
|
||||||
/// All contact manifold contain themselves contact points between the colliders.
|
/// All contact manifold contain themselves contact points between the colliders.
|
||||||
|
/// Note that contact points in the contact manifold do not take into account the
|
||||||
|
/// [`Collider::contact_skin`] which only affects the constraint solver and the
|
||||||
|
/// [`SolverContact`].
|
||||||
pub manifolds: Vec<ContactManifold>,
|
pub manifolds: Vec<ContactManifold>,
|
||||||
/// Is there any active contact in this contact pair?
|
/// Is there any active contact in this contact pair?
|
||||||
pub has_any_active_contact: bool,
|
pub has_any_active_contact: bool,
|
||||||
/// Was a `CollisionEvent::Started` emitted for this collider?
|
/// Was a `CollisionEvent::Started` emitted for this collider?
|
||||||
pub(crate) start_event_emited: bool,
|
pub(crate) start_event_emitted: bool,
|
||||||
pub(crate) workspace: Option<ContactManifoldsWorkspace>,
|
pub(crate) workspace: Option<ContactManifoldsWorkspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +133,7 @@ impl ContactPair {
|
|||||||
collider2,
|
collider2,
|
||||||
has_any_active_contact: false,
|
has_any_active_contact: false,
|
||||||
manifolds: Vec::new(),
|
manifolds: Vec::new(),
|
||||||
start_event_emited: false,
|
start_event_emitted: false,
|
||||||
workspace: None,
|
workspace: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +210,7 @@ impl ContactPair {
|
|||||||
colliders: &ColliderSet,
|
colliders: &ColliderSet,
|
||||||
events: &dyn EventHandler,
|
events: &dyn EventHandler,
|
||||||
) {
|
) {
|
||||||
self.start_event_emited = true;
|
self.start_event_emitted = true;
|
||||||
|
|
||||||
events.handle_collision_event(
|
events.handle_collision_event(
|
||||||
bodies,
|
bodies,
|
||||||
@@ -222,7 +226,7 @@ impl ContactPair {
|
|||||||
colliders: &ColliderSet,
|
colliders: &ColliderSet,
|
||||||
events: &dyn EventHandler,
|
events: &dyn EventHandler,
|
||||||
) {
|
) {
|
||||||
self.start_event_emited = false;
|
self.start_event_emitted = false;
|
||||||
|
|
||||||
events.handle_collision_event(
|
events.handle_collision_event(
|
||||||
bodies,
|
bodies,
|
||||||
@@ -299,6 +303,10 @@ pub struct SolverContact {
|
|||||||
pub tangent_velocity: Vector<Real>,
|
pub tangent_velocity: Vector<Real>,
|
||||||
/// Whether or not this contact existed during the last timestep.
|
/// Whether or not this contact existed during the last timestep.
|
||||||
pub is_new: bool,
|
pub is_new: bool,
|
||||||
|
/// Impulse used to warmstart the solve for the normal constraint.
|
||||||
|
pub warmstart_impulse: Real,
|
||||||
|
/// Impulse used to warmstart the solve for the friction constraints.
|
||||||
|
pub warmstart_tangent_impulse: TangentImpulse<Real>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SolverContact {
|
impl SolverContact {
|
||||||
@@ -351,16 +359,10 @@ impl ContactManifoldData {
|
|||||||
pub trait ContactManifoldExt {
|
pub trait ContactManifoldExt {
|
||||||
/// Computes the sum of all the impulses applied by contacts from this contact manifold.
|
/// Computes the sum of all the impulses applied by contacts from this contact manifold.
|
||||||
fn total_impulse(&self) -> Real;
|
fn total_impulse(&self) -> Real;
|
||||||
/// Computes the maximum impulse applied by contacts from this contact manifold.
|
|
||||||
fn max_impulse(&self) -> Real;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactManifoldExt for ContactManifold {
|
impl ContactManifoldExt for ContactManifold {
|
||||||
fn total_impulse(&self) -> Real {
|
fn total_impulse(&self) -> Real {
|
||||||
self.points.iter().map(|pt| pt.data.impulse).sum()
|
self.points.iter().map(|pt| pt.data.impulse).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_impulse(&self) -> Real {
|
|
||||||
self.points.iter().fold(0.0, |a, pt| a.max(pt.data.impulse))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
//! Structures related to geometry: colliders, shapes, etc.
|
//! Structures related to geometry: colliders, shapes, etc.
|
||||||
|
|
||||||
pub use self::broad_phase_multi_sap::{BroadPhasePairEvent, ColliderPair};
|
pub use self::broad_phase::BroadPhase;
|
||||||
|
pub use self::broad_phase_multi_sap::{BroadPhaseMultiSap, BroadPhasePairEvent, ColliderPair};
|
||||||
pub use self::broad_phase_multi_sap::BroadPhase;
|
|
||||||
// pub use self::broad_phase_qbvh::BroadPhase;
|
|
||||||
pub use self::collider_components::*;
|
pub use self::collider_components::*;
|
||||||
pub use self::contact_pair::{
|
pub use self::contact_pair::{
|
||||||
ContactData, ContactManifoldData, ContactPair, IntersectionPair, SolverContact, SolverFlags,
|
ContactData, ContactManifoldData, ContactPair, IntersectionPair, SolverContact, SolverFlags,
|
||||||
@@ -51,10 +49,12 @@ pub type Aabb = parry::bounding_volume::Aabb;
|
|||||||
pub type Ray = parry::query::Ray;
|
pub type Ray = parry::query::Ray;
|
||||||
/// The intersection between a ray and a collider.
|
/// The intersection between a ray and a collider.
|
||||||
pub type RayIntersection = parry::query::RayIntersection;
|
pub type RayIntersection = parry::query::RayIntersection;
|
||||||
/// The the projection of a point on a collider.
|
/// The projection of a point on a collider.
|
||||||
pub type PointProjection = parry::query::PointProjection;
|
pub type PointProjection = parry::query::PointProjection;
|
||||||
/// The the time of impact between two shapes.
|
/// The result of a shape-cast between two shapes.
|
||||||
pub type TOI = parry::query::TOI;
|
pub type ShapeCastHit = parry::query::ShapeCastHit;
|
||||||
|
/// The default broad-phase implementation recommended for general-purpose usage.
|
||||||
|
pub type DefaultBroadPhase = BroadPhaseMultiSap;
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
/// Flags providing more information regarding a collision event.
|
/// Flags providing more information regarding a collision event.
|
||||||
@@ -180,7 +180,7 @@ impl ContactForceEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use self::broad_phase_multi_sap::SAPProxyIndex;
|
pub(crate) use self::broad_phase::BroadPhaseProxyIndex;
|
||||||
pub(crate) use self::narrow_phase::ContactManifoldIndex;
|
pub(crate) use self::narrow_phase::ContactManifoldIndex;
|
||||||
pub(crate) use parry::partitioning::Qbvh;
|
pub(crate) use parry::partitioning::Qbvh;
|
||||||
pub use parry::shape::*;
|
pub use parry::shape::*;
|
||||||
@@ -203,6 +203,7 @@ mod interaction_graph;
|
|||||||
mod interaction_groups;
|
mod interaction_groups;
|
||||||
mod narrow_phase;
|
mod narrow_phase;
|
||||||
|
|
||||||
|
mod broad_phase;
|
||||||
mod broad_phase_qbvh;
|
mod broad_phase_qbvh;
|
||||||
mod collider;
|
mod collider;
|
||||||
mod collider_set;
|
mod collider_set;
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ use crate::dynamics::{
|
|||||||
RigidBodyType,
|
RigidBodyType,
|
||||||
};
|
};
|
||||||
use crate::geometry::{
|
use crate::geometry::{
|
||||||
BroadPhasePairEvent, ColliderChanges, ColliderGraphIndex, ColliderHandle, ColliderPair,
|
BoundingVolume, BroadPhasePairEvent, ColliderChanges, ColliderGraphIndex, ColliderHandle,
|
||||||
ColliderSet, CollisionEvent, ContactData, ContactManifold, ContactManifoldData, ContactPair,
|
ColliderPair, ColliderSet, CollisionEvent, ContactData, ContactManifold, ContactManifoldData,
|
||||||
InteractionGraph, IntersectionPair, SolverContact, SolverFlags, TemporaryInteractionIndex,
|
ContactPair, InteractionGraph, IntersectionPair, SolverContact, SolverFlags,
|
||||||
|
TemporaryInteractionIndex,
|
||||||
};
|
};
|
||||||
use crate::math::{Real, Vector};
|
use crate::math::{Real, Vector};
|
||||||
use crate::pipeline::{
|
use crate::pipeline::{
|
||||||
@@ -342,7 +343,7 @@ impl NarrowPhase {
|
|||||||
islands.wake_up(bodies, parent.handle, true)
|
islands.wake_up(bodies, parent.handle, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pair.start_event_emited {
|
if pair.start_event_emitted {
|
||||||
events.handle_collision_event(
|
events.handle_collision_event(
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
@@ -354,7 +355,7 @@ impl NarrowPhase {
|
|||||||
} else {
|
} else {
|
||||||
// If there is no island, don’t wake-up bodies, but do send the Stopped collision event.
|
// If there is no island, don’t wake-up bodies, but do send the Stopped collision event.
|
||||||
for (a, b, pair) in self.contact_graph.interactions_with(contact_graph_id) {
|
for (a, b, pair) in self.contact_graph.interactions_with(contact_graph_id) {
|
||||||
if pair.start_event_emited {
|
if pair.start_event_emitted {
|
||||||
events.handle_collision_event(
|
events.handle_collision_event(
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
@@ -370,7 +371,7 @@ impl NarrowPhase {
|
|||||||
.intersection_graph
|
.intersection_graph
|
||||||
.interactions_with(intersection_graph_id)
|
.interactions_with(intersection_graph_id)
|
||||||
{
|
{
|
||||||
if pair.start_event_emited {
|
if pair.start_event_emitted {
|
||||||
events.handle_collision_event(
|
events.handle_collision_event(
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
@@ -709,7 +710,6 @@ impl NarrowPhase {
|
|||||||
let co1 = &colliders[handle1];
|
let co1 = &colliders[handle1];
|
||||||
let co2 = &colliders[handle2];
|
let co2 = &colliders[handle2];
|
||||||
|
|
||||||
// TODO: remove the `loop` once labels on blocks is stabilized.
|
|
||||||
'emit_events: {
|
'emit_events: {
|
||||||
if !co1.changes.needs_narrow_phase_update()
|
if !co1.changes.needs_narrow_phase_update()
|
||||||
&& !co2.changes.needs_narrow_phase_update()
|
&& !co2.changes.needs_narrow_phase_update()
|
||||||
@@ -767,7 +767,6 @@ impl NarrowPhase {
|
|||||||
edge.weight.intersecting = query_dispatcher
|
edge.weight.intersecting = query_dispatcher
|
||||||
.intersection_test(&pos12, &*co1.shape, &*co2.shape)
|
.intersection_test(&pos12, &*co1.shape, &*co2.shape)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
break 'emit_events;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_events = co1.flags.active_events | co2.flags.active_events;
|
let active_events = co1.flags.active_events | co2.flags.active_events;
|
||||||
@@ -789,6 +788,7 @@ impl NarrowPhase {
|
|||||||
pub(crate) fn compute_contacts(
|
pub(crate) fn compute_contacts(
|
||||||
&mut self,
|
&mut self,
|
||||||
prediction_distance: Real,
|
prediction_distance: Real,
|
||||||
|
dt: Real,
|
||||||
bodies: &RigidBodySet,
|
bodies: &RigidBodySet,
|
||||||
colliders: &ColliderSet,
|
colliders: &ColliderSet,
|
||||||
impulse_joints: &ImpulseJointSet,
|
impulse_joints: &ImpulseJointSet,
|
||||||
@@ -810,7 +810,6 @@ impl NarrowPhase {
|
|||||||
let co1 = &colliders[pair.collider1];
|
let co1 = &colliders[pair.collider1];
|
||||||
let co2 = &colliders[pair.collider2];
|
let co2 = &colliders[pair.collider2];
|
||||||
|
|
||||||
// TODO: remove the `loop` once labels on blocks are supported.
|
|
||||||
'emit_events: {
|
'emit_events: {
|
||||||
if !co1.changes.needs_narrow_phase_update()
|
if !co1.changes.needs_narrow_phase_update()
|
||||||
&& !co2.changes.needs_narrow_phase_update()
|
&& !co2.changes.needs_narrow_phase_update()
|
||||||
@@ -819,17 +818,11 @@ impl NarrowPhase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: avoid lookup into bodies.
|
let rb1 = co1.parent.map(|co_parent1| &bodies[co_parent1.handle]);
|
||||||
let mut rb_type1 = RigidBodyType::Fixed;
|
let rb2 = co2.parent.map(|co_parent2| &bodies[co_parent2.handle]);
|
||||||
let mut rb_type2 = RigidBodyType::Fixed;
|
|
||||||
|
|
||||||
if let Some(co_parent1) = &co1.parent {
|
let rb_type1 = rb1.map(|rb| rb.body_type).unwrap_or(RigidBodyType::Fixed);
|
||||||
rb_type1 = bodies[co_parent1.handle].body_type;
|
let rb_type2 = rb2.map(|rb| rb.body_type).unwrap_or(RigidBodyType::Fixed);
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(co_parent2) = &co2.parent {
|
|
||||||
rb_type2 = bodies[co_parent2.handle].body_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with contacts disabled between bodies attached by joints.
|
// Deal with contacts disabled between bodies attached by joints.
|
||||||
if let (Some(co_parent1), Some(co_parent2)) = (&co1.parent, &co2.parent) {
|
if let (Some(co_parent1), Some(co_parent2)) = (&co1.parent, &co2.parent) {
|
||||||
@@ -901,11 +894,37 @@ impl NarrowPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pos12 = co1.pos.inv_mul(&co2.pos);
|
let pos12 = co1.pos.inv_mul(&co2.pos);
|
||||||
|
|
||||||
|
let contact_skin_sum = co1.contact_skin() + co2.contact_skin();
|
||||||
|
let soft_ccd_prediction1 = rb1.map(|rb| rb.soft_ccd_prediction()).unwrap_or(0.0);
|
||||||
|
let soft_ccd_prediction2 = rb2.map(|rb| rb.soft_ccd_prediction()).unwrap_or(0.0);
|
||||||
|
let effective_prediction_distance = if soft_ccd_prediction1 > 0.0 || soft_ccd_prediction2 > 0.0 {
|
||||||
|
let aabb1 = co1.compute_collision_aabb(0.0);
|
||||||
|
let aabb2 = co2.compute_collision_aabb(0.0);
|
||||||
|
let inv_dt = crate::utils::inv(dt);
|
||||||
|
|
||||||
|
let linvel1 = rb1.map(|rb| rb.linvel()
|
||||||
|
.cap_magnitude(soft_ccd_prediction1 * inv_dt)).unwrap_or_default();
|
||||||
|
let linvel2 = rb2.map(|rb| rb.linvel()
|
||||||
|
.cap_magnitude(soft_ccd_prediction2 * inv_dt)).unwrap_or_default();
|
||||||
|
|
||||||
|
if !aabb1.intersects(&aabb2) && !aabb1.intersects_moving_aabb(&aabb2, linvel2 - linvel1) {
|
||||||
|
pair.clear();
|
||||||
|
break 'emit_events;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
prediction_distance.max(
|
||||||
|
dt * (linvel1 - linvel2).norm()) + contact_skin_sum
|
||||||
|
} else {
|
||||||
|
prediction_distance + contact_skin_sum
|
||||||
|
};
|
||||||
|
|
||||||
let _ = query_dispatcher.contact_manifolds(
|
let _ = query_dispatcher.contact_manifolds(
|
||||||
&pos12,
|
&pos12,
|
||||||
&*co1.shape,
|
&*co1.shape,
|
||||||
&*co2.shape,
|
&*co2.shape,
|
||||||
prediction_distance,
|
effective_prediction_distance,
|
||||||
&mut pair.manifolds,
|
&mut pair.manifolds,
|
||||||
&mut pair.workspace,
|
&mut pair.workspace,
|
||||||
);
|
);
|
||||||
@@ -924,14 +943,8 @@ impl NarrowPhase {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let zero = RigidBodyDominance(0); // The value doesn't matter, it will be MAX because of the effective groups.
|
let zero = RigidBodyDominance(0); // The value doesn't matter, it will be MAX because of the effective groups.
|
||||||
let dominance1 = co1
|
let dominance1 = rb1.map(|rb| rb.dominance).unwrap_or(zero);
|
||||||
.parent
|
let dominance2 = rb2.map(|rb| rb.dominance).unwrap_or(zero);
|
||||||
.map(|p1| bodies[p1.handle].dominance)
|
|
||||||
.unwrap_or(zero);
|
|
||||||
let dominance2 = co2
|
|
||||||
.parent
|
|
||||||
.map(|p2| bodies[p2.handle].dominance)
|
|
||||||
.unwrap_or(zero);
|
|
||||||
|
|
||||||
pair.has_any_active_contact = false;
|
pair.has_any_active_contact = false;
|
||||||
|
|
||||||
@@ -948,12 +961,22 @@ impl NarrowPhase {
|
|||||||
|
|
||||||
// Generate solver contacts.
|
// Generate solver contacts.
|
||||||
for (contact_id, contact) in manifold.points.iter().enumerate() {
|
for (contact_id, contact) in manifold.points.iter().enumerate() {
|
||||||
assert!(
|
if contact_id > u8::MAX as usize {
|
||||||
contact_id <= u8::MAX as usize,
|
log::warn!("A contact manifold cannot contain more than 255 contacts currently, dropping contact in excess.");
|
||||||
"A contact manifold cannot contain more than 255 contacts currently."
|
break;
|
||||||
);
|
}
|
||||||
|
|
||||||
if contact.dist < prediction_distance {
|
let effective_contact_dist = contact.dist - co1.contact_skin() - co2.contact_skin();
|
||||||
|
|
||||||
|
let keep_solver_contact = effective_contact_dist < prediction_distance || {
|
||||||
|
let world_pt1 = world_pos1 * contact.local_p1;
|
||||||
|
let world_pt2 = world_pos2 * contact.local_p2;
|
||||||
|
let vel1 = rb1.map(|rb| rb.velocity_at_point(&world_pt1)).unwrap_or_default();
|
||||||
|
let vel2 = rb2.map(|rb| rb.velocity_at_point(&world_pt2)).unwrap_or_default();
|
||||||
|
effective_contact_dist + (vel2 - vel1).dot(&manifold.data.normal) * dt < prediction_distance
|
||||||
|
};
|
||||||
|
|
||||||
|
if keep_solver_contact {
|
||||||
// Generate the solver contact.
|
// Generate the solver contact.
|
||||||
let world_pt1 = world_pos1 * contact.local_p1;
|
let world_pt1 = world_pos1 * contact.local_p1;
|
||||||
let world_pt2 = world_pos2 * contact.local_p2;
|
let world_pt2 = world_pos2 * contact.local_p2;
|
||||||
@@ -962,11 +985,13 @@ impl NarrowPhase {
|
|||||||
let solver_contact = SolverContact {
|
let solver_contact = SolverContact {
|
||||||
contact_id: contact_id as u8,
|
contact_id: contact_id as u8,
|
||||||
point: effective_point,
|
point: effective_point,
|
||||||
dist: contact.dist,
|
dist: effective_contact_dist,
|
||||||
friction,
|
friction,
|
||||||
restitution,
|
restitution,
|
||||||
tangent_velocity: Vector::zeros(),
|
tangent_velocity: Vector::zeros(),
|
||||||
is_new: contact.data.impulse == 0.0,
|
is_new: contact.data.impulse == 0.0,
|
||||||
|
warmstart_impulse: contact.data.warmstart_impulse,
|
||||||
|
warmstart_tangent_impulse: contact.data.warmstart_tangent_impulse,
|
||||||
};
|
};
|
||||||
|
|
||||||
manifold.data.solver_contacts.push(solver_contact);
|
manifold.data.solver_contacts.push(solver_contact);
|
||||||
@@ -1000,9 +1025,36 @@ impl NarrowPhase {
|
|||||||
manifold.data.normal = modifiable_normal;
|
manifold.data.normal = modifiable_normal;
|
||||||
manifold.data.user_data = modifiable_user_data;
|
manifold.data.user_data = modifiable_user_data;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
break 'emit_events;
|
/*
|
||||||
|
* TODO: When using the block solver in 3D, I’d expect this sort to help, but
|
||||||
|
* it makes the domino demo worse. Needs more investigation.
|
||||||
|
fn sort_solver_contacts(mut contacts: &mut [SolverContact]) {
|
||||||
|
while contacts.len() > 2 {
|
||||||
|
let first = contacts[0];
|
||||||
|
let mut furthest_id = 1;
|
||||||
|
let mut furthest_dist = na::distance(&first.point, &contacts[1].point);
|
||||||
|
|
||||||
|
for (candidate_id, candidate) in contacts.iter().enumerate().skip(2) {
|
||||||
|
let candidate_dist = na::distance(&first.point, &candidate.point);
|
||||||
|
|
||||||
|
if candidate_dist > furthest_dist {
|
||||||
|
furthest_dist = candidate_dist;
|
||||||
|
furthest_id = candidate_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if furthest_id > 1 {
|
||||||
|
contacts.swap(1, furthest_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
contacts = &mut contacts[2..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort_solver_contacts(&mut manifold.data.solver_contacts);
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_events = co1.flags.active_events | co2.flags.active_events;
|
let active_events = co1.flags.active_events | co2.flags.active_events;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
#![allow(clippy::needless_range_loop)] // TODO: remove this? I find that in the math code using indices adds clarity.
|
#![allow(clippy::needless_range_loop)] // TODO: remove this? I find that in the math code using indices adds clarity.
|
||||||
#![allow(clippy::module_inception)]
|
#![allow(clippy::module_inception)]
|
||||||
|
#![allow(unexpected_cfgs)] // This happens due to the dim2/dim3/f32/f64 cfg.
|
||||||
|
|
||||||
#[cfg(all(feature = "dim2", feature = "f32"))]
|
#[cfg(all(feature = "dim2", feature = "f32"))]
|
||||||
pub extern crate parry2d as parry;
|
pub extern crate parry2d as parry;
|
||||||
@@ -166,6 +167,10 @@ pub mod math {
|
|||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
pub type JacobianViewMut<'a, N> = na::MatrixViewMut3xX<'a, N>;
|
pub type JacobianViewMut<'a, N> = na::MatrixViewMut3xX<'a, N>;
|
||||||
|
|
||||||
|
/// The type of impulse applied for friction constraints.
|
||||||
|
#[cfg(feature = "dim2")]
|
||||||
|
pub type TangentImpulse<N> = na::Vector1<N>;
|
||||||
|
|
||||||
/// The maximum number of possible rotations and translations of a rigid body.
|
/// The maximum number of possible rotations and translations of a rigid body.
|
||||||
#[cfg(feature = "dim2")]
|
#[cfg(feature = "dim2")]
|
||||||
pub const SPATIAL_DIM: usize = 3;
|
pub const SPATIAL_DIM: usize = 3;
|
||||||
@@ -195,6 +200,10 @@ pub mod math {
|
|||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
pub type JacobianViewMut<'a, N> = na::MatrixViewMut6xX<'a, N>;
|
pub type JacobianViewMut<'a, N> = na::MatrixViewMut6xX<'a, N>;
|
||||||
|
|
||||||
|
/// The type of impulse applied for friction constraints.
|
||||||
|
#[cfg(feature = "dim3")]
|
||||||
|
pub type TangentImpulse<N> = na::Vector2<N>;
|
||||||
|
|
||||||
/// The maximum number of possible rotations and translations of a rigid body.
|
/// The maximum number of possible rotations and translations of a rigid body.
|
||||||
#[cfg(feature = "dim3")]
|
#[cfg(feature = "dim3")]
|
||||||
pub const SPATIAL_DIM: usize = 6;
|
pub const SPATIAL_DIM: usize = 6;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl CollisionPipeline {
|
|||||||
fn detect_collisions(
|
fn detect_collisions(
|
||||||
&mut self,
|
&mut self,
|
||||||
prediction_distance: Real,
|
prediction_distance: Real,
|
||||||
broad_phase: &mut BroadPhase,
|
broad_phase: &mut dyn BroadPhase,
|
||||||
narrow_phase: &mut NarrowPhase,
|
narrow_phase: &mut NarrowPhase,
|
||||||
bodies: &mut RigidBodySet,
|
bodies: &mut RigidBodySet,
|
||||||
colliders: &mut ColliderSet,
|
colliders: &mut ColliderSet,
|
||||||
@@ -58,8 +58,10 @@ impl CollisionPipeline {
|
|||||||
self.broadphase_collider_pairs.clear();
|
self.broadphase_collider_pairs.clear();
|
||||||
|
|
||||||
broad_phase.update(
|
broad_phase.update(
|
||||||
|
0.0,
|
||||||
prediction_distance,
|
prediction_distance,
|
||||||
colliders,
|
colliders,
|
||||||
|
bodies,
|
||||||
modified_colliders,
|
modified_colliders,
|
||||||
removed_colliders,
|
removed_colliders,
|
||||||
&mut self.broad_phase_events,
|
&mut self.broad_phase_events,
|
||||||
@@ -80,6 +82,7 @@ impl CollisionPipeline {
|
|||||||
narrow_phase.register_pairs(None, colliders, bodies, &self.broad_phase_events, events);
|
narrow_phase.register_pairs(None, colliders, bodies, &self.broad_phase_events, events);
|
||||||
narrow_phase.compute_contacts(
|
narrow_phase.compute_contacts(
|
||||||
prediction_distance,
|
prediction_distance,
|
||||||
|
0.0,
|
||||||
bodies,
|
bodies,
|
||||||
colliders,
|
colliders,
|
||||||
&ImpulseJointSet::new(),
|
&ImpulseJointSet::new(),
|
||||||
@@ -107,7 +110,7 @@ impl CollisionPipeline {
|
|||||||
pub fn step(
|
pub fn step(
|
||||||
&mut self,
|
&mut self,
|
||||||
prediction_distance: Real,
|
prediction_distance: Real,
|
||||||
broad_phase: &mut BroadPhase,
|
broad_phase: &mut dyn BroadPhase,
|
||||||
narrow_phase: &mut NarrowPhase,
|
narrow_phase: &mut NarrowPhase,
|
||||||
bodies: &mut RigidBodySet,
|
bodies: &mut RigidBodySet,
|
||||||
colliders: &mut ColliderSet,
|
colliders: &mut ColliderSet,
|
||||||
@@ -192,13 +195,13 @@ mod tests {
|
|||||||
let _ = collider_set.insert(collider_b);
|
let _ = collider_set.insert(collider_b);
|
||||||
|
|
||||||
let integration_parameters = IntegrationParameters::default();
|
let integration_parameters = IntegrationParameters::default();
|
||||||
let mut broad_phase = BroadPhase::new();
|
let mut broad_phase = BroadPhaseMultiSap::new();
|
||||||
let mut narrow_phase = NarrowPhase::new();
|
let mut narrow_phase = NarrowPhase::new();
|
||||||
let mut collision_pipeline = CollisionPipeline::new();
|
let mut collision_pipeline = CollisionPipeline::new();
|
||||||
let physics_hooks = ();
|
let physics_hooks = ();
|
||||||
|
|
||||||
collision_pipeline.step(
|
collision_pipeline.step(
|
||||||
integration_parameters.prediction_distance,
|
integration_parameters.prediction_distance(),
|
||||||
&mut broad_phase,
|
&mut broad_phase,
|
||||||
&mut narrow_phase,
|
&mut narrow_phase,
|
||||||
&mut rigid_body_set,
|
&mut rigid_body_set,
|
||||||
@@ -244,13 +247,13 @@ mod tests {
|
|||||||
let _ = collider_set.insert(collider_b);
|
let _ = collider_set.insert(collider_b);
|
||||||
|
|
||||||
let integration_parameters = IntegrationParameters::default();
|
let integration_parameters = IntegrationParameters::default();
|
||||||
let mut broad_phase = BroadPhase::new();
|
let mut broad_phase = BroadPhaseMultiSap::new();
|
||||||
let mut narrow_phase = NarrowPhase::new();
|
let mut narrow_phase = NarrowPhase::new();
|
||||||
let mut collision_pipeline = CollisionPipeline::new();
|
let mut collision_pipeline = CollisionPipeline::new();
|
||||||
let physics_hooks = ();
|
let physics_hooks = ();
|
||||||
|
|
||||||
collision_pipeline.step(
|
collision_pipeline.step(
|
||||||
integration_parameters.prediction_distance,
|
integration_parameters.prediction_distance(),
|
||||||
&mut broad_phase,
|
&mut broad_phase,
|
||||||
&mut narrow_phase,
|
&mut narrow_phase,
|
||||||
&mut rigid_body_set,
|
&mut rigid_body_set,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::math::{Isometry, Point, Real, Vector, DIM};
|
|||||||
use crate::pipeline::debug_render_pipeline::debug_render_backend::DebugRenderObject;
|
use crate::pipeline::debug_render_pipeline::debug_render_backend::DebugRenderObject;
|
||||||
use crate::pipeline::debug_render_pipeline::DebugRenderStyle;
|
use crate::pipeline::debug_render_pipeline::DebugRenderStyle;
|
||||||
use crate::utils::SimdBasis;
|
use crate::utils::SimdBasis;
|
||||||
|
use parry::utils::IsometryOpt;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -115,16 +116,19 @@ impl DebugRenderPipeline {
|
|||||||
if backend.filter_object(object) {
|
if backend.filter_object(object) {
|
||||||
for manifold in &pair.manifolds {
|
for manifold in &pair.manifolds {
|
||||||
for contact in manifold.contacts() {
|
for contact in manifold.contacts() {
|
||||||
|
let world_subshape_pos1 =
|
||||||
|
manifold.subshape_pos1.prepend_to(co1.position());
|
||||||
backend.draw_line(
|
backend.draw_line(
|
||||||
object,
|
object,
|
||||||
co1.position() * contact.local_p1,
|
world_subshape_pos1 * contact.local_p1,
|
||||||
co2.position() * contact.local_p2,
|
manifold.subshape_pos2.prepend_to(co2.position())
|
||||||
|
* contact.local_p2,
|
||||||
self.style.contact_depth_color,
|
self.style.contact_depth_color,
|
||||||
);
|
);
|
||||||
backend.draw_line(
|
backend.draw_line(
|
||||||
object,
|
object,
|
||||||
co1.position() * contact.local_p1,
|
world_subshape_pos1 * contact.local_p1,
|
||||||
co1.position()
|
world_subshape_pos1
|
||||||
* (contact.local_p1
|
* (contact.local_p1
|
||||||
+ manifold.local_n1 * self.style.contact_normal_length),
|
+ manifold.local_n1 * self.style.contact_normal_length),
|
||||||
self.style.contact_normal_color,
|
self.style.contact_normal_color,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user