#![forbid(unsafe_code)]
#![allow(
clippy::enum_glob_use, // TODO: Add? Used a lot on the opcodes
)]
use macaw::Vec3;
pub use saft_sdf::*;
mod program;
pub use program::*;
mod compiler;
pub use compiler::*;
mod graph;
pub use graph::*;
mod grid3;
pub use grid3::*;
mod mesh;
pub use mesh::*;
mod marching_cubes;
pub use marching_cubes::*;
pub mod sphere_tracing;
mod trace;
pub use trace::*;
mod codegen;
pub use codegen::*;
pub use macaw::BoundingBox;
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
pub struct MeshOptions {
pub mean_resolution: f32,
pub max_resolution: f32,
pub min_resolution: f32,
}
impl MeshOptions {
pub fn low() -> Self {
Self {
mean_resolution: 32.0,
max_resolution: 64.0,
min_resolution: 8.0,
}
}
}
impl Default for MeshOptions {
fn default() -> Self {
Self {
mean_resolution: 64.0,
max_resolution: 128.0,
min_resolution: 8.0,
}
}
}
pub fn transform_positions_in_place(
mesh: &mut TriangleMesh,
world_from_grid_f: impl Fn(Vec3) -> Vec3 + Send + Sync,
) {
#[cfg(feature = "with_rayon")]
{
use rayon::prelude::*;
mesh.positions.par_iter_mut().for_each(|p| {
*p = world_from_grid_f((*p).into()).into();
});
}
#[cfg(not(feature = "with_rayon"))]
{
mesh.positions.iter_mut().for_each(|p| {
*p = world_from_grid_f((*p).into()).into();
});
}
}
pub fn gather_colors_in_place(
mesh: &mut TriangleMesh,
color_world: impl Fn(Vec3) -> Vec3 + Send + Sync,
) {
#[cfg(feature = "with_rayon")]
{
use rayon::prelude::*;
mesh.colors = mesh
.positions
.par_iter()
.map(|p| color_world(Vec3::new(p[0], p[1], p[2])).into())
.collect();
}
#[cfg(not(feature = "with_rayon"))]
{
mesh.colors = mesh
.positions
.iter()
.map(|p| color_world(Vec3::new(p[0], p[1], p[2])).into())
.collect();
}
}
pub fn mesh_from_sdf_func(
bb: &BoundingBox,
resolution: [usize; 3],
sd_world: impl Fn(Vec3) -> f32 + Send + Sync,
color_world: impl Fn(Vec3) -> Vec3 + Send + Sync,
) -> Result<TriangleMesh, Error> {
puffin::profile_function!();
use macaw::*;
let world_from_grid_scale = bb.size().x / (resolution[0] as f32 - 1.0);
let grid_from_world_scale = 1.0 / world_from_grid_scale;
let world_from_grid_f = |pos_in_grid: Vec3| bb.min + world_from_grid_scale * pos_in_grid;
let world_from_grid_i = |pos_in_grid: Index3| {
let pos_in_grid = Vec3::new(
pos_in_grid[0] as f32,
pos_in_grid[1] as f32,
pos_in_grid[2] as f32,
);
world_from_grid_f(pos_in_grid)
};
let sd_in_grid = |pos_in_grid| {
let pos_in_world = world_from_grid_i(pos_in_grid);
grid_from_world_scale * sd_world(pos_in_world)
};
let mut grid = Grid3::<f32>::new(resolution);
grid.set_truncated(sd_in_grid, 2.0);
if !grid.data()[grid.data().len() / 2].is_finite() {
return Err(Error::EvaluatedToNaN);
}
let mut mesh = grid.marching_cubes();
transform_positions_in_place(&mut mesh, world_from_grid_f);
gather_colors_in_place(&mut mesh, color_world);
Ok(mesh)
}
pub fn mesh_from_sdf_program(
program: &Program,
bb: &BoundingBox,
resolution: [usize; 3],
) -> Result<TriangleMesh, Error> {
let color_func = |pos_in_world| {
let mut rgbd_context = Interpreter::new_context(&program.opcodes, &program.constants);
Interpreter::<RgbWithDistance>::interpret(&mut rgbd_context, pos_in_world)
.unwrap()
.material()
.rgb()
};
let d_func = |pos_in_world| {
let mut d_context = Interpreter::new_context(&program.opcodes, &program.constants);
Interpreter::<f32>::interpret(&mut d_context, pos_in_world)
.unwrap()
.distance()
};
mesh_from_sdf_func(bb, resolution, d_func, color_func)
}
pub fn mesh_from_sdf(
graph: &Graph,
node: NodeId,
options: MeshOptions,
) -> Result<TriangleMesh, Error> {
puffin::profile_function!();
let (bb, resolution) = sdf_bb_and_resolution(graph.bounding_box(node), options);
let program = compile(graph, node);
mesh_from_sdf_program(&program, &bb, resolution)
}
pub fn sdf_bb_and_resolution(bb: BoundingBox, options: MeshOptions) -> (BoundingBox, [usize; 3]) {
assert!(bb.is_finite(), "Bad saft bounding box: {:?}", bb);
assert!(bb.volume() > 0.0, "Bad saft bounding box: {:?}", bb);
let grid_padding = 1.0;
let grid_from_world_scale = options.mean_resolution / bb.volume().cbrt();
let padding = grid_padding / grid_from_world_scale;
let bb = bb.expanded(Vec3::splat(padding));
let grid_from_world_scale = options.mean_resolution / bb.volume().cbrt();
let resolution = [
grid_from_world_scale * bb.size().x,
grid_from_world_scale * bb.size().y,
grid_from_world_scale * bb.size().z,
];
let max_side = resolution[0].max(resolution[1]).max(resolution[2]);
let max_factor = if max_side > options.max_resolution {
options.max_resolution / max_side
} else {
1.0
};
let min_side = resolution[0].min(resolution[1]).min(resolution[2]);
let min_factor = if min_side < options.min_resolution {
options.min_resolution / min_side
} else {
1.0
};
let factor = min_factor.max(max_factor);
let grid_resolution = [
(factor * resolution[0]).ceil() as usize,
(factor * resolution[1]).ceil() as usize,
(factor * resolution[2]).ceil() as usize,
];
(bb, grid_resolution)
}
pub fn surface_distance_to(graph: &Graph, node: NodeId, pos: Vec3) -> f32 {
let program = compile(graph, node);
let mut d_context = Interpreter::new_context(&program.opcodes, &program.constants);
Interpreter::<f32>::interpret(&mut d_context, pos).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let mut graph = Graph::default();
let node = graph.sphere(Vec3::default(), 1.0);
let node2 = graph.sphere(Vec3::default(), 0.5);
let node3 = graph.op_translate(node2, -Vec3::X);
let node4 = graph.op_translate(node2, Vec3::X);
let node5 = graph.op_translate(node2, Vec3::Y);
let union_node = graph.op_union_multi(vec![node, node3, node4, node5]);
let program = compile(&graph, union_node);
let mut grid = Grid3::<f32>::new([64, 64, 64]);
grid.set_truncated_sync(
|pos| {
let mut context = Interpreter::new_context(&program.opcodes, &program.constants);
let pos = Vec3::new(pos[0] as f32, pos[1] as f32, pos[2] as f32);
Interpreter::<f32>::interpret(&mut context, pos).unwrap()
},
2.0,
);
let mut grid2 = Grid3::new([64, 64, 64]);
grid2.set_truncated(
|pos| {
let pos = Vec3::new(pos[0] as f32, pos[1] as f32, pos[2] as f32);
sd_op_union(
sd_sphere(pos, Vec3::default(), 1.0),
sd_op_union(
sd_sphere(pos + Vec3::X, Vec3::default(), 0.5),
sd_op_union(
sd_sphere(pos - Vec3::X, Vec3::default(), 0.5),
sd_sphere(pos - Vec3::Y, Vec3::default(), 0.5),
),
),
)
},
2.0,
);
assert!(grid == grid2);
}
}