#![allow(missing_docs)]
#![allow(dead_code)]
#[derive(Debug, Clone)]
pub enum ForceFieldKind {
Uniform {
force: [f64; 3],
},
RadialAttract {
center: [f64; 3],
strength: f64,
falloff_exp: f64,
},
RadialRepel {
center: [f64; 3],
strength: f64,
falloff_exp: f64,
},
Vortex {
center: [f64; 3],
axis: [f64; 3],
tangential_strength: f64,
axial_strength: f64,
},
Wind {
direction: [f64; 3],
base_speed: f64,
},
Explosion {
center: [f64; 3],
peak_force: f64,
wave_speed: f64,
thickness: f64,
start_time: f64,
},
Turbulent {
base_force: [f64; 3],
amplitude: f64,
frequency: f64,
},
}
impl ForceFieldKind {
pub fn force_at(&self, pos: [f64; 3], time: f64) -> [f64; 3] {
match self {
ForceFieldKind::Uniform { force } => *force,
ForceFieldKind::RadialAttract {
center,
strength,
falloff_exp,
} => {
let d = vec3_sub(pos, *center);
let r2 = vec3_dot(d, d);
if r2 < 1e-12 {
return [0.0; 3];
}
let r = r2.sqrt();
let mag = -strength / r.powf(*falloff_exp); let dir = vec3_scale(d, 1.0 / r);
vec3_scale(dir, mag)
}
ForceFieldKind::RadialRepel {
center,
strength,
falloff_exp,
} => {
let d = vec3_sub(pos, *center);
let r2 = vec3_dot(d, d);
if r2 < 1e-12 {
return [0.0; 3];
}
let r = r2.sqrt();
let mag = strength / r.powf(*falloff_exp); let dir = vec3_scale(d, 1.0 / r);
vec3_scale(dir, mag)
}
ForceFieldKind::Vortex {
center,
axis,
tangential_strength,
axial_strength,
} => {
let r = vec3_sub(pos, *center);
let axis_n = vec3_normalize(*axis);
let proj_len = vec3_dot(r, axis_n);
let radial = vec3_sub(r, vec3_scale(axis_n, proj_len));
let radial_len = vec3_len(radial);
if radial_len < 1e-12 {
return [0.0; 3];
}
let tangential_dir = vec3_normalize(vec3_cross(axis_n, radial));
let tangential = vec3_scale(tangential_dir, tangential_strength * radial_len);
let axial = vec3_scale(axis_n, *axial_strength);
vec3_add(tangential, axial)
}
ForceFieldKind::Wind {
direction,
base_speed,
} => {
let dir_n = vec3_normalize(*direction);
vec3_scale(dir_n, *base_speed)
}
ForceFieldKind::Explosion {
center,
peak_force,
wave_speed,
thickness,
start_time,
} => {
if time < *start_time {
return [0.0; 3];
}
let wave_radius = wave_speed * (time - start_time);
let d = vec3_sub(pos, *center);
let r = vec3_len(d);
if r < 1e-12 {
return [0.0; 3];
}
let delta = r - wave_radius;
let envelope = (-delta * delta / (2.0 * thickness * thickness)).exp();
if envelope < 1e-9 {
return [0.0; 3];
}
let dir = vec3_scale(d, 1.0 / r);
vec3_scale(dir, peak_force * envelope)
}
ForceFieldKind::Turbulent {
base_force,
amplitude,
frequency,
} => {
let phase_offset = spatial_hash(pos);
let t_mod = (2.0 * std::f64::consts::PI * frequency * time + phase_offset).sin();
let perturb = [
amplitude * t_mod * (phase_offset * 0.123).sin(),
amplitude * t_mod * (phase_offset * 0.456).cos(),
amplitude * t_mod * (phase_offset * 0.789).sin(),
];
vec3_add(*base_force, perturb)
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AabbRegion {
pub min: [f64; 3],
pub max: [f64; 3],
}
impl AabbRegion {
pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
Self { min, max }
}
pub fn sphere(center: [f64; 3], radius: f64) -> Self {
Self {
min: [center[0] - radius, center[1] - radius, center[2] - radius],
max: [center[0] + radius, center[1] + radius, center[2] + radius],
}
}
pub fn contains(&self, pos: [f64; 3]) -> bool {
pos[0] >= self.min[0]
&& pos[0] <= self.max[0]
&& pos[1] >= self.min[1]
&& pos[1] <= self.max[1]
&& pos[2] >= self.min[2]
&& pos[2] <= self.max[2]
}
}
#[derive(Debug, Clone)]
pub struct ForceFieldEntry {
pub kind: ForceFieldKind,
pub enabled: bool,
pub region: Option<AabbRegion>,
pub multiplier: f64,
}
impl ForceFieldEntry {
pub fn force_at(&self, pos: [f64; 3], time: f64) -> [f64; 3] {
if !self.enabled {
return [0.0; 3];
}
if self.region.as_ref().is_some_and(|reg| !reg.contains(pos)) {
return [0.0; 3];
}
vec3_scale(self.kind.force_at(pos, time), self.multiplier)
}
}
#[derive(Debug, Clone, Default)]
pub struct ForceFieldSystem {
fields: Vec<ForceFieldEntry>,
}
impl ForceFieldSystem {
pub fn new() -> Self {
Self { fields: Vec::new() }
}
pub fn add(&mut self, kind: ForceFieldKind) -> usize {
let id = self.fields.len();
self.fields.push(ForceFieldEntry {
kind,
enabled: true,
region: None,
multiplier: 1.0,
});
id
}
pub fn add_bounded(&mut self, kind: ForceFieldKind, region: AabbRegion) -> usize {
let id = self.fields.len();
self.fields.push(ForceFieldEntry {
kind,
enabled: true,
region: Some(region),
multiplier: 1.0,
});
id
}
pub fn add_scaled(&mut self, kind: ForceFieldKind, multiplier: f64) -> usize {
let id = self.fields.len();
self.fields.push(ForceFieldEntry {
kind,
enabled: true,
region: None,
multiplier,
});
id
}
pub fn disable(&mut self, id: usize) {
if let Some(f) = self.fields.get_mut(id) {
f.enabled = false;
}
}
pub fn enable(&mut self, id: usize) {
if let Some(f) = self.fields.get_mut(id) {
f.enabled = true;
}
}
pub fn remove(&mut self, id: usize) {
if let Some(f) = self.fields.get_mut(id) {
f.enabled = false;
f.multiplier = 0.0;
}
}
pub fn set_multiplier(&mut self, id: usize, multiplier: f64) {
if let Some(f) = self.fields.get_mut(id) {
f.multiplier = multiplier;
}
}
pub fn force_at(&self, pos: [f64; 3], time: f64) -> [f64; 3] {
let mut total = [0.0_f64; 3];
for f in &self.fields {
let fv = f.force_at(pos, time);
total[0] += fv[0];
total[1] += fv[1];
total[2] += fv[2];
}
total
}
pub fn apply_to_batch(&self, items: &mut [([f64; 3], [f64; 3])], time: f64) {
for (pos, acc) in items.iter_mut() {
let fv = self.force_at(*pos, time);
acc[0] += fv[0];
acc[1] += fv[1];
acc[2] += fv[2];
}
}
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
pub fn active_count(&self) -> usize {
self.fields
.iter()
.filter(|f| f.enabled && f.multiplier.abs() > 1e-15)
.count()
}
pub fn fields(&self) -> &[ForceFieldEntry] {
&self.fields
}
}
#[inline]
fn vec3_add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
#[inline]
fn vec3_sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[inline]
fn vec3_scale(a: [f64; 3], s: f64) -> [f64; 3] {
[a[0] * s, a[1] * s, a[2] * s]
}
#[inline]
fn vec3_dot(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[inline]
fn vec3_len(a: [f64; 3]) -> f64 {
vec3_dot(a, a).sqrt()
}
#[inline]
fn vec3_cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[inline]
fn vec3_normalize(a: [f64; 3]) -> [f64; 3] {
let l = vec3_len(a);
if l > 1e-15 {
vec3_scale(a, 1.0 / l)
} else {
[0.0, 1.0, 0.0]
}
}
#[inline]
fn spatial_hash(pos: [f64; 3]) -> f64 {
let ix = (pos[0] * 1000.0) as i64;
let iy = (pos[1] * 1000.0) as i64;
let iz = (pos[2] * 1000.0) as i64;
let h = ix.wrapping_mul(2654435761_i64)
^ iy.wrapping_mul(1234567891_i64)
^ iz.wrapping_mul(9876543211_i64);
let h_f = (h.unsigned_abs() % 1_000_000) as f64 / 1_000_000.0;
h_f * 2.0 * std::f64::consts::PI
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uniform_field() {
let k = ForceFieldKind::Uniform {
force: [0.0, -9.81, 0.0],
};
let f = k.force_at([5.0, 10.0, -3.0], 1.0);
assert!((f[1] + 9.81).abs() < 1e-12);
assert!(f[0].abs() < 1e-12);
}
#[test]
fn radial_attract_toward_center() {
let k = ForceFieldKind::RadialAttract {
center: [0.0; 3],
strength: 10.0,
falloff_exp: 2.0,
};
let f = k.force_at([1.0, 0.0, 0.0], 0.0);
assert!(f[0] < 0.0);
}
#[test]
fn radial_repel_away_from_center() {
let k = ForceFieldKind::RadialRepel {
center: [0.0; 3],
strength: 10.0,
falloff_exp: 2.0,
};
let f = k.force_at([1.0, 0.0, 0.0], 0.0);
assert!(f[0] > 0.0); }
#[test]
fn vortex_perpendicular_to_axis_and_radial() {
let k = ForceFieldKind::Vortex {
center: [0.0; 3],
axis: [0.0, 1.0, 0.0],
tangential_strength: 5.0,
axial_strength: 0.0,
};
let f = k.force_at([1.0, 0.0, 0.0], 0.0);
assert!(f[2].abs() > 1e-10);
assert!(f[1].abs() < 1e-10); }
#[test]
fn explosion_before_start_is_zero() {
let k = ForceFieldKind::Explosion {
center: [0.0; 3],
peak_force: 1000.0,
wave_speed: 10.0,
thickness: 1.0,
start_time: 5.0,
};
let f = k.force_at([1.0, 0.0, 0.0], 3.0); assert_eq!(f, [0.0; 3]);
}
#[test]
fn explosion_peak_at_wave_front() {
let peak = 1000.0_f64;
let speed = 10.0_f64;
let t = 1.0_f64;
let k = ForceFieldKind::Explosion {
center: [0.0; 3],
peak_force: peak,
wave_speed: speed,
thickness: 0.5,
start_time: 0.0,
};
let f = k.force_at([speed * t, 0.0, 0.0], t);
let mag = vec3_len(f);
assert!((mag - peak).abs() < 1.0, "mag={mag}, expected≈{peak}");
}
#[test]
fn turbulent_deterministic() {
let k = ForceFieldKind::Turbulent {
base_force: [1.0, 0.0, 0.0],
amplitude: 0.1,
frequency: 1.0,
};
let f1 = k.force_at([1.0, 2.0, 3.0], 0.5);
let f2 = k.force_at([1.0, 2.0, 3.0], 0.5);
assert_eq!(f1, f2); }
#[test]
fn system_accumulates_fields() {
let mut sys = ForceFieldSystem::new();
sys.add(ForceFieldKind::Uniform {
force: [1.0, 0.0, 0.0],
});
sys.add(ForceFieldKind::Uniform {
force: [0.0, 1.0, 0.0],
});
let f = sys.force_at([0.0; 3], 0.0);
assert!((f[0] - 1.0).abs() < 1e-12);
assert!((f[1] - 1.0).abs() < 1e-12);
}
#[test]
fn system_disable_field() {
let mut sys = ForceFieldSystem::new();
let id = sys.add(ForceFieldKind::Uniform {
force: [100.0, 0.0, 0.0],
});
sys.disable(id);
let f = sys.force_at([0.0; 3], 0.0);
assert!(f[0].abs() < 1e-12);
}
#[test]
fn bounded_field_outside_region() {
let mut sys = ForceFieldSystem::new();
sys.add_bounded(
ForceFieldKind::Uniform {
force: [100.0, 0.0, 0.0],
},
AabbRegion::new([0.0; 3], [1.0; 3]),
);
let f = sys.force_at([5.0, 0.0, 0.0], 0.0);
assert!(f[0].abs() < 1e-12);
}
#[test]
fn bounded_field_inside_region() {
let mut sys = ForceFieldSystem::new();
sys.add_bounded(
ForceFieldKind::Uniform {
force: [100.0, 0.0, 0.0],
},
AabbRegion::new([0.0; 3], [10.0; 3]),
);
let f = sys.force_at([5.0, 5.0, 5.0], 0.0);
assert!((f[0] - 100.0).abs() < 1e-12);
}
#[test]
fn apply_to_batch() {
let mut sys = ForceFieldSystem::new();
sys.add(ForceFieldKind::Uniform {
force: [1.0, 2.0, 3.0],
});
let mut batch = [
([0.0_f64; 3], [0.0_f64; 3]),
([1.0, 1.0, 1.0], [0.0_f64; 3]),
];
sys.apply_to_batch(&mut batch, 0.0);
for (_, acc) in &batch {
assert!((acc[0] - 1.0).abs() < 1e-12);
assert!((acc[1] - 2.0).abs() < 1e-12);
}
}
#[test]
fn active_count() {
let mut sys = ForceFieldSystem::new();
let id0 = sys.add(ForceFieldKind::Uniform { force: [0.0; 3] });
let _id1 = sys.add(ForceFieldKind::Uniform { force: [0.0; 3] });
assert_eq!(sys.active_count(), 2);
sys.disable(id0);
assert_eq!(sys.active_count(), 1);
}
}