#![cfg_attr(feature = "2d", doc = "use bevy_xpbd_2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "use bevy_xpbd_3d::prelude::*;")]
mod distance;
mod fixed;
mod prismatic;
mod revolute;
mod spherical;
pub use distance::*;
pub use fixed::*;
pub use prismatic::*;
pub use revolute::*;
pub use spherical::*;
use crate::prelude::*;
use bevy::prelude::*;
pub trait Joint: Component + PositionConstraint + AngularConstraint {
fn new(entity1: Entity, entity2: Entity) -> Self;
fn with_compliance(self, compliance: Scalar) -> Self;
fn with_local_anchor_1(self, anchor: Vector) -> Self;
fn with_local_anchor_2(self, anchor: Vector) -> Self;
fn with_linear_velocity_damping(self, damping: Scalar) -> Self;
fn with_angular_velocity_damping(self, damping: Scalar) -> Self;
fn local_anchor_1(&self) -> Vector;
fn local_anchor_2(&self) -> Vector;
fn damping_linear(&self) -> Scalar;
fn damping_angular(&self) -> Scalar;
#[allow(clippy::too_many_arguments)]
fn align_position(
&self,
body1: &mut RigidBodyQueryItem,
body2: &mut RigidBodyQueryItem,
r1: Vector,
r2: Vector,
lagrange: &mut Scalar,
compliance: Scalar,
dt: Scalar,
) -> Vector {
let world_r1 = body1.rotation.rotate(r1);
let world_r2 = body2.rotation.rotate(r2);
let delta_x = DistanceLimit::new(0.0, 0.0).compute_correction(
body1.current_position() + world_r1,
body2.current_position() + world_r2,
);
let magnitude = delta_x.length();
if magnitude <= Scalar::EPSILON {
return Vector::ZERO;
}
let dir = delta_x / magnitude;
let w1 = PositionConstraint::compute_generalized_inverse_mass(self, body1, world_r1, dir);
let w2 = PositionConstraint::compute_generalized_inverse_mass(self, body2, world_r2, dir);
let gradients = [dir, -dir];
let w = [w1, w2];
let delta_lagrange =
self.compute_lagrange_update(*lagrange, magnitude, &gradients, &w, compliance, dt);
*lagrange += delta_lagrange;
self.apply_positional_correction(body1, body2, delta_lagrange, dir, world_r1, world_r2);
self.compute_force(*lagrange, dir, dt)
}
fn align_orientation(
&self,
body1: &mut RigidBodyQueryItem,
body2: &mut RigidBodyQueryItem,
delta_q: Vector3,
lagrange: &mut Scalar,
compliance: Scalar,
dt: Scalar,
) -> Torque {
let angle = delta_q.length();
if angle <= Scalar::EPSILON {
return Torque::ZERO;
}
let axis = delta_q / angle;
let w1 = AngularConstraint::compute_generalized_inverse_mass(self, body1, axis);
let w2 = AngularConstraint::compute_generalized_inverse_mass(self, body2, axis);
let gradients = {
#[cfg(feature = "2d")]
{
[Vector::Y * axis.z, Vector::NEG_Y * axis.z]
}
#[cfg(feature = "3d")]
{
[axis, -axis]
}
};
let w = [w1, w2];
let delta_lagrange =
self.compute_lagrange_update(*lagrange, angle, &gradients, &w, compliance, dt);
*lagrange += delta_lagrange;
self.apply_angular_correction(body1, body2, delta_lagrange, axis);
self.compute_torque(*lagrange, axis, dt)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
pub struct DistanceLimit {
pub min: Scalar,
pub max: Scalar,
}
impl DistanceLimit {
pub const ZERO: Self = Self { min: 0.0, max: 0.0 };
pub fn new(min: Scalar, max: Scalar) -> Self {
Self { min, max }
}
pub fn compute_correction(&self, p1: Vector, p2: Vector) -> Vector {
let pos_offset = p2 - p1;
let distance = pos_offset.length();
if distance <= Scalar::EPSILON {
return Vector::ZERO;
}
if distance < self.min {
-pos_offset / distance * (distance - self.min)
} else if distance > self.max {
-pos_offset / distance * (distance - self.max)
} else {
Vector::ZERO
}
}
fn compute_correction_along_axis(&self, p1: Vector, p2: Vector, axis: Vector) -> Vector {
let pos_offset = p2 - p1;
let a = pos_offset.dot(axis);
if a < self.min {
axis * (self.min - a)
} else if a > self.max {
-axis * (a - self.max)
} else {
Vector::ZERO
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AngleLimit {
pub alpha: Scalar,
pub beta: Scalar,
}
impl AngleLimit {
pub const ZERO: Self = Self {
alpha: 0.0,
beta: 0.0,
};
pub fn new(alpha: Scalar, beta: Scalar) -> Self {
Self { alpha, beta }
}
fn compute_correction(
&self,
n: Vector3,
n1: Vector3,
n2: Vector3,
max_correction: Scalar,
) -> Option<Vector3> {
let mut phi = n1.cross(n2).dot(n).asin();
if n1.dot(n2) < 0.0 {
phi = PI - phi;
}
if phi > PI {
phi -= 2.0 * PI;
}
if phi < -PI {
phi += 2.0 * PI;
}
if phi < self.alpha || phi > self.beta {
phi = phi.clamp(self.alpha, self.beta);
let rot = Quaternion::from_axis_angle(n, phi);
let mut omega = rot.mul_vec3(n1).cross(n2);
phi = omega.length();
if phi > max_correction {
omega *= max_correction / phi;
}
return Some(omega);
}
None
}
}