Skip to main content

rapier3d/dynamics/soft_body/
soft_body_motion.rs

1//! Moving a soft body's particles from outside: pinning, attachments to rigid bodies, user forces and impulses.
2use super::SoftBody;
3use crate::dynamics::{RigidBodyHandle, RigidBodySet};
4use crate::math::{Real, Vector};
5#[cfg(not(feature = "std"))]
6#[allow(unused_imports)]
7use simba::scalar::{ComplexField as _, RealField as _};
8
9/// A particle of a soft body attached to a rigid body (see [`SoftBody::attach_particle`]): a
10/// point-to-point constraint between the particle and a point of the body.
11#[derive(Copy, Clone, Debug)]
12#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
13pub struct SoftParticleAttachment {
14    /// The attached particle.
15    pub particle: u32,
16    /// The rigid body it is attached to.
17    pub body: RigidBodyHandle,
18    /// The attachment point in the rigid body's local frame.
19    pub local_anchor: Vector,
20    /// Accumulated impulse of the last step (warm-start state), one per world axis.
21    pub(crate) impulse: Vector,
22}
23
24impl SoftParticleAttachment {
25    /// The impulse this attachment applied during the last substep (world axes, positive when
26    /// pulling the particle back toward the anchor).
27    pub fn impulse(&self) -> Vector {
28        self.impulse
29    }
30}
31
32impl SoftBody {
33    /// Pins (`pinned = true`) or releases (`pinned = false`) the `i`-th particle: a pinned one
34    /// is kinematic, holding its position or following [`Self::set_particle_kinematic_target`] or
35    /// [`Self::set_particle_velocity`]; release restores its nominal mass and keeps its velocity.
36    pub fn set_particle_pinned(&mut self, i: usize, pinned: bool) {
37        let particle = &mut self.particles[i];
38        if pinned {
39            // Held where it is until given a target or a velocity.
40            particle.inv_mass = 0.0;
41            particle.velocity = Vector::ZERO;
42            particle.next_position = Some(particle.position);
43        } else {
44            particle.inv_mass = crate::utils::inv(particle.mass);
45            particle.next_position = None;
46        }
47        self.modified = true;
48    }
49
50    /// Attaches the `i`-th particle to a rigid body by a two-way point-to-point constraint (unlike
51    /// pinning), undone by [`Self::detach_particle`]; the anchor is its current position in
52    /// `body`'s local frame, and a twice-attached particle keeps both attachments.
53    pub fn attach_particle(&mut self, i: usize, body: RigidBodyHandle, bodies: &RigidBodySet) {
54        let particle = &self.particles[i];
55        let local_anchor = bodies
56            .get(body)
57            .map(|rb| rb.position().inverse_transform_point(particle.position))
58            .unwrap_or(particle.position);
59        self.attachments.push(SoftParticleAttachment {
60            particle: i as u32,
61            body,
62            local_anchor,
63            impulse: Vector::ZERO,
64        });
65        self.attachments_modified = true;
66        self.modified = true;
67    }
68
69    /// Detaches the `i`-th particle from every rigid body it was attached to with
70    /// [`Self::attach_particle`]. Returns whether it was attached at all.
71    pub fn detach_particle(&mut self, i: usize) -> bool {
72        let before = self.attachments.len();
73        self.attachments.retain(|a| a.particle != i as u32);
74        let detached = self.attachments.len() != before;
75        if detached {
76            self.attachments_modified = true;
77            self.modified = true;
78        }
79        detached
80    }
81
82    /// The particles of this soft body attached to rigid bodies.
83    pub fn particle_attachments(&self) -> &[SoftParticleAttachment] {
84        &self.attachments
85    }
86
87    /// Adds `force` to the user force of every particle (persistent until
88    /// [`Self::reset_forces`], like a rigid body's forces). Pinned particles ignore it.
89    pub fn add_force(&mut self, force: Vector, wake_up: bool) {
90        for p in &mut self.particles {
91            p.force += force;
92        }
93        if wake_up {
94            self.wake_up();
95        }
96    }
97
98    /// Adds `force` to the user force of the `i`-th particle (persistent until
99    /// [`Self::reset_forces`]).
100    pub fn add_particle_force(&mut self, i: usize, force: Vector, wake_up: bool) {
101        self.particles[i].force += force;
102        if wake_up {
103            self.wake_up();
104        }
105    }
106
107    /// Clears the user forces of every particle.
108    pub fn reset_forces(&mut self, wake_up: bool) {
109        for p in &mut self.particles {
110            p.force = Vector::ZERO;
111        }
112        if wake_up {
113            self.wake_up();
114        }
115    }
116
117    /// Applies a linear velocity change (`impulse` per unit of mass) to every free particle of
118    /// this soft body: the whole body is kicked at the same velocity.
119    pub fn apply_impulse(&mut self, impulse: Vector, wake_up: bool) {
120        for p in &mut self.particles {
121            if p.inv_mass > 0.0 {
122                p.velocity += impulse;
123            }
124        }
125        if wake_up {
126            self.wake_up();
127        }
128    }
129
130    /// Applies an impulse to the `i`-th particle (its velocity changes by `impulse / mass`;
131    /// pinned particles ignore it).
132    pub fn apply_particle_impulse(&mut self, i: usize, impulse: Vector, wake_up: bool) {
133        let p = &mut self.particles[i];
134        p.velocity += impulse * p.inv_mass;
135        if wake_up {
136            self.wake_up();
137        }
138    }
139
140    /// Applies `impulse` to every free particle within `falloff_radius` of world point `point`,
141    /// scaled linearly to zero at that radius and divided by the particle's mass; a
142    /// `falloff_radius` of zero or less gives every particle the whole impulse.
143    pub fn apply_impulse_at_point(
144        &mut self,
145        impulse: Vector,
146        point: Vector,
147        falloff_radius: Real,
148        wake_up: bool,
149    ) {
150        for p in &mut self.particles {
151            if p.inv_mass == 0.0 {
152                continue;
153            }
154            let scale = if falloff_radius > 0.0 {
155                1.0 - (p.position - point).length() / falloff_radius
156            } else {
157                1.0
158            };
159            if scale > 0.0 {
160                p.velocity += impulse * (scale * p.inv_mass);
161            }
162        }
163        if wake_up {
164            self.wake_up();
165        }
166    }
167
168    /// Applies an impulse of `magnitude` pointing away from world point `center` to every free
169    /// particle within `falloff_radius`, scaled linearly to zero at that radius (a particle on the
170    /// center gets nothing); a `falloff_radius` of zero or less pushes every particle fully.
171    pub fn apply_radial_impulse(
172        &mut self,
173        center: Vector,
174        magnitude: Real,
175        falloff_radius: Real,
176        wake_up: bool,
177    ) {
178        for p in &mut self.particles {
179            if p.inv_mass == 0.0 {
180                continue;
181            }
182            let offset = p.position - center;
183            let distance = offset.length();
184            if distance <= Real::EPSILON {
185                continue;
186            }
187            let scale = if falloff_radius > 0.0 {
188                1.0 - distance / falloff_radius
189            } else {
190                1.0
191            };
192            if scale > 0.0 {
193                p.velocity += offset * (magnitude * scale * p.inv_mass / distance);
194            }
195        }
196        if wake_up {
197            self.wake_up();
198        }
199    }
200}