use super::Shape;
use crate::sdf::SdfNode;
use std::sync::Arc;
#[derive(Clone)]
pub struct SdfShape {
node: Arc<SdfNode>,
bounds: (i32, i32, i32, i32, i32, i32),
}
impl SdfShape {
pub fn new(node: SdfNode) -> Option<Self> {
let b = node.bounds()?;
let bounds = (
checked_floor(b.min[0])?,
checked_floor(b.min[1])?,
checked_floor(b.min[2])?,
checked_ceil(b.max[0])?,
checked_ceil(b.max[1])?,
checked_ceil(b.max[2])?,
);
validate_bounds(bounds)?;
Some(Self {
node: Arc::new(node),
bounds,
})
}
pub fn with_bounds(node: SdfNode, min: (i32, i32, i32), max: (i32, i32, i32)) -> Option<Self> {
let bounds = (min.0, min.1, min.2, max.0, max.1, max.2);
validate_bounds(bounds)?;
Some(Self {
node: Arc::new(node),
bounds,
})
}
fn eval_center(&self, x: i32, y: i32, z: i32) -> f32 {
self.node
.eval(x as f32 + 0.5, y as f32 + 0.5, z as f32 + 0.5)
}
}
fn checked_floor(value: f32) -> Option<i32> {
checked_rounded(value, f32::floor)
}
fn checked_ceil(value: f32) -> Option<i32> {
checked_rounded(value, f32::ceil)
}
fn checked_rounded(value: f32, round: fn(f32) -> f32) -> Option<i32> {
let rounded = round(value);
if !rounded.is_finite()
|| f64::from(rounded) < f64::from(i32::MIN)
|| f64::from(rounded) > f64::from(i32::MAX)
{
return None;
}
Some(rounded as i32)
}
fn validate_bounds(bounds: (i32, i32, i32, i32, i32, i32)) -> Option<u64> {
crate::sdf::checked_sample_volume(
[bounds.0, bounds.1, bounds.2],
[bounds.3, bounds.4, bounds.5],
)
.ok()
}
impl Shape for SdfShape {
fn contains(&self, x: i32, y: i32, z: i32) -> bool {
let (x0, y0, z0, x1, y1, z1) = self.bounds;
x >= x0
&& x <= x1
&& y >= y0
&& y <= y1
&& z >= z0
&& z <= z1
&& self.eval_center(x, y, z) <= 0.0
}
fn points(&self) -> Vec<(i32, i32, i32)> {
let mut points = Vec::new();
self.for_each_point(|x, y, z| points.push((x, y, z)));
points
}
fn normal_at(&self, x: i32, y: i32, z: i32) -> (f64, f64, f64) {
let (fx, fy, fz) = (x as f32 + 0.5, y as f32 + 0.5, z as f32 + 0.5);
crate::sdf::numerical_normal(&self.node, [fx, fy, fz], 0.5)
.map(|normal| (normal[0], normal[1], normal[2]))
.unwrap_or((0.0, 1.0, 0.0))
}
fn bounds(&self) -> (i32, i32, i32, i32, i32, i32) {
self.bounds
}
fn for_each_point<F>(&self, mut f: F)
where
F: FnMut(i32, i32, i32),
{
let (x0, y0, z0, x1, y1, z1) = self.bounds;
for x in x0..=x1 {
for y in y0..=y1 {
for z in z0..=z1 {
if self.eval_center(x, y, z) <= 0.0 {
f(x, y, z);
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::{SdfShape, Shape};
use crate::sdf::SdfNode;
#[test]
fn normal_at_extreme_valid_coordinate_uses_representable_neighbors() {
let shape = SdfShape::with_bounds(
SdfNode::Plane {
normal: [1.0, 0.0, 0.0],
offset: 0.0,
},
(i32::MAX, 0, 0),
(i32::MAX, 0, 0),
)
.unwrap();
let normal = shape.normal_at(i32::MAX, 0, 0);
assert!((normal.0 - 1.0).abs() < 1e-9);
assert!(normal.1.abs() < 1e-9);
assert!(normal.2.abs() < 1e-9);
}
}