use crate::{
type_utils::*, PB_Command, PB_Face32, PB_KeyValuePair, PB_Matrix4x432, PB_Model32, PB_Reply,
PB_Vertex32, TBError,
};
use fast_surface_nets::{ndshape::ConstShape, surface_nets, SurfaceNetsBuffer};
use ilattice::glam::{IVec3, Vec3A};
use ilattice::prelude::*;
use rayon::prelude::*;
use std::collections::HashMap;
use std::time;
const UNPADDED_CHUNK_SIDE: u32 = 14_u32;
type PaddedChunkShape = fast_surface_nets::ndshape::ConstShape3u32<
{ UNPADDED_CHUNK_SIDE + 2 },
{ UNPADDED_CHUNK_SIDE + 2 },
{ UNPADDED_CHUNK_SIDE + 2 },
>;
const DEFAULT_SDF_VALUE: f32 = 999.0;
type Extent3i = Extent<IVec3>;
#[allow(clippy::type_complexity)]
fn parse_input_pb_model(
obj: &PB_Model32,
) -> Result<(Vec<(u32, u32)>, linestring::linestring_3d::Aabb3<f32>), TBError> {
let mut aabb = linestring::linestring_3d::Aabb3::<f32>::default();
if obj.vertices.len() >= u32::MAX as usize {
return Err(TBError::Overflow(format!(
"Input data contains too many vertices. {}",
obj.vertices.len()
)));
}
for vertex in obj.vertices.iter() {
aabb.update_point(cgmath::Point3 {
x: vertex.x,
y: vertex.y,
z: vertex.z,
});
}
let mut edges = Vec::<(u32, u32)>::with_capacity(obj.vertices.len() + 100);
for face in obj.faces.iter() {
if face.vertices.len() > 2 {
return Err(TBError::ModelContainsFaces("Model can't contain any faces, only edges. Use the 2d_outline tool to remove faces".to_string()));
}
if face.vertices.len() < 2 {
return Err(TBError::InvalidInputData(
"Edge containing none or only one vertex".to_string(),
));
};
edges.push((
*face.vertices.first().unwrap(),
*face.vertices.last().unwrap(),
));
}
Ok((edges, aabb))
}
fn build_voxel(
radius_multiplier: f32,
divisions: f32,
vertices: &[PB_Vertex32],
edges: Vec<(u32, u32)>,
aabb: linestring::linestring_3d::Aabb3<f32>,
verbose: bool,
) -> Result<
(
f32, // <- voxel_size
Vec<(Vec3A, SurfaceNetsBuffer)>,
),
TBError,
> {
let dimensions = aabb.get_high().unwrap() - aabb.get_low().unwrap();
let max_dimension = dimensions.x.max(dimensions.y).max(dimensions.z);
let radius = max_dimension * radius_multiplier; let scale = (divisions / max_dimension) as f32;
if verbose {
println!(
"Voxelizing using tube radius. {} = {}*{}*{}",
radius, max_dimension, radius_multiplier, scale
);
println!(
"Voxelizing using divisions = {}, max dimension = {}, scale factor={} (max_dimension*scale={})",
divisions,
max_dimension,
scale,
(max_dimension as f32) * scale
);
println!();
}
let vertices: Vec<Vec3A> = vertices
.iter()
.map(|v| Vec3A::new(v.x, v.y, v.z) * scale)
.collect();
let chunks_extent = {
let min_p = aabb.get_low().unwrap().to_float();
let max_p = aabb.get_high().unwrap().to_float();
(Extent::<Vec3A>::from_min_and_lub(min_p, max_p).padded(radius)
* (scale / (UNPADDED_CHUNK_SIDE as f32)))
.padded(1.0 / (UNPADDED_CHUNK_SIDE as f32))
.containing_integer_extent()
};
let now = time::Instant::now();
let sdf_chunks: Vec<_> = {
let radius = radius * scale;
let unpadded_chunk_shape = IVec3::from([UNPADDED_CHUNK_SIDE as i32; 3]);
chunks_extent
.iter3()
.par_bridge()
.filter_map(move |p| {
let unpadded_chunk_extent =
Extent3i::from_min_and_shape(p * unpadded_chunk_shape, unpadded_chunk_shape);
generate_and_process_sdf_chunk(unpadded_chunk_extent, &vertices, &edges, radius)
})
.collect()
};
if verbose {
println!(
"process_chunks() duration: {:?} generated {} chunks",
now.elapsed(),
sdf_chunks.len()
);
}
Ok((1.0 / scale, sdf_chunks))
}
fn generate_and_process_sdf_chunk(
unpadded_chunk_extent: Extent3i,
vertices: &[Vec3A],
edges: &[(u32, u32)],
thickness: f32,
) -> Option<(Vec3A, SurfaceNetsBuffer)> {
let padded_chunk_extent = unpadded_chunk_extent.padded(1);
let filtered_edges: Vec<_> = edges
.iter()
.filter_map(|(e0, e1)| {
let (e0, e1) = (*e0 as usize, *e1 as usize);
let tube_extent = Extent::from_min_and_lub(
vertices[e0].min(vertices[e1]) - Vec3A::from([thickness; 3]),
vertices[e0].max(vertices[e1]) + Vec3A::from([thickness; 3]),
)
.containing_integer_extent();
if !padded_chunk_extent.intersection(&tube_extent).is_empty() {
Some((e0, e1))
} else {
None
}
})
.collect();
#[cfg(not(feature = "display_chunks"))]
if filtered_edges.is_empty() {
return None;
}
let mut array = { [DEFAULT_SDF_VALUE; PaddedChunkShape::SIZE as usize] };
#[cfg(feature = "display_chunks")]
let corners: Vec<_> = unpadded_chunk_extent
.corners3()
.iter()
.map(|p| p.to_float())
.collect();
let mut some_neg_or_zero_found = false;
let mut some_pos_found = false;
for pwo in padded_chunk_extent.iter3() {
let v = {
let p = pwo - unpadded_chunk_extent.minimum + 1;
&mut array[PaddedChunkShape::linearize([p.x as u32, p.y as u32, p.z as u32]) as usize]
};
let pwo = pwo.to_float();
#[cfg(feature = "display_chunks")]
{
let mut x = *v;
for c in corners.iter() {
x = x.min(c.distance(pwo) - 1.);
}
*v = (*v).min(x);
}
for (from_v, to_v) in filtered_edges
.iter()
.map(|(e0, e1)| (vertices[*e0], vertices[*e1]))
{
let pa = pwo - from_v;
let ba = to_v - from_v;
let t = pa.dot(ba) / ba.dot(ba);
let h = t.clamp(0.0, 1.0);
*v = (*v).min((pa - (ba * h)).length() - thickness);
}
if *v > 0.0 {
some_pos_found = true;
} else {
some_neg_or_zero_found = true;
}
}
if some_pos_found && some_neg_or_zero_found {
let mut sn_buffer = SurfaceNetsBuffer::default();
surface_nets(
&array,
&PaddedChunkShape {},
[0; 3],
[UNPADDED_CHUNK_SIDE + 1; 3],
&mut sn_buffer,
);
if sn_buffer.positions.is_empty() {
None
} else {
Some((padded_chunk_extent.minimum.to_float(), sn_buffer))
}
} else {
None
}
}
pub(crate) fn build_output_bp_model(
pb_model_name: String,
pb_world: Option<PB_Matrix4x432>,
voxel_size: f32,
mesh_buffers: Vec<(Vec3A, SurfaceNetsBuffer)>,
verbose: bool,
) -> Result<PB_Model32, TBError> {
let now = time::Instant::now();
let (mut pb_vertices, mut pb_faces) = {
let (vertex_capacity, face_capacity) = mesh_buffers
.iter()
.fold((0_usize, 0_usize), |(v, f), chunk| {
(v + chunk.1.positions.len(), f + chunk.1.indices.len())
});
if vertex_capacity >= u32::MAX as usize {
return Err(TBError::Overflow(format!("Generated mesh contains too many vertices to be referenced by u32: {}. Reduce the resolution.", vertex_capacity)));
}
if face_capacity >= u32::MAX as usize {
return Err(TBError::Overflow(format!("Generated mesh contains too many faces to be referenced by u32: {}. Reduce the resolution.", vertex_capacity)));
}
(
Vec::with_capacity(vertex_capacity),
Vec::with_capacity(face_capacity),
)
};
for (vertex_offset, mesh_buffer) in mesh_buffers.iter() {
let indices_offset = pb_vertices.len() as u32;
for pv in mesh_buffer.positions.iter() {
pb_vertices.push(PB_Vertex32 {
x: (voxel_size * (pv[0] + vertex_offset.x)),
y: (voxel_size * (pv[1] + vertex_offset.y)),
z: (voxel_size * (pv[2] + vertex_offset.z)),
});
}
for vertex_id in mesh_buffer.indices.iter() {
pb_faces.push(*vertex_id + indices_offset);
}
}
if verbose {
println!(
"Vertex return model packaging duration: {:?}",
now.elapsed()
);
}
Ok(PB_Model32 {
name: pb_model_name,
world_orientation: pb_world,
vertices: pb_vertices,
faces: vec![PB_Face32 { vertices: pb_faces }],
})
}
pub(crate) fn command(
a_command: PB_Command,
options: HashMap<String, String>,
verbose: bool,
) -> Result<PB_Reply, TBError> {
let now = time::Instant::now();
if verbose {
println!(
r#"___________ ____ ____ .__
\_ _____/_____ ___\ \ / /______ ___ ____ | |
| __)/ ___// \ Y / _ \ \/ // __ \| |
| \ \___ \| | \ ( <_> > <\ ___/| |__
\___ //____ >___| /\___/ \____/__/\_ \\___ >____/
\/ \/ \/ \/ \/"#
);
}
if a_command.models32.len() > 1 {
return Err(TBError::InvalidInputData(format!(
"This operation only supports one model as input:{}",
a_command.models32.len()
)));
}
if a_command.models32.is_empty() {
return Err(TBError::InvalidInputData(
"Model did not contain any data (using model32)".to_string(),
));
}
let cmd_arg_radius_multiplier = options
.get("RADIUS")
.ok_or_else(|| TBError::InvalidInputData("Missing the RADIUS parameter".to_string()))?
.parse::<f32>()
.map_err(|_| {
TBError::InvalidInputData("Could not parse the RADIUS parameter".to_string())
})?
/ 100.0;
let cmd_arg_divisions = options
.get("DIVISIONS")
.ok_or_else(|| TBError::InvalidInputData("Missing the DIVISIONS parameter".to_string()))?
.parse::<f32>()
.map_err(|_| {
TBError::InvalidInputData("Could not parse the DIVISIONS parameter".to_string())
})?;
if !(9.9..400.1).contains(&cmd_arg_divisions) {
return Err(TBError::InvalidInputData(format!(
"The valid range of DIVISIONS is [{}..{}[% :({})",
10, 400, cmd_arg_divisions
)));
}
if verbose {
for model in a_command.models32.iter() {
println!("model.name:{:?}, ", model.name);
println!("model.vertices:{:?}, ", model.vertices.len());
println!("model.faces:{:?}, ", model.faces.len());
println!(
"model.world_orientation:{:?}, ",
model.world_orientation.as_ref().map_or(0, |_| 16)
);
println!("Tube radius:{:?} multiplier ", cmd_arg_radius_multiplier);
println!("Voxel divisions:{:?} ", cmd_arg_divisions);
println!();
}
}
let (edges, aabb) = parse_input_pb_model(&a_command.models32[0])?;
let (voxel_size, mesh) = build_voxel(
cmd_arg_radius_multiplier,
cmd_arg_divisions,
&a_command.models32[0].vertices,
edges,
aabb,
verbose,
)?;
let packed_faces_model = build_output_bp_model(
a_command.command.clone(),
a_command.models32[0].world_orientation.clone(),
voxel_size,
mesh,
verbose,
)?;
if verbose {
println!(
"Total number of vertices: {}",
packed_faces_model.vertices.len()
);
println!(
"Total number of triangles: {}",
packed_faces_model
.faces
.iter()
.map(|x| x.vertices.len())
.sum::<usize>()
/ 3
);
}
let reply = PB_Reply {
options: vec![
PB_KeyValuePair {
key: "ONLY_EDGES".to_string(),
value: "False".to_string(),
},
PB_KeyValuePair {
key: "PACKED_FACES".to_string(),
value: "True".to_string(),
},
PB_KeyValuePair {
key: "REMOVE_DOUBLES".to_string(),
value: "True".to_string(),
},
],
models: Vec::with_capacity(0),
models32: vec![packed_faces_model],
};
if verbose {
println!("total duration: {:?}", now.elapsed());
}
Ok(reply)
}