anput_physics/density_fields/
sphere.rs1use 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 .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}