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 = dent_depth.min(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] = (self.deformation.data[i] + delta).min(max_d);
146 }
147 }
148 }
149}
150
151impl PhysicsModifier for PressureModifier {
152 #[inline]
153 fn modify_distance(&self, x: f32, y: f32, z: f32, original_dist: f32) -> f32 {
154 if !self.enabled {
155 return original_dist;
156 }
157
158 let mut d = original_dist;
159
160 let deform = self.deformation.sample(x, y, z);
162 if deform > 0.0 {
163 d += deform;
164 }
165
166 d -= self.config.internal_pressure * self.config.expansion_rate;
169
170 d
171 }
172
173 fn update(&mut self, dt: f32) {
174 if !self.enabled {
175 return;
176 }
177
178 self.yield_deformation(dt);
180
181 self.pressure.diffuse(dt, self.config.diffusion_rate);
183
184 self.pressure.decay(self.config.decay_rate, dt);
186
187 self.deformation.clamp(0.0, self.config.max_deformation);
189 }
190
191 fn name(&self) -> &'static str {
192 "pressure"
193 }
194
195 fn is_active(&self) -> bool {
196 self.enabled
197 }
198}
199
200impl PressureModifier {
205 pub const PARTICIPANT_KIND: ParticipantKind =
208 ParticipantKind::new(u32::from_be_bytes(*b"PRES"));
209
210 fn decode_state(bytes: &[u8]) -> Result<Self, StateError> {
211 let mut r = StateReader::new(bytes)?;
212 let config = PressureConfig {
213 diffusion_rate: r.f32()?,
214 decay_rate: r.f32()?,
215 yield_threshold: r.f32()?,
216 deformation_rate: r.f32()?,
217 max_deformation: r.f32()?,
218 internal_pressure: r.f32()?,
219 expansion_rate: r.f32()?,
220 };
221 let enabled = r.bool()?;
222 let pressure = r.field()?;
223 let deformation = r.field()?;
224 r.finish()?;
225 Ok(Self {
226 config,
227 pressure,
228 deformation,
229 enabled,
230 })
231 }
232}
233
234impl Participant for PressureModifier {
241 fn kind(&self) -> ParticipantKind {
242 Self::PARTICIPANT_KIND
243 }
244
245 fn substep(&mut self, _ctx: &mut SubstepCtx<'_>, h: Fix128) -> Result<(), ParticipantFault> {
246 PhysicsModifier::update(self, h.to_f32());
247 Ok(())
248 }
249
250 fn observe(&self, out: &mut ObservationSink) {
251 observe_max(out, 0, &self.pressure);
252 observe_sum(out, 1, &self.deformation);
253 }
254
255 fn write_state(&self, out: &mut Vec<u8>) {
256 let mut w = StateWriter::new(out);
257 let c = &self.config;
258 for v in [
259 c.diffusion_rate,
260 c.decay_rate,
261 c.yield_threshold,
262 c.deformation_rate,
263 c.max_deformation,
264 c.internal_pressure,
265 c.expansion_rate,
266 ] {
267 w.f32(v);
268 }
269 w.bool(self.enabled);
270 w.field(&self.pressure);
271 w.field(&self.deformation);
272 }
273
274 fn check_state(&self, bytes: &[u8]) -> Result<(), StateError> {
275 Self::decode_state(bytes).map(drop)
276 }
277
278 fn read_state(&mut self, bytes: &[u8]) {
279 match Self::decode_state(bytes) {
280 Ok(state) => *self = state,
281 Err(e) => panic!("read_state called with a payload check_state refuses: {e:?}"),
282 }
283 }
284}
285
286#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::sdf_collider::{ClosureSdf, SdfField};
294 use crate::sim_modifier::SingleModifiedSdf;
295
296 fn unit_sphere() -> ClosureSdf {
297 ClosureSdf::new(
298 |x, y, z| z.mul_add(z, x.mul_add(x, y * y)).sqrt() - 1.0,
299 |x, y, z| {
300 let len = z.mul_add(z, x.mul_add(x, y * y)).sqrt();
301 if len < 1e-10 {
302 (0.0, 1.0, 0.0)
303 } else {
304 (x / len, y / len, z / len)
305 }
306 },
307 )
308 }
309
310 #[test]
311 fn test_pressure_no_force() {
312 let config = PressureConfig::default();
313 let modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
314 let modified = SingleModifiedSdf::new(Box::new(unit_sphere()), modifier);
315
316 let d = modified.distance(2.0, 0.0, 0.0);
317 assert!(
318 (d - 1.0).abs() < 0.01,
319 "No pressure should not change SDF, got {d}"
320 );
321 }
322
323 #[test]
324 fn test_pressure_dent() {
325 let config = PressureConfig {
326 deformation_rate: 0.1,
327 max_deformation: 2.0,
328 ..Default::default()
329 };
330 let mut modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
331
332 modifier.apply_impact(1.0, 0.0, 0.0, 5.0, 0.5);
334
335 let deform = modifier.deformation_at(1.0, 0.0, 0.0);
336 assert!(
337 deform > 0.0,
338 "Impact should create deformation, got {deform}"
339 );
340
341 let d = modifier.modify_distance(1.0, 0.0, 0.0, 0.0);
343 assert!(d > 0.0, "Dent should increase distance, got {d}");
344 }
345
346 #[test]
347 fn test_pressure_yield() {
348 let config = PressureConfig {
349 yield_threshold: 5.0,
350 deformation_rate: 0.1,
351 decay_rate: 0.0, ..Default::default()
353 };
354 let mut modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
355
356 modifier.apply_pressure_at(0.0, 0.0, 0.0, 200.0, 1.5);
358
359 for _ in 0..10 {
361 modifier.update(0.016);
362 }
363
364 let deform = modifier.deformation_at(0.0, 0.0, 0.0);
366 assert!(
367 deform > 0.0,
368 "High pressure should cause permanent deformation, got {deform}"
369 );
370 }
371
372 #[test]
373 fn test_pressure_decay() {
374 let config = PressureConfig {
375 decay_rate: 5.0, yield_threshold: 10000.0, ..Default::default()
378 };
379 let mut modifier = PressureModifier::new(config, 8, (-2.0, -2.0, -2.0), (2.0, 2.0, 2.0));
380
381 modifier.apply_pressure_at(0.0, 0.0, 0.0, 100.0, 0.5);
382 let before = modifier.pressure_at(0.0, 0.0, 0.0);
383
384 for _ in 0..20 {
385 modifier.update(0.016);
386 }
387
388 let after = modifier.pressure_at(0.0, 0.0, 0.0);
389 assert!(
390 after < before * 0.5,
391 "Pressure should decay, before={before}, after={after}"
392 );
393 }
394
395 fn sample_modifier() -> PressureModifier {
396 let mut m = PressureModifier::new(
397 PressureConfig {
398 diffusion_rate: 0.125,
399 decay_rate: 0.25,
400 yield_threshold: 3.5,
401 deformation_rate: 0.75,
402 max_deformation: 1.5,
403 internal_pressure: 2.0,
404 expansion_rate: 0.0625,
405 },
406 2,
407 (-1.0, -2.0, -3.0),
408 (1.0, 2.0, 3.0),
409 );
410 for (i, v) in m.pressure.data.iter_mut().enumerate() {
411 *v = i as f32 * 0.5;
412 }
413 m.deformation.data[5] = 0.375;
414 m.enabled = false;
415 m
416 }
417
418 #[test]
422 fn internal_pressure_and_disabled_modifier() {
423 let mut m = PressureModifier::new(
424 PressureConfig {
425 internal_pressure: 2.0,
426 expansion_rate: 0.0625,
427 ..Default::default()
428 },
429 1,
430 (0.0, 0.0, 0.0),
431 (1.0, 1.0, 1.0),
432 );
433 assert_eq!(m.modify_distance(0.5, 0.5, 0.5, 1.0), 0.875);
434 m.pressure.data[0] = 50.0;
435 m.enabled = false;
436 assert!(!m.is_active());
437 assert_eq!(m.modify_distance(0.5, 0.5, 0.5, 1.0), 1.0);
438 m.update(1.0);
439 assert_eq!(m.pressure.data[0], 50.0);
440 assert_eq!(m.deformation.data[0], 0.0);
441 }
442
443 #[test]
448 fn participant_state_round_trip() {
449 assert_eq!(
450 PressureModifier::PARTICIPANT_KIND,
451 ParticipantKind::new(0x5052_4553)
452 );
453 let src = sample_modifier();
454 assert_eq!(Participant::kind(&src), PressureModifier::PARTICIPANT_KIND);
455 let mut bytes = Vec::new();
456 src.write_state(&mut bytes);
457 assert_eq!(src.check_state(&bytes), Ok(()));
458 let mut dst = PressureModifier::new(
459 PressureConfig::default(),
460 1,
461 (0.0, 0.0, 0.0),
462 (1.0, 1.0, 1.0),
463 );
464 dst.read_state(&bytes);
465 assert_eq!(dst.config, src.config);
466 assert!(!dst.enabled);
467 assert_eq!(dst.pressure.data, src.pressure.data);
468 assert_eq!(dst.deformation.data, src.deformation.data);
469 let mut again = Vec::new();
470 dst.write_state(&mut again);
471 assert_eq!(again, bytes);
472 }
473
474 #[test]
478 fn participant_state_refusals() {
479 let src = sample_modifier();
480 let mut bytes = Vec::new();
481 src.write_state(&mut bytes);
482 assert_eq!(bytes[32], 0);
483 let mut bad = bytes.clone();
484 bad[32] = 2;
485 assert_eq!(src.check_state(&bad), Err(StateError::InvalidValue));
486 assert!(matches!(
487 src.check_state(&bytes[..bytes.len() - 1]),
488 Err(StateError::Length { .. })
489 ));
490 let mut long = bytes;
491 long.push(0);
492 assert!(matches!(
493 src.check_state(&long),
494 Err(StateError::Length { .. })
495 ));
496 }
497
498 #[test]
499 #[should_panic(expected = "read_state called with a payload check_state refuses")]
500 fn participant_read_state_panics_on_a_refused_payload() {
501 let mut m = sample_modifier();
502 m.read_state(&[]);
503 }
504
505 #[test]
511 fn participant_substep_in_a_world() {
512 let mut m = PressureModifier::new(
513 PressureConfig {
514 diffusion_rate: 0.0,
515 decay_rate: 0.0,
516 yield_threshold: 1.0,
517 deformation_rate: 1.0,
518 max_deformation: 10.0,
519 ..Default::default()
520 },
521 1,
522 (0.0, 0.0, 0.0),
523 (1.0, 1.0, 1.0),
524 );
525 m.pressure.data[0] = 3.0;
526 let mut world = crate::solver::PhysicsWorld::new(crate::solver::SolverConfig {
527 substeps: 1,
528 ..Default::default()
529 });
530 world.add_participant(Box::new(m)).expect("register");
531 world.step(Fix128::from_ratio(1, 4));
532 let Some(crate::world_participant::Observed::Exact(sink)) = world.observe_participant(0)
533 else {
534 panic!("observation");
535 };
536 assert_eq!(
537 sink.values(),
538 &[(0, Fix128::from_int(3)), (1, Fix128::from_ratio(1, 2))]
539 );
540 }
541}