1#[allow(dead_code)]
4#[derive(Clone, Debug)]
5pub struct ClothBlendConfig {
6 pub min_offset: f32,
8 pub max_push: f32,
10 pub smooth_iterations: u32,
12 pub weight_threshold: f32,
14}
15
16#[allow(dead_code)]
17#[derive(Clone, Debug)]
18pub struct ClothLayer {
19 pub id: u32,
21 pub weight: f32,
23 pub vertices: Vec<[f32; 3]>,
25 pub rest_vertices: Vec<[f32; 3]>,
27}
28
29#[allow(dead_code)]
30#[derive(Clone, Debug)]
31pub struct ClothBlendResult {
32 pub vertices: Vec<[f32; 3]>,
34 pub energy: Vec<f32>,
36 pub total_energy: f32,
38}
39
40#[allow(dead_code)]
46pub fn default_cloth_blend_config() -> ClothBlendConfig {
47 ClothBlendConfig {
48 min_offset: 0.002,
49 max_push: 0.05,
50 smooth_iterations: 3,
51 weight_threshold: 1e-4,
52 }
53}
54
55#[allow(dead_code)]
57pub fn new_cloth_layer(id: u32, vertices: Vec<[f32; 3]>) -> ClothLayer {
58 ClothLayer {
59 id,
60 weight: 1.0,
61 rest_vertices: vertices.clone(),
62 vertices,
63 }
64}
65
66#[allow(dead_code)]
74pub fn blend_cloth_layers(layers: &[ClothLayer], config: &ClothBlendConfig) -> Vec<[f32; 3]> {
75 if layers.is_empty() {
76 return Vec::new();
77 }
78 let n = layers[0].vertices.len();
79 let mut out = vec![[0.0_f32; 3]; n];
80 let mut weight_sum = 0.0_f32;
81
82 for layer in layers {
83 if layer.weight < config.weight_threshold {
84 continue;
85 }
86 if layer.vertices.len() != n {
87 continue;
88 }
89 for (o, v) in out.iter_mut().zip(layer.vertices.iter()) {
90 o[0] += v[0] * layer.weight;
91 o[1] += v[1] * layer.weight;
92 o[2] += v[2] * layer.weight;
93 }
94 weight_sum += layer.weight;
95 }
96
97 if weight_sum > 1e-8 {
98 let inv = 1.0 / weight_sum;
99 for o in &mut out {
100 o[0] *= inv;
101 o[1] *= inv;
102 o[2] *= inv;
103 }
104 }
105 out
106}
107
108#[allow(dead_code)]
111pub fn apply_body_offset(vertices: &mut [[f32; 3]], centre: [f32; 3], offset: f32) {
112 for v in vertices.iter_mut() {
113 let dx = v[0] - centre[0];
114 let dy = v[1] - centre[1];
115 let dz = v[2] - centre[2];
116 let len = (dx * dx + dy * dy + dz * dz).sqrt();
117 if len > 1e-8 {
118 let scale = offset / len;
119 v[0] += dx * scale;
120 v[1] += dy * scale;
121 v[2] += dz * scale;
122 }
123 }
124}
125
126#[allow(dead_code)]
129pub fn cloth_collision_push(
130 vertices: &mut [[f32; 3]],
131 centre: [f32; 3],
132 radius: f32,
133 max_push: f32,
134) {
135 for v in vertices.iter_mut() {
136 let dx = v[0] - centre[0];
137 let dy = v[1] - centre[1];
138 let dz = v[2] - centre[2];
139 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
140 if dist < radius && dist > 1e-8 {
141 let push = (radius - dist).min(max_push);
142 let inv = 1.0 / dist;
143 v[0] += dx * inv * push;
144 v[1] += dy * inv * push;
145 v[2] += dz * inv * push;
146 }
147 }
148}
149
150#[allow(dead_code)]
153pub fn smooth_cloth_blend(weights: &[f32], iterations: u32) -> Vec<f32> {
154 if weights.is_empty() {
155 return Vec::new();
156 }
157 let n = weights.len();
158 let mut cur = weights.to_vec();
159 let mut tmp = vec![0.0_f32; n];
160 for _ in 0..iterations {
161 for i in 0..n {
162 let prev = if i == 0 { n - 1 } else { i - 1 };
163 let next = if i + 1 == n { 0 } else { i + 1 };
164 tmp[i] = (cur[prev] + cur[i] + cur[next]) / 3.0;
165 }
166 cur.copy_from_slice(&tmp);
167 }
168 cur
169}
170
171#[allow(dead_code)]
177pub fn cloth_layer_count(layers: &[ClothLayer]) -> usize {
178 layers.len()
179}
180
181#[allow(dead_code)]
184pub fn cloth_blend_energy(layer: &ClothLayer) -> ClothBlendResult {
185 let n = layer.vertices.len().min(layer.rest_vertices.len());
186 let mut energy = Vec::with_capacity(n);
187 let mut total = 0.0_f32;
188 for i in 0..n {
189 let v = &layer.vertices[i];
190 let r = &layer.rest_vertices[i];
191 let e = {
192 let dx = v[0] - r[0];
193 let dy = v[1] - r[1];
194 let dz = v[2] - r[2];
195 (dx * dx + dy * dy + dz * dz).sqrt()
196 };
197 energy.push(e);
198 total += e;
199 }
200 ClothBlendResult {
201 vertices: layer.vertices.clone(),
202 energy,
203 total_energy: total,
204 }
205}
206
207#[allow(dead_code)]
209pub fn normalize_cloth_weights(layers: &mut [ClothLayer]) {
210 let sum: f32 = layers.iter().map(|l| l.weight).sum();
211 if sum > 1e-8 {
212 for l in layers.iter_mut() {
213 l.weight /= sum;
214 }
215 }
216}
217
218#[allow(dead_code)]
220pub fn set_layer_weight(layers: &mut [ClothLayer], id: u32, weight: f32) {
221 for l in layers.iter_mut() {
222 if l.id == id {
223 l.weight = weight.clamp(0.0, 1.0);
224 return;
225 }
226 }
227}
228
229#[allow(dead_code)]
231pub fn get_layer_weight(layers: &[ClothLayer], id: u32) -> Option<f32> {
232 layers.iter().find(|l| l.id == id).map(|l| l.weight)
233}
234
235#[allow(dead_code)]
237pub fn cloth_to_rest(layer: &mut ClothLayer) {
238 layer.vertices = layer.rest_vertices.clone();
239}
240
241#[cfg(test)]
246mod tests {
247 use super::*;
248
249 fn make_layer(id: u32, n: usize, weight: f32) -> ClothLayer {
250 let verts: Vec<[f32; 3]> = (0..n).map(|i| [i as f32 * 0.1, 0.0, 0.0]).collect();
251 let mut l = new_cloth_layer(id, verts);
252 l.weight = weight;
253 l
254 }
255
256 #[test]
257 fn test_default_cloth_blend_config() {
258 let c = default_cloth_blend_config();
259 assert!(c.min_offset >= 0.0);
260 assert!(c.max_push > 0.0);
261 assert!(c.smooth_iterations > 0);
262 }
263
264 #[test]
265 fn test_new_cloth_layer() {
266 let l = new_cloth_layer(0, vec![[1.0, 0.0, 0.0], [2.0, 0.0, 0.0]]);
267 assert_eq!(l.vertices.len(), 2);
268 assert_eq!(l.rest_vertices.len(), 2);
269 assert_eq!(l.id, 0);
270 assert!((l.weight - 1.0).abs() < 1e-6);
271 }
272
273 #[test]
274 fn test_blend_cloth_layers_single() {
275 let cfg = default_cloth_blend_config();
276 let l = make_layer(0, 3, 1.0);
277 let blended = blend_cloth_layers(std::slice::from_ref(&l), &cfg);
278 assert_eq!(blended.len(), 3);
279 assert!((blended[0][0] - l.vertices[0][0]).abs() < 1e-5);
280 }
281
282 #[test]
283 fn test_blend_cloth_layers_average() {
284 let cfg = default_cloth_blend_config();
285 let mut la = make_layer(0, 1, 1.0);
286 let mut lb = make_layer(1, 1, 1.0);
287 la.vertices[0][0] = 0.0;
288 lb.vertices[0][0] = 2.0;
289 let blended = blend_cloth_layers(&[la, lb], &cfg);
290 assert!((blended[0][0] - 1.0).abs() < 1e-5);
291 }
292
293 #[test]
294 fn test_blend_cloth_layers_empty() {
295 let cfg = default_cloth_blend_config();
296 let blended = blend_cloth_layers(&[], &cfg);
297 assert!(blended.is_empty());
298 }
299
300 #[test]
301 fn test_blend_cloth_layers_skips_low_weight() {
302 let cfg = default_cloth_blend_config();
303 let mut la = make_layer(0, 1, 1.0);
304 let mut lb = make_layer(1, 1, 0.0); la.vertices[0][0] = 0.0;
306 lb.vertices[0][0] = 10.0;
307 let blended = blend_cloth_layers(&[la, lb], &cfg);
308 assert!(
309 blended[0][0].abs() < 1e-5,
310 "zero-weight layer should be skipped"
311 );
312 }
313
314 #[test]
315 fn test_apply_body_offset() {
316 let mut verts = vec![[1.0_f32, 0.0, 0.0]];
317 apply_body_offset(&mut verts, [0.0, 0.0, 0.0], 0.1);
318 assert!(verts[0][0] > 1.0, "vertex should be pushed outward");
319 }
320
321 #[test]
322 fn test_cloth_collision_push() {
323 let mut verts = vec![[0.5_f32, 0.0, 0.0]]; cloth_collision_push(&mut verts, [0.0, 0.0, 0.0], 1.0, 1.0);
325 assert!(
326 verts[0][0] >= 1.0 - 1e-4,
327 "should be pushed to sphere surface"
328 );
329 }
330
331 #[test]
332 fn test_cloth_collision_push_outside() {
333 let mut verts = vec![[2.0_f32, 0.0, 0.0]]; cloth_collision_push(&mut verts, [0.0, 0.0, 0.0], 1.0, 1.0);
335 assert!(
336 (verts[0][0] - 2.0).abs() < 1e-6,
337 "vertex outside sphere should not move"
338 );
339 }
340
341 #[test]
342 fn test_smooth_cloth_blend_uniform() {
343 let weights = vec![0.5_f32; 5];
344 let smoothed = smooth_cloth_blend(&weights, 2);
345 assert_eq!(smoothed.len(), 5);
346 for w in &smoothed {
347 assert!((w - 0.5).abs() < 1e-5);
348 }
349 }
350
351 #[test]
352 fn test_smooth_cloth_blend_empty() {
353 let smoothed = smooth_cloth_blend(&[], 3);
354 assert!(smoothed.is_empty());
355 }
356
357 #[test]
358 fn test_cloth_layer_count() {
359 let layers: Vec<ClothLayer> = (0..4).map(|i| make_layer(i, 2, 1.0)).collect();
360 assert_eq!(cloth_layer_count(&layers), 4);
361 }
362
363 #[test]
364 fn test_cloth_blend_energy_rest() {
365 let l = new_cloth_layer(0, vec![[0.0, 0.0, 0.0]; 3]);
366 let res = cloth_blend_energy(&l);
367 assert!(res.total_energy < 1e-6, "energy should be zero at rest");
368 }
369
370 #[test]
371 fn test_cloth_blend_energy_displaced() {
372 let mut l = new_cloth_layer(0, vec![[0.0, 0.0, 0.0]]);
373 l.vertices[0][0] = 1.0;
374 let res = cloth_blend_energy(&l);
375 assert!((res.total_energy - 1.0).abs() < 1e-5);
376 }
377
378 #[test]
379 fn test_normalize_cloth_weights() {
380 let mut layers: Vec<ClothLayer> = (0..3).map(|i| make_layer(i, 1, 2.0)).collect();
381 normalize_cloth_weights(&mut layers[..]);
382 let sum: f32 = layers.iter().map(|l| l.weight).sum();
383 assert!((sum - 1.0).abs() < 1e-5);
384 }
385
386 #[test]
387 fn test_set_get_layer_weight() {
388 let mut layers = vec![make_layer(5, 1, 0.5)];
389 set_layer_weight(&mut layers[..], 5, 0.8);
390 assert!((get_layer_weight(&layers, 5).expect("should succeed") - 0.8).abs() < 1e-6);
391 }
392
393 #[test]
394 fn test_get_layer_weight_missing() {
395 let layers: Vec<ClothLayer> = vec![];
396 assert!(get_layer_weight(&layers, 99).is_none());
397 }
398
399 #[test]
400 fn test_cloth_to_rest() {
401 let mut l = new_cloth_layer(0, vec![[0.0, 0.0, 0.0]]);
402 l.vertices[0][0] = 5.0;
403 cloth_to_rest(&mut l);
404 assert!((l.vertices[0][0] - 0.0).abs() < 1e-6);
405 }
406}