extern crate nalgebra as na;
extern crate parry2d_f64 as parry;
use crate::geometry::{WeightedEdge, WeightedTriangle, WeightedVertex, Triangulation, TriangleLogic};
use na::Point2;
use rand::{distributions::Uniform, rngs::ThreadRng, thread_rng, Rng};
use std::collections::HashMap;
#[allow(dead_code)]
pub fn generate_vertices(n: u32) -> Vec<WeightedVertex> {
let mut rng: ThreadRng = thread_rng();
let distribution: Uniform<f64> = Uniform::new(0.0, 10.0);
(0..n)
.map(|_| WeightedVertex::new(rng.sample(distribution), rng.sample(distribution), 0.0))
.collect()
}
#[allow(dead_code)]
pub fn generate_weighted_vertices(n: u32) -> Vec<WeightedVertex> {
let mut rng: ThreadRng = thread_rng();
let distribution: Uniform<f64> = Uniform::new(0.0, 10.0);
(0..n)
.map(|_| {
WeightedVertex::new(
rng.sample(distribution),
rng.sample(distribution),
rng.sample(distribution),
)
})
.collect()
}
#[allow(dead_code)]
pub fn get_fourth_point(triangle: &WeightedTriangle, edge: &WeightedEdge) -> WeightedVertex {
if triangle.a != edge.a && triangle.a != edge.b {
WeightedVertex::new(triangle.a.x(), triangle.a.y(), triangle.a.w())
} else if triangle.b != edge.a && triangle.b != edge.b {
WeightedVertex::new(triangle.b.x(), triangle.b.y(), triangle.b.w())
} else {
WeightedVertex::new(triangle.c.x(), triangle.c.y(), triangle.c.w())
}
}
pub fn barycentric_coords(vertex: &WeightedVertex, triangle: &WeightedTriangle) -> (f64, f64, f64) {
let v0 = triangle.b.as_vec2() - triangle.a.as_vec2();
let v1 = triangle.c.as_vec2() - triangle.a.as_vec2();
let v2 = vertex.as_vec2() - triangle.a.as_vec2();
let d00 = v0.dot(&v0);
let d01 = v0.dot(&v1);
let d11 = v1.dot(&v1);
let d20 = v2.dot(&v0);
let d21 = v2.dot(&v1);
let inv_denom = 1. / (d00 * d11 - d01 * d01);
let v = (d11 * d20 - d01 * d21) * inv_denom;
let w = (d00 * d21 - d01 * d20) * inv_denom;
let u = 1. - v - w;
assert!(v + w + u - 1. < 0.0001);
(v, w, u)
}
pub fn point_in_tri(vertex: &WeightedVertex, triangle: &WeightedTriangle) -> bool {
let (v, w, u) = barycentric_coords(vertex, triangle);
v >= 0.0 && w >= 0.0 && u >= 0.0
}
pub fn c_hull(points: &Vec<WeightedVertex>) -> Vec<WeightedVertex> {
let points2d: Vec<Point2<f64>> = points.iter().map(|v| Point2::new(v.x(), v.y())).collect();
let c_hull = parry::transformation::convex_hull(&points2d);
c_hull
.iter()
.map(|p| {
for v in points {
if v.x() == p.x && v.y() == p.y {
return WeightedVertex::new(p.x, p.y, v.w());
}
}
panic!("One of the vertices should match the convex hull vertices.")
})
.collect()
}
pub fn induced_subcomplex(vertices: &[WeightedVertex], triangulation: &Triangulation) -> Vec<WeightedTriangle> {
let subcomplex: Vec<WeightedTriangle> = triangulation.triangles().iter().filter(|tri| {
tri.has_vertex(&vertices[0]) && tri.has_vertex(&vertices[1]) && tri.has_vertex(&vertices[2])
|| tri.has_vertex(&vertices[0]) && tri.has_vertex(&vertices[1]) && tri.has_vertex(&vertices[3])
|| tri.has_vertex(&vertices[1]) && tri.has_vertex(&vertices[2]) && tri.has_vertex(&vertices[3])
|| tri.has_vertex(&vertices[0]) && tri.has_vertex(&vertices[2]) && tri.has_vertex(&vertices[3])
}).copied().collect();
subcomplex
}
pub fn is_flippable_strict(induced_subcomplex: &[WeightedTriangle]) -> bool {
let points = induced_subcomplex
.iter()
.flat_map(|t| [Point2::new(t.a.x(), t.a.y()), Point2::new(t.b.x(), t.b.y()), Point2::new(t.c.x(), t.c.y())] )
.collect::<Vec<Point2<f64>>>();
let c_hull = parry::transformation::convex_hull(&points);
points.iter().all(|item| c_hull.contains(item))
}
pub fn is_flippable(point: &WeightedVertex, edge: &WeightedEdge, pk: &WeightedVertex) -> bool {
let points: Vec<Point2<f64>> = vec![
Point2::new(point.x(), point.y()),
Point2::new(edge.a.x(), edge.a.y()),
Point2::new(edge.b.x(), edge.b.y()),
Point2::new(pk.x(), pk.y()),
];
let c_hull = parry::transformation::convex_hull(&points);
points.iter().all(|item| c_hull.contains(item))
}
pub fn keep_unique_elements<T: Eq + std::hash::Hash>(elements: Vec<T>) -> Vec<T> {
elements
.into_iter()
.fold(HashMap::new(), |mut map, element| {
*map.entry(element).or_insert(0) += 1;
map
})
.into_iter()
.filter_map(|(element, count)| (count == 1).then_some(element))
.collect()
}
#[allow(dead_code)]
pub fn keep_unique_elements2<T: Eq + std::hash::Hash + Clone>(elements: &mut Vec<T>) {
let mut freq_map = HashMap::new();
for element in elements.iter() {
*freq_map.entry(element.clone()).or_insert(0) += 1;
}
elements.retain(|element| freq_map.get(element) == Some(&1));
}
#[allow(warnings)]
pub fn remove_duplicates<T: std::cmp::PartialEq<T>>(elements: &mut Vec<T>) {
let mut indices_to_remove: Vec<usize> = vec![];
for i in 0..elements.len() {
let element: &T = &elements[i];
for j in 0..elements.len() {
let comp_element: &T = &elements[j];
if element == comp_element && i != j {
indices_to_remove.push(i);
break;
}
}
}
for idx in indices_to_remove.iter().rev() {
elements.remove(*idx);
}
}
#[cfg(test)]
mod tests {
use super::*;
use na::{Point2, Vector2};
#[test]
fn test_barycentric_coords() {
let tri = WeightedTriangle::new(
WeightedVertex::new(0.0, 0.0, 0.0),
WeightedVertex::new(1.0, 0.0, 0.0),
WeightedVertex::new(0.0, 1.0, 0.0),
);
let point = WeightedVertex::new(0.5, 0.5, 0.0);
let (v, w, u) = barycentric_coords(&point, &tri);
assert_eq!(v, 0.5);
assert_eq!(w, 0.5);
assert_eq!(u, 0.0);
let point = WeightedVertex::new(0.0, 0.0, 0.0);
let (v, w, u) = barycentric_coords(&point, &tri);
assert_eq!(v, 0.0);
assert_eq!(w, 0.0);
assert_eq!(u, 1.0);
let point = WeightedVertex::new(1.0, 0.0, 0.0);
let (v, w, u) = barycentric_coords(&point, &tri);
assert_eq!(v, 0.0);
assert_eq!(w, 1.0);
assert_eq!(u, 0.0);
let point = WeightedVertex::new(0.0, 1.0, 0.0);
let (v, w, u) = barycentric_coords(&point, &tri);
assert_eq!(v, 1.0);
assert_eq!(w, 0.0);
assert_eq!(u, 0.0);
}
#[test]
fn test_convex_hull() {
let points = vec![
Vector2::new(0.5, 0.5),
Vector2::new(2.5, 2.0),
Vector2::new(1.0, 2.5),
Vector2::new(2.0, 3.0),
Vector2::new(1.0, 1.0),
];
let true_hull = vec![
Point2::new(0.5, 0.5),
Point2::new(2.5, 2.0),
Point2::new(2.0, 3.0),
Point2::new(1.0, 2.5),
];
let points2d: Vec<Point2<f64>> = points.iter().map(|v| Point2::new(v.x, v.y)).collect();
let c_hull = parry::transformation::convex_hull(&points2d);
assert!(c_hull != points2d);
assert!(true_hull.iter().all(|item| true_hull.contains(item)));
}
#[test]
fn test_edge_flippable() {
pub const LARGE_NUM: f64 = 10000.0;
let p_2_lw: WeightedVertex = WeightedVertex::new(0.0, LARGE_NUM, 0.0);
let p_1_lw: WeightedVertex = WeightedVertex::new(LARGE_NUM, -LARGE_NUM, 0.0);
assert!(is_flippable(
&WeightedVertex::new(0.5, 0.5, 0.0),
&WeightedEdge::new(
WeightedVertex::new(2.5, 2.0, 0.0),
WeightedVertex::new(1.0, 2.5, 0.0)
),
&WeightedVertex::new(2.0, 3.0, 0.0)
));
assert!(is_flippable(
&WeightedVertex::new(2.5, 2.0, 0.0),
&WeightedEdge::new(WeightedVertex::new(2.0, 3.0, 0.0), p_2_lw),
&p_1_lw
));
}
#[test]
fn test_edge_not_flippable() {
pub const LARGE_NUM: f64 = 10000.0;
let p_2_lw: WeightedVertex = WeightedVertex::new(0.0, LARGE_NUM, 0.0);
assert!(!is_flippable(
&WeightedVertex::new(-5., 1., 0.0),
&WeightedEdge::new(
WeightedVertex::new(-4., 2., 0.0),
WeightedVertex::new(-2.5, 2., 0.0),
),
&WeightedVertex::new(-5., 3., 0.0)
));
assert!(!is_flippable(
&WeightedVertex::new(-1.5, 0., 0.0),
&WeightedEdge::new(WeightedVertex::new(0., 1.5, 0.0), p_2_lw),
&WeightedVertex::new(2., -0.5, 0.0)
));
}
}