Implement limits for revolute joints.

This commit is contained in:
Sébastien Crozet
2021-08-07 14:29:11 +02:00
committed by Sébastien Crozet
parent fd778b607f
commit ac77c95c9c
6 changed files with 354 additions and 16 deletions

View File

@@ -177,6 +177,85 @@ fn create_revolute_joints(
}
}
fn create_revolute_joints_with_limits(
bodies: &mut RigidBodySet,
colliders: &mut ColliderSet,
joints: &mut JointSet,
origin: Point<f32>,
) {
let ground = bodies.insert(
RigidBodyBuilder::new_static()
.translation(origin.coords)
.build(),
);
let platform1 = bodies.insert(
RigidBodyBuilder::new_dynamic()
.translation(origin.coords)
.build(),
);
colliders.insert_with_parent(
ColliderBuilder::cuboid(4.0, 0.2, 2.0).build(),
platform1,
bodies,
);
let shift = vector![0.0, 0.0, 6.0];
let platform2 = bodies.insert(
RigidBodyBuilder::new_dynamic()
.translation(origin.coords + shift)
.build(),
);
colliders.insert_with_parent(
ColliderBuilder::cuboid(4.0, 0.2, 2.0).build(),
platform2,
bodies,
);
let mut joint1 = RevoluteJoint::new(
Point::origin(),
Vector::z_axis(),
Point::origin(),
Vector::z_axis(),
);
joint1.limits_enabled = true;
joint1.limits = [-0.2, 0.2];
joints.insert(ground, platform1, joint1);
let mut joint2 = RevoluteJoint::new(
Point::origin(),
Vector::z_axis(),
Point::from(-shift),
Vector::z_axis(),
);
joint2.limits_enabled = true;
joint2.limits = [-0.3, 0.3];
joints.insert(platform1, platform2, joint2);
// Lets add a couple of cuboids that will fall on the platforms, triggering the joint limits.
let cuboid_body1 = bodies.insert(
RigidBodyBuilder::new_dynamic()
.translation(origin.coords + vector![-2.0, 4.0, 0.0])
.build(),
);
colliders.insert_with_parent(
ColliderBuilder::cuboid(0.6, 0.6, 0.6).friction(1.0).build(),
cuboid_body1,
bodies,
);
let cuboid_body2 = bodies.insert(
RigidBodyBuilder::new_dynamic()
.translation(origin.coords + shift + vector![2.0, 16.0, 0.0])
.build(),
);
colliders.insert_with_parent(
ColliderBuilder::cuboid(0.6, 0.6, 0.6).friction(1.0).build(),
cuboid_body2,
bodies,
);
}
fn create_fixed_joints(
bodies: &mut RigidBodySet,
colliders: &mut ColliderSet,
@@ -442,6 +521,12 @@ pub fn init_world(testbed: &mut Testbed) {
point![20.0, 0.0, 0.0],
3,
);
create_revolute_joints_with_limits(
&mut bodies,
&mut colliders,
&mut joints,
point![34.0, 0.0, 0.0],
);
create_fixed_joints(
&mut bodies,
&mut colliders,

View File

@@ -19,6 +19,7 @@ pub struct PrismaticJoint {
pub(crate) local_axis2: Unit<Vector<Real>>,
pub(crate) basis1: [Vector<Real>; DIM - 1],
pub(crate) basis2: [Vector<Real>; DIM - 1],
/// The impulse applied by this joint on the first body.
///
/// The impulse applied to the second body is given by `-impulse`.
@@ -29,6 +30,7 @@ pub struct PrismaticJoint {
/// The impulse applied to the second body is given by `-impulse`.
#[cfg(feature = "dim2")]
pub impulse: Vector2<Real>,
/// Whether or not this joint should enforce translational limits along its axis.
pub limits_enabled: bool,
/// The min an max relative position of the attached bodies along this joint's axis.

View File

@@ -24,6 +24,15 @@ pub struct RevoluteJoint {
/// The impulse applied to the second body is given by `-impulse`.
pub impulse: Vector5<Real>,
/// Whether or not this joint should enforce translational limits along its axis.
pub limits_enabled: bool,
/// The min an max relative position of the attached bodies along this joint's axis.
pub limits: [Real; 2],
/// The impulse applied by this joint on the first body to enforce the position limit along this joint's axis.
///
/// The impulse applied to the second body is given by `-impulse`.
pub limits_impulse: Real,
/// The target relative angular velocity the motor will attempt to reach.
pub motor_target_vel: Real,
/// The target relative angle along the joint axis the motor will attempt to reach.
@@ -67,6 +76,9 @@ impl RevoluteJoint {
basis2: local_axis2.orthonormal_basis(),
impulse: na::zero(),
world_ang_impulse: na::zero(),
limits_enabled: false,
limits: [-Real::MAX, Real::MAX],
limits_impulse: 0.0,
motor_target_vel: 0.0,
motor_target_pos: 0.0,
motor_stiffness: 0.0,
@@ -82,7 +94,9 @@ impl RevoluteJoint {
/// Can a SIMD constraint be used for resolving this joint?
pub fn supports_simd_constraints(&self) -> bool {
// SIMD revolute constraints don't support motors right now.
self.motor_max_impulse == 0.0 || (self.motor_stiffness == 0.0 && self.motor_damping == 0.0)
!self.limits_enabled
&& (self.motor_max_impulse == 0.0
|| (self.motor_stiffness == 0.0 && self.motor_damping == 0.0))
}
/// Set the spring-like model used by the motor to reach the desired target velocity and position.
@@ -120,21 +134,31 @@ impl RevoluteJoint {
body_pos1: &Isometry<Real>,
body_pos2: &Isometry<Real>,
) -> Real {
let motor_axis1 = body_pos1 * self.local_axis1;
let ref1 = body_pos1 * self.basis1[0];
let ref2 = body_pos2 * self.basis2[0];
Self::estimate_motor_angle_from_params(
&(body_pos1 * self.local_axis1),
&(body_pos1 * self.basis1[0]),
&(body_pos2 * self.basis2[0]),
self.motor_last_angle,
)
}
let last_angle_cycles = (self.motor_last_angle / Real::two_pi()).trunc() * Real::two_pi();
pub fn estimate_motor_angle_from_params(
axis1: &Unit<Vector<Real>>,
tangent1: &Vector<Real>,
tangent2: &Vector<Real>,
motor_last_angle: Real,
) -> Real {
let last_angle_cycles = (motor_last_angle / Real::two_pi()).trunc() * Real::two_pi();
// Measure the position between 0 and 2-pi
let new_angle = if ref1.cross(&ref2).dot(&motor_axis1) < 0.0 {
Real::two_pi() - ref1.angle(&ref2)
let new_angle = if tangent1.cross(&tangent2).dot(&axis1) < 0.0 {
Real::two_pi() - tangent1.angle(&tangent2)
} else {
ref1.angle(&ref2)
tangent1.angle(&tangent2)
};
// The last angle between 0 and 2-pi
let last_angle_zero_two_pi = self.motor_last_angle - last_angle_cycles;
let last_angle_zero_two_pi = motor_last_angle - last_angle_cycles;
// Figure out the smallest angle differance.
let mut angle_diff = new_angle - last_angle_zero_two_pi;
@@ -144,6 +168,6 @@ impl RevoluteJoint {
angle_diff += Real::two_pi()
}
self.motor_last_angle + angle_diff
motor_last_angle + angle_diff
}
}

View File

@@ -256,8 +256,8 @@ impl PrismaticVelocityConstraint {
limits_impulse_limits.0 = if max_enabled { -Real::INFINITY } else { 0.0 };
limits_impulse_limits.1 = if min_enabled { Real::INFINITY } else { 0.0 };
limits_active = min_enabled || max_enabled;
if limits_active {
limits_rhs = (anchor_linvel2.dot(&axis2) - anchor_linvel1.dot(&axis1))
* params.velocity_solve_fraction;

View File

@@ -21,6 +21,11 @@ pub(crate) struct RevolutePositionConstraint {
ang_inv_lhs: AngularInertia<Real>,
limits_enabled: bool,
limits: [Real; 2],
motor_last_angle: Real,
local_anchor1: Point<Real>,
local_anchor2: Point<Real>,
@@ -61,6 +66,9 @@ impl RevolutePositionConstraint {
position2: ids2.active_set_offset,
local_basis1: cparams.basis1,
local_basis2: cparams.basis2,
limits_enabled: cparams.limits_enabled,
limits: cparams.limits,
motor_last_angle: cparams.motor_last_angle,
}
}
@@ -119,6 +127,31 @@ impl RevolutePositionConstraint {
Rotation::new(self.ii2.transform_vector(-ang_impulse)) * position2.rotation;
}
/*
* Limits part.
*/
if self.limits_enabled {
let angle = RevoluteJoint::estimate_motor_angle_from_params(
&(position1 * self.local_axis1),
&(position1 * self.local_basis1[0]),
&(position2 * self.local_basis2[0]),
self.motor_last_angle,
);
let ang_error = (angle - self.limits[1]).max(0.0) - (self.limits[0] - angle).max(0.0);
if ang_error != 0.0 {
let axis2 = position2 * self.local_axis2;
let ang_impulse = self
.ang_inv_lhs
.transform_vector(*axis2 * ang_error * params.joint_erp);
position1.rotation =
Rotation::new(self.ii1.transform_vector(ang_impulse)) * position1.rotation;
position2.rotation =
Rotation::new(self.ii2.transform_vector(-ang_impulse)) * position2.rotation;
}
}
positions[self.position1 as usize] = position1;
positions[self.position2 as usize] = position2;
}
@@ -133,9 +166,14 @@ pub(crate) struct RevolutePositionGroundConstraint {
anchor1: Point<Real>,
local_anchor2: Point<Real>,
axis1: Unit<Vector<Real>>,
local_axis2: Unit<Vector<Real>>,
basis1: [Vector<Real>; 2],
limits_enabled: bool,
limits: [Real; 2],
motor_last_angle: Real,
local_axis2: Unit<Vector<Real>>,
local_basis2: [Vector<Real>; 2],
}
@@ -189,6 +227,9 @@ impl RevolutePositionGroundConstraint {
position2: ids2.active_set_offset,
basis1,
local_basis2,
limits_enabled: cparams.limits_enabled,
limits: cparams.limits,
motor_last_angle: cparams.motor_last_angle,
}
}
@@ -230,6 +271,25 @@ impl RevolutePositionGroundConstraint {
position2.rotation = Rotation::new(-ang_error) * position2.rotation;
}
/*
* Limits part.
*/
if self.limits_enabled {
let angle = RevoluteJoint::estimate_motor_angle_from_params(
&self.axis1,
&self.basis1[0],
&(position2 * self.local_basis2[0]),
self.motor_last_angle,
);
let ang_error = (angle - self.limits[1]).max(0.0) - (self.limits[0] - angle).max(0.0);
if ang_error != 0.0 {
let axis2 = position2 * self.local_axis2;
position2.rotation =
Rotation::new(*axis2 * -ang_error * params.joint_erp) * position2.rotation;
}
}
positions[self.position2 as usize] = position2;
}
}

View File

@@ -30,6 +30,12 @@ pub(crate) struct RevoluteVelocityConstraint {
motor_axis1: Vector<Real>,
motor_axis2: Vector<Real>,
limits_active: bool,
limits_impulse: Real,
limits_rhs: Real,
limits_inv_lhs: Real,
limits_impulse_limits: (Real, Real),
basis1: Matrix3x2<Real>,
basis2: Matrix3x2<Real>,
@@ -145,8 +151,11 @@ impl RevoluteVelocityConstraint {
joint.motor_damping,
);
if stiffness != 0.0 {
if stiffness != 0.0 || joint.limits_enabled {
motor_angle = joint.estimate_motor_angle(&poss1.position, &poss2.position);
}
if stiffness != 0.0 {
motor_rhs += (motor_angle - joint.motor_target_pos) * stiffness;
}
@@ -167,6 +176,47 @@ impl RevoluteVelocityConstraint {
motor_rhs /= gamma;
}
/*
* Setup limit constraint.
*/
let mut limits_active = false;
let mut limits_rhs = 0.0;
let mut limits_inv_lhs = 0.0;
let mut limits_impulse_limits = (0.0, 0.0);
let mut limits_impulse = 0.0;
if joint.limits_enabled {
// TODO: we should allow predictive constraint activation.
let (min_limit, max_limit) = (joint.limits[0], joint.limits[1]);
let min_enabled = motor_angle < min_limit;
let max_enabled = max_limit < motor_angle;
limits_impulse_limits.0 = if max_enabled { -Real::INFINITY } else { 0.0 };
limits_impulse_limits.1 = if min_enabled { Real::INFINITY } else { 0.0 };
limits_active = min_enabled || max_enabled;
if limits_active {
limits_rhs = (vels2.angvel.dot(&motor_axis2) - vels1.angvel.dot(&motor_axis1))
* params.velocity_solve_fraction;
limits_rhs += ((motor_angle - max_limit).max(0.0)
- (min_limit - motor_angle).max(0.0))
* velocity_based_erp_inv_dt;
limits_inv_lhs = crate::utils::inv(
motor_axis2.dot(&ii2.transform_vector(motor_axis2))
+ motor_axis1.dot(&ii1.transform_vector(motor_axis1)),
);
limits_impulse = joint
.limits_impulse
.max(limits_impulse_limits.0)
.min(limits_impulse_limits.1)
* params.warmstart_coeff;
}
}
/*
* Adjust the warmstart impulse.
* If the velocity along the free axis is somewhat high,
@@ -205,6 +255,11 @@ impl RevoluteVelocityConstraint {
motor_axis2,
motor_impulse,
motor_angle,
limits_impulse,
limits_impulse_limits,
limits_active,
limits_inv_lhs,
limits_rhs,
}
}
@@ -228,7 +283,7 @@ impl RevoluteVelocityConstraint {
.transform_vector(ang_impulse2 + self.r2.gcross(lin_impulse2));
/*
* Motor
* Warmstart motor.
*/
if self.motor_inv_lhs != 0.0 {
mj_lambda1.angular += self
@@ -239,6 +294,14 @@ impl RevoluteVelocityConstraint {
.transform_vector(self.motor_axis2 * self.motor_impulse);
}
// Warmstart limits.
if self.limits_active {
let limit_impulse1 = -self.motor_axis2 * self.limits_impulse;
let limit_impulse2 = self.motor_axis2 * self.limits_impulse;
mj_lambda1.angular += self.ii1_sqrt.transform_vector(limit_impulse1);
mj_lambda2.angular += self.ii2_sqrt.transform_vector(limit_impulse2);
}
mj_lambdas[self.mj_lambda1 as usize] = mj_lambda1;
mj_lambdas[self.mj_lambda2 as usize] = mj_lambda2;
}
@@ -270,6 +333,31 @@ impl RevoluteVelocityConstraint {
.transform_vector(ang_impulse2 + self.r2.gcross(lin_impulse2));
}
fn solve_limits(&mut self, mj_lambda1: &mut DeltaVel<Real>, mj_lambda2: &mut DeltaVel<Real>) {
if self.limits_active {
let limits_torquedir1 = -self.motor_axis2;
let limits_torquedir2 = self.motor_axis2;
let ang_vel1 = self.ii1_sqrt.transform_vector(mj_lambda1.angular);
let ang_vel2 = self.ii2_sqrt.transform_vector(mj_lambda2.angular);
let ang_dvel = limits_torquedir1.dot(&ang_vel1)
+ limits_torquedir2.dot(&ang_vel2)
+ self.limits_rhs;
let new_impulse = (self.limits_impulse - ang_dvel * self.limits_inv_lhs)
.max(self.limits_impulse_limits.0)
.min(self.limits_impulse_limits.1);
let dimpulse = new_impulse - self.limits_impulse;
self.limits_impulse = new_impulse;
let ang_impulse1 = limits_torquedir1 * dimpulse;
let ang_impulse2 = limits_torquedir2 * dimpulse;
mj_lambda1.angular += self.ii1_sqrt.transform_vector(ang_impulse1);
mj_lambda2.angular += self.ii2_sqrt.transform_vector(ang_impulse2);
}
}
fn solve_motors(&mut self, mj_lambda1: &mut DeltaVel<Real>, mj_lambda2: &mut DeltaVel<Real>) {
if self.motor_inv_lhs != 0.0 {
let ang_vel1 = self.ii1_sqrt.transform_vector(mj_lambda1.angular);
@@ -294,6 +382,7 @@ impl RevoluteVelocityConstraint {
let mut mj_lambda1 = mj_lambdas[self.mj_lambda1 as usize];
let mut mj_lambda2 = mj_lambdas[self.mj_lambda2 as usize];
self.solve_limits(&mut mj_lambda1, &mut mj_lambda2);
self.solve_dofs(&mut mj_lambda1, &mut mj_lambda2);
self.solve_motors(&mut mj_lambda1, &mut mj_lambda2);
@@ -310,6 +399,7 @@ impl RevoluteVelocityConstraint {
revolute.prev_axis1 = self.motor_axis1;
revolute.motor_last_angle = self.motor_angle;
revolute.motor_impulse = self.motor_impulse;
revolute.limits_impulse = self.limits_impulse;
}
}
}
@@ -333,6 +423,12 @@ pub(crate) struct RevoluteVelocityGroundConstraint {
motor_max_impulse: Real,
motor_angle: Real, // Exists just for writing it into the joint.
limits_active: bool,
limits_impulse: Real,
limits_rhs: Real,
limits_inv_lhs: Real,
limits_impulse_limits: (Real, Real),
basis2: Matrix3x2<Real>,
im2: Real,
@@ -458,8 +554,11 @@ impl RevoluteVelocityGroundConstraint {
joint.motor_damping,
);
if stiffness != 0.0 {
if stiffness != 0.0 || joint.limits_enabled {
motor_angle = joint.estimate_motor_angle(&poss1.position, &poss2.position);
}
if stiffness != 0.0 {
motor_rhs += (motor_angle - joint.motor_target_pos) * stiffness;
}
@@ -480,6 +579,43 @@ impl RevoluteVelocityGroundConstraint {
let motor_impulse = na::clamp(joint.motor_impulse, -motor_max_impulse, motor_max_impulse)
* params.warmstart_coeff;
/*
* Setup limit constraint.
*/
let mut limits_active = false;
let mut limits_rhs = 0.0;
let mut limits_inv_lhs = 0.0;
let mut limits_impulse_limits = (0.0, 0.0);
let mut limits_impulse = 0.0;
if joint.limits_enabled {
// TODO: we should allow predictive constraint activation.
let (min_limit, max_limit) = (joint.limits[0], joint.limits[1]);
let min_enabled = motor_angle < min_limit;
let max_enabled = max_limit < motor_angle;
limits_impulse_limits.0 = if max_enabled { -Real::INFINITY } else { 0.0 };
limits_impulse_limits.1 = if min_enabled { Real::INFINITY } else { 0.0 };
limits_active = min_enabled || max_enabled;
if limits_active {
limits_rhs = (vels2.angvel.dot(&axis2) - vels1.angvel.dot(&axis1))
* params.velocity_solve_fraction;
limits_rhs += ((motor_angle - max_limit).max(0.0)
- (min_limit - motor_angle).max(0.0))
* velocity_based_erp_inv_dt;
limits_inv_lhs = crate::utils::inv(axis2.dot(&ii2.transform_vector(axis2)));
limits_impulse = joint
.limits_impulse
.max(limits_impulse_limits.0)
.min(limits_impulse_limits.1)
* params.warmstart_coeff;
}
}
let result = RevoluteVelocityGroundConstraint {
joint_id,
mj_lambda2: ids2.active_set_offset,
@@ -496,6 +632,11 @@ impl RevoluteVelocityGroundConstraint {
motor_max_impulse,
motor_rhs,
motor_angle,
limits_impulse,
limits_impulse_limits,
limits_active,
limits_inv_lhs,
limits_rhs,
};
AnyJointVelocityConstraint::RevoluteGroundConstraint(result)
@@ -521,6 +662,12 @@ impl RevoluteVelocityGroundConstraint {
.transform_vector(self.motor_axis2 * self.motor_impulse);
}
// Warmstart limits.
if self.limits_active {
let limit_impulse2 = self.motor_axis2 * self.limits_impulse;
mj_lambda2.angular += self.ii2_sqrt.transform_vector(limit_impulse2);
}
mj_lambdas[self.mj_lambda2 as usize] = mj_lambda2;
}
@@ -542,6 +689,24 @@ impl RevoluteVelocityGroundConstraint {
.transform_vector(ang_impulse + self.r2.gcross(lin_impulse));
}
fn solve_limits(&mut self, mj_lambda2: &mut DeltaVel<Real>) {
if self.limits_active {
let limits_torquedir2 = self.motor_axis2;
let ang_vel2 = self.ii2_sqrt.transform_vector(mj_lambda2.angular);
let ang_dvel = limits_torquedir2.dot(&ang_vel2) + self.limits_rhs;
let new_impulse = (self.limits_impulse - ang_dvel * self.limits_inv_lhs)
.max(self.limits_impulse_limits.0)
.min(self.limits_impulse_limits.1);
let dimpulse = new_impulse - self.limits_impulse;
self.limits_impulse = new_impulse;
let ang_impulse2 = limits_torquedir2 * dimpulse;
mj_lambda2.angular += self.ii2_sqrt.transform_vector(ang_impulse2);
}
}
fn solve_motors(&mut self, mj_lambda2: &mut DeltaVel<Real>) {
if self.motor_inv_lhs != 0.0 {
let ang_vel2 = self.ii2_sqrt.transform_vector(mj_lambda2.angular);
@@ -563,6 +728,7 @@ impl RevoluteVelocityGroundConstraint {
pub fn solve(&mut self, mj_lambdas: &mut [DeltaVel<Real>]) {
let mut mj_lambda2 = mj_lambdas[self.mj_lambda2 as usize];
self.solve_limits(&mut mj_lambda2);
self.solve_dofs(&mut mj_lambda2);
self.solve_motors(&mut mj_lambda2);
@@ -576,6 +742,7 @@ impl RevoluteVelocityGroundConstraint {
revolute.impulse = self.impulse;
revolute.motor_impulse = self.motor_impulse;
revolute.motor_last_angle = self.motor_angle;
revolute.limits_impulse = self.limits_impulse;
}
}
}