1use gizmo_core::Entity;
2use gizmo_math::{Quat, Vec3};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Joint {
7 pub entity_a: Entity,
8 pub entity_b: Entity,
9 pub local_anchor_a: Vec3,
10 pub local_anchor_b: Vec3,
11 pub break_force: f32,
12 pub break_torque: f32,
13 #[serde(skip)]
14 pub is_broken: bool,
15 pub collision_enabled: bool,
16 pub data: JointData,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub enum JointData {
21 Fixed,
22 Hinge(HingeJointData),
23 BallSocket(BallSocketJointData),
24 Slider(SliderJointData),
25 Spring(SpringJointData),
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29pub enum JointType {
30 Fixed,
31 Hinge,
32 BallSocket,
33 Slider,
34 Spring,
35}
36
37#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
38pub struct HingeJointData {
39 pub axis: Vec3,
40 pub use_limits: bool,
41 pub lower_limit: f32,
42 pub upper_limit: f32,
43 pub use_motor: bool,
44 pub motor_target_velocity: f32,
45 pub motor_max_force: f32,
46 #[serde(skip)]
47 pub current_angle: f32,
48}
49
50#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
51pub struct BallSocketJointData {
52 pub use_cone_limit: bool,
53 pub cone_limit_angle: f32,
54 #[serde(default)]
55 pub initial_relative_rotation: Option<Quat>,
56}
57
58#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
59pub struct SliderJointData {
60 pub axis: Vec3,
61 pub use_limits: bool,
62 pub lower_limit: f32,
63 pub upper_limit: f32,
64 pub use_motor: bool,
65 pub motor_target_velocity: f32,
66 pub motor_max_force: f32,
67 #[serde(skip)]
68 pub current_position: f32,
69 #[serde(default)]
70 pub initial_relative_rotation: Option<Quat>,
71}
72
73#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
74pub struct SpringJointData {
75 pub rest_length: f32,
76 pub stiffness: f32,
77 pub damping: f32,
78 pub min_length: f32,
79 pub max_length: Option<f32>,
80}
81
82impl Joint {
83 pub fn joint_type(&self) -> &'static str {
84 match &self.data {
85 JointData::Fixed => "Fixed",
86 JointData::Hinge(_) => "Hinge",
87 JointData::BallSocket(_) => "BallSocket",
88 JointData::Slider(_) => "Slider",
89 JointData::Spring(_) => "Spring",
90 }
91 }
92
93 pub fn fixed(
94 entity_a: Entity,
95 entity_b: Entity,
96 local_anchor_a: Vec3,
97 local_anchor_b: Vec3,
98 ) -> Self {
99 debug_assert_ne!(
100 entity_a, entity_b,
101 "Joint: entity_a and entity_b must be different"
102 );
103 Self {
104 entity_a,
105 entity_b,
106 local_anchor_a,
107 local_anchor_b,
108 break_force: f32::INFINITY,
109 break_torque: f32::INFINITY,
110 is_broken: false,
111 collision_enabled: false,
112 data: JointData::Fixed,
113 }
114 }
115
116 pub fn hinge(
117 entity_a: Entity,
118 entity_b: Entity,
119 local_anchor_a: Vec3,
120 local_anchor_b: Vec3,
121 axis: Vec3,
122 ) -> Self {
123 debug_assert_ne!(
124 entity_a, entity_b,
125 "Joint: entity_a and entity_b must be different"
126 );
127 let safe_axis = if axis.length_squared() > 1e-6 {
128 axis.normalize()
129 } else {
130 Vec3::Y
131 };
132 Self {
133 entity_a,
134 entity_b,
135 local_anchor_a,
136 local_anchor_b,
137 break_force: f32::INFINITY,
138 break_torque: f32::INFINITY,
139 is_broken: false,
140 collision_enabled: false,
141 data: JointData::Hinge(HingeJointData {
142 axis: safe_axis,
143 use_limits: false,
144 lower_limit: -std::f32::consts::PI,
145 upper_limit: std::f32::consts::PI,
146 use_motor: false,
147 motor_target_velocity: 0.0,
148 motor_max_force: 0.0,
149 current_angle: 0.0,
150 }),
151 }
152 }
153
154 pub fn ball_socket(
155 entity_a: Entity,
156 entity_b: Entity,
157 local_anchor_a: Vec3,
158 local_anchor_b: Vec3,
159 ) -> Self {
160 debug_assert_ne!(
161 entity_a, entity_b,
162 "Joint: entity_a and entity_b must be different"
163 );
164 Self {
165 entity_a,
166 entity_b,
167 local_anchor_a,
168 local_anchor_b,
169 break_force: f32::INFINITY,
170 break_torque: f32::INFINITY,
171 is_broken: false,
172 collision_enabled: false,
173 data: JointData::BallSocket(BallSocketJointData {
174 use_cone_limit: false,
175 cone_limit_angle: std::f32::consts::PI,
176 initial_relative_rotation: None,
177 }),
178 }
179 }
180
181 pub fn slider(
182 entity_a: Entity,
183 entity_b: Entity,
184 local_anchor_a: Vec3,
185 local_anchor_b: Vec3,
186 axis: Vec3,
187 ) -> Self {
188 debug_assert_ne!(
189 entity_a, entity_b,
190 "Joint: entity_a and entity_b must be different"
191 );
192 let safe_axis = if axis.length_squared() > 1e-6 {
193 axis.normalize()
194 } else {
195 Vec3::Y
196 };
197 Self {
198 entity_a,
199 entity_b,
200 local_anchor_a,
201 local_anchor_b,
202 break_force: f32::INFINITY,
203 break_torque: f32::INFINITY,
204 is_broken: false,
205 collision_enabled: false,
206 data: JointData::Slider(SliderJointData {
207 axis: safe_axis,
208 use_limits: false,
209 lower_limit: -10.0,
210 upper_limit: 10.0,
211 use_motor: false,
212 motor_target_velocity: 0.0,
213 motor_max_force: 0.0,
214 current_position: 0.0,
215 initial_relative_rotation: None,
216 }),
217 }
218 }
219
220 pub fn spring(
221 entity_a: Entity,
222 entity_b: Entity,
223 local_anchor_a: Vec3,
224 local_anchor_b: Vec3,
225 rest_length: f32,
226 stiffness: f32,
227 damping: f32,
228 ) -> Self {
229 debug_assert_ne!(
230 entity_a, entity_b,
231 "Joint: entity_a and entity_b must be different"
232 );
233 Self {
234 entity_a,
235 entity_b,
236 local_anchor_a,
237 local_anchor_b,
238 break_force: f32::INFINITY,
239 break_torque: f32::INFINITY,
240 is_broken: false,
241 collision_enabled: false,
242 data: JointData::Spring(SpringJointData {
243 rest_length,
244 stiffness,
245 damping,
246 min_length: 0.0,
247 max_length: None,
248 }),
249 }
250 }
251
252 pub fn with_break_force(mut self, force: f32, torque: f32) -> Self {
253 self.break_force = force;
254 self.break_torque = torque;
255 self
256 }
257
258 pub fn with_collision(mut self, enabled: bool) -> Self {
259 self.collision_enabled = enabled;
260 self
261 }
262
263 pub fn check_break(&mut self, applied_force: f32, applied_torque: f32) -> bool {
264 if applied_force > self.break_force {
265 self.is_broken = true;
266 return true;
267 }
268 if applied_torque > self.break_torque {
269 self.is_broken = true;
270 return true;
271 }
272 false
273 }
274}