1use std::cmp::Ordering;
2
3use crate::{Scalar, components::BodyAccessInfo, density_fields::DensityField};
4use vek::{Aabb, Vec3};
5
6pub struct CubeDensityField<const LOCKING: bool> {
7 pub density: Scalar,
8 pub extents: Vec3<Scalar>,
9 pub edge_thickness: Vec3<Scalar>,
10}
11
12impl<const LOCKING: bool> CubeDensityField<LOCKING> {
13 pub fn new_hard(density: Scalar, extents: Vec3<Scalar>) -> Self {
14 Self {
15 density,
16 extents,
17 edge_thickness: Default::default(),
18 }
19 }
20
21 pub fn new_soft(density: Scalar, extents: Vec3<Scalar>) -> Self {
22 Self {
23 density,
24 extents: Default::default(),
25 edge_thickness: extents,
26 }
27 }
28
29 pub fn new_soft_edge(
30 density: Scalar,
31 extents: Vec3<Scalar>,
32 edge_thickness: Vec3<Scalar>,
33 ) -> Self {
34 Self {
35 density,
36 extents,
37 edge_thickness,
38 }
39 }
40
41 #[inline]
42 pub fn total_extents(&self) -> Vec3<Scalar> {
43 self.extents + self.edge_thickness
44 }
45}
46
47impl<const LOCKING: bool> DensityField for CubeDensityField<LOCKING> {
48 fn aabb(&self, info: &BodyAccessInfo) -> Aabb<Scalar> {
49 info.world_space_particles::<LOCKING, ()>()
50 .map(|(matrix, _)| {
51 let extents = self.total_extents();
52 let mut aabb = Aabb::new_empty(matrix.mul_point(Default::default()));
53 for corner in [
54 Vec3::new(-extents.x, -extents.y, -extents.z),
55 Vec3::new(extents.x, -extents.y, -extents.z),
56 Vec3::new(extents.x, extents.y, -extents.z),
57 Vec3::new(-extents.x, extents.y, -extents.z),
58 Vec3::new(-extents.x, -extents.y, extents.z),
59 Vec3::new(extents.x, -extents.y, extents.z),
60 Vec3::new(extents.x, extents.y, extents.z),
61 Vec3::new(-extents.x, extents.y, extents.z),
62 ] {
63 aabb.expand_to_contain_point(matrix.mul_point(corner));
64 }
65 aabb
66 })
67 .reduce(|accum, aabb| accum.union(aabb))
68 .unwrap_or_default()
69 }
70
71 fn density_at_point(&self, point: Vec3<Scalar>, info: &BodyAccessInfo) -> Scalar {
72 info.world_space_particles::<LOCKING, ()>()
73 .map(|(matrix, _)| {
74 let point = matrix.inverted().mul_point(point).into_array();
75 let extents = self.extents.into_array();
76 let edge_thickness = self.edge_thickness.into_array();
77 (0..2)
78 .map(|index| {
79 let point = point[index].abs();
80 let extent = extents[index];
81 let edge = edge_thickness[index];
82 let distance = point - extent;
83 (edge, distance)
84 })
85 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
86 .map(|(edge, distance)| {
87 let factor = if distance < 0.0 {
88 1.0
89 } else if edge > Scalar::EPSILON {
90 1.0 - (distance / edge).clamp(0.0, 1.0)
91 } else {
92 0.0
93 };
94 factor * self.density
95 })
96 .unwrap_or_default()
97 })
98 .reduce(|accum, density| accum.max(density))
99 .unwrap_or_default()
100 }
101
102 fn normal_at_point(
103 &self,
104 point: Vec3<Scalar>,
105 _: Vec3<Scalar>,
106 info: &BodyAccessInfo,
107 ) -> Vec3<Scalar> {
108 let extents = self.total_extents().into_array();
109 info.world_space_particles::<LOCKING, ()>()
110 .map(|(matrix, _)| {
111 let inv_matrix = matrix.inverted();
112 let point = inv_matrix.mul_point(point);
113 if point.is_approx_zero() {
114 return Vec3::zero();
115 }
116 let direction = [
117 (
118 extents[0],
119 (point.x + extents[0]).abs(),
120 Vec3::new(-1.0, 0.0, 0.0),
121 ),
122 (
123 extents[1],
124 (point.y + extents[1]).abs(),
125 Vec3::new(0.0, -1.0, 0.0),
126 ),
127 (
128 extents[2],
129 (point.z + extents[2]).abs(),
130 Vec3::new(0.0, 0.0, -1.0),
131 ),
132 (
133 extents[0],
134 (extents[0] - point.x).abs(),
135 Vec3::new(1.0, 0.0, 0.0),
136 ),
137 (
138 extents[1],
139 (extents[1] - point.y).abs(),
140 Vec3::new(0.0, 1.0, 0.0),
141 ),
142 (
143 extents[2],
144 (extents[2] - point.z).abs(),
145 Vec3::new(0.0, 0.0, 1.0),
146 ),
147 ]
148 .into_iter()
149 .filter(|(size, _, _)| *size > Scalar::EPSILON)
150 .min_by(|(_, a, _), (_, b, _)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
151 .map(|(_, _, normal)| normal)
152 .unwrap_or_default();
153 matrix.mul_direction(direction)
154 })
155 .reduce(|accum, direction| accum + direction)
156 .and_then(|normal| normal.try_normalized())
158 .unwrap_or_default()
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::{
166 components::{
167 BodyDensityFieldRelation, BodyParentRelation, BodyParticleRelation, PhysicsBody,
168 PhysicsParticle, Position,
169 },
170 density_fields::{DensityFieldBox, DensityRange},
171 };
172 use anput::world::World;
173
174 #[test]
175 fn test_cube_density_field() {
176 let mut world = World::default();
177 let object = world
178 .spawn((
179 PhysicsBody,
180 PhysicsParticle,
181 Position::new(Vec3::new(1.0, 2.0, 3.0)),
182 DensityFieldBox::new(CubeDensityField::<true>::new_hard(1.0, 10.0.into())),
183 ))
184 .unwrap();
185 world
186 .relate::<true, _>(BodyParticleRelation, object, object)
187 .unwrap();
188 world
189 .relate::<true, _>(BodyDensityFieldRelation, object, object)
190 .unwrap();
191 world
192 .relate::<true, _>(BodyParentRelation, object, object)
193 .unwrap();
194
195 let cube = world
196 .entity::<true, &DensityFieldBox>(object)
197 .unwrap()
198 .as_any()
199 .downcast_ref::<CubeDensityField<true>>()
200 .unwrap();
201 let info = BodyAccessInfo::of_world(object, &world);
202
203 assert_eq!(
204 cube.aabb(&info),
205 Aabb {
206 min: Vec3::new(-9.0, -8.0, -7.0),
207 max: Vec3::new(11.0, 12.0, 13.0),
208 }
209 );
210
211 assert_eq!(cube.density_at_point(Vec3::new(1.0, 2.0, 3.0), &info), 1.0);
212 assert_eq!(
213 cube.density_at_point(Vec3::new(-9.0, -8.0, -7.0), &info),
214 0.0
215 );
216 assert_eq!(
217 cube.density_at_point(Vec3::new(11.0, 12.0, 13.0), &info),
218 0.0
219 );
220 assert_eq!(
221 cube.density_at_point(Vec3::new(-8.0, -7.0, -6.0), &info),
222 1.0
223 );
224 assert_eq!(
225 cube.density_at_point(Vec3::new(10.0, 11.0, 12.0), &info),
226 1.0
227 );
228
229 assert_eq!(
230 cube.density_at_region(
231 Aabb {
232 min: Vec3::new(-8.0, -7.0, -6.0),
233 max: Vec3::new(9.0, 10.0, 11.0)
234 },
235 &info
236 ),
237 DensityRange { min: 1.0, max: 1.0 }
238 );
239 assert_eq!(
240 cube.density_at_region(
241 Aabb {
242 min: Vec3::new(-10.0, -9.0, -8.0),
243 max: Vec3::new(12.0, 13.0, 14.0)
244 },
245 &info
246 ),
247 DensityRange { min: 0.0, max: 1.0 }
248 );
249 assert_eq!(
250 cube.density_at_region(
251 Aabb {
252 min: Vec3::new(100.0, 100.0, 100.0),
253 max: Vec3::new(200.0, 200.0, 200.0)
254 },
255 &info
256 ),
257 DensityRange { min: 0.0, max: 0.0 }
258 );
259
260 assert_eq!(
261 cube.normal_at_point(Vec3::new(1.0, 2.0, 3.0), Default::default(), &info),
262 Vec3::new(0.0, 0.0, 0.0)
263 );
264 assert_eq!(
265 cube.normal_at_point(Vec3::new(2.0, 2.0, 3.0), Default::default(), &info),
266 Vec3::new(1.0, 0.0, 0.0)
267 );
268 assert_eq!(
269 cube.normal_at_point(Vec3::new(0.0, 2.0, 3.0), Default::default(), &info),
270 Vec3::new(-1.0, 0.0, 0.0)
271 );
272 assert_eq!(
273 cube.normal_at_point(Vec3::new(1.0, 3.0, 3.0), Default::default(), &info),
274 Vec3::new(0.0, 1.0, 0.0)
275 );
276 assert_eq!(
277 cube.normal_at_point(Vec3::new(1.0, 1.0, 3.0), Default::default(), &info),
278 Vec3::new(0.0, -1.0, 0.0)
279 );
280 }
281}