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}