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::{f32::Affine3A, IVec3, Mat3, Vec2, Vec3, Vec3A};
use ilattice::prelude::*;
use rayon::prelude::*;
use std::collections::HashMap;
use std::{borrow::Borrow, time};
const UN_PADDED_CHUNK_SIDE: u32 = 14_u32;
type PaddedChunkShape = fast_surface_nets::ndshape::ConstShape3u32<
{ UN_PADDED_CHUNK_SIDE + 2 },
{ UN_PADDED_CHUNK_SIDE + 2 },
{ UN_PADDED_CHUNK_SIDE + 2 },
>;
const DEFAULT_SDF_VALUE: f32 = 999.0;
type Extent3i = Extent<IVec3>;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Copy, Clone)]
pub(crate) enum Plane {
XY,
XZ,
YZ,
}
struct UnpackedModel {
vertices: Vec<(Vec2, f32)>,
edges: Vec<(u32, u32)>,
aabb: Extent<Vec3A>,
world_orientation: Option<PB_Matrix4x432>,
model_name: String,
}
#[allow(clippy::type_complexity)]
fn parse_input_pb_model(
mut a_command: PB_Command,
cmd_arg_radius_dimension: Plane,
) -> Result<UnpackedModel, TBError> {
let obj = a_command.models32.pop().ok_or_else(|| {
TBError::InvalidInputData("Model did not contain any data (using model32)".to_string())
})?;
if obj.vertices.len() >= u32::MAX as usize {
return Err(TBError::Overflow(format!(
"Input data contains too many vertices. {}",
obj.vertices.len()
)));
}
let mut aabb: Option<Extent<Vec3A>> = None;
let vertices: Vec<_> = obj
.vertices
.into_iter()
.map(|vertex| {
let (point2, radius) = match cmd_arg_radius_dimension {
Plane::YZ => (Vec2::new(vertex.y, vertex.z), vertex.x),
Plane::XZ => (Vec2::new(vertex.x, vertex.z), vertex.y),
Plane::XY => (Vec2::new(vertex.x, vertex.y), vertex.z),
};
let v_aabb =
Extent::from_min_and_shape(Vec3A::new(point2.x, point2.y, 0.0), Vec3A::splat(0.0))
.padded(radius);
aabb = if let Some(aabb) = aabb {
Some(aabb.bound_union(&v_aabb))
} else {
Some(v_aabb)
};
(point2, radius)
})
.collect();
let mut edges = Vec::<(u32, u32)>::with_capacity(vertices.len() + 100);
for face in obj.faces.into_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(UnpackedModel {
vertices,
edges,
aabb: aabb.unwrap(),
world_orientation: obj.world_orientation,
model_name: obj.name,
})
}
#[allow(clippy::many_single_char_names)]
fn build_voxel(
divisions: f32,
vertices: Vec<(Vec2, f32)>,
edges: Vec<(u32, u32)>,
aabb: Extent<Vec3A>,
verbose: bool,
) -> Result<
(
f32, // <- voxel_size
Vec<(Vec3A, SurfaceNetsBuffer)>,
),
TBError,
> {
let max_dimension = {
let dimensions = aabb.shape;
dimensions.x.max(dimensions.y).max(dimensions.z)
};
let scale = (divisions / max_dimension) as f32;
if verbose {
println!(
"Voxelizing using divisions = {}, max dimension = {}, scale factor={} (max_dimension*scale={})",
divisions,
max_dimension,
scale,
(max_dimension as f32) * scale
);
println!();
}
let rounded_cones: Vec<(RoundedCone, Extent3i)> = edges
.into_par_iter()
.map(|(e0, e1)| {
let (v0, r0) = vertices[e0 as usize];
let (v0, r0) = (Vec2::new(v0.x, v0.y) * scale, r0 * scale);
let (v1, r1) = vertices[e1 as usize];
let (v1, r1) = (Vec2::new(v1.x, v1.y) * scale, r1 * scale);
let ex0 =
Extent::<Vec3A>::from_min_and_shape(Vec3A::new(v0.x, v0.y, 0.0), Vec3A::splat(0.0))
.padded(r0);
let ex1 =
Extent::<Vec3A>::from_min_and_shape(Vec3A::new(v1.x, v1.y, 0.0), Vec3A::splat(0.0))
.padded(r1);
let v = v1 - v0;
let _c = v0 + v * 0.5; let h = v.length();
let b = (r0 - r1) / h;
let a = (1.0 - b * b).sqrt();
let rotation = Mat3::from_rotation_z(v.angle_between(Vec2::new(0.0, 1.0)));
let translation = rotation.transform_point2(v0);
let translation = -Vec3::new(translation.x(), translation.y(), 0.0);
let m = Affine3A::from_mat3_translation(rotation, translation);
(
RoundedCone { r0, r1, h, b, a, m },
ex0.bound_union(&ex1).containing_integer_extent(),
)
})
.collect();
let chunks_extent = {
(aabb * (scale / (UN_PADDED_CHUNK_SIDE as f32)))
.padded(1.0 / (UN_PADDED_CHUNK_SIDE as f32))
.containing_integer_extent()
};
let now = time::Instant::now();
let sdf_chunks: Vec<_> = {
let un_padded_chunk_shape = IVec3::from([UN_PADDED_CHUNK_SIDE as i32; 3]);
chunks_extent
.iter3()
.par_bridge()
.filter_map(move |p| {
let un_padded_chunk_extent =
Extent3i::from_min_and_shape(p * un_padded_chunk_shape, un_padded_chunk_shape);
generate_and_process_sdf_chunk(un_padded_chunk_extent, &rounded_cones)
})
.collect()
};
if verbose {
println!(
"process_chunks() duration: {:?} generated {} chunks",
now.elapsed(),
sdf_chunks.len()
);
}
Ok((1.0 / scale, sdf_chunks))
}
struct RoundedCone {
r0: f32,
r1: f32,
h: f32,
b: f32,
a: f32,
m: Affine3A,
}
fn generate_and_process_sdf_chunk(
un_padded_chunk_extent: Extent3i,
rounded_cones: &[(RoundedCone, Extent3i)],
) -> Option<(Vec3A, SurfaceNetsBuffer)> {
let padded_chunk_extent = un_padded_chunk_extent.padded(1);
let filtered_cones: Vec<_> = rounded_cones
.iter()
.enumerate()
.filter_map(|(index, sdf)| {
if !padded_chunk_extent.intersection(sdf.1.borrow()).is_empty() {
Some(index as u32)
} else {
None
}
})
.collect();
#[cfg(not(feature = "display_chunks"))]
if filtered_cones.is_empty() {
return None;
}
let mut array = { [DEFAULT_SDF_VALUE; PaddedChunkShape::SIZE as usize] };
#[cfg(feature = "display_chunks")]
let corners: Vec<_> = un_padded_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 - un_padded_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 index in filtered_cones.iter() {
let cone = &rounded_cones[*index as usize].0;
let pwo = cone.m.transform_point3a(pwo);
let q = Vec2::new(Vec2::new(pwo.x(), pwo.z()).length(), pwo.y());
let k = q.dot(Vec2::new(-cone.b, cone.a));
let new_v = if k < 0.0 {
q.length() - cone.r0
} else if k > cone.a * cone.h {
(q - Vec2::new(0.0, cone.h)).length() - cone.r1
} else {
q.dot(Vec2::new(cone.a, cone.b)) - cone.r0
};
*v = (*v).min(new_v);
}
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],
[UN_PADDED_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)>,
cmd_arg_radius_axis: Plane,
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;
match cmd_arg_radius_axis {
Plane::XY =>
{
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)),
});
}
}
Plane::XZ =>
{
for pv in mesh_buffer.positions.iter() {
pb_vertices.push(PB_Vertex32 {
x: (voxel_size * (pv[0] + vertex_offset.x)),
y: (voxel_size * (pv[2] + vertex_offset.z)),
z: (voxel_size * (pv[1] + vertex_offset.y)),
});
}
}
Plane::YZ =>
{
for pv in mesh_buffer.positions.iter() {
pb_vertices.push(PB_Vertex32 {
x: (voxel_size * (pv[2] + vertex_offset.z)),
y: (voxel_size * (pv[0] + vertex_offset.x)),
z: (voxel_size * (pv[1] + vertex_offset.y)),
});
}
}
}
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#"___________ _____ _____ ____ ___ __
\_ _____/ ______ ____ / \ / _ \ \ \ / / ____ ___ ___ ____ | |
| ___) / ___// \ / \ / \ / /_\ \ \ \ / / __ \\ \/ // __ \| |
| \__ \___ \| | \ \ \ | \ \ / ( \_\ )\ /\ ___/_ |__
/___ / /____ \___| /____/\_ /____|__ / \ / \____//__/\_ \\___ /____/
\/ \/ \/ \/ \/ \/ \/ \/ "#
);
}
if a_command.models32.len() > 1 {
return Err(TBError::InvalidInputData(format!(
"This operation only supports one model as input:{}",
a_command.models32.len()
)));
}
let cmd_arg_radius_axis = match options
.get("RADIUS_AXIS")
.ok_or_else(|| TBError::InvalidInputData("Missing the RADIUS_AXIS parameter".to_string()))?
.as_str()
{
"XY" => Ok(Plane::XY),
"XZ" => Ok(Plane::XZ),
"YZ" => Ok(Plane::YZ),
something_else => Err(TBError::InvalidInputData(format!(
"Invalid RADIUS_AXIS parameter: {}",
something_else
))),
}?;
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..600.1).contains(&cmd_arg_divisions) {
return Err(TBError::InvalidInputData(format!(
"The valid range of DIVISIONS is [{}..{}[% :({})",
10, 600, 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!("Voxel divisions:{:?} ", cmd_arg_divisions);
println!(
"2d point axis:{:?}. Radius is the third axis",
cmd_arg_radius_axis
);
println!();
}
}
let unpacked = parse_input_pb_model(a_command, cmd_arg_radius_axis)?;
let (voxel_size, mesh) = build_voxel(
cmd_arg_divisions,
unpacked.vertices,
unpacked.edges,
unpacked.aabb,
verbose,
)?;
let packed_faces_model = build_output_bp_model(
unpacked.model_name,
unpacked.world_orientation,
voxel_size,
mesh,
cmd_arg_radius_axis,
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::default(),
models32: vec![packed_faces_model],
};
if verbose {
println!("total duration: {:?}", now.elapsed());
}
Ok(reply)
}