1use crate::PhysicsError;
4use crate::body::{RigidBodyHandle, Slot};
5use ferravex_math::{Quat, Vec3};
6use std::ops::{Index, IndexMut};
7pub const MAX_JOINTS: usize = 4096;
8#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct JointHandle {
10 pub(crate) index: usize,
11 pub(crate) generation: u32,
12}
13#[derive(Clone, Copy, Debug, PartialEq)]
14pub enum JointKind {
15 Fixed,
16 Hinge {
17 axis1: Vec3,
18 axis2: Vec3,
19 },
20 Distance {
21 length: f32,
22 },
23 Spring {
25 length: f32,
26 stiffness: f32,
27 damping: f32,
28 },
29}
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct JointMotor {
32 pub target_velocity: f32,
33 pub target_position: Option<f32>,
34 pub stiffness: f32,
35 pub max_speed: f32,
36 pub max_torque: f32,
37}
38impl JointMotor {
39 pub fn velocity(speed: f32, max_torque: f32) -> Self {
40 Self {
41 target_velocity: speed,
42 target_position: None,
43 stiffness: 0.,
44 max_speed: speed.abs(),
45 max_torque,
46 }
47 }
48 pub fn position(angle: f32, stiffness: f32, max_speed: f32, max_torque: f32) -> Self {
49 Self {
50 target_velocity: 0.,
51 target_position: Some(angle),
52 stiffness,
53 max_speed,
54 max_torque,
55 }
56 }
57 fn valid(self) -> bool {
58 self.target_velocity.is_finite()
59 && self.target_velocity.abs() <= 1000.
60 && self
61 .target_position
62 .is_none_or(|p| p.is_finite() && p.abs() <= std::f32::consts::PI)
63 && [self.stiffness, self.max_speed, self.max_torque]
64 .iter()
65 .all(|v| v.is_finite() && (0. ..=1e9).contains(v))
66 && self.max_speed <= 1000.
67 }
68}
69#[derive(Clone, Copy, Debug, PartialEq)]
70pub struct JointDesc {
71 pub kind: JointKind,
72 pub local_anchor1: Vec3,
73 pub local_anchor2: Vec3,
74 pub reference_rotation: Option<Quat>,
76 pub contacts_enabled: bool,
77 pub enabled: bool,
78 pub limits: Option<[f32; 2]>,
80 pub motor: Option<JointMotor>,
81}
82impl JointDesc {
83 pub fn new(kind: JointKind) -> Self {
84 Self {
85 kind,
86 local_anchor1: Vec3::ZERO,
87 local_anchor2: Vec3::ZERO,
88 reference_rotation: None,
89 contacts_enabled: false,
90 enabled: true,
91 limits: None,
92 motor: None,
93 }
94 }
95 pub(crate) fn valid(self, planar: bool) -> bool {
96 let scalar = |v: f32| v.is_finite() && (0. ..=1e6).contains(&v);
97 let hinge = matches!(self.kind, JointKind::Hinge { .. });
98 [self.local_anchor1, self.local_anchor2]
99 .iter()
100 .all(|p| p.is_finite() && p.abs().max_element() <= 1e6 && (!planar || p.z == 0.))
101 && self.reference_rotation.is_none_or(|q| {
102 q.is_finite()
103 && (q.length_squared() - 1.).abs() < 0.001
104 && (!planar || (q * Vec3::Z - Vec3::Z).length_squared() < 1e-6)
105 })
106 && match self.kind {
107 JointKind::Fixed => true,
108 JointKind::Distance { length } => scalar(length),
109 JointKind::Spring {
110 length,
111 stiffness,
112 damping,
113 } => scalar(length) && scalar(stiffness) && stiffness > 0. && scalar(damping),
114 JointKind::Hinge { axis1, axis2 } => [axis1, axis2].iter().all(|a| {
115 a.is_finite()
116 && (a.length_squared() - 1.).abs() < 0.001
117 && (!planar || a.distance_squared(Vec3::Z) < 1e-6)
118 }),
119 }
120 && self.limits.is_none_or(|[lo, hi]| {
121 hinge
122 && lo.is_finite()
123 && hi.is_finite()
124 && lo <= hi
125 && lo >= -std::f32::consts::PI
126 && hi <= std::f32::consts::PI
127 })
128 && self.motor.is_none_or(|m| hinge && m.valid())
129 }
130}
131#[derive(Clone, Copy, Debug, Default)]
132pub(crate) struct CachedRow {
133 pub impulse: f32,
134 pub axis: Vec3,
135}
136#[derive(Clone, Debug)]
137pub struct Joint {
138 pub(crate) a: RigidBodyHandle,
139 pub(crate) b: RigidBodyHandle,
140 pub(crate) desc: JointDesc,
141 pub(crate) dirty: bool,
142 pub(crate) revisions: [u64; 2],
143 pub(crate) dt: f32,
144 pub(crate) cache: [CachedRow; 8],
145}
146impl Joint {
147 pub fn bodies(&self) -> [RigidBodyHandle; 2] {
148 [self.a, self.b]
149 }
150 pub fn description(&self) -> JointDesc {
151 self.desc
152 }
153 pub fn set_motor(&mut self, motor: Option<JointMotor>) -> Result<(), PhysicsError> {
154 let desc = JointDesc { motor, ..self.desc };
155 self.set_description(desc)
156 }
157 pub fn set_limits(&mut self, limits: Option<[f32; 2]>) -> Result<(), PhysicsError> {
158 self.set_description(JointDesc {
159 limits,
160 ..self.desc
161 })
162 }
163 pub fn set_enabled(&mut self, enabled: bool) {
164 if self.desc.enabled != enabled {
165 self.desc.enabled = enabled;
166 self.dirty = true;
167 }
168 }
169 pub fn set_contacts_enabled(&mut self, enabled: bool) {
170 if self.desc.contacts_enabled != enabled {
171 self.desc.contacts_enabled = enabled;
172 self.dirty = true;
173 }
174 }
175 fn set_description(&mut self, desc: JointDesc) -> Result<(), PhysicsError> {
176 if !desc.valid(false) {
179 return Err(PhysicsError::InvalidJoint);
180 }
181 if self.desc != desc {
182 self.desc = desc;
183 self.dirty = true;
184 }
185 Ok(())
186 }
187}
188#[derive(Clone, Debug)]
189pub struct JointSet {
190 slots: Vec<Slot<Joint>>,
191 len: usize,
192}
193crate::body::arena!(JointSet, Joint, JointHandle);