use crate::{
dynamics::joints::{EntityConstraint, JointSystems},
prelude::*,
};
use bevy::{
ecs::{
entity::{EntityMapper, MapEntities},
reflect::ReflectMapEntities,
},
prelude::*,
};
#[doc = include_str!("./images/point_constraint.svg")]
#[doc = include_str!("./images/swing_twist_limit.svg")]
#[derive(Component, Clone, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, MapEntities, PartialEq)]
pub struct SphericalJoint {
pub body1: Entity,
pub body2: Entity,
pub frame1: JointFrame,
pub frame2: JointFrame,
pub twist_axis: Vector,
pub swing_limit: Option<AngleLimit>,
pub twist_limit: Option<AngleLimit>,
pub point_compliance: Scalar,
pub swing_compliance: Scalar,
pub twist_compliance: Scalar,
}
impl EntityConstraint<2> for SphericalJoint {
fn entities(&self) -> [Entity; 2] {
[self.body1, self.body2]
}
}
impl SphericalJoint {
pub const DEFAULT_TWIST_AXIS: Vector = Vector::Y;
#[inline]
pub const fn new(body1: Entity, body2: Entity) -> Self {
Self {
body1,
body2,
frame1: JointFrame::IDENTITY,
frame2: JointFrame::IDENTITY,
twist_axis: Self::DEFAULT_TWIST_AXIS,
swing_limit: None,
twist_limit: None,
point_compliance: 0.0,
swing_compliance: 0.0,
twist_compliance: 0.0,
}
}
#[inline]
pub const fn with_twist_axis(mut self, twist_axis: Vector) -> Self {
self.twist_axis = twist_axis;
self
}
#[inline]
pub fn with_local_frame1(mut self, frame: impl Into<Isometry>) -> Self {
self.frame1 = JointFrame::local(frame);
self
}
#[inline]
pub fn with_local_frame2(mut self, frame: impl Into<Isometry>) -> Self {
self.frame2 = JointFrame::local(frame);
self
}
#[inline]
pub const fn with_anchor(mut self, anchor: Vector) -> Self {
self.frame1.anchor = JointAnchor::FromGlobal(anchor);
self.frame2.anchor = JointAnchor::FromGlobal(anchor);
self
}
#[inline]
pub const fn with_local_anchor1(mut self, anchor: Vector) -> Self {
self.frame1.anchor = JointAnchor::Local(anchor);
self
}
#[inline]
pub const fn with_local_anchor2(mut self, anchor: Vector) -> Self {
self.frame2.anchor = JointAnchor::Local(anchor);
self
}
#[inline]
pub fn with_basis(mut self, basis: impl Into<Rot>) -> Self {
let basis = basis.into();
self.frame1.basis = JointBasis::FromGlobal(basis);
self.frame2.basis = JointBasis::FromGlobal(basis);
self
}
#[inline]
pub fn with_local_basis1(mut self, basis: impl Into<Rot>) -> Self {
self.frame1.basis = JointBasis::Local(basis.into());
self
}
#[inline]
pub fn with_local_basis2(mut self, basis: impl Into<Rot>) -> Self {
self.frame2.basis = JointBasis::Local(basis.into());
self
}
#[inline]
pub fn local_frame1(&self) -> Option<Isometry> {
self.frame1.get_local_isometry()
}
#[inline]
pub fn local_frame2(&self) -> Option<Isometry> {
self.frame2.get_local_isometry()
}
#[inline]
pub const fn local_anchor1(&self) -> Option<Vector> {
match self.frame1.anchor {
JointAnchor::Local(anchor) => Some(anchor),
_ => None,
}
}
#[inline]
pub const fn local_anchor2(&self) -> Option<Vector> {
match self.frame2.anchor {
JointAnchor::Local(anchor) => Some(anchor),
_ => None,
}
}
#[inline]
pub const fn local_basis1(&self) -> Option<Rot> {
match self.frame1.basis {
JointBasis::Local(basis) => Some(basis),
_ => None,
}
}
#[inline]
pub const fn local_basis2(&self) -> Option<Rot> {
match self.frame2.basis {
JointBasis::Local(basis) => Some(basis),
_ => None,
}
}
#[inline]
pub fn local_twist_axis1(&self) -> Option<Vector> {
match self.frame1.basis {
JointBasis::Local(basis) => Some(basis * self.twist_axis),
_ => None,
}
}
#[inline]
pub fn local_twist_axis2(&self) -> Option<Vector> {
match self.frame2.basis {
JointBasis::Local(basis) => Some(basis * self.twist_axis),
_ => None,
}
}
#[inline]
pub const fn with_swing_limits(mut self, min: Scalar, max: Scalar) -> Self {
self.swing_limit = Some(AngleLimit::new(min, max));
self
}
#[inline]
pub const fn with_twist_limits(mut self, min: Scalar, max: Scalar) -> Self {
self.twist_limit = Some(AngleLimit::new(min, max));
self
}
#[inline]
#[deprecated(
since = "0.4.0",
note = "Use `with_point_compliance`, `with_swing_compliance`, and `with_twist_compliance` instead."
)]
pub const fn with_compliance(mut self, compliance: Scalar) -> Self {
self.point_compliance = compliance;
self.swing_compliance = compliance;
self.twist_compliance = compliance;
self
}
#[inline]
pub const fn with_point_compliance(mut self, compliance: Scalar) -> Self {
self.point_compliance = compliance;
self
}
#[inline]
pub const fn with_swing_compliance(mut self, compliance: Scalar) -> Self {
self.swing_compliance = compliance;
self
}
#[inline]
pub const fn with_twist_compliance(mut self, compliance: Scalar) -> Self {
self.twist_compliance = compliance;
self
}
}
impl MapEntities for SphericalJoint {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
self.body1 = entity_mapper.get_mapped(self.body1);
self.body2 = entity_mapper.get_mapped(self.body2);
}
}
pub(super) fn plugin(app: &mut App) {
app.add_systems(
PhysicsSchedule,
update_local_frames.in_set(JointSystems::PrepareLocalFrames),
);
}
fn update_local_frames(
mut joints: Query<&mut SphericalJoint, Changed<SphericalJoint>>,
bodies: Query<(&Position, &Rotation)>,
) {
for mut joint in &mut joints {
if matches!(joint.frame1.anchor, JointAnchor::Local(_))
&& matches!(joint.frame2.anchor, JointAnchor::Local(_))
&& matches!(joint.frame1.basis, JointBasis::Local(_))
&& matches!(joint.frame2.basis, JointBasis::Local(_))
{
continue;
}
let Ok([(pos1, rot1), (pos2, rot2)]) = bodies.get_many(joint.entities()) else {
continue;
};
let [frame1, frame2] =
JointFrame::compute_local(joint.frame1, joint.frame2, pos1.0, pos2.0, rot1, rot2);
joint.frame1 = frame1;
joint.frame2 = frame2;
}
}
#[cfg(feature = "debug-plugin")]
impl DebugRenderConstraint<2> for SphericalJoint {
type Context = ();
fn debug_render(
&self,
positions: [Vector; 2],
rotations: [Rotation; 2],
_context: &mut Self::Context,
gizmos: &mut Gizmos<PhysicsGizmos>,
config: &PhysicsGizmos,
) {
let [pos1, pos2] = positions;
let [rot1, rot2] = rotations;
let Some(local_anchor1) = self.local_anchor1() else {
return;
};
let Some(local_anchor2) = self.local_anchor2() else {
return;
};
let anchor1 = pos1 + rot1 * local_anchor1;
let anchor2 = pos2 + rot2 * local_anchor2;
if let Some(anchor_color) = config.joint_anchor_color {
gizmos.draw_line(pos1, anchor1, anchor_color);
gizmos.draw_line(pos2, anchor2, anchor_color);
}
if let Some(color) = config.joint_separation_color {
gizmos.draw_line(anchor1, anchor2, color);
}
}
}