Skip to main content

gizmo_physics_rigid/joints/
data.rs

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}