use crate::{
type_utils, PB_Command, PB_Face, PB_KeyValuePair, PB_Model, PB_Reply, PB_Vertex, TBError,
};
use boostvoronoi as BV;
use cgmath::{Angle, EuclideanSpace, SquareMatrix, Transform, UlpsEq};
use itertools::Itertools;
use linestring::{linestring_3d, linestring_3d::Plane};
use rayon::prelude::*;
use std::collections::HashMap;
#[inline(always)]
fn make_edge_key(v0: usize, v1: usize) -> (usize, usize) {
if v0 < v1 {
(v0, v1)
} else {
(v1, v0)
}
}
#[inline(always)]
fn transmute_to_u64(a: &cgmath::Point3<f64>) -> (u64, u64) {
(a.x.to_bits(), a.y.to_bits())
}
#[allow(clippy::type_complexity)]
fn parse_input(
input_pb_model: &PB_Model,
) -> Result<
(
ahash::AHashSet<(usize, usize)>,
Vec<cgmath::Point3<f64>>,
linestring_3d::Aabb3<f64>,
),
TBError,
> {
let mut aabb = linestring_3d::Aabb3::<f64>::default();
for v in input_pb_model.vertices.iter() {
aabb.update_point(cgmath::Point3::new(v.x as f64, v.y as f64, v.z as f64))
}
let plane =
Plane::get_plane_relaxed(aabb, super::EPSILON, f64::default_max_ulps()).ok_or_else(|| {
let aabbe_d = aabb.get_high().unwrap() - aabb.get_low().unwrap();
let aabbe_c = (aabb.get_high().unwrap().to_vec() + aabb.get_low().unwrap().to_vec())/2.0;
TBError::InputNotPLane(format!(
"Input data not in one plane and/or plane not intersecting origin: Δ({},{},{}) C({},{},{})",
aabbe_d.x, aabbe_d.y, aabbe_d.z,aabbe_c.x, aabbe_c.y, aabbe_c.z
))
})?;
println!("centerline: data was in plane:{:?} aabb:{:?}", plane, aabb);
let mut edge_set = ahash::AHashSet::<(usize, usize)>::default();
for face in input_pb_model.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(
"Points are not supported for this operation".to_string(),
));
}
let v0 = *face.vertices.get(0).unwrap() as usize;
let v1 = *face.vertices.get(1).unwrap() as usize;
let key = make_edge_key(v0, v1);
let _ = edge_set.insert(key);
}
for p in input_pb_model.vertices.iter() {
if !p.x.is_finite() || !p.y.is_finite() || !p.z.is_finite() {
return Err(TBError::InvalidInputData(format!(
"Only valid coordinates are allowed ({},{},{})",
p.x, p.y, p.z
)));
}
}
let p: Vec<cgmath::Point3<f64>> = input_pb_model
.vertices
.iter()
.map(|v| cgmath::Point3 {
x: v.x,
y: v.y,
z: v.z,
})
.collect();
Ok((edge_set, p, aabb))
}
fn build_output_bp_model(
a_command: &PB_Command,
shapes: Vec<(
linestring::linestring_2d::LineStringSet2<f64>,
centerline::Centerline<i64, f64>,
)>,
cmd_arg_weld: bool,
inverted_transform: cgmath::Matrix4<f64>,
cmd_arg_negative_radius: bool,
cmd_arg_keep_input: bool,
) -> Result<PB_Model, TBError> {
let transform_point = |point: cgmath::Point3<f64>| -> cgmath::Point3<f64> {
if cmd_arg_negative_radius {
inverted_transform.transform_point(point)
} else {
let point = inverted_transform.transform_point(point);
cgmath::Point3 {
x: point.x,
y: point.y,
z: -point.z,
}
}
};
let input_pb_model = &a_command.models[0];
let estimated_capacity: usize = (shapes
.iter()
.map::<usize, _>(|(ls, cent)| {
ls.set().iter().map(|ls| ls.points().len()).sum::<usize>()
+ cent.lines.iter().flatten().count()
+ cent
.line_strings
.iter()
.flatten()
.map(|ls| ls.len())
.sum::<usize>()
})
.sum::<usize>()
* 5)
/ 4;
let mut output_pb_model_vertices = Vec::<PB_Vertex>::with_capacity(estimated_capacity);
let mut output_pb_model_faces = Vec::<PB_Face>::with_capacity(estimated_capacity);
let mut v_map = ahash::AHashMap::<(u64, u64), usize>::default();
for shape in shapes.into_iter() {
if cmd_arg_keep_input {
for linestring in shape.0.set().iter() {
if linestring.points().len() < 2 {
return Err(TBError::InternalError(
"Linestring with less than 2 points found".to_string(),
));
}
let v0 = type_utils::xy_to_3d(linestring.points().first().unwrap());
let v1 = type_utils::xy_to_3d(linestring.points().last().unwrap());
let v0_key = transmute_to_u64(&v0);
let v0_index = *v_map.entry(v0_key).or_insert_with(|| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices
.push(PB_Vertex::from(inverted_transform.transform_point(v0)));
new_index
});
let v1_key = transmute_to_u64(&v1);
let v1_index = *v_map.entry(v1_key).or_insert_with(|| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices
.push(PB_Vertex::from(inverted_transform.transform_point(v1)));
new_index
});
let vertex_index_iterator = Some(v0_index)
.into_iter()
.chain(
linestring
.points()
.iter()
.skip(1)
.take(linestring.points().len() - 2)
.map(|p| {
let v2 = type_utils::xy_to_3d(p);
let v2_key = transmute_to_u64(&v2);
let v2_index = *v_map.entry(v2_key).or_insert_with(|| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices.push(PB_Vertex::from(
inverted_transform.transform_point(v2),
));
new_index
});
v2_index
}),
)
.chain(Some(v1_index).into_iter());
for p in vertex_index_iterator.tuple_windows::<(_, _)>() {
output_pb_model_faces.push(PB_Face {
vertices: vec![p.0 as u64, p.1 as u64],
});
}
}
}
if !cmd_arg_weld {
v_map.clear()
}
for line in shape.1.lines.iter().flatten() {
let v0 = line.start;
let v1 = line.end;
if v0 == v1 {
continue;
}
let v0_key = transmute_to_u64(&v0);
let v0_index = *v_map.entry(v0_key).or_insert_with(|| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices.push(PB_Vertex::from(transform_point(v0)));
new_index
});
let v1_key = transmute_to_u64(&v1);
let v1_index = *v_map.entry(v1_key).or_insert_with(|| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices.push(PB_Vertex::from(transform_point(v1)));
new_index
});
if v0_index == v1_index {
println!(
"v0_index==v1_index, but v0!=v1 v0:{:?} v1:{:?} v0_index:{:?} v1_index:{:?}",
v0, v1, v0_index, v1_index
);
continue;
}
output_pb_model_faces.push(PB_Face {
vertices: vec![v0_index as u64, v1_index as u64],
});
}
for linestring in shape.1.line_strings.iter().flatten() {
if linestring.points().len() < 2 {
return Err(TBError::InternalError(
"Linestring with less than 2 points found".to_string(),
));
}
let v0 = linestring.points().first().unwrap();
let v1 = linestring.points().last().unwrap();
let v0_key = transmute_to_u64(v0);
let v0_index = *v_map.entry(v0_key).or_insert_with(|| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices.push(PB_Vertex::from(transform_point(*v0)));
new_index
});
let v1_key = transmute_to_u64(v1);
let v1_index = *v_map.entry(v1_key).or_insert_with(|| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices.push(PB_Vertex::from(transform_point(*v1)));
new_index
});
let vertex_index_iterator = Some(v0_index)
.into_iter()
.chain(
linestring
.points()
.iter()
.skip(1)
.take(linestring.points().len() - 2)
.map(|p| {
let new_index = output_pb_model_vertices.len();
output_pb_model_vertices.push(PB_Vertex::from(transform_point(*p)));
new_index
}),
)
.chain(Some(v1_index).into_iter());
for p in vertex_index_iterator.tuple_windows::<(_, _)>() {
output_pb_model_faces.push(PB_Face {
vertices: vec![p.0 as u64, p.1 as u64],
});
}
}
}
Ok(PB_Model {
name: input_pb_model.name.clone(),
world_orientation: input_pb_model.world_orientation.clone(),
vertices: output_pb_model_vertices,
faces: output_pb_model_faces,
})
}
pub(crate) fn command(
a_command: PB_Command,
options: HashMap<String, String>,
) -> Result<PB_Reply, TBError> {
println!(
r#"_________ __ .__ .__
\_ ___ \ ____ _____/ |_ ___________| | |__| ____ ____
/ \ \/_/ __ \ / \ __\/ __ \_ __ \ | | |/ \_/ __ \
\ \___\ ___/| | \ | \ ___/| | \/ |_| | | \ ___/
\______ /\___ >___| /__| \___ >__| |____/__|___| /\___ >
\/ \/ \/ \/ \/ \/ "#
);
let cmd_arg_angle = {
let value = options
.get("ANGLE")
.ok_or_else(|| TBError::InvalidInputData("Missing the ANGLE parameter".to_string()))?;
value.parse::<f64>().map_err(|_| {
TBError::InvalidInputData(format!("Could not parse the ANGLE parameter:{:?}", value))
})?
};
if !(0.0..=90.0).contains(&cmd_arg_angle) {
return Err(TBError::InvalidInputData(format!(
"The valid range of ANGLE is [0..90] :({})",
cmd_arg_angle
)));
}
let cmd_arg_remove_internals = {
let tmp_true = "true".to_string();
let value = options.get("REMOVE_INTERNALS").unwrap_or(&tmp_true);
value.parse::<bool>().map_err(|_| {
TBError::InvalidInputData(format!(
"Could not parse the REMOVE_INTERNALS parameter {:?}",
value
))
})?
};
let cmd_arg_discrete_distance = {
let value = options.get("DISTANCE").ok_or_else(|| {
TBError::InvalidInputData("Missing the DISTANCE parameter".to_string())
})?;
value.parse::<f64>().map_err(|_| {
TBError::InvalidInputData(format!(
"Could not parse the DISTANCE parameter {:?}",
value
))
})?
};
if !(0.004..100.0).contains(&cmd_arg_discrete_distance) {
return Err(TBError::InvalidInputData(format!(
"The valid range of DISTANCE is [0.005..100[% :({})",
cmd_arg_discrete_distance
)));
}
let cmd_arg_max_voronoi_dimension = {
let tmp_value = super::DEFAULT_MAX_VORONOI_DIMENSION.to_string();
let value = options.get("MAX_VORONOI_DIMENSION").unwrap_or(&tmp_value);
value.parse::<f64>().map_err(|_| {
TBError::InvalidInputData(format!(
"Could not parse the MAX_VORONOI_DIMENSION parameter {:?}",
value
))
})?
};
if !(super::DEFAULT_MAX_VORONOI_DIMENSION..100_000_000.0)
.contains(&cmd_arg_max_voronoi_dimension)
{
return Err(TBError::InvalidInputData(format!(
"The valid range of MAX_VORONOI_DIMENSION is [{}..100_000_000[% :({})",
super::DEFAULT_MAX_VORONOI_DIMENSION,
cmd_arg_max_voronoi_dimension
)));
}
let cmd_arg_simplify = {
let tmp_true = "true".to_string();
let value = options.get("SIMPLIFY").unwrap_or(&tmp_true);
value.parse::<bool>().map_err(|_| {
TBError::InvalidInputData(format!(
"Could not parse the SIMPLIFY parameter {:?}",
value
))
})?
};
let (cmd_arg_weld, cmd_arg_keep_input) = {
let tmp_true = "true".to_string();
let value = options.get("KEEP_INPUT").unwrap_or(&tmp_true);
let cmd_arg_keep_input = value.parse::<bool>().map_err(|_| {
TBError::InvalidInputData(format!(
"Could not parse the KEEP_INPUT parameter {:?}",
value
))
})?;
let value = options.get("WELD").unwrap_or(&tmp_true);
let mut cmd_arg_weld = value.parse::<bool>().map_err(|_| {
TBError::InvalidInputData(format!("Could not parse the WELD parameter {:?}", value))
})?;
if !cmd_arg_keep_input {
cmd_arg_weld = false;
}
(cmd_arg_weld, cmd_arg_keep_input)
};
let cmd_arg_negative_radius = {
let tmp_true = "true".to_string();
let value = options.get("NEGATIVE_RADIUS").unwrap_or(&tmp_true);
value.parse::<bool>().map_err(|_| {
TBError::InvalidInputData(format!(
"Could not parse the NEGATIVE_RADIUS parameter {:?}",
value
))
})?
};
let max_distance = cmd_arg_max_voronoi_dimension * cmd_arg_discrete_distance / 100.0;
if a_command.models.is_empty() || a_command.models[0].vertices.is_empty() {
return Err(TBError::InvalidInputData(
"Model did not contain any data".to_string(),
));
}
let dot_limit = cgmath::Deg::<f64>(cmd_arg_angle).cos();
if a_command.models.len() > 1 {
println!("centerline models.len(): {}", a_command.models.len());
return Err(TBError::InvalidInputData(
"This operation only supports one model as input".to_string(),
));
}
println!("centerline got command: \"{}\"", a_command.command);
for model in a_command.models.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!("ANGLE:{:?}°", cmd_arg_angle);
println!("REMOVE_INTERNALS:{:?}", cmd_arg_remove_internals);
println!("SIMPLIFY:{:?}", cmd_arg_simplify);
println!("WELD:{:?}", cmd_arg_weld);
println!("KEEP_INPUT:{:?}", cmd_arg_keep_input);
println!("DISTANCE:{:?}%", cmd_arg_discrete_distance);
println!("NEGATIVE_RADIUS:{:?}", cmd_arg_negative_radius);
println!("MAX_VORONOI_DIMENSION:{:?}", cmd_arg_max_voronoi_dimension);
println!("max_distance:{:?}", max_distance);
println!();
}
let (edges, points, total_aabb) = parse_input(&a_command.models[0])?;
println!("-> divide_into_shapes");
let lines = centerline::divide_into_shapes(edges, points)?;
println!("-> get_transform_relaxed");
let (_plane, transform, _voronoi_input_aabb) = centerline::get_transform_relaxed(
total_aabb,
cmd_arg_max_voronoi_dimension,
super::EPSILON,
f64::default_max_ulps(),
)?;
let inverted_transform = transform
.invert()
.ok_or(TBError::CouldNotCalculateInvertMatrix)?;
let mut raw_data: Vec<linestring::linestring_2d::LineStringSet2<f64>> = lines
.par_iter()
.map(|x| x.transform(&transform).copy_to_2d(linestring_3d::Plane::XY))
.collect();
{
let truncate_float = |x: f64| -> f64 { x as i64 as f64 };
for r in raw_data.iter_mut() {
r.operation(&truncate_float);
}
}
let raw_data: Vec<linestring::linestring_2d::LineStringSet2<f64>> = raw_data
.into_par_iter()
.map(|mut x| {
let _ = x.calculate_convex_hull();
x
})
.collect();
let raw_data = centerline::consolidate_shapes(raw_data)?;
let shapes = raw_data
.into_par_iter()
.map(|shape| {
let mut segments =
Vec::<BV::Line<i64>>::with_capacity(shape.set().iter().map(|x| x.len()).sum());
for lines in shape.set().iter() {
for lineseq in lines.as_lines_iter() {
segments.push(BV::Line::new(
BV::Point {
x: lineseq.start.x as i64,
y: lineseq.start.y as i64,
},
BV::Point {
x: lineseq.end.x as i64,
y: lineseq.end.y as i64,
},
))
}
}
let mut c = centerline::Centerline::<i64, f64>::with_segments(segments);
if let Err(centerline_error) = c.build_voronoi() {
return Err(centerline_error.into());
}
if cmd_arg_remove_internals {
if let Err(centerline_error) =
c.calculate_centerline(dot_limit, max_distance, shape.get_internals())
{
return Err(centerline_error.into());
}
} else if let Err(centerline_error) =
c.calculate_centerline(dot_limit, max_distance, None)
{
return Err(centerline_error.into());
}
if cmd_arg_simplify && c.line_strings.is_some() {
c.line_strings = Some(
c.line_strings
.take()
.unwrap()
.into_par_iter()
.map(|ls| {
ls.simplify(max_distance)
})
.collect(),
);
}
Ok((shape, c))
})
.collect::<Result<
Vec<(
linestring::linestring_2d::LineStringSet2<f64>,
centerline::Centerline<i64, f64>,
)>,
TBError,
>>()?;
let model = build_output_bp_model(
&a_command,
shapes,
cmd_arg_weld,
inverted_transform,
cmd_arg_negative_radius,
cmd_arg_keep_input,
)?;
let mut reply = PB_Reply {
options: vec![PB_KeyValuePair {
key: "ONLY_EDGES".to_string(),
value: "True".to_string(),
}],
models: Vec::with_capacity(1),
models32: Vec::with_capacity(0),
};
println!(
"<-PB_Reply vertices:{:?}, faces:{:?}",
model.vertices.len(),
model.faces.len()
);
reply.models.push(model);
Ok(reply)
}