space_filling/solver/adf/
mod.rs1#![allow(clippy::mut_from_ref)]
5use {
6 crate::{
7 solver::LineSearch,
8 geometry::{Shape, shapes, P2, WorldSpace, BoundingBox},
9 sdf::SDF,
10 },
11 quadtree::{
12 Quadtree, TraverseCommand
13 },
14 std::{
15 sync::{
16 Arc, atomic::{AtomicBool, Ordering}
17 },
18 fmt::{Debug, Formatter}
19 },
20 euclid::{Point2D, Box2D, Rect},
21 num_traits::{Float, Signed}
22};
23
24#[cfg(test)] mod tests;
25pub(crate) mod quadtree;
26
27#[derive(Clone)]
28pub struct ADF<Float> {
29 pub tree: Quadtree<Vec<Arc<dyn Fn(P2<Float>) -> Float>>, Float>,
30 ipm_gd_lattice_density: u32,
33 ipm_line_config: LineSearch<Float>
34}
35
36unsafe impl<Float> Send for ADF<Float> {}
37unsafe impl<Float> Sync for ADF<Float> {}
38
39impl <_Float: Float> SDF<_Float> for &[Arc<dyn Fn(P2<_Float>) -> _Float>] {
40 fn sdf(&self, pixel: P2<_Float>) -> _Float {
41 self.iter()
42 .map(|f| f(pixel))
43 .reduce(|a, b| if a <= b { a } else { b })
44 .unwrap_or(_Float::max_value() / (_Float::one() + _Float::one()))
45 }
46}
47
48fn sdf_partialord<_Float: Float + Signed>(
49 f: impl Fn(P2<_Float>) -> _Float,
50 g: impl Fn(P2<_Float>) -> _Float,
51 domain: Rect<_Float, WorldSpace>,
52 lattice_density: u32,
53 line_search: LineSearch<_Float>
54) -> bool {
55 let boundary_constraint = |v| shapes::Rect { size: domain.size.to_vector().to_point() }
56 .translate(domain.center().to_vector())
57 .sdf(v); let control_points = |rect: Rect<_, _>| {
60 let p = (0..lattice_density).map(move |x| _Float::from(x).unwrap() / _Float::from(lattice_density - 1).unwrap());
61 itertools::iproduct!(p.clone(), p)
62 .map(move |p| rect.origin + rect.size.to_vector().component_mul(p.into()))
63 };
64
65 let test = |v| line_search.optimize_normal(
66 |v| if domain.contains(v) { g(v) - f(v) } else { -boundary_constraint(v) },
67 v
68 );
69
70 !match lattice_density {
71 1 => test(domain.center()),
72 2..=u32::MAX => control_points(domain).any(test),
73 _ => panic!("Invalid perturbation grid density: {lattice_density}")
74 }
75}
76
77impl <_Float: Float + Signed + Sync> ADF<_Float> {
78 pub fn new(max_depth: u8, init: Vec<Arc<dyn Fn(P2<_Float>) -> _Float>>) -> Self {
81 Self {
82 tree: Quadtree::new(max_depth, init),
83 ipm_gd_lattice_density: 1,
84 ipm_line_config: LineSearch::default()
85 }
86 }
87 pub fn with_gd_lattice_density(mut self, density: u32) -> Self {
89 self.ipm_gd_lattice_density = density;
90 self
91 }
92 pub fn with_ipm_line_config(mut self, line_config: LineSearch<_Float>) -> Self {
94 self.ipm_line_config = line_config;
95 self
96 }
97 #[deprecated] #[allow(unused)]
121 fn higher_all(
122 f: &(dyn Fn(Point2D<f64, WorldSpace>) -> f64),
123 g: &(dyn Fn(Point2D<f64, WorldSpace>) -> f64),
124 d: Rect<f64, WorldSpace>
125 ) -> bool {
126 let control_points = |rect: Rect<_, _>| {
127 let n = 5;
128 let p = (0..n).map(move |x| x as f64 / (n - 1) as f64);
129 itertools::iproduct!(p.clone(), p)
130 .map(move |p| rect.origin + rect.size.to_vector().component_mul(p.into()))
131 };
132
133 !control_points(d)
134 .any(|v| g(v) > f(v))
135 }
136
137 pub fn insert_sdf_domain(&mut self, domain: Rect<_Float, WorldSpace>, f: Arc<dyn Fn(P2<_Float>) -> _Float + Send + Sync>) -> bool {
139 let change_exists = AtomicBool::new(false);
140
141 self.tree.traverse_managed_parallel(|node| {
142 if !node.rect.intersects(&domain) {
144 return TraverseCommand::Skip;
145 }
146
147 if node.children.is_some() {
149 return TraverseCommand::Ok;
150 }
151
152 if sdf_partialord(
154 f.as_ref(),
155 |p| node.data.as_slice().sdf(p),
156 node.rect,
157 self.ipm_gd_lattice_density,
158 self.ipm_line_config
159 ) {
160 return TraverseCommand::Skip;
161 }
162
163 if sdf_partialord(
165 |p| node.data.as_slice().sdf(p),
166 f.as_ref(),
167 node.rect,
168 self.ipm_gd_lattice_density,
169 self.ipm_line_config
170 ) {
171 node.data = vec![f.clone()];
172 change_exists.store(true, Ordering::Relaxed);
173 return TraverseCommand::Skip;
174 };
175
176 change_exists.store(true, Ordering::Relaxed);
177 const BUCKET_SIZE: usize = 3;
178
179 let prune = |data: &[Arc<dyn Fn(P2<_Float>) -> _Float>], rect| {
181 let mut g = vec![];
182 for (i, f) in data.iter().enumerate() {
183 let sdf_old = |p|
184 data.iter().enumerate()
185 .filter_map(|(j, f)| if i != j {
186 Some(f(p))
187 } else { None })
188 .fold(_Float::max_value() / (_Float::one() + _Float::one()), |a, b| a.min(b));
189 if !sdf_partialord(
191 f.as_ref(),
192 sdf_old,
193 rect,
194 self.ipm_gd_lattice_density,
195 self.ipm_line_config
196 ) {
197 g.push(f.clone())
198 }
199 };
200 g
201 };
202
203 if node.depth == node.max_depth || node.data.len() < BUCKET_SIZE {
205
206 node.data.push(f.clone());
207 } else { let mut g = node.data.clone();
220 g.push(f.clone());
221
222 node.subdivide(|rect_ch| prune(g.as_slice(), rect_ch));
223 }
231 TraverseCommand::Skip
233 });
234
235 change_exists.load(Ordering::SeqCst)
236 }
237
238 pub unsafe fn as_mut(&self) -> &mut Self {
241 let ptr = self as *const _ as usize;
242 &mut *(ptr as *const Self as *mut _)
243 }
244}
245
246impl <_Float: Float> SDF<_Float> for ADF<_Float> {
247 fn sdf(&self, pixel: P2<_Float>) -> _Float {
248 match self.tree.pt_to_node(pixel) {
249 Some(node) => node.data.as_slice().sdf(pixel),
250 None => self.tree.data.as_slice().sdf(pixel),
251 }}}
252
253impl <_Float: Float> BoundingBox<_Float> for ADF<_Float> {
254 fn bounding_box(&self) -> Box2D<_Float, WorldSpace> {
255 Box2D::new(
256 P2::splat(_Float::zero()),
257 P2::splat(_Float::one())
258 )}}
259
260impl <_Float: Float> Debug for ADF<_Float> {
261 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
262 use humansize::{FileSize, file_size_opts as options};
263
264 let mut total_nodes = 0u64;
265 let mut total_size = 0usize;
266 let mut max_depth = 0u8;
267 self.tree.traverse(&mut |node| {
268 total_nodes += 1;
269 total_size += std::mem::size_of::<Self>()
270 + node.data.capacity() * std::mem::size_of::<Arc<dyn Fn(P2<f64>) -> f64>>();
271 max_depth = (max_depth).max(node.depth);
272 Ok(())
273 }).ok();
274 f.debug_struct("ADF")
275 .field("total_nodes", &total_nodes)
276 .field("max_depth", &max_depth)
277 .field("size", &total_size.file_size(options::BINARY).unwrap())
278 .finish()
279 }
280}