bevy_xpbd_3d/constraints/joints/
spherical.rs1use crate::prelude::*;
4use bevy::{
5 ecs::entity::{EntityMapper, MapEntities},
6 prelude::*,
7};
8
9#[derive(Component, Clone, Copy, Debug, PartialEq)]
13#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
14pub struct SphericalJoint {
15 pub entity1: Entity,
17 pub entity2: Entity,
19 pub local_anchor1: Vector,
21 pub local_anchor2: Vector,
23 pub swing_axis: Vector3,
25 pub twist_axis: Vector3,
27 pub swing_limit: Option<AngleLimit>,
29 pub twist_limit: Option<AngleLimit>,
31 pub damping_linear: Scalar,
33 pub damping_angular: Scalar,
35 pub position_lagrange: Scalar,
37 pub swing_lagrange: Scalar,
39 pub twist_lagrange: Scalar,
41 pub compliance: Scalar,
43 pub force: Vector,
45 pub swing_torque: Torque,
47 pub twist_torque: Torque,
49}
50
51impl XpbdConstraint<2> for SphericalJoint {
52 fn entities(&self) -> [Entity; 2] {
53 [self.entity1, self.entity2]
54 }
55
56 fn clear_lagrange_multipliers(&mut self) {
57 self.position_lagrange = 0.0;
58 self.swing_lagrange = 0.0;
59 self.twist_lagrange = 0.0;
60 }
61
62 fn solve(&mut self, bodies: [&mut RigidBodyQueryItem; 2], dt: Scalar) {
63 let [body1, body2] = bodies;
64 let compliance = self.compliance;
65
66 let mut lagrange = self.position_lagrange;
68 self.force = self.align_position(
69 body1,
70 body2,
71 self.local_anchor1,
72 self.local_anchor2,
73 &mut lagrange,
74 compliance,
75 dt,
76 );
77 self.position_lagrange = lagrange;
78
79 self.swing_torque = self.apply_swing_limits(body1, body2, dt);
81
82 self.twist_torque = self.apply_twist_limits(body1, body2, dt);
84 }
85}
86
87impl Joint for SphericalJoint {
88 fn new(entity1: Entity, entity2: Entity) -> Self {
89 Self {
90 entity1,
91 entity2,
92 local_anchor1: Vector::ZERO,
93 local_anchor2: Vector::ZERO,
94 swing_axis: Vector3::X,
95 twist_axis: Vector3::Y,
96 swing_limit: None,
97 twist_limit: None,
98 damping_linear: 1.0,
99 damping_angular: 1.0,
100 position_lagrange: 0.0,
101 swing_lagrange: 0.0,
102 twist_lagrange: 0.0,
103 compliance: 0.0,
104 force: Vector::ZERO,
105 #[cfg(feature = "2d")]
106 swing_torque: 0.0,
107 #[cfg(feature = "3d")]
108 swing_torque: Vector::ZERO,
109 #[cfg(feature = "2d")]
110 twist_torque: 0.0,
111 #[cfg(feature = "3d")]
112 twist_torque: Vector::ZERO,
113 }
114 }
115
116 fn with_compliance(self, compliance: Scalar) -> Self {
117 Self { compliance, ..self }
118 }
119
120 fn with_local_anchor_1(self, anchor: Vector) -> Self {
121 Self {
122 local_anchor1: anchor,
123 ..self
124 }
125 }
126
127 fn with_local_anchor_2(self, anchor: Vector) -> Self {
128 Self {
129 local_anchor2: anchor,
130 ..self
131 }
132 }
133
134 fn with_linear_velocity_damping(self, damping: Scalar) -> Self {
135 Self {
136 damping_linear: damping,
137 ..self
138 }
139 }
140
141 fn with_angular_velocity_damping(self, damping: Scalar) -> Self {
142 Self {
143 damping_angular: damping,
144 ..self
145 }
146 }
147
148 fn local_anchor_1(&self) -> Vector {
149 self.local_anchor1
150 }
151
152 fn local_anchor_2(&self) -> Vector {
153 self.local_anchor2
154 }
155
156 fn damping_linear(&self) -> Scalar {
157 self.damping_linear
158 }
159
160 fn damping_angular(&self) -> Scalar {
161 self.damping_angular
162 }
163}
164
165impl SphericalJoint {
166 pub fn with_swing_limits(self, min: Scalar, max: Scalar) -> Self {
168 Self {
169 swing_limit: Some(AngleLimit::new(min, max)),
170 ..self
171 }
172 }
173
174 #[cfg(feature = "3d")]
176 pub fn with_twist_limits(self, min: Scalar, max: Scalar) -> Self {
177 Self {
178 twist_limit: Some(AngleLimit::new(min, max)),
179 ..self
180 }
181 }
182
183 fn apply_swing_limits(
185 &mut self,
186 body1: &mut RigidBodyQueryItem,
187 body2: &mut RigidBodyQueryItem,
188 dt: Scalar,
189 ) -> Torque {
190 if let Some(joint_limit) = self.swing_limit {
191 let a1 = body1.rotation.rotate_vec3(self.swing_axis);
192 let a2 = body2.rotation.rotate_vec3(self.swing_axis);
193
194 let n = a1.cross(a2);
195 let n_magnitude = n.length();
196
197 if n_magnitude <= Scalar::EPSILON {
198 return Torque::ZERO;
199 }
200
201 let n = n / n_magnitude;
202
203 if let Some(dq) = joint_limit.compute_correction(n, a1, a2, PI) {
204 let mut lagrange = self.swing_lagrange;
205 let torque =
206 self.align_orientation(body1, body2, dq, &mut lagrange, self.compliance, dt);
207 self.swing_lagrange = lagrange;
208 return torque;
209 }
210 }
211 Torque::ZERO
212 }
213
214 fn apply_twist_limits(
216 &mut self,
217 body1: &mut RigidBodyQueryItem,
218 body2: &mut RigidBodyQueryItem,
219 dt: Scalar,
220 ) -> Torque {
221 if let Some(joint_limit) = self.twist_limit {
222 let a1 = body1.rotation.rotate_vec3(self.swing_axis);
223 let a2 = body2.rotation.rotate_vec3(self.swing_axis);
224
225 let b1 = body1.rotation.rotate_vec3(self.twist_axis);
226 let b2 = body2.rotation.rotate_vec3(self.twist_axis);
227
228 let n = a1 + a2;
229 let n_magnitude = n.length();
230
231 if n_magnitude <= Scalar::EPSILON {
232 return Torque::ZERO;
233 }
234
235 let n = n / n_magnitude;
236
237 let n1 = b1 - n.dot(b1) * n;
238 let n2 = b2 - n.dot(b2) * n;
239 let n1_magnitude = n1.length();
240 let n2_magnitude = n2.length();
241
242 if n1_magnitude <= Scalar::EPSILON || n2_magnitude <= Scalar::EPSILON {
243 return Torque::ZERO;
244 }
245
246 let n1 = n1 / n1_magnitude;
247 let n2 = n2 / n2_magnitude;
248
249 let max_correction = if a1.dot(a2) > -0.5 { 2.0 * PI } else { dt };
250
251 if let Some(dq) = joint_limit.compute_correction(n, n1, n2, max_correction) {
252 let mut lagrange = self.twist_lagrange;
253 let torque =
254 self.align_orientation(body1, body2, dq, &mut lagrange, self.compliance, dt);
255 self.twist_lagrange = lagrange;
256 return torque;
257 }
258 }
259 Torque::ZERO
260 }
261}
262
263impl PositionConstraint for SphericalJoint {}
264
265impl AngularConstraint for SphericalJoint {}
266
267impl MapEntities for SphericalJoint {
268 fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
269 self.entity1 = entity_mapper.map_entity(self.entity1);
270 self.entity2 = entity_mapper.map_entity(self.entity2);
271 }
272}