anput_physics/density_fields/
sphere.rs

1use crate::{
2    Scalar,
3    components::Position,
4    density_fields::{BodyAccessInfo, DensityField, DensityRange},
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                if distance < self.radius {
61                    self.density
62                } else {
63                    1.0 - ((distance - self.radius) / self.edge_thickness).clamp(0.0, 1.0)
64                }
65            })
66            .reduce(|accum, density| accum.max(density))
67            .unwrap_or_default()
68    }
69
70    fn density_at_region(&self, region: Aabb<Scalar>, info: &BodyAccessInfo) -> DensityRange {
71        [
72            region.center(),
73            Vec3::new(region.min.x, region.min.y, region.min.z),
74            Vec3::new(region.max.x, region.min.y, region.min.z),
75            Vec3::new(region.min.x, region.max.y, region.min.z),
76            Vec3::new(region.max.x, region.max.y, region.min.z),
77            Vec3::new(region.min.x, region.min.y, region.max.z),
78            Vec3::new(region.max.x, region.min.y, region.max.z),
79            Vec3::new(region.min.x, region.max.y, region.max.z),
80            Vec3::new(region.max.x, region.max.y, region.max.z),
81        ]
82        .into_iter()
83        .map(|point| DensityRange::converged(self.density_at_point(point, info)))
84        .reduce(|accum, density| accum.min_max(&density))
85        .unwrap_or_default()
86    }
87
88    fn normal_at_point(
89        &self,
90        point: Vec3<Scalar>,
91        _: Vec3<Scalar>,
92        info: &BodyAccessInfo,
93    ) -> Vec3<Scalar> {
94        info.particles::<LOCKING, &Position>()
95            .map(|position| {
96                let direction = point - position.current;
97                if direction.is_approx_zero() {
98                    position.change()
99                } else {
100                    direction
101                }
102            })
103            .reduce(|accum, direction| accum + direction)
104            .and_then(|normal| normal.try_normalized())
105            .unwrap_or_default()
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::{
113        components::{
114            BodyDensityFieldRelation, BodyParentRelation, BodyParticleRelation, PhysicsBody,
115            PhysicsParticle,
116        },
117        density_fields::{DensityFieldBox, DensityRange},
118    };
119    use anput::world::World;
120
121    #[test]
122    fn test_sphere_density_field() {
123        let mut world = World::default();
124        let object = world
125            .spawn((
126                PhysicsBody,
127                PhysicsParticle,
128                Position::new(Vec3::new(1.0, 2.0, 3.0)),
129                DensityFieldBox::new(SphereDensityField::<true>::new_hard(1.0, 10.0)),
130            ))
131            .unwrap();
132        world
133            .relate::<true, _>(BodyParticleRelation, object, object)
134            .unwrap();
135        world
136            .relate::<true, _>(BodyDensityFieldRelation, object, object)
137            .unwrap();
138        world
139            .relate::<true, _>(BodyParentRelation, object, object)
140            .unwrap();
141
142        let sphere = world
143            .entity::<true, &DensityFieldBox>(object)
144            .unwrap()
145            .as_any()
146            .downcast_ref::<SphereDensityField<true>>()
147            .unwrap();
148        let info = BodyAccessInfo::of_world(object, &world);
149
150        assert_eq!(
151            sphere.aabb(&info),
152            Aabb {
153                min: Vec3::new(-9.0, -8.0, -7.0),
154                max: Vec3::new(11.0, 12.0, 13.0),
155            }
156        );
157
158        assert_eq!(
159            sphere.density_at_point(Vec3::new(1.0, 2.0, 3.0), &info),
160            1.0
161        );
162        assert_eq!(
163            sphere.density_at_point(Vec3::new(-9.0, -8.0, -7.0), &info),
164            0.0
165        );
166        assert_eq!(
167            sphere.density_at_point(Vec3::new(11.0, 12.0, 13.0), &info),
168            0.0
169        );
170
171        assert_eq!(
172            sphere.density_at_region(
173                Aabb {
174                    min: Vec3::new(-9.0, -8.0, -7.0),
175                    max: Vec3::new(11.0, 12.0, 13.0)
176                },
177                &info
178            ),
179            DensityRange { min: 0.0, max: 1.0 }
180        );
181        assert_eq!(
182            sphere.density_at_region(
183                Aabb {
184                    min: Vec3::new(-4.0, -3.0, -2.0),
185                    max: Vec3::new(6.0, 7.0, 8.0)
186                },
187                &info
188            ),
189            DensityRange { min: 1.0, max: 1.0 }
190        );
191        assert_eq!(
192            sphere.density_at_region(
193                Aabb {
194                    min: Vec3::new(10.0, 10.0, 10.0),
195                    max: Vec3::new(11.0, 11.0, 11.0)
196                },
197                &info
198            ),
199            DensityRange { min: 0.0, max: 0.0 }
200        );
201
202        assert_eq!(
203            sphere.normal_at_point(Vec3::new(1.0, 2.0, 3.0), Default::default(), &info),
204            Vec3::zero()
205        );
206        assert_eq!(
207            sphere.normal_at_point(Vec3::new(2.0, 2.0, 3.0), Default::default(), &info),
208            Vec3::new(1.0, 0.0, 0.0)
209        );
210        assert_eq!(
211            sphere.normal_at_point(Vec3::new(0.0, 2.0, 3.0), Default::default(), &info),
212            Vec3::new(-1.0, 0.0, 0.0)
213        );
214        assert_eq!(
215            sphere.normal_at_point(Vec3::new(1.0, 3.0, 3.0), Default::default(), &info),
216            Vec3::new(0.0, 1.0, 0.0)
217        );
218        assert_eq!(
219            sphere.normal_at_point(Vec3::new(1.0, 1.0, 3.0), Default::default(), &info),
220            Vec3::new(0.0, -1.0, 0.0)
221        );
222        assert_eq!(
223            sphere.normal_at_point(Vec3::new(2.0, 3.0, 3.0), Default::default(), &info),
224            Vec3::new(1.0, 1.0, 0.0).normalized()
225        );
226    }
227}