saft/
lib.rs

1//! Signed distance field function compiler/interpreter/discretizer/mesher.
2
3// crate-specific exceptions:
4#![forbid(unsafe_code)]
5#![allow(
6    clippy::enum_glob_use,      // TODO: Add? Used a lot on the opcodes
7)]
8
9use macaw::Vec3;
10
11pub use saft_sdf::*;
12
13mod program;
14pub use program::*;
15
16mod compiler;
17pub use compiler::*;
18
19mod graph;
20pub use graph::*;
21
22mod grid3;
23pub use grid3::*;
24
25mod mesh;
26pub use mesh::*;
27
28mod marching_cubes;
29pub use marching_cubes::*;
30
31pub mod sphere_tracing;
32
33mod trace;
34pub use trace::*;
35
36mod codegen;
37pub use codegen::*;
38
39pub use macaw::BoundingBox;
40#[derive(Clone, Copy, Debug, PartialEq)]
41#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
42#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
43pub struct MeshOptions {
44    /// Desired mean resolution on the axis.
45    ///
46    /// the total number of grid points will be close to resolution ^ 3
47    pub mean_resolution: f32,
48
49    /// Trying to fit the number of cubes to the mean resolution can lead to some
50    /// extreme cases if the box is very narrow. Use these parameters to clamp
51    /// the resolution to desired "sane" bounds.
52    pub max_resolution: f32,
53    pub min_resolution: f32,
54}
55
56impl MeshOptions {
57    pub fn low() -> Self {
58        Self {
59            mean_resolution: 32.0,
60            max_resolution: 64.0,
61            min_resolution: 8.0,
62        }
63    }
64}
65
66impl Default for MeshOptions {
67    fn default() -> Self {
68        Self {
69            mean_resolution: 64.0,
70            max_resolution: 128.0,
71            min_resolution: 8.0,
72        }
73    }
74}
75
76pub fn transform_positions_in_place(
77    mesh: &mut TriangleMesh,
78    world_from_grid_f: impl Fn(Vec3) -> Vec3 + Send + Sync,
79) {
80    #[cfg(feature = "with_rayon")]
81    {
82        use rayon::prelude::*;
83
84        mesh.positions.par_iter_mut().for_each(|p| {
85            // transform to world:
86            *p = world_from_grid_f((*p).into()).into();
87        });
88    }
89
90    #[cfg(not(feature = "with_rayon"))]
91    {
92        mesh.positions.iter_mut().for_each(|p| {
93            // transform to world:
94            *p = world_from_grid_f((*p).into()).into();
95        });
96    }
97}
98
99pub fn gather_colors_in_place(
100    mesh: &mut TriangleMesh,
101    color_world: impl Fn(Vec3) -> Vec3 + Send + Sync,
102) {
103    #[cfg(feature = "with_rayon")]
104    {
105        use rayon::prelude::*;
106
107        mesh.colors = mesh
108            .positions
109            .par_iter()
110            .map(|p| color_world(Vec3::new(p[0], p[1], p[2])).into())
111            .collect();
112    }
113
114    #[cfg(not(feature = "with_rayon"))]
115    {
116        mesh.colors = mesh
117            .positions
118            .iter()
119            .map(|p| color_world(Vec3::new(p[0], p[1], p[2])).into())
120            .collect();
121    }
122}
123
124pub fn mesh_from_sdf_func(
125    bb: &BoundingBox,
126    resolution: [usize; 3],
127    sd_world: impl Fn(Vec3) -> f32 + Send + Sync,
128    color_world: impl Fn(Vec3) -> Vec3 + Send + Sync,
129) -> Result<TriangleMesh, Error> {
130    use macaw::*;
131
132    let world_from_grid_scale = bb.size().x / (resolution[0] as f32 - 1.0);
133    let grid_from_world_scale = 1.0 / world_from_grid_scale;
134
135    let world_from_grid_f = |pos_in_grid: Vec3| bb.min + world_from_grid_scale * pos_in_grid;
136
137    let world_from_grid_i = |pos_in_grid: Index3| {
138        let pos_in_grid = Vec3::new(
139            pos_in_grid[0] as f32,
140            pos_in_grid[1] as f32,
141            pos_in_grid[2] as f32,
142        );
143        world_from_grid_f(pos_in_grid)
144    };
145
146    let sd_in_grid = |pos_in_grid| {
147        let pos_in_world = world_from_grid_i(pos_in_grid);
148        grid_from_world_scale * sd_world(pos_in_world)
149    };
150
151    let mut grid = Grid3::<f32>::new(resolution);
152    grid.set_truncated(sd_in_grid, 2.0);
153
154    // Check a single sample for NaN. Often a NaN will end up in the whole grid, so this'll catch it.
155    if !grid.data()[grid.data().len() / 2].is_finite() {
156        return Err(Error::EvaluatedToNaN);
157    }
158
159    let mut mesh = grid.marching_cubes();
160
161    transform_positions_in_place(&mut mesh, world_from_grid_f);
162    gather_colors_in_place(&mut mesh, color_world);
163
164    Ok(mesh)
165}
166
167pub fn mesh_from_sdf_program(
168    program: &Program,
169    bb: &BoundingBox,
170    resolution: [usize; 3],
171) -> Result<TriangleMesh, Error> {
172    let color_func = |pos_in_world| {
173        let mut rgbd_context = Interpreter::new_context(&program.opcodes, &program.constants);
174        Interpreter::<RgbWithDistance>::interpret(&mut rgbd_context, pos_in_world)
175            .unwrap()
176            .material()
177            .rgb()
178    };
179
180    let d_func = |pos_in_world| {
181        let mut d_context = Interpreter::new_context(&program.opcodes, &program.constants);
182        Interpreter::<f32>::interpret(&mut d_context, pos_in_world)
183            .unwrap()
184            .distance()
185    };
186
187    mesh_from_sdf_func(bb, resolution, d_func, color_func)
188}
189
190pub fn mesh_from_sdf(
191    graph: &Graph,
192    node: NodeId,
193    options: MeshOptions,
194) -> Result<TriangleMesh, Error> {
195    let (bb, resolution) = sdf_bb_and_resolution(graph.bounding_box(node), options);
196    let program = compile(graph, node);
197
198    mesh_from_sdf_program(&program, &bb, resolution)
199}
200
201/// Pick a good expanded bounding box and grid size from the given tight bounding box
202pub fn sdf_bb_and_resolution(bb: BoundingBox, options: MeshOptions) -> (BoundingBox, [usize; 3]) {
203    assert!(bb.is_finite(), "Bad saft bounding box: {:?}", bb);
204    assert!(bb.volume() > 0.0, "Bad saft bounding box: {:?}", bb);
205
206    // Add at least this many grid points on each side
207    let grid_padding = 1.0;
208
209    // preliminary so we can pad
210    let grid_from_world_scale = options.mean_resolution / bb.volume().cbrt();
211    let padding = grid_padding / grid_from_world_scale;
212    let bb = bb.expanded(Vec3::splat(padding));
213
214    // now actual:
215    let grid_from_world_scale = options.mean_resolution / bb.volume().cbrt();
216
217    let resolution = [
218        grid_from_world_scale * bb.size().x,
219        grid_from_world_scale * bb.size().y,
220        grid_from_world_scale * bb.size().z,
221    ];
222
223    let max_side = resolution[0].max(resolution[1]).max(resolution[2]);
224    let max_factor = if max_side > options.max_resolution {
225        options.max_resolution / max_side
226    } else {
227        1.0
228    };
229    let min_side = resolution[0].min(resolution[1]).min(resolution[2]);
230    let min_factor = if min_side < options.min_resolution {
231        options.min_resolution / min_side
232    } else {
233        1.0
234    };
235
236    // Let the minimum overrule the maximum.
237    let factor = min_factor.max(max_factor);
238
239    let grid_resolution = [
240        (factor * resolution[0]).ceil() as usize,
241        (factor * resolution[1]).ceil() as usize,
242        (factor * resolution[2]).ceil() as usize,
243    ];
244
245    /*
246    // Useful for debugging the above calculations. Turns out it's not as intuitive as expected to get it right.
247    println!(
248        "max_res: {} min_res: {} max_factor: {} min_factor: {} original_resolution: {:?} grid_resolution: {:?}",
249        options.max_resolution, options.min_resolution, max_factor, min_factor, resolution, grid_resolution
250    );
251    */
252
253    (bb, grid_resolution)
254}
255
256pub fn surface_distance_to(graph: &Graph, node: NodeId, pos: Vec3) -> f32 {
257    let program = compile(graph, node);
258    let mut d_context = Interpreter::new_context(&program.opcodes, &program.constants);
259    Interpreter::<f32>::interpret(&mut d_context, pos).unwrap()
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn it_works() {
268        let mut graph = Graph::default();
269        let node = graph.sphere(Vec3::default(), 1.0);
270
271        let node2 = graph.sphere(Vec3::default(), 0.5);
272        let node3 = graph.op_translate(node2, -Vec3::X);
273        let node4 = graph.op_translate(node2, Vec3::X);
274        let node5 = graph.op_translate(node2, Vec3::Y);
275
276        let union_node = graph.op_union_multi(vec![node, node3, node4, node5]);
277
278        let program = compile(&graph, union_node);
279        let mut grid = Grid3::<f32>::new([64, 64, 64]);
280        grid.set_truncated_sync(
281            |pos| {
282                let mut context = Interpreter::new_context(&program.opcodes, &program.constants);
283
284                let pos = Vec3::new(pos[0] as f32, pos[1] as f32, pos[2] as f32);
285                Interpreter::<f32>::interpret(&mut context, pos).unwrap()
286            },
287            2.0,
288        );
289
290        let mut grid2 = Grid3::new([64, 64, 64]);
291        grid2.set_truncated(
292            |pos| {
293                let pos = Vec3::new(pos[0] as f32, pos[1] as f32, pos[2] as f32);
294                sd_op_union(
295                    sd_sphere(pos, Vec3::default(), 1.0),
296                    sd_op_union(
297                        sd_sphere(pos + Vec3::X, Vec3::default(), 0.5),
298                        sd_op_union(
299                            sd_sphere(pos - Vec3::X, Vec3::default(), 0.5),
300                            sd_sphere(pos - Vec3::Y, Vec3::default(), 0.5),
301                        ),
302                    ),
303                )
304            },
305            2.0,
306        );
307
308        // grid and grid2 should be equal.
309        assert!(grid == grid2);
310    }
311}