Merge pull request #70 from dimforge/lock_rotations

Add a more convenient way of locking the rotations (or translations) of a rigid-body
This commit is contained in:
Sébastien Crozet
2020-11-30 15:55:54 +01:00
committed by GitHub
10 changed files with 290 additions and 20 deletions

View File

@@ -3,6 +3,12 @@
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.
- 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::principal_inertia` for setting the principal inertia of a rigid-body, and/or
preventing the rigid-body from rotating along a specific axis.
- Change `RigidBodyBuilder::mass` by adding a bool parameter indicating whether or not the collider
contributions should be taken into account in the future too.
## 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

View File

@@ -16,6 +16,7 @@ mod damping2;
mod debug_box_ball2; mod debug_box_ball2;
mod heightfield2; mod heightfield2;
mod joints2; mod joints2;
mod locked_rotation2;
mod platform2; mod platform2;
mod pyramid2; mod pyramid2;
mod restitution2; mod restitution2;
@@ -59,6 +60,7 @@ pub fn main() {
("Damping", damping2::init_world), ("Damping", damping2::init_world),
("Heightfield", heightfield2::init_world), ("Heightfield", heightfield2::init_world),
("Joints", joints2::init_world), ("Joints", joints2::init_world),
("Locked rotations", locked_rotation2::init_world),
("Platform", platform2::init_world), ("Platform", platform2::init_world),
("Pyramid", pyramid2::init_world), ("Pyramid", pyramid2::init_world),
("Restitution", restitution2::init_world), ("Restitution", restitution2::init_world),

View File

@@ -0,0 +1,63 @@
use na::Point2;
use rapier2d::dynamics::{JointSet, RigidBodyBuilder, RigidBodySet};
use rapier2d::geometry::{ColliderBuilder, ColliderSet};
use rapier_testbed2d::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 joints = JointSet::new();
/*
* The ground
*/
let ground_size = 5.0;
let ground_height = 0.1;
let rigid_body = RigidBodyBuilder::new_static()
.translation(0.0, -ground_height)
.build();
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(ground_size, ground_height).build();
colliders.insert(collider, handle, &mut bodies);
/*
* A rectangle that only rotate.
*/
let rigid_body = RigidBodyBuilder::new_dynamic()
.translation(0.0, 3.0)
.lock_translations()
.build();
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(2.0, 0.6).build();
colliders.insert(collider, handle, &mut bodies);
/*
* A tilted capsule that cannot rotate.
*/
let rigid_body = RigidBodyBuilder::new_dynamic()
.translation(0.0, 5.0)
.rotation(1.0)
.lock_rotations()
.build();
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::capsule_y(0.6, 0.4).build();
colliders.insert(collider, handle, &mut bodies);
/*
* Set up the testbed.
*/
testbed.set_world(bodies, colliders, joints);
testbed.look_at(Point2::new(0.0, 0.0), 40.0);
}
fn main() {
let testbed = Testbed::from_builders(0, vec![("Boxes", init_world)]);
testbed.run()
}

View File

@@ -61,7 +61,7 @@ pub fn init_world(testbed: &mut Testbed) {
* Setup a callback to control the platform. * Setup a callback to control the platform.
*/ */
testbed.add_callback(move |_, physics, _, _, time| { testbed.add_callback(move |_, physics, _, _, time| {
let mut platform = physics.bodies.get_mut(platform_handle).unwrap(); let platform = physics.bodies.get_mut(platform_handle).unwrap();
let mut next_pos = *platform.position(); let mut next_pos = *platform.position();
let dt = 0.016; let dt = 0.016;

View File

@@ -26,6 +26,7 @@ mod fountain3;
mod heightfield3; mod heightfield3;
mod joints3; mod joints3;
mod keva3; mod keva3;
mod locked_rotation3;
mod platform3; mod platform3;
mod primitives3; mod primitives3;
mod restitution3; mod restitution3;
@@ -78,6 +79,7 @@ pub fn main() {
("Domino", domino3::init_world), ("Domino", domino3::init_world),
("Heightfield", heightfield3::init_world), ("Heightfield", heightfield3::init_world),
("Joints", joints3::init_world), ("Joints", joints3::init_world),
("Locked rotations", locked_rotation3::init_world),
("Platform", platform3::init_world), ("Platform", platform3::init_world),
("Restitution", restitution3::init_world), ("Restitution", restitution3::init_world),
("Stacks", stacks3::init_world), ("Stacks", stacks3::init_world),

View File

@@ -0,0 +1,64 @@
use na::{Point3, Vector3};
use rapier3d::dynamics::{JointSet, RigidBodyBuilder, RigidBodySet};
use rapier3d::geometry::{ColliderBuilder, ColliderSet};
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 joints = JointSet::new();
/*
* The ground
*/
let ground_size = 5.0;
let ground_height = 0.1;
let rigid_body = RigidBodyBuilder::new_static()
.translation(0.0, -ground_height, 0.0)
.build();
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size).build();
colliders.insert(collider, handle, &mut bodies);
/*
* A rectangle that only rotates along the `x` axis.
*/
let rigid_body = RigidBodyBuilder::new_dynamic()
.translation(0.0, 3.0, 0.0)
.lock_translations()
.principal_inertia(Vector3::zeros(), Vector3::new(true, false, false))
.build();
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(0.2, 0.6, 2.0).build();
colliders.insert(collider, handle, &mut bodies);
/*
* A tilted capsule that cannot rotate.
*/
let rigid_body = RigidBodyBuilder::new_dynamic()
.translation(0.0, 5.0, 0.0)
.rotation(Vector3::x() * 1.0)
.lock_rotations()
.build();
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::capsule_y(0.6, 0.4).build();
colliders.insert(collider, handle, &mut bodies);
/*
* Set up the testbed.
*/
testbed.set_world(bodies, colliders, joints);
testbed.look_at(Point3::new(10.0, 3.0, 0.0), Point3::new(0.0, 3.0, 0.0));
}
fn main() {
let testbed = Testbed::from_builders(0, vec![("Boxes", init_world)]);
testbed.run()
}

View File

@@ -3,7 +3,7 @@ use crate::geometry::{
Collider, ColliderHandle, ColliderSet, InteractionGraph, RigidBodyGraphIndex, Collider, ColliderHandle, ColliderSet, InteractionGraph, RigidBodyGraphIndex,
}; };
use crate::math::{AngVector, AngularInertia, Isometry, Point, Rotation, Translation, Vector}; use crate::math::{AngVector, AngularInertia, Isometry, Point, Rotation, Translation, Vector};
use crate::utils::{WCross, WDot}; use crate::utils::{self, WCross, WDot};
use num::Zero; use num::Zero;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -25,6 +25,17 @@ pub enum BodyStatus {
// Disabled, // Disabled,
} }
bitflags::bitflags! {
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
/// Flags affecting the behavior of the constraints solver for a given contact manifold.
pub(crate) struct RigidBodyFlags: u8 {
const IGNORE_COLLIDER_MASS = 1 << 0;
const IGNORE_COLLIDER_ANGULAR_INERTIA_X = 1 << 1;
const IGNORE_COLLIDER_ANGULAR_INERTIA_Y = 1 << 2;
const IGNORE_COLLIDER_ANGULAR_INERTIA_Z = 1 << 3;
}
}
bitflags::bitflags! { bitflags::bitflags! {
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
/// Flags affecting the behavior of the constraints solver for a given contact manifold. /// Flags affecting the behavior of the constraints solver for a given contact manifold.
@@ -46,7 +57,7 @@ pub struct RigidBody {
pub(crate) position: Isometry<f32>, pub(crate) position: Isometry<f32>,
pub(crate) predicted_position: Isometry<f32>, pub(crate) predicted_position: Isometry<f32>,
/// The local mass properties of the rigid-body. /// The local mass properties of the rigid-body.
pub mass_properties: MassProperties, pub(crate) mass_properties: MassProperties,
/// The world-space center of mass of the rigid-body. /// The world-space center of mass of the rigid-body.
pub world_com: Point<f32>, pub world_com: Point<f32>,
/// The square-root of the inverse angular inertia tensor of the rigid-body. /// The square-root of the inverse angular inertia tensor of the rigid-body.
@@ -69,6 +80,7 @@ pub struct RigidBody {
pub(crate) active_set_id: usize, pub(crate) active_set_id: usize,
pub(crate) active_set_offset: usize, pub(crate) active_set_offset: usize,
pub(crate) active_set_timestamp: u32, pub(crate) active_set_timestamp: u32,
flags: RigidBodyFlags,
pub(crate) changes: RigidBodyChanges, pub(crate) changes: RigidBodyChanges,
/// The status of the body, governing how it is affected by external forces. /// The status of the body, governing how it is affected by external forces.
pub body_status: BodyStatus, pub body_status: BodyStatus,
@@ -97,6 +109,7 @@ impl RigidBody {
active_set_id: 0, active_set_id: 0,
active_set_offset: 0, active_set_offset: 0,
active_set_timestamp: 0, active_set_timestamp: 0,
flags: RigidBodyFlags::empty(),
changes: RigidBodyChanges::all(), changes: RigidBodyChanges::all(),
body_status: BodyStatus::Dynamic, body_status: BodyStatus::Dynamic,
user_data: 0, user_data: 0,
@@ -123,6 +136,12 @@ impl RigidBody {
} }
} }
/// The mass properties of this rigid-body.
#[inline]
pub fn mass_properties(&self) -> &MassProperties {
&self.mass_properties
}
/// The handles of colliders attached to this rigid body. /// The handles of colliders attached to this rigid body.
pub fn colliders(&self) -> &[ColliderHandle] { pub fn colliders(&self) -> &[ColliderHandle] {
&self.colliders[..] &self.colliders[..]
@@ -153,7 +172,7 @@ impl RigidBody {
/// ///
/// Returns zero if this rigid body has an infinite mass. /// Returns zero if this rigid body has an infinite mass.
pub fn mass(&self) -> f32 { pub fn mass(&self) -> f32 {
crate::utils::inv(self.mass_properties.inv_mass) utils::inv(self.mass_properties.inv_mass)
} }
/// The predicted position of this rigid-body. /// The predicted position of this rigid-body.
@@ -176,10 +195,40 @@ impl RigidBody {
.mass_properties() .mass_properties()
.transform_by(coll.position_wrt_parent()); .transform_by(coll.position_wrt_parent());
self.colliders.push(handle); self.colliders.push(handle);
self.mass_properties += mass_properties; self.mass_properties += Self::filter_collider_mass_props(mass_properties, self.flags);
self.update_world_mass_properties(); self.update_world_mass_properties();
} }
fn filter_collider_mass_props(
mut props: MassProperties,
flags: RigidBodyFlags,
) -> MassProperties {
if flags.contains(RigidBodyFlags::IGNORE_COLLIDER_MASS) {
props.inv_mass = 0.0;
}
#[cfg(feature = "dim2")]
{
if flags.contains(RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_Z) {
props.inv_principal_inertia_sqrt = 0.0;
}
}
#[cfg(feature = "dim3")]
{
if flags.contains(RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_X) {
props.inv_principal_inertia_sqrt.x = 0.0;
}
if flags.contains(RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_Y) {
props.inv_principal_inertia_sqrt.y = 0.0;
}
if flags.contains(RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_Z) {
props.inv_principal_inertia_sqrt.z = 0.0;
}
}
props
}
pub(crate) fn update_colliders_positions(&mut self, colliders: &mut ColliderSet) { pub(crate) fn update_colliders_positions(&mut self, colliders: &mut ColliderSet) {
for handle in &self.colliders { for handle in &self.colliders {
let collider = &mut colliders[*handle]; let collider = &mut colliders[*handle];
@@ -196,7 +245,7 @@ impl RigidBody {
let mass_properties = coll let mass_properties = coll
.mass_properties() .mass_properties()
.transform_by(coll.position_wrt_parent()); .transform_by(coll.position_wrt_parent());
self.mass_properties -= mass_properties; self.mass_properties -= Self::filter_collider_mass_props(mass_properties, self.flags);
self.update_world_mass_properties(); self.update_world_mass_properties();
} }
} }
@@ -491,6 +540,7 @@ pub struct RigidBodyBuilder {
linear_damping: f32, linear_damping: f32,
angular_damping: f32, angular_damping: f32,
body_status: BodyStatus, body_status: BodyStatus,
flags: RigidBodyFlags,
mass_properties: MassProperties, mass_properties: MassProperties,
can_sleep: bool, can_sleep: bool,
sleeping: bool, sleeping: bool,
@@ -507,6 +557,7 @@ impl RigidBodyBuilder {
linear_damping: 0.0, linear_damping: 0.0,
angular_damping: 0.0, angular_damping: 0.0,
body_status, body_status,
flags: RigidBodyFlags::empty(),
mass_properties: MassProperties::zero(), mass_properties: MassProperties::zero(),
can_sleep: true, can_sleep: true,
sleeping: false, sleeping: false,
@@ -579,18 +630,100 @@ impl RigidBodyBuilder {
self self
} }
/// Prevents this rigid-body from translating because of forces.
///
/// This is equivalent to `self.mass(0.0, false)`. See the
/// documentation of [`RigidBodyBuilder::mass`] for more details.
pub fn lock_translations(self) -> Self {
self.mass(0.0, false)
}
/// Prevents this rigid-body from rotating because of forces.
///
/// This is equivalent to `self.principal_inertia(0.0, false)` (in 2D) or
/// `self.principal_inertia(Vector3::zeros(), Vector3::repeat(false))` (in 3D).
///
/// See the documentation of [`RigidBodyBuilder::principal_inertia`] for more details.
pub fn lock_rotations(self) -> Self {
#[cfg(feature = "dim2")]
return self.principal_inertia(0.0, false);
#[cfg(feature = "dim3")]
return self.principal_inertia(Vector::zeros(), Vector::repeat(false));
}
/// Sets the mass of the rigid-body being built. /// Sets the mass of the rigid-body being built.
/// ///
/// Note that the final mass of the rigid-bodies depends /// In order to lock the translations of this rigid-body (by
/// on the initial mass of the rigid-body (set by this method) /// making them kinematic), call `.mass(0.0, false)`.
///
/// If `colliders_contribution_enabled` is `false`, then the mass specified here
/// will be the final mass of the rigid-body created by this builder.
/// If `colliders_contribution_enabled` is `true`, then the final mass of the rigid-body
/// will depends on the initial mass set by this method to which is added
/// the contributions of all the colliders with non-zero density attached to
/// this rigid-body.
pub fn mass(mut self, mass: f32, colliders_contribution_enabled: bool) -> Self {
self.mass_properties.inv_mass = utils::inv(mass);
self.flags.set(
RigidBodyFlags::IGNORE_COLLIDER_MASS,
!colliders_contribution_enabled,
);
self
}
/// Sets the angular inertia of this rigid-body.
///
/// In order to lock the rotations of this rigid-body (by
/// making them kinematic), call `.principal_inertia(0.0, false)`.
///
/// If `colliders_contribution_enabled` is `false`, then the principal inertia specified here
/// will be the final principal inertia of the rigid-body created by this builder.
/// If `colliders_contribution_enabled` is `true`, then the final principal of the rigid-body
/// will depend on the initial principal inertia set by this method to which is added
/// the contributions of all the colliders with non-zero density attached to this rigid-body.
#[cfg(feature = "dim2")]
pub fn principal_inertia(mut self, inertia: f32, colliders_contribution_enabled: bool) -> Self {
self.mass_properties.inv_principal_inertia_sqrt = utils::inv(inertia);
self.flags.set(
RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_X
| RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_Y
| RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_Z,
!colliders_contribution_enabled,
);
self
}
/// Sets the principal angular inertia of this rigid-body.
///
/// In order to lock the rotations of this rigid-body (by
/// making them kinematic), call `.principal_inertia(Vector3::zeros(), Vector3::repeat(false))`.
///
/// If `colliders_contribution_enabled[i]` is `false`, then the principal inertia specified here
/// along the `i`-th local axis of the rigid-body, will be the final principal inertia along
/// the `i`-th local axis of the rigid-body created by this builder.
/// If `colliders_contribution_enabled[i]` is `true`, then the final principal of the rigid-body
/// along its `i`-th local axis will depend on the initial principal inertia set by this method
/// to which is added the contributions of all the colliders with non-zero density /// to which is added the contributions of all the colliders with non-zero density
/// attached to this rigid-body. /// attached to this rigid-body.
/// #[cfg(feature = "dim3")]
/// Therefore, if you want your provided mass to be the final pub fn principal_inertia(
/// mass of your rigid-body, don't attach colliders to it, or mut self,
/// only attach colliders with densities equal to zero. inertia: AngVector<f32>,
pub fn mass(mut self, mass: f32) -> Self { colliders_contribution_enabled: AngVector<bool>,
self.mass_properties.inv_mass = crate::utils::inv(mass); ) -> Self {
self.mass_properties.inv_principal_inertia_sqrt = inertia.map(utils::inv);
self.flags.set(
RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_X,
!colliders_contribution_enabled.x,
);
self.flags.set(
RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_Y,
!colliders_contribution_enabled.y,
);
self.flags.set(
RigidBodyFlags::IGNORE_COLLIDER_ANGULAR_INERTIA_Z,
!colliders_contribution_enabled.z,
);
self self
} }
@@ -656,6 +789,7 @@ impl RigidBodyBuilder {
rb.mass_properties = self.mass_properties; rb.mass_properties = self.mass_properties;
rb.linear_damping = self.linear_damping; rb.linear_damping = self.linear_damping;
rb.angular_damping = self.angular_damping; rb.angular_damping = self.angular_damping;
rb.flags = self.flags;
if self.can_sleep && self.sleeping { if self.can_sleep && self.sleeping {
rb.sleep(); rb.sleep();

View File

@@ -219,7 +219,7 @@ impl Box2dWorld {
} }
pub fn sync(&self, bodies: &mut RigidBodySet, colliders: &mut ColliderSet) { pub fn sync(&self, bodies: &mut RigidBodySet, colliders: &mut ColliderSet) {
for (handle, mut body) in bodies.iter_mut() { for (handle, body) in bodies.iter_mut() {
if let Some(pb2_handle) = self.rapier2box2d.get(&handle) { if let Some(pb2_handle) = self.rapier2box2d.get(&handle) {
let b2_body = self.world.body(*pb2_handle); let b2_body = self.world.body(*pb2_handle);
let pos = b2_transform_to_na_isometry(b2_body.transform().clone()); let pos = b2_transform_to_na_isometry(b2_body.transform().clone());

View File

@@ -158,7 +158,7 @@ impl NPhysicsWorld {
pub fn sync(&self, bodies: &mut RigidBodySet, colliders: &mut ColliderSet) { pub fn sync(&self, bodies: &mut RigidBodySet, colliders: &mut ColliderSet) {
for (rapier_handle, nphysics_handle) in self.rapier2nphysics.iter() { for (rapier_handle, nphysics_handle) in self.rapier2nphysics.iter() {
let mut rb = bodies.get_mut(*rapier_handle).unwrap(); let rb = bodies.get_mut(*rapier_handle).unwrap();
let ra = self.bodies.rigid_body(*nphysics_handle).unwrap(); let ra = self.bodies.rigid_body(*nphysics_handle).unwrap();
let pos = *ra.position(); let pos = *ra.position();
rb.set_position(pos, false); rb.set_position(pos, false);

View File

@@ -20,12 +20,11 @@ use na::{self, Point2, Point3, Vector3};
use rapier::dynamics::{ use rapier::dynamics::{
ActivationStatus, IntegrationParameters, JointSet, RigidBodyHandle, RigidBodySet, ActivationStatus, IntegrationParameters, JointSet, RigidBodyHandle, RigidBodySet,
}; };
#[cfg(feature = "dim3")]
use rapier::geometry::Ray;
use rapier::geometry::{ use rapier::geometry::{
BroadPhase, ColliderHandle, ColliderSet, ContactEvent, InteractionGroups, NarrowPhase, BroadPhase, ColliderHandle, ColliderSet, ContactEvent, NarrowPhase, ProximityEvent,
ProximityEvent,
}; };
#[cfg(feature = "dim3")]
use rapier::geometry::{InteractionGroups, Ray};
use rapier::math::Vector; use rapier::math::Vector;
use rapier::pipeline::{ChannelEventCollector, PhysicsPipeline, QueryPipeline}; use rapier::pipeline::{ChannelEventCollector, PhysicsPipeline, QueryPipeline};