#![cfg_attr(
feature = "2d",
doc = r#"In 2D, rigid bodies can normally translate along the x and y axes and rotate about the z axis.
Therefore, they have 2 translational DOF and 1 rotational DOF, a total of 3 DOF."#
)]
#![cfg_attr(
feature = "3d",
doc = r#"In 3D, rigid bodies can normally translate and rotate along the x, y, and z axes.
Therefore, they have 3 translational DOF and 3 rotational DOF, a total of 6 DOF."#
)]
#![cfg_attr(feature="2d", doc = include_str!("./images/2d_dofs.svg"))]
#![cfg_attr(feature="3d", doc = include_str!("./images/3d_dofs.svg"))]
#![cfg_attr(
feature = "3d",
doc = "| [`SphericalJoint`] | - | 3 Rotations |"
)]
#![cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
#![doc = include_str!("./images/joint_frame.svg")]
#![cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#![cfg_attr(feature = "2d", doc = " .with_anchor(Vec2::new(5.0, 2.0))")]
#![cfg_attr(feature = "3d", doc = " .with_anchor(Vec3::new(5.0, 2.0, 0.0))")]
#![cfg_attr(feature = "2d", doc = " .with_local_basis1(PI / 4.0),")]
#![cfg_attr(
feature = "3d",
doc = " .with_local_basis1(Quat::from_rotation_z(PI / 4.0)),"
)]
#![cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#![cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#![cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#![cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#![cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
mod distance;
mod fixed;
mod motor;
mod prismatic;
mod revolute;
#[cfg(feature = "3d")]
mod spherical;
#[cfg(test)]
mod tests;
pub use distance::DistanceJoint;
pub use fixed::FixedJoint;
pub use motor::{AngularMotor, LinearMotor, MotorModel};
pub use prismatic::PrismaticJoint;
pub use revolute::RevoluteJoint;
#[cfg(feature = "3d")]
pub use spherical::SphericalJoint;
use crate::{dynamics::solver::joint_graph::JointGraph, prelude::*};
use bevy::{
ecs::{entity::MapEntities, lifecycle::HookContext, world::DeferredWorld},
prelude::*,
};
pub struct JointPlugin;
impl Plugin for JointPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
fixed::plugin,
distance::plugin,
prismatic::plugin,
revolute::plugin,
#[cfg(feature = "3d")]
spherical::plugin,
));
app.configure_sets(
PhysicsSchedule,
JointSystems::PrepareLocalFrames
.after(SolverSystems::PrepareSolverBodies)
.before(SolverSystems::PrepareJoints),
);
}
}
#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)]
pub enum JointSystems {
PrepareLocalFrames,
}
pub trait EntityConstraint<const ENTITY_COUNT: usize>: MapEntities {
fn entities(&self) -> [Entity; ENTITY_COUNT];
}
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub struct DistanceLimit {
pub min: Scalar,
pub max: Scalar,
}
impl From<Scalar> for DistanceLimit {
fn from(limit: Scalar) -> DistanceLimit {
DistanceLimit {
min: limit,
max: limit,
}
}
}
impl From<[Scalar; 2]> for DistanceLimit {
fn from([min, max]: [Scalar; 2]) -> DistanceLimit {
DistanceLimit { min, max }
}
}
impl From<(Scalar, Scalar)> for DistanceLimit {
fn from((min, max): (Scalar, Scalar)) -> DistanceLimit {
DistanceLimit { min, max }
}
}
impl DistanceLimit {
pub const ZERO: Self = Self { min: 0.0, max: 0.0 };
pub const fn new(min: Scalar, max: Scalar) -> Self {
Self { min, max }
}
pub fn compute_correction(&self, separation: Vector) -> (Vector, Scalar) {
let distance_squared = separation.length_squared();
if distance_squared <= Scalar::EPSILON {
return (Vector::ZERO, 0.0);
}
let distance = distance_squared.sqrt();
if distance < self.min {
(separation / distance, (self.min - distance))
} else if distance > self.max {
(-separation / distance, (distance - self.max))
} else {
(Vector::ZERO, 0.0)
}
}
pub fn compute_correction_along_axis(&self, separation: Vector, axis: Vector) -> Vector {
let a = separation.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, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub struct AngleLimit {
pub min: Scalar,
pub max: Scalar,
}
impl From<Scalar> for AngleLimit {
fn from(limit: Scalar) -> AngleLimit {
AngleLimit {
min: limit,
max: limit,
}
}
}
impl From<[Scalar; 2]> for AngleLimit {
fn from([min, max]: [Scalar; 2]) -> AngleLimit {
AngleLimit { min, max }
}
}
impl From<(Scalar, Scalar)> for AngleLimit {
fn from((min, max): (Scalar, Scalar)) -> AngleLimit {
AngleLimit { min, max }
}
}
impl AngleLimit {
pub const ZERO: Self = Self { min: 0.0, max: 0.0 };
pub const fn new(min: Scalar, max: Scalar) -> Self {
Self { min, max }
}
#[cfg(feature = "2d")]
pub fn compute_correction(
&self,
angle_difference: Scalar,
max_correction: Scalar,
) -> Option<Scalar> {
let correction = if angle_difference < self.min {
angle_difference - self.min
} else if angle_difference > self.max {
angle_difference - self.max
} else {
return None;
};
Some(correction.min(max_correction))
}
#[cfg(feature = "3d")]
pub fn compute_correction(
&self,
limit_axis: Vector,
axis1: Vector,
axis2: Vector,
max_correction: Scalar,
) -> Option<Vector> {
let mut phi = axis1.cross(axis2).dot(limit_axis).asin();
if axis1.dot(axis2) < 0.0 {
phi = PI - phi;
}
if phi > PI {
phi -= TAU;
}
if phi < self.min || phi > self.max {
phi = phi.clamp(self.min, self.max);
let rot = Quaternion::from_axis_angle(limit_axis, phi);
return Some((rot * axis1).cross(axis2).clamp_length_max(max_correction));
}
None
}
}
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[derive(Reflect, Clone, Copy, Component, Debug, Default)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default)]
pub struct JointDisabled;
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[derive(Component, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, PartialEq)]
#[component(on_add = JointCollisionDisabled::on_add, on_remove = JointCollisionDisabled::on_remove)]
pub struct JointCollisionDisabled;
impl JointCollisionDisabled {
fn on_add(mut world: DeferredWorld, ctx: HookContext) {
let entity = ctx.entity;
let mut joint_graph = world.resource_mut::<JointGraph>();
if let Some(joint_edge) = joint_graph.get_mut(entity) {
joint_edge.collision_disabled = true;
}
}
fn on_remove(mut world: DeferredWorld, ctx: HookContext) {
let entity = ctx.entity;
let mut joint_graph = world.resource_mut::<JointGraph>();
if let Some(joint_edge) = joint_graph.get_mut(entity) {
joint_edge.collision_disabled = false;
}
}
}
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, PartialEq)]
pub struct JointDamping {
pub linear: Scalar,
pub angular: Scalar,
}
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, PartialEq)]
pub struct JointForces {
force: Vector,
torque: AngularVector,
motor_force: Scalar,
}
impl JointForces {
#[inline]
pub const fn new() -> Self {
Self {
force: Vector::ZERO,
torque: AngularVector::ZERO,
motor_force: 0.0,
}
}
#[inline]
pub const fn force(&self) -> Vector {
self.force
}
#[inline]
pub const fn torque(&self) -> AngularVector {
self.torque
}
#[inline]
pub const fn motor_force(&self) -> Scalar {
self.motor_force
}
#[inline]
pub const fn set_force(&mut self, force: Vector) {
self.force = force;
}
#[inline]
pub const fn set_torque(&mut self, torque: AngularVector) {
self.torque = torque;
}
#[inline]
pub const fn set_motor_force(&mut self, motor_force: Scalar) {
self.motor_force = motor_force;
}
}
#[doc = include_str!("./images/joint_frame.svg")]
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
#[cfg_attr(feature = "2d", doc = " .with_anchor(Vec2::new(5.0, 2.0))")]
#[cfg_attr(feature = "3d", doc = " .with_anchor(Vec3::new(5.0, 2.0, 0.0))")]
#[cfg_attr(feature = "2d", doc = " .with_local_basis1(PI / 4.0),")]
#[cfg_attr(
feature = "3d",
doc = " .with_local_basis1(Quat::from_rotation_z(PI / 4.0)),"
)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub struct JointFrame {
pub anchor: JointAnchor,
pub basis: JointBasis,
}
impl JointFrame {
pub const IDENTITY: Self = Self {
anchor: JointAnchor::ZERO,
basis: JointBasis::IDENTITY,
};
#[inline]
pub fn local(isometry: impl Into<Isometry>) -> Self {
let isometry: Isometry = isometry.into();
#[cfg(feature = "2d")]
let anchor = isometry.translation.adjust_precision();
#[cfg(feature = "3d")]
let anchor = Vec3::from(isometry.translation).adjust_precision();
Self {
anchor: JointAnchor::Local(anchor),
#[cfg(feature = "2d")]
basis: JointBasis::Local(Rotation::from_sin_cos(
isometry.rotation.sin as Scalar,
isometry.rotation.cos as Scalar,
)),
#[cfg(feature = "3d")]
basis: JointBasis::Local(isometry.rotation.adjust_precision()),
}
}
#[inline]
pub fn global(isometry: impl Into<Isometry>) -> Self {
let isometry: Isometry = isometry.into();
#[cfg(feature = "2d")]
let anchor = isometry.translation.adjust_precision();
#[cfg(feature = "3d")]
let anchor = Vec3::from(isometry.translation).adjust_precision();
Self {
anchor: JointAnchor::FromGlobal(anchor),
#[cfg(feature = "2d")]
basis: JointBasis::FromGlobal(Rotation::from_sin_cos(
isometry.rotation.sin as Scalar,
isometry.rotation.cos as Scalar,
)),
#[cfg(feature = "3d")]
basis: JointBasis::FromGlobal(isometry.rotation.adjust_precision()),
}
}
#[inline]
#[allow(clippy::unnecessary_cast)]
pub fn get_local_isometry(&self) -> Option<Isometry> {
let translation = match self.anchor {
JointAnchor::Local(anchor) => anchor.f32(),
JointAnchor::FromGlobal(_) => return None,
};
let rotation = match self.basis {
#[cfg(feature = "2d")]
JointBasis::Local(basis) => Rot2::from_sin_cos(basis.sin as f32, basis.cos as f32),
#[cfg(feature = "3d")]
JointBasis::Local(basis) => basis.f32(),
JointBasis::FromGlobal(_) => return None,
};
Some(Isometry::new(translation, rotation))
}
#[inline]
#[allow(clippy::unnecessary_cast)]
pub fn get_global_isometry(&self) -> Option<Isometry> {
let translation = match self.anchor {
JointAnchor::FromGlobal(anchor) => anchor.f32(),
JointAnchor::Local(_) => return None,
};
let rotation = match self.basis {
#[cfg(feature = "2d")]
JointBasis::FromGlobal(basis) => Rot2::from_sin_cos(basis.sin as f32, basis.cos as f32),
#[cfg(feature = "3d")]
JointBasis::FromGlobal(basis) => basis.f32(),
JointBasis::Local(_) => return None,
};
Some(Isometry::new(translation, rotation))
}
#[inline]
#[must_use]
pub fn compute_local(
frame1: Self,
frame2: Self,
pos1: Vector,
pos2: Vector,
rot1: &Rotation,
rot2: &Rotation,
) -> [JointFrame; 2] {
let [local_anchor1, local_anchor2] =
JointAnchor::compute_local(frame1.anchor, frame2.anchor, pos1, pos2, rot1, rot2);
let [local_basis1, local_basis2] =
JointBasis::compute_local(frame1.basis, frame2.basis, rot1, rot2);
[
JointFrame {
anchor: local_anchor1,
basis: local_basis1,
},
JointFrame {
anchor: local_anchor2,
basis: local_basis2,
},
]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub enum JointAnchor {
Local(Vector),
FromGlobal(Vector),
}
impl Default for JointAnchor {
fn default() -> Self {
Self::ZERO
}
}
impl JointAnchor {
pub const ZERO: Self = Self::Local(Vector::ZERO);
pub fn compute_local(
anchor1: Self,
anchor2: Self,
pos1: Vector,
pos2: Vector,
rot1: &Rotation,
rot2: &Rotation,
) -> [Self; 2] {
let [local_anchor1, local_anchor2] = match [anchor1, anchor2] {
[JointAnchor::Local(anchor1), JointAnchor::Local(anchor2)] => [anchor1, anchor2],
[
JointAnchor::FromGlobal(anchor1),
JointAnchor::FromGlobal(anchor2),
] => [
rot1.inverse() * (anchor1 - pos1),
rot2.inverse() * (anchor2 - pos2),
],
[
JointAnchor::Local(anchor1),
JointAnchor::FromGlobal(anchor2),
] => [anchor1, rot2.inverse() * (anchor2 - pos2)],
[
JointAnchor::FromGlobal(anchor1),
JointAnchor::Local(anchor2),
] => [rot1.inverse() * (anchor1 - pos1), anchor2],
};
[
JointAnchor::Local(local_anchor1),
JointAnchor::Local(local_anchor2),
]
}
}
impl From<JointAnchor> for JointFrame {
fn from(anchor: JointAnchor) -> Self {
Self {
anchor,
basis: JointBasis::IDENTITY,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub enum JointBasis {
Local(Rot),
FromGlobal(Rot),
}
impl Default for JointBasis {
fn default() -> Self {
Self::IDENTITY
}
}
impl JointBasis {
pub const IDENTITY: Self = Self::Local(Rot::IDENTITY);
#[inline]
#[cfg(feature = "2d")]
pub fn from_local_x(x_axis: Vector) -> Self {
Self::Local(orthonormal_basis([
x_axis,
Vector::new(-x_axis.y, x_axis.x),
]))
}
#[inline]
#[cfg(feature = "2d")]
pub fn from_local_y(y_axis: Vector) -> Self {
Self::Local(orthonormal_basis([
Vector::new(y_axis.y, -y_axis.x),
y_axis,
]))
}
#[inline]
#[cfg(feature = "3d")]
pub fn from_local_xy(x_axis: Vector, y_axis: Vector) -> Self {
Self::Local(orthonormal_basis([x_axis, y_axis, x_axis.cross(y_axis)]))
}
#[inline]
#[cfg(feature = "3d")]
pub fn from_local_xz(x_axis: Vector, z_axis: Vector) -> Self {
Self::Local(orthonormal_basis([x_axis, z_axis.cross(x_axis), z_axis]))
}
#[inline]
#[cfg(feature = "3d")]
pub fn from_local_yz(y_axis: Vector, z_axis: Vector) -> Self {
Self::Local(orthonormal_basis([y_axis.cross(z_axis), y_axis, z_axis]))
}
#[inline]
#[cfg(feature = "2d")]
pub fn from_global_x(x_axis: Vector) -> Self {
Self::FromGlobal(orthonormal_basis([
x_axis,
Vector::new(-x_axis.y, x_axis.x),
]))
}
#[inline]
#[cfg(feature = "2d")]
pub fn from_global_y(y_axis: Vector) -> Self {
Self::FromGlobal(orthonormal_basis([
Vector::new(y_axis.y, -y_axis.x),
y_axis,
]))
}
#[inline]
#[cfg(feature = "3d")]
pub fn from_global_xy(x_axis: Vector, y_axis: Vector) -> Self {
Self::FromGlobal(orthonormal_basis([x_axis, y_axis, x_axis.cross(y_axis)]))
}
#[inline]
#[cfg(feature = "3d")]
pub fn from_global_xz(x_axis: Vector, z_axis: Vector) -> Self {
Self::FromGlobal(orthonormal_basis([x_axis, z_axis.cross(x_axis), z_axis]))
}
#[inline]
#[cfg(feature = "3d")]
pub fn from_global_yz(y_axis: Vector, z_axis: Vector) -> Self {
Self::FromGlobal(orthonormal_basis([y_axis.cross(z_axis), y_axis, z_axis]))
}
pub fn compute_local(rotation1: Self, rotation2: Self, rot1: &Rot, rot2: &Rot) -> [Self; 2] {
let [local_basis1, local_basis2] = match [rotation1, rotation2] {
[JointBasis::Local(basis1), JointBasis::Local(basis2)] => [basis1, basis2],
[
JointBasis::FromGlobal(basis1),
JointBasis::FromGlobal(basis2),
] => [basis1 * rot1.inverse(), basis2 * rot2.inverse()],
[JointBasis::Local(basis1), JointBasis::FromGlobal(basis2)] => {
[basis1, basis2 * rot2.inverse()]
}
[JointBasis::FromGlobal(basis1), JointBasis::Local(basis2)] => {
[basis1 * rot1.inverse(), basis2]
}
};
[
JointBasis::Local(local_basis1),
JointBasis::Local(local_basis2),
]
}
}
impl From<JointBasis> for JointFrame {
fn from(basis: JointBasis) -> Self {
Self {
anchor: JointAnchor::ZERO,
basis,
}
}
}