use centerline::Centerline;
use centerline::{CenterlineError, LineStringSet2};
use vector_traits::prelude::*;
use fltk::app::redraw;
use fltk::enums::*;
use fltk::group::Pack;
use fltk::valuator::HorNiceSlider;
use fltk::{app, dialog, draw, frame::*, menu, window};
use boostvoronoi::InputType;
use fltk::app::MouseWheel;
use fltk::button::RoundButton;
use fltk::dialog::FileDialogType;
use fltk::menu::MenuButton;
use fltk::prelude::{GroupExt, MenuExt, ValuatorExt, WidgetBase, WidgetExt, WindowExt};
#[allow(unused_imports)]
use itertools::Itertools;
use linestring::linestring_2d::{Line2, LineString2, SimpleAffine};
use linestring::linestring_3d;
use linestring::linestring_3d::LineString3;
use rayon::prelude::*;
use std::cell::{RefCell, RefMut};
use std::fs::File;
use std::io::BufReader;
use std::rc::Rc;
use obj;
#[allow(unused_imports)]
use vector_traits::glam::{DVec3, Vec3};
use vector_traits::num_traits::{AsPrimitive, Float, FloatConst};
use vector_traits::prelude::{GenericScalar, GenericVector3, HasXY};
#[macro_use]
extern crate bitflags;
const HF: i32 = 590;
const WF: i32 = 790;
#[allow(dead_code)]
const H: i32 = 650;
const W: i32 = 800;
#[derive(Debug, Clone, Copy)]
pub enum GuiMessage {
SliderPreChanged(f64),
SliderPostChanged(f64),
SliderDotChanged(f64),
Filter(DrawFilterFlag),
MenuChoiceLoad,
MenuChoiceSaveOutline,
MenuChoiceSaveCenterLine,
}
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct DrawFilterFlag: u32 {
const THREAD_GROUP_HULL = 0b000000000000001;
const THREAD_GROUP_AABB = 0b000000000000010;
const INTERNAL_GEOMETRY = 0b000000000000100;
const REMOVE_INTERNAL_EDGES = 0b000000000001000;
const DRAW_ALL = 0b111111111111111;
}
}
struct Shape<I: InputType, T: GenericVector3>
where
f64: AsPrimitive<T::Scalar>,
{
raw_data: LineStringSet2<<T as GenericVector3>::Vector2>,
centerline: Option<Centerline<I, T>>,
simplified_centerline: Option<Vec<Vec<T>>>,
}
#[derive(Debug, Clone)]
struct Configuration<T: GenericVector3> {
input_distance: T::Scalar,
input_distance_dirty: bool,
centerline_distance: T::Scalar,
centerline_scaled_distance: T::Scalar,
centerline_distance_dirty: bool,
normalized_dot: T::Scalar,
normalized_dot_dirty: bool,
last_message: Option<GuiMessage>,
draw_flag: DrawFilterFlag,
inverse_transform: T::Affine,
}
struct SharedData<I: InputType, T: GenericVector3>
where
f64: AsPrimitive<T::Scalar>,
{
shapes: Option<Vec<Shape<I, T>>>,
configuration: Configuration<T>,
affine: SimpleAffine<<T as GenericVector3>::Vector2>,
}
fn main() -> Result<(), CenterlineError> {
typed_main::<i32, DVec3>() }
fn typed_main<I: InputType + Send + 'static, T: GenericVector3 + 'static>()
-> Result<(), CenterlineError>
where
I: AsPrimitive<T::Scalar>,
T::Scalar: AsPrimitive<I> + AsPrimitive<i32>,
i32: AsPrimitive<T::Scalar>,
f32: AsPrimitive<T::Scalar>,
f64: AsPrimitive<T::Scalar>,
{
let app = app::App::default();
let mut wind = window::Window::default()
.with_size(W, HF + 150)
.center_screen()
.with_label("Centerline finder");
let mut frame = Frame::new(5, 5, WF, HF, "");
frame.set_color(Color::Black);
frame.set_frame(FrameType::DownBox);
let mut pack = Pack::new(5, 5 + HF, (W - 10) / 2, H - 10, "");
pack.set_spacing(27);
let mut slider_pre = HorNiceSlider::default()
.with_size(100, 25)
.with_label("Input data simplification distance");
slider_pre.set_tooltip(
"This slider defines how much the line simplification should remove from the input lines",
);
slider_pre.set_value(0.5);
slider_pre.set_frame(FrameType::PlasticUpBox);
slider_pre.set_color(Color::White);
let mut slider_dot = HorNiceSlider::default()
.with_size(100, 25)
.with_label("Angle: 50.0000°");
slider_dot.set_tooltip(
"This slider defines the angle predicate of the edge removal process.\
Voronoi edges that are touching the geometry will be removed if the angle between the edge \
and the input geometry exceeds this value",
);
slider_dot.set_value(0.55);
slider_dot.set_frame(FrameType::PlasticUpBox);
slider_dot.set_color(Color::White);
let mut slider_post = HorNiceSlider::default()
.with_size(100, 25)
.with_label("Centerline data simplification distance");
slider_post.set_tooltip(
"This slider defines how much the line simplification should remove from the output lines",
);
slider_post.set_value(0.5);
slider_post.set_frame(FrameType::PlasticUpBox);
slider_post.set_color(Color::White);
pack.end();
let mut pack = Pack::new(2 * 5 + (W - 10) / 2, 5 + HF, (W - 10) / 2 - 5, H - 10, "");
pack.set_spacing(1);
let mut menu_but = MenuButton::default().with_size(170, 25).with_label("Menu");
menu_but.set_frame(FrameType::PlasticUpBox);
let mut thread_group_aabb_button = RoundButton::default()
.with_size(180, 25)
.with_label("Thread group AABB");
thread_group_aabb_button.toggle(false);
thread_group_aabb_button.set_frame(FrameType::PlasticUpBox);
let mut thread_group_hull_button = RoundButton::default()
.with_size(180, 25)
.with_label("Thread group convex hull");
thread_group_hull_button.toggle(false);
thread_group_hull_button.set_frame(FrameType::PlasticUpBox);
let mut internal_geometry_button = RoundButton::default()
.with_size(180, 25)
.with_label("Internal geometry convex hull");
internal_geometry_button.toggle(false);
internal_geometry_button.set_frame(FrameType::PlasticUpBox);
let mut internal_edges_button = RoundButton::default()
.with_size(180, 25)
.with_label("Remove internal edges");
internal_edges_button.set_tooltip(
"If this button is enabled the inner geometry \
(think the inside of the letter O) will be hidden.",
);
internal_edges_button.toggle(true);
internal_edges_button.set_frame(FrameType::PlasticUpBox);
pack.end();
wind.set_color(Color::White);
wind.end();
wind.show();
let shared_data_rc = Rc::new(RefCell::new(SharedData::<I, T> {
shapes: None,
configuration: Configuration {
centerline_distance: 0.0.into(),
centerline_scaled_distance: 256.0.into(),
centerline_distance_dirty: true,
normalized_dot: 0.38.into(),
normalized_dot_dirty: true,
input_distance: 0.0.into(),
input_distance_dirty: true,
last_message: None,
draw_flag: DrawFilterFlag::DRAW_ALL
^ DrawFilterFlag::THREAD_GROUP_AABB
^ DrawFilterFlag::THREAD_GROUP_HULL
^ DrawFilterFlag::INTERNAL_GEOMETRY,
inverse_transform: T::Affine::identity(),
},
affine: SimpleAffine::default(),
}));
let (sender, receiver) = app::channel::<GuiMessage>();
sender.send(GuiMessage::SliderPreChanged(50.0));
slider_pre.set_callback(move |s| {
let value = s.value() * 100.0;
s.set_label(&format!(
" Input data simplification distance: {:.4} ",
value
));
sender.send(GuiMessage::SliderPreChanged(value));
});
slider_dot.set_callback(move |s| {
let value = s.value() * 90.0;
s.set_label(&format!(" Angle: {:.4}° ", value));
let value = (f64::PI() * value / 180.0).cos();
sender.send(GuiMessage::SliderDotChanged(value));
});
slider_post.set_callback(move |s| {
let value = s.value() * 100.0;
s.set_label(&format!(
" Centerline simplification distance: {:.4} ",
value
));
sender.send(GuiMessage::SliderPostChanged(value));
});
internal_geometry_button.emit(
sender,
GuiMessage::Filter(DrawFilterFlag::INTERNAL_GEOMETRY),
);
internal_edges_button.emit(
sender,
GuiMessage::Filter(DrawFilterFlag::REMOVE_INTERNAL_EDGES),
);
thread_group_aabb_button.emit(
sender,
GuiMessage::Filter(DrawFilterFlag::THREAD_GROUP_AABB),
);
thread_group_hull_button.emit(
sender,
GuiMessage::Filter(DrawFilterFlag::THREAD_GROUP_HULL),
);
menu_but.add_emit(
"Load from file",
Shortcut::None,
menu::MenuFlag::Normal,
sender,
GuiMessage::MenuChoiceLoad,
);
menu_but.add_emit(
"Save outline to file",
Shortcut::None,
menu::MenuFlag::Normal,
sender,
GuiMessage::MenuChoiceSaveOutline,
);
menu_but.add_emit(
"Save centerline to file",
Shortcut::None,
menu::MenuFlag::Normal,
sender,
GuiMessage::MenuChoiceSaveCenterLine,
);
{
let data = Rc::clone(&shared_data_rc);
add_data_from_file(data, "example/logo.obj")?;
}
let shared_data_c = Rc::clone(&shared_data_rc);
wind.draw(move |_| {
let draw_fn = |line: Result<[T::Scalar; 4], _>, cross: bool| {
if let Ok(line) = line {
let (x1, y1, x2, y2): (i32, i32, i32, i32) =
(line[0].as_(), line[1].as_(), line[2].as_(), line[3].as_());
draw::draw_line(x1, y1, x2, y2);
if cross {
draw::draw_line(x1 - 2, y1 - 2, x1 + 2, y1 + 2);
draw::draw_line(x1 + 2, y1 - 2, x1 - 2, y1 + 2);
}
}
};
let mut data_bm: RefMut<_> = shared_data_c.borrow_mut();
draw::set_draw_color(Color::White);
draw::draw_rectf(5, 5, WF, HF);
draw::set_line_style(draw::LineStyle::Solid, 1);
let opt_shapes = data_bm.shapes.take();
if let Some(vec_shapes) = opt_shapes {
let cross = match data_bm.configuration.last_message {
Some(GuiMessage::SliderPreChanged(_)) => true,
_ => false,
};
for shape in vec_shapes.iter() {
if let Some(ref centerline) = shape.centerline {
draw::set_line_style(draw::LineStyle::Solid, 2);
draw::set_draw_color(Color::Red);
for a_line in centerline.segments.iter() {
draw_fn(
data_bm.affine.transform_ab_a([
a_line.start.x.as_(),
a_line.start.y.as_(),
a_line.end.x.as_(),
a_line.end.y.as_(),
]),
cross,
);
}
if data_bm
.configuration
.draw_flag
.contains(DrawFilterFlag::THREAD_GROUP_AABB)
{
draw::set_line_style(draw::LineStyle::Solid, 1);
draw::set_draw_color(Color::Dark1);
let aabb: Vec<<T as GenericVector3>::Vector2> =
shape.raw_data.get_aabb().clone().into();
for a_line in aabb.window_iter() {
draw_fn(
data_bm.affine.transform_ab_a([
a_line.start.x() as T::Scalar,
a_line.start.y() as T::Scalar,
a_line.end.x() as T::Scalar,
a_line.end.y() as T::Scalar,
]),
false,
);
}
}
if data_bm
.configuration
.draw_flag
.contains(DrawFilterFlag::THREAD_GROUP_HULL)
{
draw::set_line_style(draw::LineStyle::Solid, 1);
draw::set_draw_color(Color::Dark2);
if let Some(hull) = shape.raw_data.get_convex_hull() {
for a_line in hull.window_iter() {
draw_fn(
data_bm.affine.transform_ab_a([
a_line.start.x(),
a_line.start.y(),
a_line.end.x(),
a_line.end.y(),
]),
false,
);
}
}
}
if data_bm
.configuration
.draw_flag
.contains(DrawFilterFlag::INTERNAL_GEOMETRY)
{
if let Some(shape_internals) = shape.raw_data.get_internals() {
draw::set_draw_color(Color::Green);
for hull in shape_internals.iter() {
for a_line in hull.1.window_iter() {
draw_fn(
data_bm.affine.transform_ab_a([
a_line.start.x(),
a_line.start.y(),
a_line.end.x(),
a_line.end.y(),
]),
false,
);
}
}
}
}
draw::set_line_style(draw::LineStyle::Solid, 1);
draw::set_draw_color(Color::Black);
for a_line in centerline.lines.iter().flatten() {
draw_fn(
data_bm.affine.transform_ab_a([
a_line.start.x(),
a_line.start.y(),
a_line.end.x(),
a_line.end.y(),
]),
false,
)
}
draw::set_draw_color(Color::Blue);
let cross = match data_bm.configuration.last_message {
Some(GuiMessage::SliderPostChanged(_)) => true,
_ => false,
};
for a_linestring in shape.simplified_centerline.iter().flatten() {
for a_line in a_linestring.window_iter() {
draw_fn(
data_bm.affine.transform_ab_a([
a_line.start.x(),
a_line.start.y(),
a_line.end.x(),
a_line.end.y(),
]),
cross,
);
}
}
}
}
data_bm.shapes = Some(vec_shapes);
}
});
let shared_data_c = Rc::clone(&shared_data_rc);
let mut mouse_drag: Option<(i32, i32)> = None;
wind.handle(move |_, ev| match ev {
fltk::enums::Event::MouseWheel => {
let event = &app::event_coords();
let mut shared_data_bm = shared_data_c.borrow_mut();
let event_dy = match app::event_dy() {
MouseWheel::Up => 3,
MouseWheel::Down => -3,
_ => 0,
};
let reverse_middle = shared_data_bm
.affine
.transform_ba(T::Vector2::new_2d(event.0.as_(), event.1.as_()));
if reverse_middle.is_err() {
println!("{:?}", reverse_middle.err().unwrap());
return false;
}
let reverse_middle = reverse_middle.unwrap();
if event_dy != 0 {
let scale_mod: T::Scalar = 1.01_f32.powf(event_dy as f32).as_();
shared_data_bm.affine.scale[0] *= scale_mod;
shared_data_bm.affine.scale[1] *= scale_mod;
}
let new_middle = shared_data_bm
.affine
.transform_ab(T::Vector2::new_2d(reverse_middle.x(), reverse_middle.y()));
if new_middle.is_err() {
println!("{:?}", new_middle.err().unwrap());
return false;
}
let new_middle = new_middle.unwrap();
shared_data_bm.affine.b_offset[0] += event.0.as_() - new_middle.x();
shared_data_bm.affine.b_offset[1] += event.1.as_() - new_middle.y();
redraw();
true
}
fltk::enums::Event::Drag => {
let event = &app::event_coords();
if mouse_drag.is_none() {
mouse_drag = Some(*event);
} else {
let md = mouse_drag.unwrap();
let mut shared_data_bm = shared_data_c.borrow_mut();
shared_data_bm.affine.b_offset[0] += (event.0 - md.0).as_();
shared_data_bm.affine.b_offset[1] += (event.1 - md.1).as_();
mouse_drag = Some(*event);
redraw();
}
true
}
fltk::enums::Event::Released => {
if mouse_drag.is_some() {
mouse_drag = None;
}
true
}
_ => false,
});
let shared_data_c1 = Rc::clone(&shared_data_rc);
let shared_data_c2 = Rc::clone(&shared_data_rc);
while app.wait() {
if let Some(msg) = receiver.recv() {
match msg {
GuiMessage::SliderPreChanged(value) => {
let mut shared_data_bm = shared_data_c1.borrow_mut();
shared_data_bm.configuration.input_distance = value.as_();
shared_data_bm.configuration.input_distance_dirty = true;
}
GuiMessage::SliderDotChanged(value) => {
let mut shared_data_bm = shared_data_c1.borrow_mut();
shared_data_bm.configuration.normalized_dot = value.as_();
shared_data_bm.configuration.normalized_dot_dirty = true;
}
GuiMessage::SliderPostChanged(value) => {
let mut shared_data_bm = shared_data_c1.borrow_mut();
shared_data_bm.configuration.centerline_distance = value.as_();
shared_data_bm.configuration.centerline_distance_dirty = true;
}
GuiMessage::Filter(flag) => {
let mut shared_data_bm = shared_data_c1.borrow_mut();
shared_data_bm.configuration.draw_flag ^= flag;
if flag.contains(DrawFilterFlag::REMOVE_INTERNAL_EDGES) {
shared_data_bm.configuration.normalized_dot_dirty = true;
}
}
GuiMessage::MenuChoiceLoad => {
let mut chooser = dialog::NativeFileChooser::new(FileDialogType::BrowseDir);
let _ = chooser.set_directory(&std::path::PathBuf::from("examples"));
let _ = chooser.set_title("select your input data");
chooser.set_filter("*.obj");
chooser.show();
if let Some(filename) = chooser.filenames().first() {
let shared_data_c = Rc::clone(&shared_data_rc);
if let Err(err) =
add_data_from_file(shared_data_c, filename.to_str().unwrap())
{
println!("Failed to read file {:?}: {:?}", filename, err);
}
if let Some(filename) = filename.to_str() {
let w = &mut wind;
w.set_label(filename);
}
}
}
GuiMessage::MenuChoiceSaveOutline => {
let mut chooser =
dialog::NativeFileChooser::new(FileDialogType::BrowseSaveFile);
let _ = chooser.set_directory(&std::path::PathBuf::from("examples"));
let _ = chooser.set_title("select file to save outline to");
chooser.set_filter("*.obj");
chooser.show();
if let Some(filename) = chooser.filenames().first() {
let shared_data_c = Rc::clone(&shared_data_rc);
let shared_data_b = shared_data_c.borrow();
let mut data_to_save = Vec::<Vec<linestring_3d::Line3<T>>>::new();
for s in shared_data_b.shapes.iter().flatten() {
if let Some(ref centerline) = s.centerline {
data_to_save.push(
centerline
.segments
.iter()
.map(|l| {
Line2::<<T as GenericVector3>::Vector2>::from([
l.start.x.as_(),
l.start.y.as_(),
l.end.x.as_(),
l.end.y.as_(),
])
.copy_to_3d(Plane::XY)
.apply_new(&|x: T| -> T {
let rv: T = shared_data_b
.configuration
.inverse_transform
.transform_point3(x);
rv
})
})
.collect(),
);
}
}
if let Err(err) = linestring::linestring_3d::save_to_obj_file(
filename.to_str().unwrap(),
"outline",
&data_to_save,
) {
println!("Failed to write file: {:?}", err);
}
}
}
GuiMessage::MenuChoiceSaveCenterLine => {
let mut chooser =
dialog::NativeFileChooser::new(FileDialogType::BrowseSaveFile);
let _ = chooser.set_directory(&std::path::PathBuf::from("examples"));
let _ = chooser.set_title("select file to save outline to");
chooser.set_filter("*.obj");
chooser.show();
if let Some(filename) = chooser.filenames().first() {
let shared_data_c = Rc::clone(&shared_data_rc);
let shared_data_b = shared_data_c.borrow();
let mut data_to_save = Vec::<Vec<linestring_3d::Line3<T>>>::new();
for s in shared_data_b.shapes.iter().flatten() {
for r in s.centerline.iter() {
for ls in r.line_strings.iter().flatten() {
data_to_save.push({
let mut lsc = ls.clone();
lsc.apply(&|x| {
shared_data_b
.configuration
.inverse_transform
.transform_point3(x)
});
lsc.window_iter().collect()
});
}
for ls in r.lines.iter().flatten() {
data_to_save.push(vec![ls.apply_new(&|x| {
shared_data_b
.configuration
.inverse_transform
.transform_point3(x)
})]);
}
}
}
if let Err(err) = linestring::linestring_3d::save_to_obj_file(
filename.to_str().unwrap(),
"centerline",
&data_to_save,
) {
println!("Failed to write file: {:?}", err);
}
}
}
}
{
let mut shared_data_bm = shared_data_c2.borrow_mut();
shared_data_bm.configuration.last_message = Some(msg);
re_calculate(shared_data_bm);
}
redraw();
}
}
Ok(())
}
fn re_calculate<I: InputType + Send, T>(mut shared_data_bm: RefMut<SharedData<I, T>>)
where
T: GenericVector3,
I: AsPrimitive<T::Scalar>,
T::Scalar: AsPrimitive<I>,
f64: AsPrimitive<T::Scalar>,
{
#[cfg(feature = "console_debug")]
{
println!("***********************");
println!("re_calculate()");
}
let shapes = shared_data_bm.shapes.take();
let configuration = shared_data_bm.configuration.clone();
if let Some(mut shapes) = shapes {
shapes = shapes
.into_par_iter() .filter_map(|x| threaded_re_calculate_error_handler(x, &configuration))
.collect();
shared_data_bm.shapes = Some(shapes);
}
shared_data_bm.configuration.normalized_dot_dirty = false;
shared_data_bm.configuration.centerline_distance_dirty = false;
shared_data_bm.configuration.input_distance_dirty = false;
redraw();
}
fn threaded_re_calculate_error_handler<I: InputType, T>(
shape: Shape<I, T>,
configuration: &Configuration<T>,
) -> Option<Shape<I, T>>
where
T: GenericVector3,
I: AsPrimitive<T::Scalar>,
T::Scalar: AsPrimitive<I>,
f64: AsPrimitive<T::Scalar>,
{
let rv = threaded_re_calculate(shape, configuration);
match rv {
Err(e) => {
println!("Error {:?}", e);
None
}
Ok(shape) => Some(shape),
}
}
fn threaded_re_calculate<I: InputType, T>(
mut shape: Shape<I, T>,
configuration: &Configuration<T>,
) -> Result<Shape<I, T>, CenterlineError>
where
T: GenericVector3,
I: AsPrimitive<T::Scalar>,
T::Scalar: AsPrimitive<I>,
f64: AsPrimitive<T::Scalar>,
{
if shape.centerline.is_none() {
shape.centerline = Some(Centerline::<I, T>::default());
}
let input_changed = if configuration.input_distance_dirty {
recalculate_voronoi_input(&mut shape, configuration)?;
true
} else {
false
};
let diagram_changed = if shape.centerline.is_none() || input_changed {
recalculate_voronoi_diagram(&mut shape, configuration)?;
true
} else {
false
};
let centerline_changed =
if shape.centerline.is_none() || diagram_changed || configuration.normalized_dot_dirty {
recalculate_centerline(&mut shape, configuration)?;
true
} else {
false
};
if shape.simplified_centerline.is_none()
|| centerline_changed
|| configuration.centerline_distance_dirty
{
simplify_centerline(&mut shape, configuration)?;
true
} else {
false
};
Ok(shape)
}
#[allow(unused_variables)]
fn recalculate_voronoi_diagram<I: InputType, T>(
shape: &mut Shape<I, T>,
configuration: &Configuration<T>,
) -> Result<(), CenterlineError>
where
T: GenericVector3,
I: AsPrimitive<T::Scalar>,
f64: AsPrimitive<T::Scalar>,
{
#[cfg(feature = "console_debug")]
println!("recalculate_voronoi_diagram()");
if let Some(mut centerline_rw) = shape.centerline.take() {
centerline_rw.build_voronoi()?;
shape.centerline = Some(centerline_rw);
}
Ok(())
}
fn recalculate_centerline<I: InputType, T>(
shape: &mut Shape<I, T>,
configuration: &Configuration<T>,
) -> Result<(), CenterlineError>
where
T: GenericVector3,
I: AsPrimitive<T::Scalar>,
f64: AsPrimitive<T::Scalar>,
{
#[cfg(feature = "console_debug")]
println!("recalculate_centerline()");
if let Some(ref mut centerline) = shape.centerline {
if configuration
.draw_flag
.contains(DrawFilterFlag::REMOVE_INTERNAL_EDGES)
{
centerline.calculate_centerline(
configuration.normalized_dot,
configuration.centerline_scaled_distance,
shape.raw_data.get_internals(),
)?;
} else {
centerline.calculate_centerline(
configuration.normalized_dot,
configuration.centerline_scaled_distance,
None,
)?;
}
} else {
dbg!(shape.centerline.is_none());
println!("centerline was none");
}
Ok(())
}
fn simplify_centerline<I: InputType, T>(
shape: &mut Shape<I, T>,
configuration: &Configuration<T>,
) -> Result<(), CenterlineError>
where
T: GenericVector3,
f64: AsPrimitive<T::Scalar>,
{
#[cfg(feature = "console_debug")]
println!("simplify_centerline()");
let mut simplified_centerline =
if let Some(simplified_centerline) = shape.simplified_centerline.take() {
simplified_centerline
} else {
Vec::<Vec<T>>::new()
};
simplified_centerline.clear();
if let Some(ref centerline) = shape.centerline {
if let Some(ref line_strings) = centerline.line_strings {
for ls in line_strings.iter() {
simplified_centerline.push(
ls.simplify_rdp(configuration.centerline_distance * 256_f32.sqrt().into()),
);
}
}
}
shape.simplified_centerline = Some(simplified_centerline);
Ok(())
}
fn recalculate_voronoi_input<I: InputType + 'static, T>(
shape: &mut Shape<I, T>,
configuration: &Configuration<T>,
) -> Result<(), CenterlineError>
where
T: GenericVector3,
T::Scalar: AsPrimitive<I>,
f64: AsPrimitive<T::Scalar>,
{
#[cfg(feature = "console_debug")]
println!("recalculate_voronoi_input()");
if let Some(mut centerline) = shape.centerline.take() {
centerline.segments.clear();
for lines in shape.raw_data.set().iter() {
if configuration.input_distance > T::Scalar::ZERO {
#[cfg(feature = "console_debug")]
let before = lines.len();
let dist_p: T::Scalar = (configuration.input_distance) * 256_f32.sqrt().into();
let s_lines = lines.simplify_rdp(dist_p);
#[cfg(feature = "console_debug")]
println!(
"reduced by {} points of {} = {}",
before - s_lines.len(),
before,
s_lines.len()
);
for lineseq in s_lines.window_iter() {
centerline.segments.push(boostvoronoi::prelude::Line::new(
boostvoronoi::prelude::Point {
x: lineseq.start.x().as_(),
y: lineseq.start.y().as_(),
},
boostvoronoi::prelude::Point {
x: lineseq.end.x().as_(),
y: lineseq.end.y().as_(),
},
));
}
} else {
#[cfg(feature = "console_debug")]
println!("no reduction");
for lineseq in lines.window_iter() {
centerline.segments.push(boostvoronoi::prelude::Line::new(
boostvoronoi::prelude::Point {
x: lineseq.start.x().as_(),
y: lineseq.start.y().as_(),
},
boostvoronoi::prelude::Point {
x: lineseq.end.x().as_(),
y: lineseq.end.y().as_(),
},
))
}
};
#[cfg(feature = "console_debug")]
println!("1set.len() {}", centerline.segments.len());
}
shape.centerline = Some(centerline);
}
Ok(())
}
#[cfg(feature = "obj-rs")]
fn add_data_from_file<I: InputType + Send, T>(
shared_data: Rc<RefCell<SharedData<I, T>>>,
filename: &str,
) -> Result<(), CenterlineError>
where
T: GenericVector3,
f32: AsPrimitive<<T as HasXY>::Scalar>,
f64: AsPrimitive<<T as HasXY>::Scalar>,
i32: AsPrimitive<<T as HasXY>::Scalar>,
{
let mut shared_data_bm = shared_data.borrow_mut();
let obj_set = {
let input = BufReader::new(File::open(filename).unwrap());
obj::raw::parse_obj(input).or_else(|x| Err(CenterlineError::ObjError(x.to_string())))?
};
let lines = centerline::remove_internal_edges(obj_set)?;
let lines = centerline::divide_into_shapes(lines.0, lines.1)?;
let mut total_aabb = <T as GenericVector3>::Aabb::default();
for l in lines.iter() {
total_aabb.add_aabb(&l.get_aabb());
}
#[cfg(feature = "console_debug")]
println!("total aabb b4:{:?}", total_aabb);
let (_plane, transform, voronoi_input_aabb) = {
let dimension: T::Scalar = (256.0 * (HF.min(WF) as f32 - 10.0)).as_();
centerline::get_transform::<T>(total_aabb, dimension)?
};
println!("Read from file:'{}', plane was {:?}", filename, _plane);
if let Some(inverse_transform) = transform.try_inverse() {
shared_data_bm.configuration.inverse_transform = inverse_transform;
} else {
shared_data_bm.configuration.inverse_transform = T::Affine::identity();
eprintln!(
"CenterlineError::CouldNotCalculateInverseMatrix {:?}",
transform
);
}
let mut raw_data: Vec<LineStringSet2<<T as GenericVector3>::Vector2>> = lines
.par_iter()
.map(|x| {
let mut xc = x.clone();
xc.apply(&|v| transform.transform_point3(v));
xc.copy_to_2d(Plane::XY)
})
.collect();
{
let truncate_float = |v: <T as GenericVector3>::Vector2| -> <T as GenericVector3>::Vector2 {
<T as GenericVector3>::Vector2::new_2d(v.x().round(), v.y().round())
};
for r in raw_data.iter_mut() {
r.apply(&truncate_float);
}
}
let raw_data: Vec<LineStringSet2<<T as GenericVector3>::Vector2>> = raw_data
.into_par_iter()
.map(|mut x| {
x.calculate_convex_hull().unwrap();
x
})
.collect();
{
let mut screen_aabb = <<T as GenericVector3>::Vector2 as GenericVector2>::Aabb::from_point(
<T as GenericVector3>::Vector2::new_2d(W.as_(), H.as_()),
);
screen_aabb.add_point(<T as GenericVector3>::Vector2::new_2d(
0.0.into(),
0.0.into(),
));
shared_data_bm.affine = SimpleAffine::new(&voronoi_input_aabb, &screen_aabb)?;
}
#[cfg(feature = "console_debug")]
println!("Started with {} shapes", raw_data.len());
let raw_data = centerline::consolidate_shapes(raw_data)?;
#[cfg(feature = "console_debug")]
println!("Reduced to {} shapes", raw_data.len());
shared_data_bm.shapes = Some(
raw_data
.into_par_iter()
.map(|x| Shape {
raw_data: x,
centerline: None,
simplified_centerline: None,
})
.collect(),
);
shared_data_bm.configuration.input_distance_dirty = true;
Ok(())
}