rapier2d/dynamics/joint/spring_joint.rs
1use crate::dynamics::joint::{GenericJoint, GenericJointBuilder, JointAxesMask};
2use crate::dynamics::{JointAxis, MotorModel};
3use crate::math::{Point, Real};
4
5#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
6#[derive(Copy, Clone, Debug, PartialEq)]
7#[repr(transparent)]
8/// A spring joint that pulls/pushes two bodies toward a target distance (like a spring or shock absorber).
9///
10/// Springs apply force based on:
11/// - **Stiffness**: How strong the spring force is (higher = stiffer spring)
12/// - **Damping**: Resistance to motion (higher = less bouncy, settles faster)
13/// - **Rest length**: The distance the spring "wants" to be
14///
15/// Use for:
16/// - Suspension systems (vehicles, furniture)
17/// - Bouncy connections
18/// - Soft constraints between objects
19/// - Procedural animation (following targets softly)
20///
21/// **Technical note**: Springs use implicit integration which adds some numerical damping.
22/// This means even zero-damping springs will eventually settle (more iterations = less damping).
23pub struct SpringJoint {
24 /// The underlying joint data.
25 pub data: GenericJoint,
26}
27
28impl SpringJoint {
29 /// Creates a new spring joint limiting the max distance between two bodies.
30 ///
31 /// The `max_dist` must be strictly greater than 0.0.
32 pub fn new(rest_length: Real, stiffness: Real, damping: Real) -> Self {
33 let data = GenericJointBuilder::new(JointAxesMask::empty())
34 .coupled_axes(JointAxesMask::LIN_AXES)
35 .motor_position(JointAxis::LinX, rest_length, stiffness, damping)
36 .motor_model(JointAxis::LinX, MotorModel::ForceBased)
37 .build();
38 Self { data }
39 }
40
41 /// The underlying generic joint.
42 pub fn data(&self) -> &GenericJoint {
43 &self.data
44 }
45
46 /// Are contacts between the attached rigid-bodies enabled?
47 pub fn contacts_enabled(&self) -> bool {
48 self.data.contacts_enabled
49 }
50
51 /// Sets whether contacts between the attached rigid-bodies are enabled.
52 pub fn set_contacts_enabled(&mut self, enabled: bool) -> &mut Self {
53 self.data.set_contacts_enabled(enabled);
54 self
55 }
56
57 /// The joint’s anchor, expressed in the local-space of the first rigid-body.
58 #[must_use]
59 pub fn local_anchor1(&self) -> Point<Real> {
60 self.data.local_anchor1()
61 }
62
63 /// Sets the joint’s anchor, expressed in the local-space of the first rigid-body.
64 pub fn set_local_anchor1(&mut self, anchor1: Point<Real>) -> &mut Self {
65 self.data.set_local_anchor1(anchor1);
66 self
67 }
68
69 /// The joint’s anchor, expressed in the local-space of the second rigid-body.
70 #[must_use]
71 pub fn local_anchor2(&self) -> Point<Real> {
72 self.data.local_anchor2()
73 }
74
75 /// Sets the joint’s anchor, expressed in the local-space of the second rigid-body.
76 pub fn set_local_anchor2(&mut self, anchor2: Point<Real>) -> &mut Self {
77 self.data.set_local_anchor2(anchor2);
78 self
79 }
80
81 /// Sets whether spring constants are mass-dependent or mass-independent.
82 ///
83 /// - `MotorModel::ForceBased` (default): Stiffness/damping are absolute values.
84 /// Heavier objects will respond differently to the same spring constants.
85 /// - `MotorModel::AccelerationBased`: Spring constants auto-scale with mass.
86 /// Objects of different masses behave more similarly.
87 ///
88 /// Most users can ignore this and use the default.
89 pub fn set_spring_model(&mut self, model: MotorModel) -> &mut Self {
90 self.data.set_motor_model(JointAxis::LinX, model);
91 self
92 }
93
94 // /// The maximum distance allowed between the attached objects.
95 // #[must_use]
96 // pub fn rest_length(&self) -> Option<Real> {
97 // self.data.limits(JointAxis::X).map(|l| l.max)
98 // }
99 //
100 // /// Sets the maximum allowed distance between the attached objects.
101 // ///
102 // /// The `max_dist` must be strictly greater than 0.0.
103 // pub fn set_rest_length(&mut self, max_dist: Real) -> &mut Self {
104 // self.data.set_limits(JointAxis::X, [0.0, max_dist]);
105 // self
106 // }
107}
108
109impl From<SpringJoint> for GenericJoint {
110 fn from(val: SpringJoint) -> GenericJoint {
111 val.data
112 }
113}
114
115/// A [SpringJoint] joint using the builder pattern.
116///
117/// This builds a spring-damper joint which applies a force proportional to the distance between two objects.
118/// See the documentation of [SpringJoint] for more information on its behavior.
119#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
120#[derive(Copy, Clone, Debug, PartialEq)]
121pub struct SpringJointBuilder(pub SpringJoint);
122
123impl SpringJointBuilder {
124 /// Creates a new builder for spring joints.
125 ///
126 /// This axis is expressed in the local-space of both rigid-bodies.
127 pub fn new(rest_length: Real, stiffness: Real, damping: Real) -> Self {
128 Self(SpringJoint::new(rest_length, stiffness, damping))
129 }
130
131 /// Sets whether contacts between the attached rigid-bodies are enabled.
132 #[must_use]
133 pub fn contacts_enabled(mut self, enabled: bool) -> Self {
134 self.0.set_contacts_enabled(enabled);
135 self
136 }
137
138 /// Sets the joint’s anchor, expressed in the local-space of the first rigid-body.
139 #[must_use]
140 pub fn local_anchor1(mut self, anchor1: Point<Real>) -> Self {
141 self.0.set_local_anchor1(anchor1);
142 self
143 }
144
145 /// Sets the joint’s anchor, expressed in the local-space of the second rigid-body.
146 #[must_use]
147 pub fn local_anchor2(mut self, anchor2: Point<Real>) -> Self {
148 self.0.set_local_anchor2(anchor2);
149 self
150 }
151
152 /// Set the spring used by this joint to reach the desired target velocity and position.
153 ///
154 /// Setting this to `MotorModel::ForceBased` (which is the default value for this joint) makes the spring constants
155 /// (stiffness and damping) parameter understood as in the regular spring-mass-damper system. With
156 /// `MotorModel::AccelerationBased`, the spring constants will be automatically scaled by the attached masses,
157 /// making the spring more mass-independent.
158 #[must_use]
159 pub fn spring_model(mut self, model: MotorModel) -> Self {
160 self.0.set_spring_model(model);
161 self
162 }
163
164 // /// Sets the maximum allowed distance between the attached bodies.
165 // ///
166 // /// The `max_dist` must be strictly greater than 0.0.
167 // #[must_use]
168 // pub fn max_distance(mut self, max_dist: Real) -> Self {
169 // self.0.set_max_distance(max_dist);
170 // self
171 // }
172
173 /// Builds the spring joint.
174 #[must_use]
175 pub fn build(self) -> SpringJoint {
176 self.0
177 }
178}
179
180impl From<SpringJointBuilder> for GenericJoint {
181 fn from(val: SpringJointBuilder) -> GenericJoint {
182 val.0.into()
183 }
184}