1#[derive(Debug, Clone)]
14#[allow(dead_code)]
15pub struct ClothMaterial {
16 pub name: String,
17 pub surface_density: f32,
19 pub structural_stiffness: f32,
21 pub shear_stiffness: f32,
23 pub bending_stiffness: f32,
25 pub damping: f32,
27 pub friction: f32,
29 pub stretch: f32,
31 pub thickness: f32,
33}
34
35impl ClothMaterial {
36 pub fn new(name: impl Into<String>) -> Self {
38 Self {
39 name: name.into(),
40 surface_density: 0.2,
41 structural_stiffness: 0.5,
42 shear_stiffness: 0.5,
43 bending_stiffness: 0.5,
44 damping: 0.3,
45 friction: 0.5,
46 stretch: 0.1,
47 thickness: 0.001,
48 }
49 }
50
51 pub fn cotton() -> Self {
55 Self {
56 name: "cotton".to_string(),
57 surface_density: 0.2,
58 structural_stiffness: 0.8,
59 shear_stiffness: 0.6,
60 bending_stiffness: 0.4,
61 damping: 0.3,
62 friction: 0.5,
63 stretch: 0.1,
64 thickness: 0.001,
65 }
66 }
67
68 pub fn silk() -> Self {
70 Self {
71 name: "silk".to_string(),
72 surface_density: 0.1,
73 structural_stiffness: 0.5,
74 shear_stiffness: 0.3,
75 bending_stiffness: 0.1,
76 damping: 0.1,
77 friction: 0.2,
78 stretch: 0.2,
79 thickness: 0.0005,
80 }
81 }
82
83 pub fn denim() -> Self {
85 Self {
86 name: "denim".to_string(),
87 surface_density: 0.5,
88 structural_stiffness: 0.95,
89 shear_stiffness: 0.8,
90 bending_stiffness: 0.8,
91 damping: 0.4,
92 friction: 0.7,
93 stretch: 0.05,
94 thickness: 0.002,
95 }
96 }
97
98 pub fn leather() -> Self {
100 Self {
101 name: "leather".to_string(),
102 surface_density: 0.7,
103 structural_stiffness: 0.98,
104 shear_stiffness: 0.9,
105 bending_stiffness: 0.95,
106 damping: 0.5,
107 friction: 0.8,
108 stretch: 0.02,
109 thickness: 0.003,
110 }
111 }
112
113 pub fn rubber() -> Self {
115 Self {
116 name: "rubber".to_string(),
117 surface_density: 0.4,
118 structural_stiffness: 0.3,
119 shear_stiffness: 0.2,
120 bending_stiffness: 0.2,
121 damping: 0.6,
122 friction: 0.9,
123 stretch: 0.9,
124 thickness: 0.002,
125 }
126 }
127
128 pub fn wool() -> Self {
130 Self {
131 name: "wool".to_string(),
132 surface_density: 0.3,
133 structural_stiffness: 0.7,
134 shear_stiffness: 0.5,
135 bending_stiffness: 0.5,
136 damping: 0.5,
137 friction: 0.6,
138 stretch: 0.3,
139 thickness: 0.002,
140 }
141 }
142
143 pub fn is_stiff(&self) -> bool {
149 self.bending_stiffness > 0.7
150 }
151
152 pub fn is_drapeable(&self) -> bool {
156 self.bending_stiffness < 0.3
157 }
158
159 pub fn terminal_velocity(&self) -> f32 {
165 let g = 9.8_f32;
166 let air_density = 1.225_f32;
167 let drag_coeff = 1.0_f32;
168 (2.0 * g * self.surface_density / (air_density * drag_coeff)).sqrt()
169 }
170
171 pub fn lerp(&self, other: &ClothMaterial, t: f32) -> ClothMaterial {
177 let t = t.clamp(0.0, 1.0);
178 let s = 1.0 - t;
179 ClothMaterial {
180 name: if t < 0.5 {
181 self.name.clone()
182 } else {
183 other.name.clone()
184 },
185 surface_density: self.surface_density * s + other.surface_density * t,
186 structural_stiffness: self.structural_stiffness * s + other.structural_stiffness * t,
187 shear_stiffness: self.shear_stiffness * s + other.shear_stiffness * t,
188 bending_stiffness: self.bending_stiffness * s + other.bending_stiffness * t,
189 damping: self.damping * s + other.damping * t,
190 friction: self.friction * s + other.friction * t,
191 stretch: self.stretch * s + other.stretch * t,
192 thickness: self.thickness * s + other.thickness * t,
193 }
194 }
195
196 pub fn with_stiffness_scale(&self, scale: f32) -> ClothMaterial {
200 ClothMaterial {
201 name: self.name.clone(),
202 surface_density: self.surface_density,
203 structural_stiffness: (self.structural_stiffness * scale).clamp(0.0, 1.0),
204 shear_stiffness: (self.shear_stiffness * scale).clamp(0.0, 1.0),
205 bending_stiffness: (self.bending_stiffness * scale).clamp(0.0, 1.0),
206 damping: self.damping,
207 friction: self.friction,
208 stretch: self.stretch,
209 thickness: self.thickness,
210 }
211 }
212
213 pub fn for_wind_strength(&self, wind: f32) -> ClothMaterial {
218 let wind = wind.clamp(0.0, 1.0);
219 ClothMaterial {
220 name: self.name.clone(),
221 surface_density: self.surface_density,
222 structural_stiffness: (self.structural_stiffness * (1.0 - wind * 0.2)).clamp(0.0, 1.0),
223 shear_stiffness: (self.shear_stiffness * (1.0 - wind * 0.1)).clamp(0.0, 1.0),
224 bending_stiffness: self.bending_stiffness,
225 damping: (self.damping + wind * 0.4).clamp(0.0, 1.0),
226 friction: self.friction,
227 stretch: self.stretch,
228 thickness: self.thickness,
229 }
230 }
231
232 pub fn all_presets() -> Vec<ClothMaterial> {
236 vec![
237 ClothMaterial::cotton(),
238 ClothMaterial::silk(),
239 ClothMaterial::denim(),
240 ClothMaterial::leather(),
241 ClothMaterial::rubber(),
242 ClothMaterial::wool(),
243 ]
244 }
245
246 pub fn find_preset(name: &str) -> Option<ClothMaterial> {
250 let lower = name.to_lowercase();
251 ClothMaterial::all_presets()
252 .into_iter()
253 .find(|m| m.name == lower)
254 }
255}
256
257#[allow(dead_code)]
261pub struct ClothStack {
262 pub layers: Vec<ClothMaterial>,
263}
264
265impl ClothStack {
266 pub fn new() -> Self {
268 Self { layers: Vec::new() }
269 }
270
271 pub fn push(&mut self, mat: ClothMaterial) {
273 self.layers.push(mat);
274 }
275
276 pub fn effective_material(&self) -> ClothMaterial {
281 if self.layers.is_empty() {
282 return ClothMaterial::new("empty");
283 }
284
285 let n = self.layers.len() as f32;
286 let mut result = ClothMaterial::new("stack");
287 result.surface_density = 0.0;
288 result.structural_stiffness = 0.0;
289 result.shear_stiffness = 0.0;
290 result.bending_stiffness = 0.0;
291 result.damping = 0.0;
292 result.friction = 0.0;
293 result.stretch = 0.0;
294 result.thickness = 0.0;
295
296 for layer in &self.layers {
297 result.surface_density += layer.surface_density;
298 result.structural_stiffness += layer.structural_stiffness;
299 result.shear_stiffness += layer.shear_stiffness;
300 result.bending_stiffness += layer.bending_stiffness;
301 result.damping += layer.damping;
302 result.friction += layer.friction;
303 result.stretch += layer.stretch;
304 result.thickness += layer.thickness;
305 }
306
307 result.surface_density /= n;
308 result.structural_stiffness /= n;
309 result.shear_stiffness /= n;
310 result.bending_stiffness /= n;
311 result.damping /= n;
312 result.friction /= n;
313 result.stretch /= n;
314 result.thickness /= n;
315
316 result
317 }
318
319 pub fn total_surface_density(&self) -> f32 {
321 self.layers.iter().map(|l| l.surface_density).sum()
322 }
323
324 pub fn layer_count(&self) -> usize {
326 self.layers.len()
327 }
328}
329
330impl Default for ClothStack {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335
336#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn cotton_preset_name() {
344 assert_eq!(ClothMaterial::cotton().name, "cotton");
345 }
346
347 #[test]
348 fn silk_preset_lighter_than_denim() {
349 assert!(
350 ClothMaterial::silk().surface_density < ClothMaterial::denim().surface_density,
351 "silk should be lighter than denim"
352 );
353 }
354
355 #[test]
356 fn denim_is_stiff() {
357 assert!(
358 ClothMaterial::denim().is_stiff(),
359 "denim bending_stiffness should be > 0.7"
360 );
361 }
362
363 #[test]
364 fn silk_is_drapeable() {
365 assert!(
366 ClothMaterial::silk().is_drapeable(),
367 "silk bending_stiffness should be < 0.3"
368 );
369 }
370
371 #[test]
372 fn lerp_at_zero_equals_self() {
373 let cotton = ClothMaterial::cotton();
374 let denim = ClothMaterial::denim();
375 let result = cotton.lerp(&denim, 0.0);
376 assert!(
377 (result.surface_density - cotton.surface_density).abs() < 1e-5,
378 "lerp(t=0) surface_density should match self"
379 );
380 assert!(
381 (result.bending_stiffness - cotton.bending_stiffness).abs() < 1e-5,
382 "lerp(t=0) bending_stiffness should match self"
383 );
384 }
385
386 #[test]
387 fn lerp_at_one_equals_other() {
388 let cotton = ClothMaterial::cotton();
389 let denim = ClothMaterial::denim();
390 let result = cotton.lerp(&denim, 1.0);
391 assert!(
392 (result.surface_density - denim.surface_density).abs() < 1e-5,
393 "lerp(t=1) surface_density should match other"
394 );
395 assert!(
396 (result.bending_stiffness - denim.bending_stiffness).abs() < 1e-5,
397 "lerp(t=1) bending_stiffness should match other"
398 );
399 }
400
401 #[test]
402 fn lerp_midpoint_density() {
403 let cotton = ClothMaterial::cotton(); let denim = ClothMaterial::denim(); let result = cotton.lerp(&denim, 0.5);
406 let expected = (0.2 + 0.5) / 2.0;
407 assert!(
408 (result.surface_density - expected).abs() < 1e-5,
409 "lerp(0.5) density expected {expected}, got {}",
410 result.surface_density
411 );
412 }
413
414 #[test]
415 fn with_stiffness_scale_halves_structural() {
416 let cotton = ClothMaterial::cotton(); let scaled = cotton.with_stiffness_scale(0.5);
418 let expected = 0.8 * 0.5;
419 assert!(
420 (scaled.structural_stiffness - expected).abs() < 1e-5,
421 "expected structural_stiffness {expected}, got {}",
422 scaled.structural_stiffness
423 );
424 }
425
426 #[test]
427 fn for_wind_strong_increases_damping() {
428 let silk = ClothMaterial::silk(); let windy = silk.for_wind_strength(1.0);
430 assert!(
431 windy.damping > silk.damping,
432 "strong wind should increase damping: {} -> {}",
433 silk.damping,
434 windy.damping
435 );
436 }
437
438 #[test]
439 fn terminal_velocity_positive() {
440 for mat in ClothMaterial::all_presets() {
441 let tv = mat.terminal_velocity();
442 assert!(
443 tv > 0.0,
444 "terminal_velocity for '{}' should be positive, got {tv}",
445 mat.name
446 );
447 }
448 }
449
450 #[test]
451 fn all_presets_count() {
452 assert_eq!(
453 ClothMaterial::all_presets().len(),
454 6,
455 "expected 6 presets: cotton, silk, denim, leather, rubber, wool"
456 );
457 }
458
459 #[test]
460 fn find_preset_by_name() {
461 let mat = ClothMaterial::find_preset("denim").expect("denim preset should exist");
462 assert_eq!(mat.name, "denim");
463 }
464
465 #[test]
466 fn find_preset_case_insensitive() {
467 let mat = ClothMaterial::find_preset("SILK").expect("SILK should match silk preset");
468 assert_eq!(mat.name, "silk");
469 }
470
471 #[test]
472 fn cloth_stack_effective_material() {
473 let mut stack = ClothStack::new();
474 stack.push(ClothMaterial::cotton()); stack.push(ClothMaterial::denim()); let eff = stack.effective_material();
477 let expected_density = (0.2 + 0.5) / 2.0;
478 assert!(
479 (eff.surface_density - expected_density).abs() < 1e-5,
480 "effective density expected {expected_density}, got {}",
481 eff.surface_density
482 );
483 }
484
485 #[test]
486 fn cloth_stack_total_density() {
487 let mut stack = ClothStack::new();
488 stack.push(ClothMaterial::cotton()); stack.push(ClothMaterial::silk()); stack.push(ClothMaterial::wool()); let total = stack.total_surface_density();
492 let expected = 0.2 + 0.1 + 0.3;
493 assert!(
494 (total - expected).abs() < 1e-5,
495 "total density expected {expected}, got {total}"
496 );
497 }
498}