anput_physics/density_fields/
sphere.rs

1use crate::{
2    Scalar,
3    components::Position,
4    density_fields::{BodyAccessInfo, DensityField},
5};
6use vek::{Aabb, Vec3};
7
8pub struct SphereDensityField<const LOCKING: bool> {
9    pub density: Scalar,
10    pub radius: Scalar,
11    pub edge_thickness: Scalar,
12}
13
14impl<const LOCKING: bool> SphereDensityField<LOCKING> {
15    pub fn new_hard(density: Scalar, radius: Scalar) -> Self {
16        SphereDensityField {
17            density,
18            radius,
19            edge_thickness: 0.0,
20        }
21    }
22
23    pub fn new_soft(density: Scalar, radius: Scalar) -> Self {
24        SphereDensityField {
25            density,
26            radius: 0.0,
27            edge_thickness: radius,
28        }
29    }
30
31    pub fn new_soft_edge(density: Scalar, radius: Scalar, edge_thickness: Scalar) -> Self {
32        SphereDensityField {
33            density,
34            radius,
35            edge_thickness,
36        }
37    }
38
39    #[inline]
40    pub fn total_radius(&self) -> Scalar {
41        self.radius + self.edge_thickness
42    }
43}
44
45impl<const LOCKING: bool> DensityField for SphereDensityField<LOCKING> {
46    fn aabb(&self, info: &BodyAccessInfo) -> Aabb<Scalar> {
47        info.particles::<LOCKING, &Position>()
48            .map(|position| Aabb {
49                min: position.current - self.total_radius(),
50                max: position.current + self.total_radius(),
51            })
52            .reduce(|accum, aabb| accum.union(aabb))
53            .unwrap_or_default()
54    }
55
56    fn density_at_point(&self, point: Vec3<Scalar>, info: &BodyAccessInfo) -> Scalar {
57        info.particles::<LOCKING, &Position>()
58            .map(|position| {
59                let distance = position.current.distance(point);
60                let factor = if distance < self.radius {
61                    1.0
62                } else if self.edge_thickness > Scalar::EPSILON {
63                    1.0 - ((distance - self.radius) / self.edge_thickness).clamp(0.0, 1.0)
64                } else {
65                    0.0
66                };
67                factor * self.density
68            })
69            .reduce(|accum, density| accum.max(density))
70            .unwrap_or_default()
71    }
72
73    fn normal_at_point(
74        &self,
75        point: Vec3<Scalar>,
76        _: Vec3<Scalar>,
77        info: &BodyAccessInfo,
78    ) -> Vec3<Scalar> {
79        info.particles::<LOCKING, &Position>()
80            .map(|position| {
81                let direction = point - position.current;
82                if direction.is_approx_zero() {
83                    position.change()
84                } else {
85                    direction
86                }
87            })
88            .reduce(|accum, direction| accum + direction)
89            // TODO: maybe weight directions so ones closer to the surface have more influence?
90            .and_then(|normal| normal.try_normalized())
91            .unwrap_or_default()
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::{
99        components::{
100            BodyDensityFieldRelation, BodyParentRelation, BodyParticleRelation, PhysicsBody,
101            PhysicsParticle,
102        },
103        density_fields::{DensityFieldBox, DensityRange},
104    };
105    use anput::world::World;
106
107    #[test]
108    fn test_sphere_density_field() {
109        let mut world = World::default();
110        let object = world
111            .spawn((
112                PhysicsBody,
113                PhysicsParticle,
114                Position::new(Vec3::new(1.0, 2.0, 3.0)),
115                DensityFieldBox::new(SphereDensityField::<true>::new_hard(1.0, 10.0)),
116            ))
117            .unwrap();
118        world
119            .relate::<true, _>(BodyParticleRelation, object, object)
120            .unwrap();
121        world
122            .relate::<true, _>(BodyDensityFieldRelation, object, object)
123            .unwrap();
124        world
125            .relate::<true, _>(BodyParentRelation, object, object)
126            .unwrap();
127
128        let sphere = world
129            .entity::<true, &DensityFieldBox>(object)
130            .unwrap()
131            .as_any()
132            .downcast_ref::<SphereDensityField<true>>()
133            .unwrap();
134        let info = BodyAccessInfo::of_world(object, &world);
135
136        assert_eq!(
137            sphere.aabb(&info),
138            Aabb {
139                min: Vec3::new(-9.0, -8.0, -7.0),
140                max: Vec3::new(11.0, 12.0, 13.0),
141            }
142        );
143
144        assert_eq!(
145            sphere.density_at_point(Vec3::new(1.0, 2.0, 3.0), &info),
146            1.0
147        );
148        assert_eq!(
149            sphere.density_at_point(Vec3::new(-9.0, -8.0, -7.0), &info),
150            0.0
151        );
152        assert_eq!(
153            sphere.density_at_point(Vec3::new(11.0, 12.0, 13.0), &info),
154            0.0
155        );
156
157        assert_eq!(
158            sphere.density_at_region(
159                Aabb {
160                    min: Vec3::new(-9.0, -8.0, -7.0),
161                    max: Vec3::new(11.0, 12.0, 13.0)
162                },
163                &info
164            ),
165            DensityRange { min: 0.0, max: 1.0 }
166        );
167        assert_eq!(
168            sphere.density_at_region(
169                Aabb {
170                    min: Vec3::new(-4.0, -3.0, -2.0),
171                    max: Vec3::new(6.0, 7.0, 8.0)
172                },
173                &info
174            ),
175            DensityRange { min: 1.0, max: 1.0 }
176        );
177        assert_eq!(
178            sphere.density_at_region(
179                Aabb {
180                    min: Vec3::new(10.0, 10.0, 10.0),
181                    max: Vec3::new(11.0, 11.0, 11.0)
182                },
183                &info
184            ),
185            DensityRange { min: 0.0, max: 0.0 }
186        );
187
188        assert_eq!(
189            sphere.normal_at_point(Vec3::new(1.0, 2.0, 3.0), Default::default(), &info),
190            Vec3::zero()
191        );
192        assert_eq!(
193            sphere.normal_at_point(Vec3::new(2.0, 2.0, 3.0), Default::default(), &info),
194            Vec3::new(1.0, 0.0, 0.0)
195        );
196        assert_eq!(
197            sphere.normal_at_point(Vec3::new(0.0, 2.0, 3.0), Default::default(), &info),
198            Vec3::new(-1.0, 0.0, 0.0)
199        );
200        assert_eq!(
201            sphere.normal_at_point(Vec3::new(1.0, 3.0, 3.0), Default::default(), &info),
202            Vec3::new(0.0, 1.0, 0.0)
203        );
204        assert_eq!(
205            sphere.normal_at_point(Vec3::new(1.0, 1.0, 3.0), Default::default(), &info),
206            Vec3::new(0.0, -1.0, 0.0)
207        );
208        assert_eq!(
209            sphere.normal_at_point(Vec3::new(2.0, 3.0, 3.0), Default::default(), &info),
210            Vec3::new(1.0, 1.0, 0.0).normalized()
211        );
212    }
213}