1use gizmo_math::{Mat3, Vec3};
2
3#[derive(Debug, Clone, Copy)]
5pub struct SoftBodyNode {
6 pub position: Vec3,
7 pub velocity: Vec3,
8 pub mass: f32,
9 pub is_fixed: bool, }
11
12#[derive(Debug, Clone, Copy)]
14pub struct Tetrahedron {
15 pub node_indices: [u32; 4],
17
18 pub rest_volume: f32,
20
21 pub inv_rest_matrix: Mat3,
24}
25
26impl Tetrahedron {
27 pub fn calculate_rest_data(p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3) -> (Mat3, f32) {
30 let e1 = p1 - p0;
32 let e2 = p2 - p0;
33 let e3 = p3 - p0;
34
35 let dm = Mat3::from_cols(e1, e2, e3);
36
37 let det = dm.determinant();
39 let volume = (det / 6.0).abs();
40
41 let inv_dm = dm.inverse();
42
43 (inv_dm, volume)
44 }
45}
46
47#[derive(Debug, Clone)]
50pub struct SoftBodyMesh {
51 pub nodes: Vec<SoftBodyNode>,
52 pub elements: Vec<Tetrahedron>,
53
54 pub youngs_modulus: f32,
57
58 pub poissons_ratio: f32,
60
61 pub lambda: f32,
63
64 pub mu: f32,
66
67 pub damping: f32,
69}
70gizmo_core::impl_component!(SoftBodyMesh);
71
72pub fn resolve_node_collision(
73 mut position: Vec3,
74 mut velocity: Vec3,
75 dt: f32,
76 rigid_colliders: &[(
77 gizmo_core::entity::Entity,
78 gizmo_physics_core::Transform,
79 gizmo_physics_core::Collider,
80 )],
81) -> (Vec3, Vec3, bool) {
82 let mut collided = false;
83 let ray = gizmo_physics_core::raycast::Ray::new(position, velocity.normalize_or_zero());
84 let dist = velocity.length() * dt;
85
86 if dist > 1e-5 {
87 for (_, col_trans, col) in rigid_colliders {
88 if let Some((d, n)) = gizmo_physics_core::raycast::Raycast::ray_shape(&ray, &col.shape, col_trans) {
89 if d <= dist + 0.1 {
90 let bounce = 0.5;
91 let friction = 0.8;
92
93 let vn = velocity.dot(n);
94 if vn < 0.0 {
95 let vt = velocity - n * vn;
96 velocity = vt * (1.0 - friction) - n * (vn * bounce);
97 }
98
99 position += ray.direction * (d - 0.1).max(0.0);
100 collided = true;
101 }
102 }
103 }
104 }
105
106 (position, velocity, collided)
107}
108
109impl SoftBodyMesh {
110 pub fn new(youngs_modulus: f32, poissons_ratio: f32) -> Self {
111 let mu = youngs_modulus / (2.0 * (1.0 + poissons_ratio));
113 let lambda = (youngs_modulus * poissons_ratio)
114 / ((1.0 + poissons_ratio) * (1.0 - 2.0 * poissons_ratio));
115
116 Self {
117 nodes: Vec::new(),
118 elements: Vec::new(),
119 youngs_modulus,
120 poissons_ratio,
121 lambda,
122 mu,
123 damping: 0.99,
124 }
125 }
126
127 pub fn add_node(&mut self, position: Vec3, mass: f32) -> u32 {
128 let idx = self.nodes.len() as u32;
129 self.nodes.push(SoftBodyNode {
130 position,
131 velocity: Vec3::ZERO,
132 mass,
133 is_fixed: false,
134 });
135 idx
136 }
137
138 pub fn add_element(&mut self, i0: u32, i1: u32, i2: u32, i3: u32) {
139 let p0 = self.nodes[i0 as usize].position;
140 let p1 = self.nodes[i1 as usize].position;
141 let p2 = self.nodes[i2 as usize].position;
142 let p3 = self.nodes[i3 as usize].position;
143
144 let (inv_rest_matrix, rest_volume) = Tetrahedron::calculate_rest_data(p0, p1, p2, p3);
145
146 self.elements.push(Tetrahedron {
147 node_indices: [i0, i1, i2, i3],
148 rest_volume,
149 inv_rest_matrix,
150 });
151 }
152
153 pub fn step(
155 &mut self,
156 dt: f32,
157 gravity: Vec3,
158 rigid_colliders: &[(
159 gizmo_core::entity::Entity,
160 gizmo_physics_core::Transform,
161 gizmo_physics_core::Collider,
162 )],
163 ) {
164 let num_nodes = self.nodes.len();
165 let mut forces: Vec<Vec3> = self.nodes.iter().map(|n| gravity * n.mass).collect();
166
167 use rayon::prelude::*;
169
170 let positions: Vec<Vec3> = self.nodes.iter().map(|n| n.position).collect();
171 const MIN_JACOBIAN: f32 = 0.1;
172
173 let elastic_forces = self
174 .elements
175 .par_iter()
176 .fold(
177 || vec![Vec3::ZERO; num_nodes],
178 |mut acc_forces, elem| {
179 let i0 = elem.node_indices[0] as usize;
180 let i1 = elem.node_indices[1] as usize;
181 let i2 = elem.node_indices[2] as usize;
182 let i3 = elem.node_indices[3] as usize;
183
184 let x0 = positions[i0];
185 let x1 = positions[i1];
186 let x2 = positions[i2];
187 let x3 = positions[i3];
188
189 let ds = Mat3::from_cols(x1 - x0, x2 - x0, x3 - x0);
190 let f = ds * elem.inv_rest_matrix;
191 let j = f.determinant();
192
193 if j < MIN_JACOBIAN {
194 return acc_forces;
195 }
196
197 let f_inv_t = f.inverse().transpose();
198 let ln_j = j.ln();
199
200 let p = f_inv_t * (-self.mu) + f * self.mu + f_inv_t * (self.lambda * ln_j);
201 let h = p * elem.inv_rest_matrix.transpose() * elem.rest_volume;
202
203 let f1 = -h.col(0);
204 let f2 = -h.col(1);
205 let f3 = -h.col(2);
206 let f0 = -(f1 + f2 + f3);
207
208 acc_forces[i0] += f0;
209 acc_forces[i1] += f1;
210 acc_forces[i2] += f2;
211 acc_forces[i3] += f3;
212
213 acc_forces
214 },
215 )
216 .reduce(
217 || vec![Vec3::ZERO; num_nodes],
218 |mut a, b| {
219 for i in 0..num_nodes {
220 a[i] += b[i];
221 }
222 a
223 },
224 );
225
226 for i in 0..num_nodes {
227 forces[i] += elastic_forces[i];
228 }
229
230 for (i, node) in self.nodes.iter_mut().enumerate() {
232 if node.is_fixed {
233 continue;
234 }
235
236 let acceleration = forces[i] / node.mass;
238 node.velocity += acceleration * dt;
239
240 node.velocity *= self.damping.powf(dt);
242
243 let next_pos = node.position + node.velocity * dt;
244
245 let (new_pos, new_vel, collided) =
246 resolve_node_collision(node.position, node.velocity, dt, rigid_colliders);
247
248 if collided {
249 node.position = new_pos;
250 node.velocity = new_vel;
251 } else {
252 node.position = next_pos;
253 }
254 }
255 }
256}