1use crate::math::Fix128;
17use crate::sim_field::ScalarField3D;
18use crate::sim_modifier::PhysicsModifier;
19use crate::sim_modifier::{observe_max, observe_sum, StateReader, StateWriter};
20use crate::world_participant::{
21 ObservationSink, Participant, ParticipantFault, ParticipantKind, StateError, SubstepCtx,
22};
23
24#[derive(Clone, Copy, Debug, PartialEq)]
30pub struct PressureConfig {
31 pub diffusion_rate: f32,
33 pub decay_rate: f32,
35 pub yield_threshold: f32,
37 pub deformation_rate: f32,
39 pub max_deformation: f32,
41 pub internal_pressure: f32,
43 pub expansion_rate: f32,
45}
46
47impl Default for PressureConfig {
48 fn default() -> Self {
49 Self {
50 diffusion_rate: 0.2,
51 decay_rate: 0.5,
52 yield_threshold: 10.0,
53 deformation_rate: 0.05,
54 max_deformation: 1.0,
55 internal_pressure: 0.0,
56 expansion_rate: 0.01,
57 }
58 }
59}
60
61pub struct PressureModifier {
67 pub config: PressureConfig,
69 pub pressure: ScalarField3D,
71 pub deformation: ScalarField3D,
73 pub enabled: bool,
75}
76
77impl PressureModifier {
78 #[must_use]
87 pub fn new(
88 config: PressureConfig,
89 resolution: usize,
90 min: (f32, f32, f32),
91 max: (f32, f32, f32),
92 ) -> Self {
93 let resolution = resolution.max(1);
94 Self {
95 config,
96 pressure: ScalarField3D::new(resolution, resolution, resolution, min, max),
97 deformation: ScalarField3D::new(resolution, resolution, resolution, min, max),
98 enabled: true,
99 }
100 }
101
102 pub fn apply_pressure_at(&mut self, x: f32, y: f32, z: f32, force: f32, radius: f32) {
106 self.pressure.splat(x, y, z, force, radius);
107 }
108
109 pub fn apply_impact(&mut self, x: f32, y: f32, z: f32, impulse: f32, radius: f32) {
113 let dent_depth = impulse * self.config.deformation_rate;
114 let clamped = crate::det_math::min(dent_depth, self.config.max_deformation);
115 self.deformation.splat(x, y, z, clamped, radius);
116 self.deformation.clamp(0.0, self.config.max_deformation);
119 }
120
121 #[must_use]
123 pub fn pressure_at(&self, x: f32, y: f32, z: f32) -> f32 {
124 self.pressure.sample(x, y, z)
125 }
126
127 #[must_use]
129 pub fn deformation_at(&self, x: f32, y: f32, z: f32) -> f32 {
130 self.deformation.sample(x, y, z)
131 }
132
133 fn yield_deformation(&mut self, dt: f32) {
135 let threshold = self.config.yield_threshold;
136 let rate = self.config.deformation_rate;
137 let max_d = self.config.max_deformation;
138
139 let n = self.pressure.cell_count();
140 for i in 0..n {
141 let p = self.pressure.data[i];
142 if p > threshold {
143 let excess = p - threshold;
144 let delta = excess * rate * dt;
145 self.deformation.data[i] =
146 crate::det_math::min(self.deformation.data[i] + delta, max_d);
147 }
148 }
149 }
150}
151
152impl PhysicsModifier for PressureModifier {
153 #[inline]
154 fn modify_distance(&self, x: f32, y: f32, z: f32, original_dist: f32) -> f32 {
155 if !self.enabled {
156 return original_dist;
157 }
158
159 let mut d = original_dist;
160
161 let deform = self.deformation.sample(x, y, z);
163 if deform > 0.0 {
164 d += deform;
165 }
166
167 d -= self.config.internal_pressure * self.config.expansion_rate;
170
171 d
172 }
173
174 fn update(&mut self, dt: f32) {
175 if !self.enabled {
176 return;
177 }
178
179 self.yield_deformation(dt);
181
182 self.pressure.diffuse(dt, self.config.diffusion_rate);
184
185 self.pressure.decay(self.config.decay_rate, dt);
187
188 self.deformation.clamp(0.0, self.config.max_deformation);
190 }
191
192 fn name(&self) -> &'static str {
193 "pressure"
194 }
195
196 fn is_active(&self) -> bool {
197 self.enabled
198 }
199}
200
201impl PressureModifier {
206 pub const PARTICIPANT_KIND: ParticipantKind =
209 ParticipantKind::new(u32::from_be_bytes(*b"PRES"));
210
211 fn decode_state(bytes: &[u8]) -> Result<Self, StateError> {
212 let mut r = StateReader::new(bytes)?;
213 let config = PressureConfig {
214 diffusion_rate: r.f32()?,
215 decay_rate: r.f32()?,
216 yield_threshold: r.f32()?,
217 deformation_rate: r.f32()?,
218 max_deformation: r.f32()?,
219 internal_pressure: r.f32()?,
220 expansion_rate: r.f32()?,
221 };
222 let enabled = r.bool()?;
223 let pressure = r.field()?;
224 let deformation = r.field()?;
225 r.finish()?;
226 Ok(Self {
227 config,
228 pressure,
229 deformation,
230 enabled,
231 })
232 }
233}
234
235impl Participant for PressureModifier {
242 fn kind(&self) -> ParticipantKind {
243 Self::PARTICIPANT_KIND
244 }
245
246 fn substep(&mut self, _ctx: &mut SubstepCtx<'_>, h: Fix128) -> Result<(), ParticipantFault> {
247 PhysicsModifier::update(self, h.to_f32());
248 Ok(())
249 }
250
251 fn observe(&self, out: &mut ObservationSink) {
252 observe_max(out, 0, &self.pressure);
253 observe_sum(out, 1, &self.deformation);
254 }
255
256 fn write_state(&self, out: &mut Vec<u8>) {
257 let mut w = StateWriter::new(out);
258 let c = &self.config;
259 for v in [
260 c.diffusion_rate,
261 c.decay_rate,
262 c.yield_threshold,
263 c.deformation_rate,
264 c.max_deformation,
265 c.internal_pressure,
266 c.expansion_rate,
267 ] {
268 w.f32(v);
269 }
270 w.bool(self.enabled);
271 w.field(&self.pressure);
272 w.field(&self.deformation);
273 }
274
275 fn check_state(&self, bytes: &[u8]) -> Result<(), StateError> {
276 Self::decode_state(bytes).map(drop)
277 }
278
279 fn read_state(&mut self, bytes: &[u8]) {
280 match Self::decode_state(bytes) {
281 Ok(state) => *self = state,
282 Err(e) => panic!("read_state called with a payload check_state refuses: {e:?}"),
283 }
284 }
285}
286
287#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::sdf_collider::{ClosureSdf, SdfField};
295 use crate::sim_modifier::SingleModifiedSdf;
296
297 fn unit_sphere() -> ClosureSdf {
298 ClosureSdf::new(
299 |x, y, z| z.mul_add(z, x.mul_add(x, y * y)).sqrt() - 1.0,
300 |x, y, z| {
301 let len = z.mul_add(z, x.mul_add(x, y * y)).sqrt();
302 if len < 1e-10 {
303 (0.0, 1.0, 0.0)
304 } else {
305 (x / len, y / len, z / len)
306 }
307 },
308 )
309 }
310
311 #[test]
312 fn test_pressure_no_force() {
313 let config = PressureConfig::default();
314 let modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
315 let modified = SingleModifiedSdf::new(Box::new(unit_sphere()), modifier);
316
317 let d = modified.distance(2.0, 0.0, 0.0);
318 assert!(
319 (d - 1.0).abs() < 0.01,
320 "No pressure should not change SDF, got {d}"
321 );
322 }
323
324 #[test]
325 fn test_pressure_dent() {
326 let config = PressureConfig {
327 deformation_rate: 0.1,
328 max_deformation: 2.0,
329 ..Default::default()
330 };
331 let mut modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
332
333 modifier.apply_impact(1.0, 0.0, 0.0, 5.0, 0.5);
335
336 let deform = modifier.deformation_at(1.0, 0.0, 0.0);
337 assert!(
338 deform > 0.0,
339 "Impact should create deformation, got {deform}"
340 );
341
342 let d = modifier.modify_distance(1.0, 0.0, 0.0, 0.0);
344 assert!(d > 0.0, "Dent should increase distance, got {d}");
345 }
346
347 #[test]
348 fn test_pressure_yield() {
349 let config = PressureConfig {
350 yield_threshold: 5.0,
351 deformation_rate: 0.1,
352 decay_rate: 0.0, ..Default::default()
354 };
355 let mut modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
356
357 modifier.apply_pressure_at(0.0, 0.0, 0.0, 200.0, 1.5);
359
360 for _ in 0..10 {
362 modifier.update(0.016);
363 }
364
365 let deform = modifier.deformation_at(0.0, 0.0, 0.0);
367 assert!(
368 deform > 0.0,
369 "High pressure should cause permanent deformation, got {deform}"
370 );
371 }
372
373 #[test]
374 fn test_pressure_decay() {
375 let config = PressureConfig {
376 decay_rate: 5.0, yield_threshold: 10000.0, ..Default::default()
379 };
380 let mut modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
381
382 modifier.apply_pressure_at(0.0, 0.0, 0.0, 100.0, 0.5);
383 let before = modifier.pressure_at(0.0, 0.0, 0.0);
384
385 for _ in 0..20 {
386 modifier.update(0.016);
387 }
388
389 let after = modifier.pressure_at(0.0, 0.0, 0.0);
390 assert!(
391 after < before * 0.5,
392 "Pressure should decay, before={before}, after={after}"
393 );
394 }
395
396 fn sample_modifier() -> PressureModifier {
397 let mut m = PressureModifier::new(
398 PressureConfig {
399 diffusion_rate: 0.125,
400 decay_rate: 0.25,
401 yield_threshold: 3.5,
402 deformation_rate: 0.75,
403 max_deformation: 1.5,
404 internal_pressure: 2.0,
405 expansion_rate: 0.0625,
406 },
407 2,
408 (-1.0, -2.0, -3.0),
409 (1.0, 2.0, 3.0),
410 );
411 for (i, v) in m.pressure.data.iter_mut().enumerate() {
412 *v = i as f32 * 0.5;
413 }
414 m.deformation.data[5] = 0.375;
415 m.enabled = false;
416 m
417 }
418
419 #[test]
423 fn internal_pressure_and_disabled_modifier() {
424 let mut m = PressureModifier::new(
425 PressureConfig {
426 internal_pressure: 2.0,
427 expansion_rate: 0.0625,
428 ..Default::default()
429 },
430 1,
431 (0.0, 0.0, 0.0),
432 (1.0, 1.0, 1.0),
433 );
434 assert_eq!(m.modify_distance(0.5, 0.5, 0.5, 1.0), 0.875);
435 m.pressure.data[0] = 50.0;
436 m.enabled = false;
437 assert!(!m.is_active());
438 assert_eq!(m.modify_distance(0.5, 0.5, 0.5, 1.0), 1.0);
439 m.update(1.0);
440 assert_eq!(m.pressure.data[0], 50.0);
441 assert_eq!(m.deformation.data[0], 0.0);
442 }
443
444 #[test]
449 fn participant_state_round_trip() {
450 assert_eq!(
451 PressureModifier::PARTICIPANT_KIND,
452 ParticipantKind::new(0x5052_4553)
453 );
454 let src = sample_modifier();
455 assert_eq!(Participant::kind(&src), PressureModifier::PARTICIPANT_KIND);
456 let mut bytes = Vec::new();
457 src.write_state(&mut bytes);
458 assert_eq!(src.check_state(&bytes), Ok(()));
459 let mut dst = PressureModifier::new(
460 PressureConfig::default(),
461 1,
462 (0.0, 0.0, 0.0),
463 (1.0, 1.0, 1.0),
464 );
465 dst.read_state(&bytes);
466 assert_eq!(dst.config, src.config);
467 assert!(!dst.enabled);
468 assert_eq!(dst.pressure.data, src.pressure.data);
469 assert_eq!(dst.deformation.data, src.deformation.data);
470 let mut again = Vec::new();
471 dst.write_state(&mut again);
472 assert_eq!(again, bytes);
473 }
474
475 #[test]
479 fn participant_state_refusals() {
480 let src = sample_modifier();
481 let mut bytes = Vec::new();
482 src.write_state(&mut bytes);
483 assert_eq!(bytes[32], 0);
484 let mut bad = bytes.clone();
485 bad[32] = 2;
486 assert_eq!(src.check_state(&bad), Err(StateError::InvalidValue));
487 assert!(matches!(
488 src.check_state(&bytes[..bytes.len() - 1]),
489 Err(StateError::Length { .. })
490 ));
491 let mut long = bytes;
492 long.push(0);
493 assert!(matches!(
494 src.check_state(&long),
495 Err(StateError::Length { .. })
496 ));
497 }
498
499 #[test]
500 #[should_panic(expected = "read_state called with a payload check_state refuses")]
501 fn participant_read_state_panics_on_a_refused_payload() {
502 let mut m = sample_modifier();
503 m.read_state(&[]);
504 }
505
506 #[test]
512 fn participant_substep_in_a_world() {
513 let mut m = PressureModifier::new(
514 PressureConfig {
515 diffusion_rate: 0.0,
516 decay_rate: 0.0,
517 yield_threshold: 1.0,
518 deformation_rate: 1.0,
519 max_deformation: 10.0,
520 ..Default::default()
521 },
522 1,
523 (0.0, 0.0, 0.0),
524 (1.0, 1.0, 1.0),
525 );
526 m.pressure.data[0] = 3.0;
527 let mut world = crate::solver::PhysicsWorld::new(crate::solver::SolverConfig {
528 substeps: 1,
529 ..Default::default()
530 });
531 world.add_participant(Box::new(m)).expect("register");
532 world.step(Fix128::from_ratio(1, 4));
533 let Some(crate::world_participant::Observed::Exact(sink)) = world.observe_participant(0)
534 else {
535 panic!("observation");
536 };
537 assert_eq!(
538 sink.values(),
539 &[(0, Fix128::from_int(3)), (1, Fix128::from_ratio(1, 2))]
540 );
541 }
542}