cg_math/validation/
mod.rs

1extern crate nalgebra as na;
2use crate::geometry::{TriangleLogic, WeightedTriangle, WeightedVertex, Triangulation};
3use na::Point2;
4extern crate parry2d_f64 as parry;
5
6/// Struct for storing the metrics of a triangulation
7#[derive(PartialEq)]
8pub struct TriangulationMetrics {
9    pub area: f64,
10    pub area3d: f64,
11    pub c_hull: f64,
12    pub epsilon: f64,
13    pub num_simplices: usize,
14    pub regularity: f64,
15    pub runtime: f64,
16}
17
18/// Struct for storing the equality metric for two triangulations
19#[derive(Debug, PartialEq)]
20pub struct EqualTriangleMetric {
21    pub equal_triangles: Vec<WeightedTriangle>,
22    pub different_triangles: Vec<WeightedTriangle>,
23    pub true_triangle_ratio: f64,
24
25}
26
27impl EqualTriangleMetric {
28    /// Create and compute the metric for the given triangulations.
29    pub fn new(triangulation: &Triangulation, baseline_triangulation: &Triangulation) -> Self {
30        let mut equal_triangles: Vec<WeightedTriangle> = vec![];
31        let mut different_triangles: Vec<WeightedTriangle> = vec![];
32
33        for triangle in triangulation.triangles() {
34            let mut not_in_baseline = true;
35            for base_triangle in baseline_triangulation.triangles() {
36                if triangle == base_triangle {
37                    equal_triangles.push(*triangle);
38                    not_in_baseline = false;
39                    break;
40                }   
41            }
42            if not_in_baseline {
43                different_triangles.push(*triangle);
44            }
45        }
46
47        let true_triangle_ratio = equal_triangles.len() as f64 / baseline_triangulation.triangles().len() as f64 * 100.0;
48
49        Self {
50            equal_triangles,
51            different_triangles,
52            true_triangle_ratio,
53        }
54    }
55
56    /// Create an empty metric.
57    pub fn empty() -> Self {
58        Self {
59            equal_triangles: vec![],
60            different_triangles: vec![],
61            true_triangle_ratio: 0.0,
62        }
63    }
64}
65
66/// Compute all result metrics for a given triangulation
67pub fn compute_metrics(
68    vertices: &Vec<WeightedVertex>,
69    triangles: &Vec<WeightedTriangle>,
70    epsilon: f64,
71    runtime: f64,
72) -> TriangulationMetrics {
73    let regularity = all_globally_regular(vertices, triangles);
74    let is_c_hull = is_c_hull(vertices, triangles);
75    let area = summed_area(triangles);
76    let area3d = summed_area3d(triangles);
77
78    TriangulationMetrics {
79        epsilon,
80        num_simplices: triangles.len(),
81        c_hull: is_c_hull as i32 as f64,
82        regularity,
83        area,
84        area3d,
85        runtime,
86    }
87}
88
89/// Given a triangulation and the a point list, check if all triangles fulfill the empty orthogonal circle property
90// TODO impl this on the triangulation struct and rename to all_empty_orthogonal_circles
91// TODO more sophisticated: for each triangle take the percentage of points that it is irregular for
92pub fn all_globally_regular(
93    vertices: &Vec<WeightedVertex>,
94    triangles: &Vec<WeightedTriangle>,
95) -> f64 {
96    let mut n_invalid_triangles = 0.0;
97
98    for triangle in triangles.iter() {
99        for vertex in vertices {
100            if !triangle.has_vertex(vertex) && !triangle.is_regular(vertex) {
101                n_invalid_triangles += 1.0;
102                break;
103            }
104        }
105    }
106    1.0 - n_invalid_triangles / triangles.len() as f64
107}
108
109/// Given a triangulation and the initial point list, check if the c-hull of the points is equal to the c-hull spanned by all the triangles.
110// TODO impl this on the triangulation struct
111pub fn is_c_hull(vertices: &[WeightedVertex], triangles: &[WeightedTriangle]) -> bool {
112    let input_points2d: Vec<Point2<f64>> =
113        vertices.iter().map(|v| Point2::new(v.x(), v.y())).collect();
114    let input_points_hull = parry::transformation::convex_hull(&input_points2d);
115
116    let triangulation_border_points2d: Vec<Point2<f64>> = get_boundary_points(triangles);
117
118    //println!("points c_hull: {:?}", input_points_hull);
119    //println!("triangulation border: {:?}", triangulation_border_points2d);
120    //println!("c-hull len: {} vs border points len: {}", input_points_hull.len(), triangulation_border_points2d.len());
121
122    triangulation_border_points2d
123        .iter()
124        .all(|item| input_points_hull.contains(item))
125}
126
127/// Get the points on the border described by the polygon that is the union of all the triangles in the triangulations
128/// Ideally this is equal to the convex hull of the polygon described by the triangulation.
129fn get_boundary_points(triangulation: &[WeightedTriangle]) -> Vec<Point2<f64>> {
130    // Note: very naive, inefficient implementation; for quickly getting the validation test up
131    let copied_triangulation1 = triangulation.to_owned();
132    let mut border_points: Vec<Point2<f64>> = vec![]; // return as Point<f64> since c-hull algo uses these
133
134    for triangle1 in copied_triangulation1 {
135        for edge1 in triangle1.edges() {
136            let mut is_border_edge = true;
137
138            let copied_triangulation2 = triangulation.to_owned();
139            for triangle2 in copied_triangulation2 {
140                if triangle1 != triangle2 {
141                    // if the edge is part of another triangle it is not on the polygonal border
142                    for edge2 in triangle2.edges() {
143                        if edge1 == edge2 {
144                            is_border_edge = false;
145                        }
146                    }
147                }
148            }
149
150            if is_border_edge {
151                let mut a_unique = true;
152                let mut b_unique = true;
153                for point in border_points.clone() {
154                    if point == Point2::new(edge1.a.x(), edge1.a.y()) {
155                        a_unique = false;
156                    }
157                    if point == Point2::new(edge1.b.x(), edge1.b.y()) {
158                        b_unique = false;
159                    }
160                }
161
162                if a_unique {
163                    border_points.push(Point2::new(edge1.a.x(), edge1.a.y()));
164                }
165                if b_unique {
166                    border_points.push(Point2::new(edge1.b.x(), edge1.b.y()))
167                }
168            }
169        }
170    }
171    border_points
172}
173
174/// Compute the summed area of all triangles in the triangulation.
175// TODO impl this on the triangulation struct
176pub fn summed_area(triangulation: &[WeightedTriangle]) -> f64 {
177    triangulation
178        .iter()
179        .fold(0.0, |sum, triangle| sum + triangle.area())
180}
181
182
183/// Compute the summed area of all triangles of the triangulation in 3d with z = w.
184// TODO impl this on the triangulation struct
185pub fn summed_area3d(triangulation: &[WeightedTriangle]) -> f64 {
186    triangulation
187        .iter()
188        .fold(0.0, |sum, triangle| sum + triangle.area3d())
189}