centerline/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2021,2023,2025 lacklustr@protonmail.com https://github.com/eadf
3
4// This file is part of the centerline crate.
5
6/*
7Copyright (c) 2021,2023 lacklustr@protonmail.com https://github.com/eadf
8
9Permission is hereby granted, free of charge, to any person obtaining a copy
10of this software and associated documentation files (the "Software"), to deal
11in the Software without restriction, including without limitation the rights
12to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13copies of the Software, and to permit persons to whom the Software is
14furnished to do so, subject to the following conditions:
15
16The above copyright notice and this permission notice shall be included in all
17copies or substantial portions of the Software.
18
19THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25SOFTWARE.
26
27or
28
29Copyright 2021,2023 lacklustr@protonmail.com https://github.com/eadf
30
31Licensed under the Apache License, Version 2.0 (the "License");
32you may not use this file except in compliance with the License.
33You may obtain a copy of the License at
34
35    http://www.apache.org/licenses/LICENSE-2.0
36
37Unless required by applicable law or agreed to in writing, software
38distributed under the License is distributed on an "AS IS" BASIS,
39WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40See the License for the specific language governing permissions and
41limitations under the License.
42*/
43#![deny(
44    rust_2018_compatibility,
45    rust_2018_idioms,
46    nonstandard_style,
47    unused,
48    future_incompatible,
49    non_camel_case_types,
50    unused_parens,
51    non_upper_case_globals,
52    unused_qualifications,
53    unused_results,
54    unused_imports,
55    unused_variables
56)]
57
58use boostvoronoi::prelude as BV;
59use linestring::LinestringError;
60use linestring::linestring_2d::{self, Line2, convex_hull};
61use linestring::linestring_3d::Line3;
62use linestring::prelude::{LineString2, LineString3};
63use ordered_float::OrderedFloat;
64use rayon::iter::{IntoParallelIterator, ParallelIterator};
65use rustc_hash::{FxHashMap, FxHashSet};
66use std::collections::VecDeque;
67use std::fmt::Debug;
68use std::line;
69use thiserror::Error;
70use vector_traits::approx::{AbsDiffEq, UlpsEq, ulps_eq};
71use vector_traits::num_traits::AsPrimitive;
72use vector_traits::num_traits::{self, real::Real};
73use vector_traits::prelude::GenericVector3;
74use vector_traits::prelude::*;
75
76#[macro_use]
77extern crate bitflags;
78
79#[derive(Error, Debug)]
80pub enum CenterlineError {
81    #[error("Something is wrong with the internal logic {0}")]
82    InternalError(String),
83
84    #[error("Something is wrong with the input data {0}")]
85    CouldNotCalculateInverseMatrix(String),
86
87    #[error("Your line-strings are self-intersecting {0}")]
88    SelfIntersectingData(String),
89
90    #[error("The input data is not 2D {0}")]
91    InputNotPLane(String),
92
93    #[error("Invalid data {0}")]
94    InvalidData(String),
95
96    #[error(transparent)]
97    BvError(#[from] BV::BvError),
98
99    #[error("Error from .obj file handling {0}")]
100    ObjError(String),
101
102    #[error(transparent)]
103    IoError(#[from] std::io::Error),
104
105    #[error(transparent)]
106    LinestringError(#[from] LinestringError),
107}
108
109bitflags! {
110    /// bit field defining various reasons for edge/vertex rejection
111    pub struct ColorFlag: BV::ColorType {
112        /// Edge is directly or indirectly connected to an INFINITE edge
113        const EXTERNAL     = 0b00000001;
114        /// Edge is secondary
115        const SECONDARY    = 0b00000010;
116        /// Edge has only one vertex
117        const INFINITE     = 0b00000100;
118        /// Edge does not pass the normalized edge<->segment dot product test
119        const DOTLIMIT     = 0b00001000;
120    }
121}
122
123type Vob32 = vob::Vob<u32>;
124
125#[doc(hidden)]
126pub trait GrowingVob {
127    /// Will create a new Vob and fill it with `false`
128    fn fill(initial_size: u32) -> Self;
129    /// Conditionally grow to fit required size, set ´bit´ to ´state´ value
130    fn set_grow(&mut self, bit: usize, state: bool);
131    /// get() with default value `false`
132    fn get_f(&self, bit: usize) -> bool;
133}
134
135impl<T: num_traits::PrimInt + Debug> GrowingVob for vob::Vob<T> {
136    #[inline]
137    fn fill(initial_size: u32) -> Self {
138        let mut v = Self::new_with_storage_type(0);
139        v.resize(initial_size as usize, false);
140        v
141    }
142
143    #[inline]
144    fn set_grow(&mut self, bit: usize, state: bool) {
145        if bit >= self.len() {
146            self.resize(bit, false);
147        }
148        let _ = self.set(bit, state);
149    }
150
151    #[inline]
152    fn get_f(&self, bit: usize) -> bool {
153        self.get(bit).unwrap_or(false)
154    }
155}
156
157#[derive(Debug)]
158struct Vertices {
159    id: u32,                      // index into the point3 list
160    connected_vertices: Vec<u32>, // list of other vertices this vertex is connected to
161    shape: Option<u32>,           // shape id
162}
163
164/// paints every connected vertex with the
165fn paint_every_connected_vertex(
166    vertices: &mut FxHashMap<u32, Vertices>,
167    already_painted: &mut Vob32,
168    vertex_id: u32,
169    color: u32,
170) -> Result<(), CenterlineError> {
171    let mut queue = VecDeque::<u32>::new();
172    queue.push_back(vertex_id);
173
174    while !queue.is_empty() {
175        // unwrap is safe here, we just checked that there are item in the queue
176        let current_vertex = queue.pop_front().unwrap();
177        if already_painted.get_f(current_vertex as usize) {
178            continue;
179        }
180
181        if let Some(vertex_obj) = vertices.get_mut(&current_vertex) {
182            if vertex_obj.shape.is_none() {
183                vertex_obj.shape = Some(color);
184                let _ = already_painted.set(current_vertex as usize, true);
185            } else {
186                // already painted for some reason
187                continue;
188            }
189            for &v in vertex_obj.connected_vertices.iter() {
190                if !already_painted.get_f(v as usize) {
191                    queue.push_back(v);
192                }
193            }
194        } else {
195            return Err(CenterlineError::InvalidData(format!(
196                "Vertex with id {} is not part of any geometry. Do you have disjoint vertices in your mesh? {}:{}",
197                current_vertex,
198                file!(),
199                line!()
200            )));
201        };
202    }
203    Ok(())
204}
205
206#[cfg(feature = "obj-rs")]
207#[allow(clippy::type_complexity)]
208/// Remove internal edges from a wavefront-obj object
209/// This requires the feature "impl-wavefront" to be active.
210pub fn remove_internal_edges<T: GenericVector3>(
211    obj: obj::raw::RawObj,
212) -> Result<(FxHashSet<(u32, u32)>, Vec<T>), CenterlineError>
213where
214    f32: AsPrimitive<<T as HasXY>::Scalar>,
215{
216    for p in obj.points.iter() {
217        // Ignore all points
218        println!("Ignored point:{p:?}");
219    }
220    let mut all_edges = FxHashSet::<(u32, u32)>::default();
221    let mut internal_edges = FxHashSet::<(u32, u32)>::default();
222
223    for i in 0..obj.lines.len() {
224        // keep all lines
225        //println!("Line:{:?}", obj.lines[i]);
226
227        let v: Vec<u32> = match &obj.lines[i] {
228            obj::raw::object::Line::P(a) => a.iter().map(|i| *i as u32).collect(),
229            obj::raw::object::Line::PT(a) => a.iter().map(|(a, _)| *a as u32).collect(),
230        };
231        //println!("Line Vec:{:?}", v);
232        let mut i1 = v.iter();
233
234        for i in v.iter().skip(1) {
235            let i1_v = *i1.next().unwrap();
236            let i2_v = *i;
237            let key = (*std::cmp::min(&i1_v, &i2_v), *std::cmp::max(&i1_v, &i2_v));
238            if all_edges.contains(&key) {
239                let _ = internal_edges.insert(key);
240            } else {
241                let _ = all_edges.insert(key);
242            }
243        }
244    }
245    //println!("Internal edges: {:?}", internal_edges);
246    //println!("All edges: {:?}", all_edges);
247    //println!("Vertices: {:?}", obj.positions);
248    for i in 0..obj.polygons.len() {
249        // keep edges without twins, drop the rest
250        let v = match &obj.polygons[i] {
251            obj::raw::object::Polygon::P(a) => {
252                //println!("P{:?}", a);
253                let mut v = a.clone();
254                v.push(a[0]);
255                v
256            }
257            obj::raw::object::Polygon::PT(a) => {
258                //println!("PT{:?}", a);
259                let mut v = a.iter().map(|x| x.0).collect::<Vec<usize>>();
260                v.push(a[0].0);
261                v
262            }
263            obj::raw::object::Polygon::PN(a) => {
264                //println!("PN{:?}", a);
265                let mut v = a.iter().map(|x| x.0).collect::<Vec<usize>>();
266                v.push(a[0].0);
267                v
268            }
269            obj::raw::object::Polygon::PTN(a) => {
270                //println!("PTN{:?}", a);
271                let mut v = a.iter().map(|x| x.0).collect::<Vec<usize>>();
272                v.push(a[0].0);
273                v
274            }
275        };
276
277        let mut i1 = v.iter();
278        for i in v.iter().skip(1) {
279            let i1_v = *i1.next().unwrap();
280            let i2_v = *i;
281            let key = (
282                *std::cmp::min(&i1_v, &i2_v) as u32,
283                *std::cmp::max(&i1_v, &i2_v) as u32,
284            );
285            if all_edges.contains(&key) {
286                let _ = internal_edges.insert(key);
287            } else {
288                let _ = all_edges.insert(key);
289            }
290        }
291    }
292    //println!("Internal edges: {:?}", internal_edges);
293    //println!("All edges: {:?}", all_edges);
294    //println!("Vertices: {:?}", obj.positions);
295
296    all_edges.retain(|x| !internal_edges.contains(x));
297
298    // all_edges should now contain the outline and none of the internal edges.
299    let vertices: Vec<T> = obj
300        .positions
301        .into_iter()
302        .map(|x| T::new_3d(x.0.as_(), x.1.as_(), x.2.as_()))
303        .collect();
304
305    Ok((all_edges, vertices))
306}
307
308/// Group input edges into connected shapes
309pub fn divide_into_shapes<T: GenericVector3>(
310    edge_set: FxHashSet<(u32, u32)>,
311    points: Vec<T>,
312) -> Result<Vec<LineStringSet3<T>>, CenterlineError> {
313    //println!("All edges: {:?}", all_edges);
314    // put all edges into a hashmap of Vertices, this will make it possible to
315    // arrange them in the order they are connected
316    let mut vertices = FxHashMap::<u32, Vertices>::default();
317    for (a, b) in edge_set.iter() {
318        debug_assert!(*a < points.len() as u32);
319        debug_assert!(*b < points.len() as u32);
320
321        vertices
322            .entry(*a)
323            .or_insert_with_key(|key| Vertices {
324                id: *key,
325                connected_vertices: Vec::<u32>::new(),
326                shape: None,
327            })
328            .connected_vertices
329            .push(*b);
330
331        vertices
332            .entry(*b)
333            .or_insert_with_key(|key| Vertices {
334                id: *key,
335                connected_vertices: Vec::<u32>::new(),
336                shape: None,
337            })
338            .connected_vertices
339            .push(*a);
340    }
341    //println!("Vertices: {:?}", vertices.iter().map(|x|x.1.id).collect::<Vec<usize>>());
342    // Do a search on one vertex, paint all connected vertices with the same number.
343    let mut unique_shape_id_generator = 0..u32::MAX;
344
345    let mut already_painted = Vob32::fill(points.len() as u32);
346    for vertex_id in 0..vertices.len() as u32 {
347        if already_painted.get_f(vertex_id as usize) {
348            continue;
349        }
350        // found an un-painted vertex
351        paint_every_connected_vertex(
352            &mut vertices,
353            &mut already_painted,
354            vertex_id,
355            unique_shape_id_generator.next().unwrap(),
356        )?;
357    }
358    let highest_shape_id_plus_one = unique_shape_id_generator.next().unwrap();
359    if highest_shape_id_plus_one == 0 {
360        return Err(CenterlineError::InternalError(format!(
361            "Could not find any shapes to separate. {}:{}",
362            file!(),
363            line!()
364        )));
365    }
366
367    // Spit all detected connected vertices into separate sets.
368    // i.e. every vertex with the same color goes into the same set.
369    let mut shape_separation = Vec::<FxHashMap<u32, Vertices>>::new();
370    for current_shape in 0..highest_shape_id_plus_one {
371        if vertices.is_empty() {
372            println!("vertices:{vertices:?}");
373            println!("current_shape:{current_shape}");
374            println!("shape_separation:{shape_separation:?}");
375
376            return Err(CenterlineError::InternalError(format!(
377                "Could not separate all shapes, ran out of vertices. {}:{}",
378                file!(),
379                line!()
380            )));
381        }
382        /*#[cfg(feature = "extract_if")] // or drain_filter
383        {
384            let drained = vertices
385                .extract_if(|_, x| {
386                    if let Some(shape) = x.shape {
387                        shape == current_shape
388                    } else {
389                        false
390                    }
391                })
392                .collect();
393            shape_separation.push(drained);
394        }
395        #[cfg(not(feature = "extract_if"))]*/
396        {
397            // inefficient version of drain_filter for +stable
398            let mut drained = FxHashMap::<u32, Vertices>::default();
399            let mut new_vertices = FxHashMap::<u32, Vertices>::default();
400            for (x0, x1) in vertices.into_iter() {
401                if x1.shape == Some(current_shape) {
402                    let _ = drained.insert(x0, x1);
403                } else {
404                    let _ = new_vertices.insert(x0, x1);
405                };
406            }
407            vertices = new_vertices;
408            shape_separation.push(drained);
409        }
410    }
411    drop(vertices);
412    // now we have a list of groups of vertices, each group are connected by edges.
413
414    let shape_separation = shape_separation;
415
416    // Create lists of linestrings3 by walking the edges of each vertex set.
417    shape_separation
418        .into_par_iter()
419        .map(|rvi| -> Result<LineStringSet3<T>, CenterlineError> {
420            if rvi.is_empty() {
421                return Err(CenterlineError::InternalError(
422                    format!("rvi.is_empty() Seems like the shape separation failed. {}:{}", file!(),line!()),
423                ));
424            }
425            let mut loops = 0_usize;
426
427            let mut rvs = LineStringSet3::<T>::with_capacity(rvi.len());
428            let mut als = Vec::<T>::with_capacity(rvi.len());
429
430            let started_with: u32 = rvi.iter().next().unwrap().1.id;
431            let mut prev: u32;
432            let mut current: u32 = started_with;
433            let mut next: u32 = started_with;
434            let mut first_loop = true;
435
436            loop {
437                prev = current;
438                current = next;
439                if let Some(current_vertex) = rvi.get(&current) {
440                    als.push(points[current as usize]);
441
442                    //assert_eq!(newV.edges.len(),2);
443                    next = *current_vertex.connected_vertices.iter().find(|x| **x != prev).ok_or_else(||{
444                        println!("current_vertex.connected_vertices {:?}", current_vertex.connected_vertices);
445                        CenterlineError::InvalidData(
446                            "Could not find next vertex. All lines must form connected loops (loops connected to other loops are not supported,yet)".to_string(),
447                        )},
448                    )?;
449                } else {
450                    break;
451                }
452                // allow the start point to be added twice (in case of a loop)
453                if !first_loop && current == started_with {
454                    break;
455                }
456                first_loop = false;
457                loops += 1;
458                if loops > rvi.len() + 1 {
459                    return Err(CenterlineError::InvalidData(
460                        "It seems like one (or more) of the line strings does not form a connected loop.(loops connected to other loops are not supported,yet)"
461                            .to_string(),
462                    ));
463                }
464            }
465            if als.last() != als.first() {
466                println!(
467                    "Linestring is not connected ! {:?} {:?}",
468                    als.first(),
469                    als.last()
470                );
471                println!("Linestring is not connected ! {als:?}");
472            }
473            rvs.push(als);
474            Ok(rvs)
475        })
476        .collect()
477}
478
479#[allow(clippy::type_complexity)]
480#[inline(always)]
481/// Calculate an affine transform that will center, flip plane to XY, and scale the arbitrary shape
482/// so that it will fill the screen.
483/// 'desired_voronoi_dimension' is the maximum axis length of the voronoi input data aabb
484/// boost_voronoi uses integers as input so float vertices have to be scaled up substantially to
485/// maintain numerical precision
486pub fn get_transform<T: GenericVector3>(
487    total_aabb: <T as GenericVector3>::Aabb,
488    desired_voronoi_dimension: T::Scalar,
489) -> Result<(Plane, T::Affine, <T::Vector2 as GenericVector2>::Aabb), CenterlineError>
490where
491    T::Scalar: ordered_float::FloatCore,
492{
493    get_transform_relaxed::<T>(
494        total_aabb,
495        desired_voronoi_dimension,
496        T::Scalar::default_epsilon(),
497        T::Scalar::default_max_ulps(),
498    )
499}
500
501#[allow(clippy::type_complexity)]
502/// Calculate an affine transform that will center, flip plane to XY, and scale the arbitrary shape
503/// so that it will fill the screen.
504/// 'desired_voronoi_dimension' is the maximum axis length of the voronoi input data aabb
505/// boost_voronoi uses integers as input so float vertices have to be scaled up substantially to
506/// maintain numerical precision
507pub fn get_transform_relaxed<T: GenericVector3>(
508    total_aabb: <T as GenericVector3>::Aabb,
509    desired_voronoi_dimension: T::Scalar,
510    epsilon: T::Scalar,
511    max_ulps: u32,
512) -> Result<(Plane, T::Affine, <T::Vector2 as GenericVector2>::Aabb), CenterlineError>
513where
514    T::Scalar: ordered_float::FloatCore,
515{
516    if total_aabb.is_empty() {
517        return Err(CenterlineError::InvalidData("Aabb was empty".to_string()));
518    }
519
520    let plane = if let Some(plane) = total_aabb.get_plane_relaxed(epsilon, max_ulps) {
521        plane
522    } else {
523        return Err(CenterlineError::InputNotPLane(format!("{total_aabb:?}")));
524    };
525    let center = total_aabb.center();
526    let (_min, _max, delta) = total_aabb.extents();
527    #[cfg(feature = "console_debug")]
528    {
529        println!("get_transform_relaxed desired_voronoi_dimension:{desired_voronoi_dimension:?}");
530        println!(
531            "Input data AABB: Center:({:?}, {:?}, {:?})",
532            center.x(),
533            center.y(),
534            center.z(),
535        );
536        println!(
537            "                   high:({:?}, {:?}, {:?})",
538            _max.x(),
539            _max.y(),
540            _max.z(),
541        );
542        println!(
543            "                    low:({:?}, {:?}, {:?})",
544            _min.x(),
545            _min.y(),
546            _min.z(),
547        );
548        println!(
549            "                  delta:({:?}, {:?}, {:?})",
550            delta.x(),
551            delta.y(),
552            delta.z(),
553        );
554    }
555    let total_transform: T::Affine = {
556        let scale: T::Scalar = desired_voronoi_dimension
557            / std::cmp::max(
558                std::cmp::max(OrderedFloat(delta.x()), OrderedFloat(delta.y())),
559                OrderedFloat(delta.z()),
560            )
561            .into_inner();
562        let plane_transform = T::Affine::from_plane_to_xy(plane);
563        let center_transform = T::Affine::from_translation(-center);
564        let scale_transform = T::Affine::from_scale(T::splat(scale));
565        scale_transform * center_transform * plane_transform
566    };
567
568    // transformed values
569    let voronoi_input_aabb = <<T as GenericVector3>::Vector2 as GenericVector2>::Aabb::from_corners(
570        total_transform.transform_point3(total_aabb.min()).to_2d(),
571        total_transform.transform_point3(total_aabb.max()).to_2d(),
572    );
573
574    #[cfg(feature = "console_debug")]
575    {
576        let (t_low0, t_high0, t_delta0) = voronoi_input_aabb.extents();
577        let t_center0 = voronoi_input_aabb.center();
578        println!(
579            "Voronoi input AABB: Center:({:?}, {:?})",
580            t_center0.x(),
581            t_center0.y(),
582        );
583        println!(
584            "                   high:({:?}, {:?})",
585            t_high0.x(),
586            t_high0.y(),
587        );
588        println!(
589            "                    low:({:?}, {:?})",
590            t_low0.x(),
591            t_low0.y(),
592        );
593        println!(
594            "                  delta:({:?}, {:?})",
595            t_delta0.x(),
596            t_delta0.y(),
597        );
598    }
599
600    Ok((plane, total_transform, voronoi_input_aabb))
601}
602
603/// try to consolidate shapes. If one AABB and convex hull (a) totally engulfs another shape (b)
604/// we put shape (b) inside (a)
605pub fn consolidate_shapes<T: GenericVector2>(
606    mut raw_data: Vec<LineStringSet2<T>>,
607) -> Result<Vec<LineStringSet2<T>>, CenterlineError>
608where
609    T::Scalar: UlpsEq,
610{
611    //for shape in raw_data.iter().enumerate() {
612    //    println!("Shape #{} aabb:{:?}", shape.0, shape.1.get_aabb());
613    //}
614    'outer_loop: loop {
615        // redo *every* test until nothing else can be done
616        for i in 0..raw_data.len() {
617            for j in i + 1..raw_data.len() {
618                //println!("testing #{} vs #{}", i, j);
619                if raw_data[i]
620                    .get_aabb()
621                    .contains_aabb_inclusive(&raw_data[j].get_aabb())
622                    && convex_hull::contains_convex_hull(
623                        raw_data[i].get_convex_hull().as_ref().unwrap(),
624                        raw_data[j].get_convex_hull().as_ref().unwrap(),
625                    )
626                {
627                    //println!("#{} contains #{}", i, j);
628                    // move stuff from j to i via a temp because of borrow checker
629                    let mut stolen_line_j =
630                        LineStringSet2::steal_from(raw_data.get_mut(j).unwrap());
631                    let line_i = raw_data.get_mut(i).unwrap();
632                    line_i.take_from_internal(&mut stolen_line_j)?;
633                    let _ = raw_data.remove(j);
634                    continue 'outer_loop;
635                } else if raw_data[j]
636                    .get_aabb()
637                    .contains_aabb_inclusive(&raw_data[i].get_aabb())
638                    && convex_hull::contains_convex_hull(
639                        raw_data[j].get_convex_hull().as_ref().unwrap(),
640                        raw_data[i].get_convex_hull().as_ref().unwrap(),
641                    )
642                {
643                    //println!("#{} contains #{}", j, i);
644                    // move stuff from i to j via a temp because of borrow checker
645                    let mut stolen_line_i =
646                        LineStringSet2::steal_from(raw_data.get_mut(i).unwrap());
647                    let line_j = raw_data.get_mut(j).unwrap();
648                    line_j.take_from_internal(&mut stolen_line_i)?;
649                    let _ = raw_data.remove(i);
650                    continue 'outer_loop;
651                }
652            }
653        }
654        break 'outer_loop;
655    }
656    Ok(raw_data)
657}
658
659/// Center line calculation object.
660/// It: * calculates the segmented voronoi diagram.
661///     * Filter out voronoi edges based on the angle to input geometry.
662///     * Collects connected edges into line strings and line segments.
663///     * Performs line simplification on those line strings.
664pub struct Centerline<I: BV::InputType, T>
665where
666    T: GenericVector3,
667{
668    /// the input data to the voronoi diagram
669    pub segments: Vec<BV::Line<I>>,
670    /// the voronoi diagram itself
671    pub diagram: BV::Diagram,
672    /// the individual two-point edges
673    pub lines: Option<Vec<Line3<T>>>,
674    /// concatenated connected edges
675    pub line_strings: Option<Vec<Vec<T>>>,
676
677    /// bit field defining edges rejected by EXTERNAL or INFINITE
678    rejected_edges: Option<Vob32>,
679    /// bit field defining edges rejected by 'rejected_edges' + dot test
680    ignored_edges: Option<Vob32>,
681
682    #[cfg(feature = "console_debug")]
683    pub debug_edges: Option<FxHashMap<usize, [T::Scalar; 4]>>,
684}
685
686impl<I: BV::InputType, T3: GenericVector3> Default for Centerline<I, T3> {
687    /// Creates a Centerline container with a set of segments
688    fn default() -> Self {
689        Self {
690            diagram: BV::Diagram::default(),
691            segments: Vec::<BV::Line<I>>::default(),
692            lines: Some(Vec::<Line3<T3>>::new()),
693            line_strings: Some(Vec::<Vec<T3>>::new()),
694            rejected_edges: None,
695            ignored_edges: None,
696            #[cfg(feature = "console_debug")]
697            debug_edges: None,
698        }
699    }
700}
701
702impl<I: BV::InputType, T3: GenericVector3> Centerline<I, T3>
703where
704    I: AsPrimitive<T3::Scalar>,
705    f64: AsPrimitive<T3::Scalar>,
706{
707    /// Creates a Centerline container with a set of segments
708    pub fn with_segments(segments: Vec<BV::Line<I>>) -> Self {
709        Self {
710            diagram: BV::Diagram::default(),
711            segments,
712            lines: Some(Vec::<Line3<T3>>::new()),
713            line_strings: Some(Vec::<Vec<T3>>::new()),
714            rejected_edges: None,
715            ignored_edges: None,
716            #[cfg(feature = "console_debug")]
717            debug_edges: None,
718        }
719    }
720
721    /// builds the voronoi diagram and filter out infinite edges and other 'outside' geometry
722    pub fn build_voronoi(&mut self) -> Result<(), CenterlineError> {
723        self.diagram = {
724            #[cfg(feature = "console_debug")]
725            {
726                print!("build_voronoi()-> input segments:[");
727                for s in self.segments.iter() {
728                    print!("[{},{},{},{}],", s.start.x, s.start.y, s.end.x, s.end.y);
729                }
730                println!("];");
731            }
732            BV::Builder::default()
733                .with_segments(self.segments.iter())?
734                .build()?
735        };
736        self.reject_external_edges()?;
737        #[cfg(feature = "console_debug")]
738        println!(
739            "build_voronoi()-> Rejected edges:{:?} {}",
740            self.rejected_edges.as_ref(),
741            &self.rejected_edges.as_ref().unwrap().get_f(0)
742        );
743        Ok(())
744    }
745
746    /// perform the angle-to-geometry test and filter out some edges.
747    /// Collect the rest of the edges into connected line-strings and line segments.
748    #[allow(clippy::type_complexity)]
749    pub fn calculate_centerline(
750        &mut self,
751        cos_angle: T3::Scalar,
752        discrete_limit: T3::Scalar,
753        ignored_regions: Option<
754            &Vec<(
755                <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
756                Vec<<T3 as GenericVector3>::Vector2>,
757            )>,
758        >,
759    ) -> Result<(), CenterlineError> {
760        self.angle_test(cos_angle)?;
761        if let Some(ignored_regions) = ignored_regions {
762            self.traverse_edges(discrete_limit, ignored_regions)?;
763        } else {
764            let ignored_regions = Vec::<(
765                <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
766                Vec<T3::Vector2>,
767            )>::with_capacity(0);
768            self.traverse_edges(discrete_limit, &ignored_regions)?;
769        }
770        Ok(())
771    }
772
773    /// Collects lines and linestrings from the centerline.
774    /// This version of calculate_centerline() tries to keep as many edges as possible.
775    /// The intention is to use the data for mesh generation.
776    /// TODO: make this return a true mesh
777    #[allow(clippy::type_complexity)]
778    pub fn calculate_centerline_mesh(
779        &mut self,
780        discrete_limit: T3::Scalar,
781        ignored_regions: Option<
782            &Vec<(
783                <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
784                Vec<T3::Vector2>,
785            )>,
786        >,
787    ) -> Result<(), CenterlineError> {
788        self.ignored_edges = self.rejected_edges.clone();
789
790        if let Some(ignored_regions) = ignored_regions {
791            self.traverse_cells(discrete_limit, ignored_regions)?;
792        } else {
793            let ignored_regions = Vec::<(
794                <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
795                Vec<T3::Vector2>,
796            )>::with_capacity(0);
797            self.traverse_cells(discrete_limit, &ignored_regions)?;
798        }
799        Ok(())
800    }
801
802    /// returns a copy of the ignored edges bit field
803    pub fn ignored_edges(&self) -> Option<Vob32> {
804        self.ignored_edges.to_owned()
805    }
806
807    /// returns a copy of the rejected edges bit field
808    pub fn rejected_edges(&self) -> Option<Vob32> {
809        self.rejected_edges.to_owned()
810    }
811
812    pub fn retrieve_point(&self, cell_id: BV::CellIndex) -> Result<BV::Point<I>, CenterlineError> {
813        let (index, category) = self.diagram.cell(cell_id)?.source_index_2();
814        let idx = index.usize();
815        match category {
816            BV::SourceCategory::SinglePoint => panic!("No points in the input data"),
817            BV::SourceCategory::SegmentStart => Ok(self.segments[idx].start),
818            BV::SourceCategory::Segment | BV::SourceCategory::SegmentEnd => {
819                Ok(self.segments[idx].end)
820            }
821        }
822    }
823
824    pub fn retrieve_segment(&self, cell_id: BV::CellIndex) -> Result<BV::Line<I>, CenterlineError> {
825        Ok(self.segments[self.diagram.cell(cell_id)?.source_index().usize()])
826    }
827
828    /// returns a reference to the internal voronoi diagram
829    pub fn diagram(&self) -> &BV::Diagram {
830        &self.diagram
831    }
832
833    /// Mark infinite edges and their adjacent edges as EXTERNAL.
834    fn reject_external_edges(&mut self) -> Result<(), CenterlineError> {
835        let mut rejected_edges = Vob32::fill(self.diagram.edges().len() as u32);
836
837        for edge in self.diagram.edges().iter() {
838            let edge_id = edge.id();
839            if edge.is_secondary() {
840                let _ = rejected_edges.set(edge_id.usize(), true);
841                //self.diagram
842                //    .edge_or_color(edge_id, ColorFlag::SECONDARY.bits)?;
843                let twin_id = self.diagram.edge_get_twin(edge_id)?;
844                //self.diagram
845                //    .edge_or_color(twin_id, ColorFlag::SECONDARY.bits);
846                let _ = rejected_edges.set(twin_id.usize(), true);
847            }
848            if !self.diagram.edge_is_finite(edge_id)? {
849                self.mark_connected_edges(edge_id, &mut rejected_edges, true)?;
850                let _ = rejected_edges.set(edge_id.usize(), true);
851            }
852        }
853
854        self.rejected_edges = Some(rejected_edges);
855        Ok(())
856    }
857
858    /// Reject edges that does not pass the angle test.
859    /// It iterates over all cells, looking for vertices that are identical to the
860    /// input segment endpoints.
861    /// It then look at edges connected to that vertex and test if the dot product
862    /// between the normalized segment vector and normalized edge vector exceeds
863    /// a predefined value.
864    /// TODO: there must be a quicker way to get this information from the voronoi diagram
865    /// maybe mark each vertex identical to input points..
866    fn angle_test(&mut self, cos_angle: T3::Scalar) -> Result<(), CenterlineError> {
867        let mut ignored_edges = self.rejected_edges.clone().unwrap();
868
869        for cell in self.diagram.cells().iter() {
870            let cell_id = cell.id();
871
872            if !cell.contains_segment() {
873                continue;
874            }
875            let segment = self.retrieve_segment(cell_id)?;
876            let point0 = T3::Vector2::new_2d(segment.start.x.as_(), segment.start.y.as_());
877            let point1 = T3::Vector2::new_2d(segment.end.x.as_(), segment.end.y.as_());
878
879            if let Some(incident_e) = cell.get_incident_edge() {
880                //println!("incident_e {:?}", incident_e);
881                let mut e = incident_e;
882                loop {
883                    e = self.diagram.edge_get_next(e)?;
884
885                    if !ignored_edges.get_f(e.usize()) {
886                        // all infinite edges should be rejected at this point, so
887                        // all edges should contain a vertex0 and vertex1
888                        if let Some(Ok(vertex0)) = self
889                            .diagram
890                            .edge_get_vertex0(e)?
891                            .map(|x| self.diagram.vertex(x))
892                        {
893                            let vertex0 = T3::Vector2::new_2d(vertex0.x().as_(), vertex0.y().as_());
894                            if let Some(Ok(vertex1)) = self
895                                .diagram
896                                .edge_get_vertex1(e)?
897                                .map(|x| self.diagram.vertex(x))
898                            {
899                                let vertex1 =
900                                    T3::Vector2::new_2d(vertex1.x().as_(), vertex1.y().as_());
901                                let _ = self.angle_test_6(
902                                    cos_angle,
903                                    &mut ignored_edges,
904                                    e,
905                                    vertex0,
906                                    vertex1,
907                                    point0,
908                                    point1,
909                                )? || self.angle_test_6(
910                                    cos_angle,
911                                    &mut ignored_edges,
912                                    e,
913                                    vertex0,
914                                    vertex1,
915                                    point1,
916                                    point0,
917                                )? || self.angle_test_6(
918                                    cos_angle,
919                                    &mut ignored_edges,
920                                    e,
921                                    vertex1,
922                                    vertex0,
923                                    point0,
924                                    point1,
925                                )? || self.angle_test_6(
926                                    cos_angle,
927                                    &mut ignored_edges,
928                                    e,
929                                    vertex1,
930                                    vertex0,
931                                    point1,
932                                    point0,
933                                )?;
934                            }
935                        }
936                    }
937
938                    if e == incident_e {
939                        break;
940                    }
941                }
942            }
943        }
944        self.ignored_edges = Some(ignored_edges);
945        Ok(())
946    }
947
948    /// set the edge as rejected if it fails the dot product test
949    #[allow(clippy::too_many_arguments)]
950    fn angle_test_6(
951        &self,
952        cos_angle: T3::Scalar,
953        ignored_edges: &mut Vob32,
954        edge_id: BV::EdgeIndex,
955        vertex0: T3::Vector2,
956        vertex1: T3::Vector2,
957        s_point_0: T3::Vector2,
958        s_point_1: T3::Vector2,
959    ) -> Result<bool, CenterlineError> {
960        if ulps_eq!(vertex0.x(), s_point_0.x()) && ulps_eq!(vertex0.y(), s_point_0.y()) {
961            // todo better to compare to the square of the dot product, fewer operations.
962            let segment_v = (s_point_1 - s_point_0).normalize();
963            let vertex_v = (vertex1 - vertex0).normalize();
964            if segment_v.dot(vertex_v).abs() < cos_angle {
965                let twin = self.diagram.edge_get_twin(edge_id)?;
966                let _ = ignored_edges.set(twin.usize(), true);
967                let _ = ignored_edges.set(edge_id.usize(), true);
968                return Ok(true);
969            }
970        }
971        Ok(false)
972    }
973
974    /// Marks this edge and all other edges connecting to it via vertex1.
975    /// Line iteration stops when connecting to input geometry.
976    /// if 'initial' is set to true it will search both ways, edge and the twin edge, but only
977    /// for the first edge.
978    fn mark_connected_edges(
979        &self,
980        edge_id: BV::EdgeIndex,
981        marked_edges: &mut Vob32,
982        initial: bool,
983    ) -> Result<(), CenterlineError> {
984        if marked_edges.get_f(edge_id.usize()) {
985            return Ok(());
986        }
987
988        let mut initial = initial;
989        let mut queue = VecDeque::<BV::EdgeIndex>::new();
990        queue.push_back(edge_id);
991
992        'outer: while !queue.is_empty() {
993            // unwrap is safe since we just checked !queue.is_empty()
994            let edge_id = queue.pop_front().unwrap();
995            if marked_edges.get_f(edge_id.usize()) {
996                initial = false;
997                continue 'outer;
998            }
999
1000            let v1 = self.diagram.edge_get_vertex1(edge_id)?;
1001            if self.diagram.edge_get_vertex0(edge_id)?.is_some() && v1.is_none() {
1002                // this edge leads to nowhere, stop following line
1003                let _ = marked_edges.set(edge_id.usize(), true);
1004                initial = false;
1005                continue 'outer;
1006            }
1007            let _ = marked_edges.set(edge_id.usize(), true);
1008
1009            #[allow(unused_assignments)]
1010            if initial {
1011                initial = false;
1012                queue.push_back(self.diagram.edge_get_twin(edge_id)?);
1013            } else {
1014                let _ = marked_edges.set(self.diagram.edge_get_twin(edge_id)?.usize(), true);
1015            }
1016
1017            if v1.is_none() || !self.diagram.edge(edge_id)?.is_primary() {
1018                // stop traversing this line if vertex1 is not found or if the edge is not primary
1019                initial = false;
1020                continue 'outer;
1021            }
1022            // v1 is always Some from this point on
1023            if let Some(v1) = v1 {
1024                let v1 = self.diagram.vertex(v1)?;
1025                if v1.is_site_point() {
1026                    // break line iteration on site points
1027                    initial = false;
1028                    continue 'outer;
1029                }
1030                //self.reject_vertex(v1, color);
1031                let mut this_edge = v1.get_incident_edge()?;
1032                let v_incident_edge = this_edge;
1033                loop {
1034                    if !marked_edges.get_f(this_edge.usize()) {
1035                        queue.push_back(this_edge);
1036                    }
1037                    this_edge = self.diagram.edge_rot_next(this_edge).ok_or_else(|| {
1038                        CenterlineError::InternalError(format!("Edge disconnected {this_edge:?}"))
1039                    })?;
1040                    if this_edge == v_incident_edge {
1041                        break;
1042                    }
1043                }
1044            }
1045            initial = false;
1046        }
1047        Ok(())
1048    }
1049
1050    /// returns true if *all* of the 'edges' are contained inside one of the 'ignored_regions'
1051    #[allow(clippy::type_complexity)]
1052    fn edges_are_inside_ignored_region(
1053        &self,
1054        edges: &Vob32,
1055        ignored_regions: &[(
1056            <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
1057            Vec<T3::Vector2>,
1058        )],
1059    ) -> Result<bool, CenterlineError> {
1060        let is_inside_region = |edge: BV::EdgeIndex,
1061                                region: &(
1062            <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
1063            Vec<T3::Vector2>,
1064        )|
1065         -> Result<bool, CenterlineError> {
1066            let v0 = self.diagram.edge_get_vertex0(edge)?.unwrap();
1067            let v0 = self.diagram.vertex(v0).unwrap();
1068            let v0 = T3::Vector2::new_2d(v0.x().as_(), v0.y().as_());
1069
1070            let v1 = self.diagram.edge_get_vertex0(edge)?.unwrap();
1071            let v1 = self.diagram.vertex(v1).unwrap();
1072            let v1 = T3::Vector2::new_2d(v1.x().as_(), v1.y().as_());
1073            Ok(region.0.contains_point_inclusive(v0)
1074                && region.0.contains_point_inclusive(v1)
1075                && convex_hull::contains_point_inclusive(&region.1, v0)
1076                && convex_hull::contains_point_inclusive(&region.1, v1))
1077        };
1078
1079        'outer: for region in ignored_regions.iter().enumerate() {
1080            for edge in edges.iter_set_bits(..) {
1081                if !is_inside_region(self.diagram().edge_index_unchecked(edge), region.1)? {
1082                    //println!("edge: {:?} is not inside region {}, skipping", edge, region.0);
1083                    continue 'outer;
1084                }
1085            }
1086            //println!("edges were all inside region {}", region.0);
1087            return Ok(true);
1088        }
1089        Ok(false)
1090    }
1091
1092    /// move across each edge and sample the lines and arcs
1093    #[allow(clippy::type_complexity)]
1094    fn traverse_edges(
1095        &mut self,
1096        maxdist: T3::Scalar,
1097        ignored_regions: &[(
1098            <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
1099            Vec<T3::Vector2>,
1100        )],
1101    ) -> Result<(), CenterlineError> {
1102        // de-couple self and containers
1103        let mut lines = self.lines.take().ok_or_else(|| {
1104            CenterlineError::InternalError(format!(
1105                "traverse_edges(): could not take lines. {}:{}",
1106                file!(),
1107                line!()
1108            ))
1109        })?;
1110        let mut linestrings = self.line_strings.take().ok_or_else(|| {
1111            CenterlineError::InternalError(format!(
1112                "traverse_edges(): could not take linestrings. {}:{}",
1113                file!(),
1114                line!()
1115            ))
1116        })?;
1117
1118        let mut ignored_edges = self
1119            .ignored_edges
1120            .take()
1121            .unwrap_or_else(|| Vob32::fill(self.diagram.edges().len() as u32));
1122
1123        #[cfg(feature = "console_debug")]
1124        let edge_lines = FxHashMap::<usize, [T3::Scalar; 4]>::default();
1125
1126        linestrings.clear();
1127        lines.clear();
1128
1129        if !ignored_regions.is_empty() {
1130            // find the groups of connected edges in this shape
1131            let mut searched_edges_v = Vec::<Vob32>::new();
1132            let mut searched_edges_s = ignored_edges.clone();
1133            for it in self.diagram.edges().iter() {
1134                // can not use iter().filter() because of the borrow checker
1135                if searched_edges_s.get_f(it.id().usize()) {
1136                    continue;
1137                }
1138                let mut edges = Vob32::fill(self.diagram.edges().len() as u32);
1139                self.mark_connected_edges(it.id(), &mut edges, true)?;
1140                let _ = searched_edges_s.or(&edges);
1141                searched_edges_v.push(edges);
1142            }
1143
1144            for edges in searched_edges_v.iter() {
1145                if self.edges_are_inside_ignored_region(edges, ignored_regions)? {
1146                    //println!("edges: are inside ignored region {:?}", edges);
1147                    let _ = ignored_edges.or(edges);
1148                    continue;
1149                } else {
1150                    //println!("edges: are NOT inside ignored regions {:?}", edges);
1151                }
1152            }
1153            // ignored_edges are now filled with the rejected edges
1154        }
1155
1156        let mut used_edges = ignored_edges.clone();
1157
1158        for it in self.diagram.edges().iter().enumerate() {
1159            // can not use iter().filter() because of the borrow checker
1160            if used_edges.get_f(it.0) {
1161                continue;
1162            }
1163            let edge_id = self.diagram().edge_index_unchecked(it.0);
1164
1165            self.traverse_edge(
1166                edge_id,
1167                false,
1168                &ignored_edges,
1169                &mut used_edges,
1170                &mut lines,
1171                &mut linestrings,
1172                maxdist,
1173            )?;
1174        }
1175
1176        // loop over each edge again, make sure they were all used or properly ignored.
1177        for it in self.diagram.edges().iter().enumerate() {
1178            // can not use iter().filter() because of the borrow checker
1179            if used_edges.get_f(it.0) {
1180                continue;
1181            }
1182            let edge_id = self.diagram().edge_index_unchecked(it.0);
1183            #[cfg(feature = "console_debug")]
1184            println!("Did not use all edges, forcing the use of edge:{edge_id:?}",);
1185
1186            self.traverse_edge(
1187                edge_id,
1188                true,
1189                &ignored_edges,
1190                &mut used_edges,
1191                &mut lines,
1192                &mut linestrings,
1193                maxdist,
1194            )?;
1195        }
1196
1197        #[cfg(feature = "console_debug")]
1198        {
1199            println!("Got {} single lines", lines.len());
1200            println!("Got {} linestrings", linestrings.len());
1201            println!(
1202                "     ignored_edges {:?}",
1203                ignored_edges
1204                    .iter_storage()
1205                    .map(|s| format!("{s:#02X} ")[2..].to_string())
1206                    .collect::<String>()
1207            );
1208            println!(
1209                "        used_edges {:?}",
1210                used_edges
1211                    .iter_storage()
1212                    .map(|s| format!("{s:#02X} ")[2..].to_string())
1213                    .collect::<String>()
1214            );
1215        }
1216        // put the containers back
1217        self.lines = Some(lines);
1218        self.line_strings = Some(linestrings);
1219        #[cfg(feature = "console_debug")]
1220        {
1221            self.debug_edges = Some(edge_lines);
1222        }
1223        Ok(())
1224    }
1225
1226    /// move across each cell and sample the lines and arcs
1227    #[allow(clippy::type_complexity)]
1228    fn traverse_cells(
1229        &mut self,
1230        max_dist: T3::Scalar,
1231        ignored_regions: &[(
1232            <<T3 as GenericVector3>::Vector2 as GenericVector2>::Aabb,
1233            Vec<T3::Vector2>,
1234        )],
1235    ) -> Result<(), CenterlineError> {
1236        // de-couple self and containers
1237        let mut lines = self.lines.take().ok_or_else(|| {
1238            CenterlineError::InternalError(format!(
1239                "traverse_edges(): could not take lines. {}:{}",
1240                file!(),
1241                line!()
1242            ))
1243        })?;
1244        let mut linestrings = self.line_strings.take().ok_or_else(|| {
1245            CenterlineError::InternalError(format!(
1246                "traverse_edges(): could not take linestrings. {}:{}",
1247                file!(),
1248                line!()
1249            ))
1250        })?;
1251
1252        let mut ignored_edges = self.ignored_edges.take().unwrap_or_else(|| Vob32::fill(0));
1253
1254        #[cfg(feature = "console_debug")]
1255        let edge_lines = FxHashMap::<usize, [T3::Scalar; 4]>::default();
1256
1257        linestrings.clear();
1258        lines.clear();
1259
1260        if !ignored_regions.is_empty() {
1261            // find the groups of connected edges in this shape
1262            let mut searched_edges_v = Vec::<Vob32>::new();
1263            let mut searched_edges_s = ignored_edges.clone();
1264            for it in self.diagram.edges().iter().enumerate() {
1265                // can not use iter().filter() because of the borrow checker
1266                if searched_edges_s.get_f(it.0) {
1267                    continue;
1268                }
1269                let mut edges = Vob32::fill(self.diagram.edges().len() as u32);
1270                self.mark_connected_edges(
1271                    self.diagram.edge_index_unchecked(it.0),
1272                    &mut edges,
1273                    true,
1274                )?;
1275                let _ = searched_edges_s.or(&edges);
1276                searched_edges_v.push(edges);
1277            }
1278
1279            for edges in searched_edges_v.iter() {
1280                if self.edges_are_inside_ignored_region(edges, ignored_regions)? {
1281                    //println!("edges: are inside ignored region {:?}", edges);
1282                    let _ = ignored_edges.or(edges);
1283                    continue;
1284                } else {
1285                    //println!("edges: are NOT inside ignored regions {:?}", edges);
1286                }
1287            }
1288            // ignored_edges are now filled with the rejected edges
1289        }
1290
1291        let mut used_edges = ignored_edges.clone();
1292
1293        for it in self.diagram.edges().iter().enumerate() {
1294            // can not use iter().filter() because of the borrow checker
1295            if used_edges.get_f(it.0) {
1296                continue;
1297            }
1298            let edge_id = self.diagram.edge_index_unchecked(it.0);
1299
1300            self.traverse_edge(
1301                edge_id,
1302                false,
1303                &ignored_edges,
1304                &mut used_edges,
1305                &mut lines,
1306                &mut linestrings,
1307                max_dist,
1308            )?;
1309        }
1310
1311        // loop over each edge again, make sure they were all used or properly ignored.
1312        for it in self.diagram.edges().iter().enumerate() {
1313            // can not use iter().filter() because of the borrow checker
1314            if used_edges.get_f(it.0) {
1315                continue;
1316            }
1317            let edge_id = self.diagram.edge_index_unchecked(it.0);
1318            #[cfg(feature = "console_debug")]
1319            println!("Did not use all edges, forcing the use of edge:{edge_id:?}",);
1320
1321            self.traverse_edge(
1322                edge_id,
1323                true,
1324                &ignored_edges,
1325                &mut used_edges,
1326                &mut lines,
1327                &mut linestrings,
1328                max_dist,
1329            )?;
1330        }
1331
1332        #[cfg(feature = "console_debug")]
1333        {
1334            println!("Got {} single lines", lines.len());
1335            println!("Got {} linestrings", linestrings.len());
1336            println!(
1337                "     ignored_edges {}",
1338                ignored_edges
1339                    .iter_storage()
1340                    .map(|s| format!("{s:#02X} ")[2..].to_string())
1341                    .collect::<String>()
1342            );
1343            println!(
1344                "        used_edges {}",
1345                used_edges
1346                    .iter_storage()
1347                    .map(|s| format!("{s:#02X} ")[2..].to_string())
1348                    .collect::<String>()
1349            );
1350        }
1351        // put the containers back
1352        self.lines = Some(lines);
1353        self.line_strings = Some(linestrings);
1354        #[cfg(feature = "console_debug")]
1355        {
1356            self.debug_edges = Some(edge_lines);
1357        }
1358        Ok(())
1359    }
1360
1361    /// Mark an edge and it's twin as used/rejected
1362    #[inline(always)]
1363    fn mark_edge_and_twin_as_used(
1364        &self,
1365        edge_id: BV::EdgeIndex,
1366        used_edges: &mut Vob32,
1367    ) -> Result<(), CenterlineError> {
1368        let _ = used_edges.set(edge_id.usize(), true);
1369        #[cfg(feature = "console_debug")]
1370        print!("marking {edge_id:?}");
1371        {
1372            let twin = self.diagram.edge_get_twin(edge_id)?;
1373            #[cfg(feature = "console_debug")]
1374            print!(" & {twin:?}");
1375            if used_edges.get_f(twin.usize()) {
1376                eprintln!(" TWIN was already used!!!!! edge id:{twin:?}");
1377            }
1378            let _ = used_edges.set(twin.usize(), true);
1379        }
1380        Ok(())
1381    }
1382
1383    #[allow(clippy::too_many_arguments)]
1384    /// move across each adjacent edge and sample the lines and arcs
1385    /// If force_seed_edge is set to false the method tries to
1386    /// start at edges with only one connection (using seed_edge as a search start point).
1387    /// Edge loops will not be processed in this mode.
1388    /// If force_seed_edge is set to true, the seed_edge will be used as a starting point.
1389    fn traverse_edge(
1390        &self,
1391        seed_edge: BV::EdgeIndex,
1392        force_seed_edge: bool,
1393        ignored_edges: &Vob32,
1394        used_edges: &mut Vob32,
1395        lines: &mut Vec<Line3<T3>>,
1396        linestrings: &mut Vec<Vec<T3>>,
1397        maxdist: T3::Scalar,
1398    ) -> Result<(), CenterlineError> {
1399        #[cfg(feature = "console_debug")]
1400        {
1401            println!();
1402            println!("->traverse_edge({seed_edge:?})");
1403        }
1404        #[cfg(feature = "console_debug")]
1405        let mut mockup = Vec::<Vec<BV::EdgeIndex>>::default();
1406
1407        let found_edge = force_seed_edge
1408            || self
1409                .diagram
1410                .edge_rot_next_iterator(seed_edge)
1411                .filter(|&x| !ignored_edges.get_f(x.usize()))
1412                .take(2) // we do not need more than 2 for the test
1413                .count()
1414                == 1;
1415        if found_edge {
1416            let mut start_points = VecDeque::<BV::EdgeIndex>::default();
1417            let mut current_edge_set = Vec::<BV::EdgeIndex>::new();
1418            start_points.push_front(seed_edge);
1419            while !start_points.is_empty() {
1420                #[cfg(feature = "console_debug")]
1421                println!();
1422                let edge = start_points.pop_front().unwrap();
1423
1424                if ignored_edges.get_f(edge.usize()) {
1425                    // Should never happen
1426                    return Err(CenterlineError::InternalError(format!(
1427                        "should never happen: edge {edge:?} already in ignore list. {}:{}",
1428                        file!(),
1429                        line!()
1430                    )));
1431                }
1432                if used_edges.get_f(edge.usize()) {
1433                    #[cfg(feature = "console_debug")]
1434                    print!(" skip");
1435                    // edge was already processed, continue
1436                    continue;
1437                }
1438                #[cfg(feature = "console_debug")]
1439                println!();
1440
1441                current_edge_set.push(edge);
1442                self.mark_edge_and_twin_as_used(edge, used_edges)?;
1443
1444                let mut next_edge = self.diagram.edge(edge)?.next()?;
1445                loop {
1446                    #[cfg(feature = "console_debug")]
1447                    print!("Inner loop next_edge={next_edge:?} ");
1448
1449                    // it does not matter if next_edge is rejected/valid, it will be fixed by the iterator
1450                    let next_edges: Vec<BV::EdgeIndex> = self
1451                        .diagram
1452                        .edge_rot_next_iterator(next_edge)
1453                        .filter(|&x| !ignored_edges.get_f(x.usize()))
1454                        .collect();
1455
1456                    #[cfg(feature = "console_debug")]
1457                    {
1458                        print!("candidates[");
1459
1460                        for &ne in next_edges.iter() {
1461                            if used_edges.get_f(ne.usize()) {
1462                                print!("!");
1463                            }
1464                            print!("{ne:?},");
1465                        }
1466                        println!("]");
1467                    }
1468                    match next_edges.len() {
1469                        1 | 2 => {
1470                            let next_edges: Vec<BV::EdgeIndex> = next_edges
1471                                .into_iter()
1472                                .filter(|&x| !used_edges.get_f(x.usize()))
1473                                .collect();
1474                            if next_edges.len() == 1 {
1475                                // continue walking the edge line
1476                                let e = next_edges.first().unwrap().to_owned();
1477                                current_edge_set.push(e);
1478                                self.mark_edge_and_twin_as_used(e, used_edges)?;
1479
1480                                next_edge = self.diagram.edge(e)?.next()?;
1481                            } else {
1482                                // terminating the line string, pushing candidates
1483                                self.convert_edges_to_lines(
1484                                    &current_edge_set,
1485                                    lines,
1486                                    linestrings,
1487                                    maxdist,
1488                                )?;
1489                                #[cfg(feature = "console_debug")]
1490                                mockup.push(current_edge_set.clone());
1491                                current_edge_set.clear();
1492
1493                                if !next_edges.is_empty() {
1494                                    #[cfg(feature = "console_debug")]
1495                                    print!("1|2 Pushing new start points: [");
1496                                    for &e in next_edges.iter() {
1497                                        if !ignored_edges.get_f(e.usize())
1498                                            && !used_edges.get_f(e.usize())
1499                                        {
1500                                            #[cfg(feature = "console_debug")]
1501                                            print!("{e:?},");
1502                                            start_points.push_back(e);
1503                                        }
1504                                    }
1505                                }
1506                                #[cfg(feature = "console_debug")]
1507                                {
1508                                    println!("]");
1509                                    println!("1|2 Starting new set");
1510                                }
1511                                break;
1512                            }
1513                            continue;
1514                        }
1515                        _ => {
1516                            // to many or too few intersections found, end this linestring and push the new candidates to the queue
1517                            self.convert_edges_to_lines(
1518                                &current_edge_set,
1519                                lines,
1520                                linestrings,
1521                                maxdist,
1522                            )?;
1523                            if !next_edges.is_empty() {
1524                                #[cfg(feature = "console_debug")]
1525                                print!("0|_ Pushing new start points: [");
1526                                for &e in next_edges.iter() {
1527                                    if !ignored_edges.get_f(e.usize())
1528                                        && !used_edges.get_f(e.usize())
1529                                    {
1530                                        #[cfg(feature = "console_debug")]
1531                                        print!("{e:?},");
1532                                        start_points.push_back(e);
1533                                    }
1534                                }
1535                                #[cfg(feature = "console_debug")]
1536                                println!("]");
1537                            }
1538                            #[cfg(feature = "console_debug")]
1539                            mockup.push(current_edge_set.clone());
1540                            current_edge_set.clear();
1541
1542                            #[cfg(feature = "console_debug")]
1543                            println!("0|_ Starting new set");
1544
1545                            break;
1546                        }
1547                    }
1548                }
1549            }
1550            /*
1551            #[cfg(feature = "console_debug")]
1552            for m in mockup.iter() {
1553                println!("mockup {:?}", m.iter().map(|x| x.0).collect::<Vec<usize>>());
1554
1555                let mut i1 = m.iter();
1556                for e2 in m.iter().skip(1) {
1557                    let e1 = i1.next().unwrap();
1558                    assert_eq!(
1559                        self.diagram.edge_get_vertex1(Some(*e1)),
1560                        self.diagram.edge_get_vertex0(Some(*e2))
1561                    );
1562                }
1563            }*/
1564        } else {
1565            #[cfg(feature = "console_debug")]
1566            println!(
1567                "<-traverse_edge({seed_edge:?}) ignoring start edge,  {:?}",
1568                self.diagram
1569                    .edge_rot_next_iterator(seed_edge)
1570                    .filter(|&x| !ignored_edges.get_f(x.usize()))
1571                    .map(|x| x.usize())
1572                    .collect::<Vec<usize>>()
1573            );
1574        }
1575        Ok(())
1576    }
1577
1578    fn convert_edges_to_lines(
1579        &self,
1580        edges: &[BV::EdgeIndex],
1581        lines: &mut Vec<Line3<T3>>,
1582        linestrings: &mut Vec<Vec<T3>>,
1583        maxdist: T3::Scalar,
1584    ) -> Result<(), CenterlineError> {
1585        #[cfg(feature = "console_debug")]
1586        {
1587            println!();
1588            println!(
1589                "Converting {:?} to lines",
1590                edges.iter().map(|&x| x.usize()).collect::<Vec<usize>>()
1591            );
1592        }
1593        match edges.len() {
1594            0 => panic!(),
1595            1 => {
1596                let edge_id = edges.first().unwrap();
1597                let edge = self.diagram.edge(*edge_id)?;
1598                match self.convert_edge_to_shape(edge) {
1599                    Ok(Shape3d::Line(l)) => lines.push(l),
1600                    Ok(Shape3d::ParabolicArc(a)) => {
1601                        linestrings.push(a.discretize_3d(maxdist));
1602                    }
1603                    Ok(Shape3d::Linestring(_s)) => {
1604                        panic!();
1605                    }
1606                    Err(_) => {
1607                        println!("Error :{edge:?}");
1608                    }
1609                }
1610            }
1611            _ => {
1612                let mut ls = Vec::<T3>::default();
1613                for edge_id in edges.iter() {
1614                    let edge = self.diagram.edge(*edge_id)?;
1615                    match self.convert_edge_to_shape(edge)? {
1616                        Shape3d::Line(l) => {
1617                            //println!("->got {:?}", l);
1618                            ls.push(l.start);
1619                            ls.push(l.end);
1620                            //println!("<-got");
1621                        }
1622                        Shape3d::ParabolicArc(a) => {
1623                            //println!("->got {:?}", a);
1624                            ls.append(&mut a.discretize_3d(maxdist));
1625                            //println!("<-got");
1626                        }
1627                        // should not happen
1628                        Shape3d::Linestring(_s) => {
1629                            return Err(CenterlineError::InternalError(format!(
1630                                "convert_edges_to_lines() got an unexpected linestring. {}:{}",
1631                                file!(),
1632                                line!()
1633                            )));
1634                        }
1635                    }
1636                }
1637                linestrings.push(ls);
1638            }
1639        }
1640        //println!("Converted {:?} to lines", edges);
1641        Ok(())
1642    }
1643
1644    fn convert_edge_to_shape(&self, edge: &BV::Edge) -> Result<Shape3d<T3>, CenterlineError> {
1645        let edge_id = edge.id();
1646        let edge_twin_id = self.diagram.edge_get_twin(edge_id)?;
1647
1648        // Edge is finite so we know that vertex0 and vertex1 is_some()
1649        let vertex0 = self.diagram.vertex(edge.vertex0().ok_or_else(|| {
1650            CenterlineError::InternalError(format!(
1651                "Could not find vertex 0. {}:{}",
1652                file!(),
1653                line!()
1654            ))
1655        })?)?;
1656
1657        let vertex1 = self.diagram.edge_get_vertex1(edge_id)?.ok_or_else(|| {
1658            CenterlineError::InternalError(format!(
1659                "Could not find vertex 1. {}:{}",
1660                file!(),
1661                line!()
1662            ))
1663        })?;
1664        let vertex1 = self.diagram.vertex(vertex1)?;
1665
1666        #[cfg(feature = "console_debug")]
1667        println!(
1668            "Converting e:{:?} to line v0:{:?} v1:{:?}",
1669            edge.id(),
1670            vertex0.get_id(),
1671            vertex1.get_id(),
1672        );
1673
1674        let start_point = T3::Vector2::new_2d(vertex0.x().as_(), vertex0.y().as_());
1675        let end_point = T3::Vector2::new_2d(vertex1.x().as_(), vertex1.y().as_());
1676        let cell_id = self.diagram.edge(edge_id)?.cell().unwrap();
1677        let cell = self.diagram.cell(cell_id)?;
1678        let twin_cell_id = self.diagram.edge(edge_twin_id)?.cell().unwrap();
1679
1680        let cell_point = if cell.contains_point() {
1681            #[cfg(feature = "console_debug")]
1682            println!("cell c:{cell_id:?}");
1683            self.retrieve_point(cell_id)?
1684        } else {
1685            #[cfg(feature = "console_debug")]
1686            println!("twin cell c:{twin_cell_id:?}",);
1687            self.retrieve_point(twin_cell_id)?
1688        };
1689        let segment = if cell.contains_point() {
1690            #[cfg(feature = "console_debug")]
1691            println!("twin segment c:{twin_cell_id:?}");
1692            self.retrieve_segment(twin_cell_id)?
1693        } else {
1694            #[cfg(feature = "console_debug")]
1695            println!("segment c:{cell_id:?}",);
1696            self.retrieve_segment(cell_id)?
1697        };
1698
1699        let segment_start_point = T3::Vector2::new_2d(segment.start.x.as_(), segment.start.y.as_());
1700        let segment_end_point = T3::Vector2::new_2d(segment.end.x.as_(), segment.end.y.as_());
1701        let cell_point = T3::Vector2::new_2d(cell_point.x.as_(), cell_point.y.as_());
1702        #[cfg(feature = "console_debug")]
1703        {
1704            println!("sp:[{},{}]", start_point.x(), start_point.y());
1705            println!("ep:[{},{}]", end_point.x(), end_point.y());
1706            println!(
1707                "cp:[{},{}] sg:[{},{},{},{}]",
1708                cell_point.x(),
1709                cell_point.y(),
1710                segment_start_point.x(),
1711                segment_start_point.y(),
1712                segment_end_point.x(),
1713                segment_end_point.y()
1714            );
1715        }
1716
1717        if edge.is_curved() {
1718            let arc = linestring_2d::VoronoiParabolicArc::new(
1719                Line2 {
1720                    start: segment_start_point,
1721                    end: segment_end_point,
1722                },
1723                cell_point,
1724                start_point,
1725                end_point,
1726            );
1727            #[cfg(feature = "console_debug")]
1728            println!("Converted {:?} to {:?}", edge.id(), arc);
1729            Ok(Shape3d::ParabolicArc(arc))
1730        } else {
1731            let distance_to_start = {
1732                if vertex0.is_site_point() {
1733                    T3::Scalar::ZERO
1734                } else if cell.contains_point() {
1735                    let cell_point = self.retrieve_point(cell_id)?;
1736                    let cell_point = T3::Vector2::new_2d(cell_point.x.as_(), cell_point.y.as_());
1737                    -cell_point.distance(start_point)
1738                } else {
1739                    let segment = self.retrieve_segment(cell_id)?;
1740                    let segment_start_point =
1741                        T3::Vector2::new_2d(segment.start.x.as_(), segment.start.y.as_());
1742                    let segment_end_point =
1743                        T3::Vector2::new_2d(segment.end.x.as_(), segment.end.y.as_());
1744                    -linestring_2d::distance_to_line_squared_safe(
1745                        segment_start_point,
1746                        segment_end_point,
1747                        start_point,
1748                    )
1749                    .sqrt()
1750                }
1751            };
1752            let distance_to_end = {
1753                if vertex1.is_site_point() {
1754                    T3::Scalar::ZERO
1755                } else {
1756                    let cell_id = self
1757                        .diagram
1758                        .edge(vertex1.get_incident_edge().unwrap())?
1759                        .cell()
1760                        .unwrap();
1761                    let cell = self.diagram.cell(cell_id)?;
1762                    if cell.contains_point() {
1763                        let cell_point = self.retrieve_point(cell_id)?;
1764                        let cell_point =
1765                            T3::Vector2::new_2d(cell_point.x.as_(), cell_point.y.as_());
1766                        -cell_point.distance(end_point)
1767                    } else {
1768                        let segment = self.retrieve_segment(cell_id)?;
1769                        let segment_start_point =
1770                            T3::Vector2::new_2d(segment.start.x.as_(), segment.start.y.as_());
1771                        let segment_end_point =
1772                            T3::Vector2::new_2d(segment.end.x.as_(), segment.end.y.as_());
1773                        -linestring_2d::distance_to_line_squared_safe(
1774                            segment_start_point,
1775                            segment_end_point,
1776                            end_point,
1777                        )
1778                        .sqrt()
1779                    }
1780                }
1781            };
1782            let line = Line3 {
1783                start: T3::new_3d(start_point.x(), start_point.y(), distance_to_start),
1784                end: T3::new_3d(end_point.x(), end_point.y(), distance_to_end),
1785            };
1786            #[cfg(feature = "console_debug")]
1787            println!("Converted {:?} to {:?}", edge.id(), line);
1788            Ok(Shape3d::Line(line))
1789        }
1790    }
1791}
1792
1793/// A set of 2d LineString, an aabb + convex_hull.
1794/// It also contains a list of aabb & convex_hulls of shapes this set has gobbled up.
1795/// This can be useful for separating out inner regions of the shape.
1796///
1797/// This struct is intended to contain related shapes. E.g. outlines of letters with holes
1798#[derive(Clone)]
1799pub struct LineStringSet2<T: GenericVector2> {
1800    set: Vec<Vec<T>>,
1801    aabb: <T as GenericVector2>::Aabb,
1802    convex_hull: Option<Vec<T>>,
1803    pub internals: Option<Vec<(<T as GenericVector2>::Aabb, Vec<T>)>>,
1804}
1805
1806impl<T: GenericVector2> Default for LineStringSet2<T> {
1807    #[inline]
1808    fn default() -> Self {
1809        Self {
1810            set: Vec::<_>::default(),
1811            aabb: <T as GenericVector2>::Aabb::default(),
1812            convex_hull: None,
1813            internals: None,
1814        }
1815    }
1816}
1817
1818impl<T: GenericVector2> LineStringSet2<T> {
1819    /// steal the content of 'other' leaving it empty
1820    pub fn steal_from(other: &mut LineStringSet2<T>) -> Self {
1821        //println!("stealing from other.aabb:{:?}", other.aabb);
1822        let mut set = Vec::<Vec<T>>::new();
1823        set.append(&mut other.set);
1824        Self {
1825            set,
1826            aabb: other.aabb,
1827            convex_hull: other.convex_hull.take(),
1828            internals: other.internals.take(),
1829            //_pd:PhantomData,
1830        }
1831    }
1832
1833    pub fn set(&self) -> &Vec<Vec<T>> {
1834        &self.set
1835    }
1836
1837    pub fn with_capacity(capacity: usize) -> Self {
1838        Self {
1839            set: Vec::<Vec<T>>::with_capacity(capacity),
1840            aabb: <T as GenericVector2>::Aabb::default(),
1841            convex_hull: None,
1842            internals: None,
1843        }
1844    }
1845
1846    pub fn get_internals(&self) -> Option<&Vec<(<T as GenericVector2>::Aabb, Vec<T>)>> {
1847        self.internals.as_ref()
1848    }
1849
1850    pub fn is_empty(&self) -> bool {
1851        self.set.is_empty()
1852    }
1853
1854    pub fn push(&mut self, ls: Vec<T>) {
1855        if !ls.is_empty() {
1856            self.set.push(ls);
1857
1858            for ls in self.set.last().unwrap().iter() {
1859                self.aabb.add_point(*ls);
1860            }
1861        }
1862    }
1863
1864    /// returns the combined convex hull of all the shapes in self.set
1865    pub fn get_convex_hull(&self) -> &Option<Vec<T>> {
1866        &self.convex_hull
1867    }
1868
1869    /// calculates the combined convex hull of all the shapes in self.set
1870    pub fn calculate_convex_hull(&mut self) -> Result<&Vec<T>, LinestringError> {
1871        // todo: this is sus
1872        let tmp: Vec<_> = self.set.iter().flatten().cloned().collect();
1873        self.convex_hull = Some(convex_hull::graham_scan(&tmp)?);
1874        Ok(self.convex_hull.as_ref().unwrap())
1875    }
1876
1877    /// Returns the axis aligned bounding box of this set.
1878    pub fn get_aabb(&self) -> <T as GenericVector2>::Aabb {
1879        self.aabb
1880    }
1881
1882    /*/// Transform each individual component of this set using the transform matrix.
1883    /// Return the result in a new object.
1884    pub fn transform(&self, matrix3x3: &M) -> Self {
1885        let internals = self.internals.as_ref().map(|internals| {
1886            internals
1887                .iter()
1888                .map(|(aabb, line)| (transform_aabb2(matrix3x3, aabb),transform_linestring2( matrix3x3,line)))
1889                .collect()
1890        });
1891
1892        let convex_hull = self
1893            .convex_hull
1894            .as_ref()
1895            .map(|convex_hull| transform_linestring2(matrix3x3, convex_hull));
1896
1897        Self {
1898            aabb: transform_aabb2(matrix3x3, &self.aabb),
1899            set: self.set.iter().map(|x| transform_linestring2(matrix3x3, x)).collect(),
1900            convex_hull,
1901            internals,
1902            _pd:PhantomData,
1903        }
1904    }*/
1905
1906    /// Copy this linestringset2 into a linestringset3, populating the axes defined by 'plane'
1907    /// An axis will always try to keep its position (e.g. y goes to y if possible).
1908    /// That way the operation is reversible (in regard to axis positions).
1909    /// The empty axis will be set to zero.
1910    pub fn copy_to_3d(&self, plane: Plane) -> LineStringSet3<T::Vector3> {
1911        let mut rv = LineStringSet3::<T::Vector3>::with_capacity(self.set.len());
1912        for ls in self.set.iter() {
1913            rv.push(ls.copy_to_3d(plane));
1914        }
1915        rv
1916    }
1917
1918    /// drains the 'other' container of all shapes and put them into 'self'
1919    pub fn take_from(&mut self, mut other: Self) {
1920        self.aabb.add_aabb(&other.aabb);
1921        self.set.append(&mut other.set);
1922    }
1923
1924    /// drains the 'other' container of all shapes and put them into 'self'
1925    /// The other container must be entirely 'inside' the convex hull of 'self'
1926    /// The 'other' container must also contain valid 'internals' and 'convex_hull' fields
1927    pub fn take_from_internal(&mut self, other: &mut Self) -> Result<(), LinestringError> {
1928        // sanity check
1929        if other.convex_hull.is_none() {
1930            return Err(LinestringError::InvalidData(
1931                "'other' did not contain a valid 'convex_hull' field".to_string(),
1932            ));
1933        }
1934        if self.aabb.is_empty() {
1935            //println!("self.aabb {:?}", self.aabb);
1936            //println!("other.aabb {:?}", other.aabb);
1937            return Err(LinestringError::InvalidData(
1938                "'self' did not contain a valid 'aabb' field".to_string(),
1939            ));
1940        }
1941        if other.aabb.is_empty() {
1942            return Err(LinestringError::InvalidData(
1943                "'other' did not contain a valid 'aabb' field".to_string(),
1944            ));
1945        }
1946        if !self.aabb.contains_aabb_inclusive(&other.aabb) {
1947            //println!("self.aabb {:?}", self.aabb);
1948            //println!("other.aabb {:?}", other.aabb);
1949            return Err(LinestringError::InvalidData(
1950                "The 'other.aabb' is not contained within 'self.aabb'".to_string(),
1951            ));
1952        }
1953        if self.internals.is_none() {
1954            self.internals = Some(Vec::<(<T as GenericVector2>::Aabb, Vec<T>)>::new())
1955        }
1956
1957        self.set.append(&mut other.set);
1958
1959        if let Some(ref mut other_internals) = other.internals {
1960            // self.internals.unwrap is safe now
1961            self.internals.as_mut().unwrap().append(other_internals);
1962        }
1963
1964        self.internals
1965            .as_mut()
1966            .unwrap()
1967            .push((other.aabb, other.convex_hull.take().unwrap()));
1968        Ok(())
1969    }
1970
1971    /// Apply an operation over each coordinate in the contained objects.
1972    /// Useful when you want to round the value of each contained coordinate.
1973    pub fn apply<F: Fn(T) -> T>(&mut self, f: &F) {
1974        for s in self.set.iter_mut() {
1975            s.apply(f);
1976        }
1977        self.aabb.apply(f);
1978        if let Some(ref mut convex_hull) = self.convex_hull {
1979            convex_hull.apply(f);
1980        }
1981        if let Some(ref mut internals) = self.internals {
1982            for i in internals.iter_mut() {
1983                i.0.apply(f);
1984                i.1.apply(f);
1985            }
1986        }
1987    }
1988}
1989
1990/// A set of line-strings + an aabb
1991/// Intended to contain related 3d shapes. E.g. outlines of letters with holes
1992#[derive(PartialEq, Clone)]
1993pub struct LineStringSet3<T: GenericVector3> {
1994    pub set: Vec<Vec<T>>,
1995    pub aabb: <T as GenericVector3>::Aabb,
1996}
1997
1998impl<T: GenericVector3> Default for LineStringSet3<T> {
1999    fn default() -> Self {
2000        Self {
2001            set: Vec::<Vec<T>>::default(),
2002            aabb: <T as GenericVector3>::Aabb::default(),
2003        }
2004    }
2005}
2006
2007impl<T: GenericVector3> LineStringSet3<T> {
2008    pub fn with_capacity(capacity: usize) -> Self {
2009        Self {
2010            set: Vec::<Vec<T>>::with_capacity(capacity),
2011            aabb: <T as GenericVector3>::Aabb::default(),
2012        }
2013    }
2014
2015    pub fn set(&self) -> &Vec<Vec<T>> {
2016        &self.set
2017    }
2018
2019    pub fn is_empty(&self) -> bool {
2020        self.set.is_empty()
2021    }
2022
2023    pub fn push(&mut self, ls: Vec<T>) {
2024        if !ls.is_empty() {
2025            self.set.push(ls);
2026
2027            for ls in self.set.last().unwrap().iter() {
2028                self.aabb.add_point(*ls);
2029            }
2030        }
2031    }
2032
2033    pub fn get_aabb(&self) -> <T as GenericVector3>::Aabb {
2034        self.aabb
2035    }
2036
2037    pub fn apply<F: Fn(T) -> T>(&mut self, f: &F) {
2038        self.set.iter_mut().for_each(|x| x.apply(f));
2039        self.aabb.apply(f);
2040    }
2041
2042    /// Copy this linestringset3 into a linestringset2, populating the axes defined by 'plane'
2043    /// An axis will always try to keep its position (e.g. y goes to y if possible).
2044    /// That way the operation is reversible (in regard to axis positions).
2045    pub fn copy_to_2d(&self, plane: Plane) -> LineStringSet2<T::Vector2> {
2046        let mut rv = LineStringSet2::with_capacity(self.set.len());
2047        for ls in self.set.iter() {
2048            rv.push(ls.copy_to_2d(plane));
2049        }
2050        rv
2051    }
2052
2053    /// drains the 'other' container of all shapes and put them into 'self'
2054    pub fn take_from(&mut self, other: &mut Self) {
2055        self.aabb.add_aabb(&other.aabb);
2056        self.set.append(&mut other.set);
2057    }
2058}
2059
2060/// Placeholder for different 3d shapes
2061pub enum Shape3d<T: GenericVector3> {
2062    Line(Line3<T>),
2063    Linestring(Vec<T>),
2064    ParabolicArc(linestring_2d::VoronoiParabolicArc<T::Vector2>),
2065}