use crate::{PB_Command, PB_Face, PB_KeyValuePair, PB_Model, PB_Reply, PB_Vertex, TBError};
use itertools::Itertools;
use linestring::linestring_3d;
use rayon::prelude::*;
use std::collections::HashMap;
fn find_linestrings(
obj: &PB_Model,
) -> Result<Vec<(usize, linestring_3d::LineString3<f64>, usize)>, TBError> {
let mut linestrings = Vec::<(usize, linestring_3d::LineString3<f64>, usize)>::new();
let mut connections_map = ahash::AHashMap::<usize, smallvec::SmallVec<[usize; 2]>>::default();
let mut edge_set = ahash::AHashSet::<(usize, usize)>::default();
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(),
));
}
let v0 = face.vertices[0] as usize;
let v1 = face.vertices[1] as usize;
{
let entry = connections_map
.entry(v0)
.or_insert_with(smallvec::SmallVec::<[usize; 2]>::new);
if !entry.contains(&v1) {
entry.push(v1);
}
let entry = connections_map
.entry(v1)
.or_insert_with(smallvec::SmallVec::<[usize; 2]>::new);
if !entry.contains(&v0) {
entry.push(v0);
}
}
let _ = edge_set.insert(make_key(v0, v1));
}
println!("Built connections_map.len():{}", connections_map.len());
let connections_map = connections_map;
for connection in connections_map.iter() {
let v0 = *connection.0;
let neighbours = connection.1;
if (neighbours.len() <= 2) && (neighbours.len() > 1) {
continue;
}
for v1 in neighbours.iter().filter(|x| **x != v0) {
let v1 = *v1;
let key = make_key(v0, v1);
if edge_set.contains(&key) {
let (end_vertex, rv) =
walk_single_line_edges(v0, v1, &mut edge_set, &connections_map, &obj.vertices)?;
linestrings.push((v0, rv, end_vertex));
}
}
}
println!(
"done with the simple edges! connections_map.len():{} edge_set.len():{} linestrings.len():{}",
connections_map.len(),
edge_set.len(),
linestrings.len()
);
let mut something_changed: bool = true;
while (!edge_set.is_empty()) && something_changed {
something_changed = false;
if let Some(suggested_edge) = edge_set
.iter()
.find(|x| connections_map.get(&x.0).map_or(0, |y| y.len()) == 2)
{
something_changed = true;
let v0 = suggested_edge.0;
let v1 = suggested_edge.1;
let (end_vertex, rv) =
walk_single_line_edges(v0, v1, &mut edge_set, &connections_map, &obj.vertices)?;
linestrings.push((v0, rv, end_vertex));
}
}
println!(
"done! connections_map.len():{} edge_set.len():{} linestrings.len():{}",
connections_map.len(),
edge_set.len(),
linestrings.len()
);
if !edge_set.is_empty() {
return Err(TBError::InternalError(format!(
"Could not convert all edges to lines:{:?}",
edge_set
)));
}
Ok(linestrings)
}
#[inline(always)]
fn make_key(v0: usize, v1: usize) -> (usize, usize) {
if v0 < v1 {
(v0, v1)
} else {
(v1, v0)
}
}
fn walk_single_line_edges(
from_v: usize,
to_v: usize,
edge_set: &mut ahash::AHashSet<(usize, usize)>,
connections_map: &ahash::AHashMap<usize, smallvec::SmallVec<[usize; 2]>>,
vertices: &[PB_Vertex],
) -> Result<(usize, linestring_3d::LineString3<f64>), TBError> {
let started_at_vertex = from_v;
let mut kill_pill = edge_set.len() + 2;
let mut from_v = from_v;
let mut to_v = to_v;
let mut end_vertex = from_v;
let mut result = linestring_3d::LineString3::<f64>::default();
{
let v0_value = vertices.get(from_v).ok_or_else(|| {
TBError::InternalError(format!("Lost an vertex somehow.. vertex index:{}", from_v))
})?;
result.push(cgmath::Point3 {
x: v0_value.x,
y: v0_value.y,
z: v0_value.z,
});
}
'outer: loop {
if to_v == started_at_vertex {
end_vertex = to_v;
break 'outer;
}
kill_pill -= 1;
if kill_pill == 0 {
return Err(TBError::InternalError("Detected infinite loop".to_string()));
}
if from_v == to_v {
return Err(TBError::InternalError(
"Detected from_v == to_v".to_string(),
));
}
let key = make_key(from_v, to_v);
if edge_set.contains(&key) {
let v1 = vertices.get(to_v).ok_or_else(|| {
TBError::InternalError(format!("Lost a vertex somehow.. index:{}", to_v))
})?;
result.push(cgmath::Point3 {
x: v1.x,
y: v1.y,
z: v1.z,
});
let _ = edge_set.remove(&key);
let connection = connections_map
.get(&to_v)
.ok_or_else(|| TBError::InternalError("edge ended unexpectedly #2".to_string()))?;
if !connection.contains(&from_v) {
return Err(TBError::InternalError(format!(
"edge did not contain 'from' vertex??? {} {:?}",
from_v, connection
)));
}
if connection.len() == 2 {
let next_v = *connection.iter().find(|x| **x != from_v).ok_or_else(|| {
TBError::InternalError("edge ended unexpectedly #1".to_string())
})?;
from_v = to_v;
to_v = next_v;
continue 'outer;
} else {
end_vertex = to_v;
break 'outer;
}
}
break 'outer;
}
let end_vertex_value = vertices.get(end_vertex).ok_or_else(|| {
TBError::InternalError(format!(
"Lost a vertex somehow.. vertex index:{}",
end_vertex
))
})?;
result.push(cgmath::Point3 {
x: end_vertex_value.x,
y: end_vertex_value.y,
z: end_vertex_value.z,
});
if end_vertex == started_at_vertex {
let _ = edge_set.remove(&make_key(from_v, to_v));
} else {
}
Ok((end_vertex, result))
}
fn simplify_rdp_percent(
pb_model: &PB_Model,
distance_multiplier: f64,
lines: Vec<(usize, linestring_3d::LineString3<f64>, usize)>,
) -> Result<Vec<(usize, linestring_3d::LineString3<f64>, usize)>, TBError> {
let mut aabb = linestring::linestring_3d::Aabb3::<f64>::default();
for v in pb_model.vertices.iter() {
aabb.update_point(cgmath::Point3::new(v.x, v.y, v.z))
}
let dimension = aabb.get_high().unwrap() - aabb.get_low().unwrap();
let dimension = dimension.x.max(dimension.y).max(dimension.z);
let discrete_distance = dimension * distance_multiplier;
let diff = lines.iter().map(|x| x.1.points().len()).sum::<usize>();
let rv: Vec<(usize, linestring_3d::LineString3<f64>, usize)> = lines
.into_par_iter()
.map(|(v0, ls, v1)| (v0, ls.simplify(discrete_distance), v1))
.collect();
let diff = diff - rv.iter().map(|x| x.1.points().len()).sum::<usize>();
println!(
"Simplifying using percentage. discrete-distance={} = {}*{}, removed {} vertices.",
discrete_distance, dimension, distance_multiplier, diff
);
Ok(rv)
}
fn build_bp_model(
a_command: &PB_Command,
lines: Vec<(usize, linestring_3d::LineString3<f64>, usize)>,
) -> Result<PB_Model, TBError> {
let input_pb_model = &a_command.models[0];
let rough_capacity = lines.iter().map(|x| x.1.len()).sum();
let mut output_pb_model = PB_Model {
name: input_pb_model.name.clone(),
world_orientation: input_pb_model.world_orientation.clone(),
vertices: Vec::<PB_Vertex>::with_capacity(rough_capacity),
faces: Vec::<PB_Face>::with_capacity(rough_capacity),
};
let mut v_map = ahash::AHashMap::<usize, usize>::default();
let mut vertex_list = Vec::<usize>::new();
for ls in lines.into_iter() {
let v0 = ls.0;
let l = ls.1;
let v1 = ls.2;
let v0_mapped = *v_map.entry(v0).or_insert(output_pb_model.vertices.len());
if v0_mapped == output_pb_model.vertices.len() {
output_pb_model
.vertices
.push(input_pb_model.vertices[v0].clone());
}
vertex_list.push(v0_mapped);
for p in l.points().iter().skip(1).take(l.points().len() - 2) {
vertex_list.push(output_pb_model.vertices.len());
output_pb_model.vertices.push(PB_Vertex {
x: p.x,
y: p.y,
z: p.z,
});
}
let v1_mapped = *v_map.entry(v1).or_insert(output_pb_model.vertices.len());
if v1_mapped == output_pb_model.vertices.len() {
output_pb_model
.vertices
.push(input_pb_model.vertices[v1].clone());
}
vertex_list.push(v1_mapped);
for v in vertex_list.iter().tuple_windows::<(_, _)>() {
output_pb_model.faces.push(PB_Face {
vertices: vec![*v.0 as u64, *v.1 as u64],
});
}
vertex_list.clear();
}
println!(
"Reduced to {} vertices (from {}) ",
output_pb_model.vertices.len(),
input_pb_model.vertices.len()
);
Ok(output_pb_model)
}
pub(crate) fn command(
a_command: PB_Command,
options: HashMap<String, String>,
) -> Result<PB_Reply, TBError> {
println!(
r#" _________.__ .__ .__ _____
/ _____/|__| _____ ______ | | |__|/ ____\__.__.
\_____ \ | |/ \\____ \| | | \ __< | |
/ \| | Y Y \ |_> > |_| || | \___ |
/_______ /|__|__|_| / __/|____/__||__| / ____|
\/ \/|__| \/ "#
);
if a_command.models.len() > 1 {
return Err(TBError::InvalidInputData(format!(
"This operation only supports one model as input:{}",
a_command.models.len()
)));
}
if a_command.models.is_empty() {
return Err(TBError::InvalidInputData(
"Model did not contain any data".to_string(),
));
}
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!();
}
let distance = options
.get("DISTANCE")
.ok_or_else(|| TBError::InvalidInputData("Missing the DISTANCE parameter".to_string()))?
.parse::<f64>()
.map_err(|_| {
TBError::InvalidInputData("Could not parse the DISTANCE parameter".to_string())
})?;
let lines = find_linestrings(&a_command.models[0])?;
let lines = simplify_rdp_percent(&a_command.models[0], distance, lines)?;
let model = build_bp_model(&a_command, lines)?;
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),
};
reply.models.push(model);
Ok(reply)
}