use core::f64;
use core::panic;
use crate::delaunay_core::math;
use crate::delaunay_core::triangulation_ext::VertexToInsert;
use crate::{
ConstrainedDelaunayTriangulation, HasPosition, HintGenerator, InsertionError,
LastUsedVertexHintGenerator, Point2, Triangulation, TriangulationExt,
};
use core::cmp::{Ordering, Reverse};
#[cfg(not(feature = "std"))]
use hashbrown::HashSet;
#[cfg(feature = "std")]
use std::collections::HashSet;
use num_traits::Zero;
use super::{
dcel_operations, FixedDirectedEdgeHandle, FixedUndirectedEdgeHandle, FixedVertexHandle,
};
use alloc::{vec, vec::Vec};
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
struct FloatOrd<S>(S);
fn bulk_load_distance_fn(center: Point2<f64>, position: Point2<f64>) -> impl Ord {
(
Reverse(FloatOrd(center.distance_2(position))),
FloatOrd(position.x),
FloatOrd(position.y),
)
}
#[allow(clippy::derive_ord_xor_partial_ord)]
impl<S> Ord for FloatOrd<S>
where
S: PartialOrd,
{
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl<S> Eq for FloatOrd<S> where S: PartialOrd {}
pub fn bulk_load<V, T>(mut elements: Vec<V>) -> Result<T, InsertionError>
where
V: HasPosition,
T: Triangulation<Vertex = V>,
{
if elements.is_empty() {
return Ok(T::new());
}
let mut point_sum = Point2::<f64>::new(0.0, 0.0);
for element in &elements {
crate::validate_vertex(element)?;
let position = element.position();
point_sum = point_sum.add(position.to_f64());
}
let initial_center = point_sum.mul(1.0 / (elements.len() as f64));
let mut result = T::with_capacity(elements.len(), elements.len() * 3, elements.len() * 2);
elements.sort_unstable_by_key(|e| bulk_load_distance_fn(initial_center, e.position().to_f64()));
let mut hull = loop {
let Some(next) = elements.pop() else {
return Ok(result);
};
result.insert(next)?;
if result.num_vertices() < 3 {
continue;
}
let thee_vertices = [
FixedVertexHandle::new(result.num_vertices() - 3),
FixedVertexHandle::new(result.num_vertices() - 2),
FixedVertexHandle::new(result.num_vertices() - 1),
];
if let Some(hull) = try_get_hull_center(&result, thee_vertices)
.and_then(|center| Hull::from_triangulation(&result, center))
{
hull_sanity_check(&result, &hull);
break hull;
}
};
if elements.is_empty() {
return Ok(result);
}
let mut buffer = Vec::new();
let mut skipped_elements = Vec::new();
while let Some(next) = elements.pop() {
skipped_elements.extend(single_bulk_insertion_step(
&mut result,
false,
&mut hull,
VertexToInsert::NewVertex(next),
&mut buffer,
))
}
if cfg!(any(fuzzing, test)) {
hull_sanity_check(&result, &hull);
}
fix_convexity(&mut result);
for skipped in skipped_elements {
result.insert_with_hint_option_impl(skipped, None);
}
Ok(result)
}
pub fn bulk_load_cdt<V, DE, UE, F, L>(
elements: Vec<V>,
edges: Vec<[usize; 2]>,
) -> Result<ConstrainedDelaunayTriangulation<V, DE, UE, F, L>, InsertionError>
where
V: HasPosition,
DE: Default,
UE: Default,
F: Default,
L: HintGenerator<<V as HasPosition>::Scalar>,
{
try_bulk_load_cdt(elements, edges, |e| {
panic!("Conflicting edge encountered: [{}, {}]", e[0], e[1])
})
}
pub fn try_bulk_load_cdt<V, DE, UE, F, L>(
mut elements: Vec<V>,
mut edges: Vec<[usize; 2]>,
mut on_conflict_found: impl FnMut([usize; 2]),
) -> Result<ConstrainedDelaunayTriangulation<V, DE, UE, F, L>, InsertionError>
where
V: HasPosition,
DE: Default,
UE: Default,
F: Default,
L: HintGenerator<<V as HasPosition>::Scalar>,
{
if elements.is_empty() {
return Ok(ConstrainedDelaunayTriangulation::new());
}
let element_indices = sort_and_dedup(&mut elements, &mut edges)?;
if elements.len() < 3 {
let mut result = ConstrainedDelaunayTriangulation::new();
for elem in elements {
result.insert(elem)?;
}
for &[from, to] in &edges {
result.add_constraint(FixedVertexHandle::new(from), FixedVertexHandle::new(to));
}
return Ok(result);
}
let mut buffer = Vec::new();
let mut skipped_vertices = HashSet::new();
let mut skipped_edges = HashSet::new();
let fix = FixedVertexHandle::new;
let mut add_constraints_for_new_vertex =
|result: &mut ConstrainedDelaunayTriangulation<V, DE, UE, F>,
edges: &mut Vec<[usize; 2]>,
skipped_vertices: &HashSet<usize>,
index| {
while let Some([from, to]) = edges.last().copied() {
if skipped_vertices.contains(&from) || skipped_vertices.contains(&to) {
skipped_edges.insert([from, to]);
edges.pop();
continue;
}
if from == index {
let [from, to] = [from, to].map(fix);
if result.try_add_constraint(from, to).is_empty() {
on_conflict_found([from.index(), to.index()]);
}
edges.pop();
} else {
break;
}
}
};
let mut elements_iter = element_indices.iter().rev().copied();
let first = fix(elements_iter.next().unwrap());
let second = fix(elements_iter.next().unwrap());
let mut result = ConstrainedDelaunayTriangulation::<V, DE, UE, F, LastUsedVertexHintGenerator>::create_with_fixed_vertices(
elements,
first,
second,
);
let third = result.insert_with_hint_option_impl(
VertexToInsert::ExistingVertex(fix(elements_iter.next().unwrap())),
Some(first),
);
add_constraints_for_new_vertex(&mut result, &mut edges, &skipped_vertices, first.index());
add_constraints_for_new_vertex(&mut result, &mut edges, &skipped_vertices, second.index());
add_constraints_for_new_vertex(&mut result, &mut edges, &skipped_vertices, third.index());
let mut last_3_vertices = [first, second, third];
let mut next_handle_to_drop = 0;
let mut hull = loop {
if let Some(hull) = try_get_hull_center(&result, last_3_vertices)
.and_then(|center| Hull::from_triangulation(&result, center))
{
break hull;
}
let Some(index) = elements_iter.next() else {
return Ok(result.adjust_hint_generator());
};
let next_handle = result.insert_with_hint_option_impl(
VertexToInsert::ExistingVertex(FixedVertexHandle::new(index)),
Some(first),
);
add_constraints_for_new_vertex(&mut result, &mut edges, &skipped_vertices, index);
last_3_vertices[next_handle_to_drop] = next_handle;
next_handle_to_drop = (next_handle_to_drop + 1) % 3;
};
let mut require_convexity = true;
for next_index in elements_iter {
require_convexity &= !edges.is_empty();
if single_bulk_insertion_step(
&mut result,
require_convexity,
&mut hull,
VertexToInsert::ExistingVertex(FixedVertexHandle::new(next_index)),
&mut buffer,
)
.is_some()
{
skipped_vertices.insert(next_index);
}
add_constraints_for_new_vertex(&mut result, &mut edges, &skipped_vertices, next_index);
}
if cfg!(any(fuzzing, test)) {
hull_sanity_check(&result, &hull);
}
if !require_convexity {
fix_convexity(&mut result);
}
for element in skipped_vertices {
result.insert_with_hint_option_impl(
VertexToInsert::ExistingVertex(FixedVertexHandle::new(element)),
Some(first),
);
}
for [from, to] in skipped_edges {
let [from, to] = [from, to].map(FixedVertexHandle::new);
if result.try_add_constraint(from, to).is_empty() {
on_conflict_found([from.index(), to.index()]);
}
}
assert_eq!(edges, Vec::<[usize; 2]>::new());
Ok(result.adjust_hint_generator())
}
fn sort_and_dedup<V>(
elements: &mut Vec<V>,
edges: &mut [[usize; 2]],
) -> Result<Vec<usize>, InsertionError>
where
V: HasPosition,
{
let mut point_sum = Point2::<f64>::new(0.0, 0.0);
for element in &*elements {
crate::validate_vertex(element)?;
let position = element.position();
point_sum = point_sum.add(position.to_f64());
}
let initial_center = point_sum.mul(1.0 / (elements.len() as f64));
for edge in edges.iter_mut() {
let [d1, d2] = edge.map(|vertex| {
bulk_load_distance_fn(initial_center, elements[vertex].position().to_f64())
});
if d1 > d2 {
edge.reverse();
}
}
edges.sort_unstable_by_key(|[from, _]| {
bulk_load_distance_fn(initial_center, elements[*from].position().to_f64())
});
let mut element_indices = (0..elements.len()).collect::<Vec<_>>();
element_indices.sort_unstable_by_key(|index| {
bulk_load_distance_fn(initial_center, elements[*index].position().to_f64())
});
let mut removed_indices = HashSet::new();
let mut last_position = None;
let mut duplicate_sets = Vec::new();
let mut current_duplicates = Vec::new();
let mut current_min_duplicate = element_indices[0];
element_indices.retain(|&index| {
let position = elements[index].position();
let mut retain = true;
if Some(position) == last_position {
retain = false;
let new_duplicate = if index < current_min_duplicate {
let previous_min = current_min_duplicate;
current_min_duplicate = index;
previous_min
} else {
index
};
removed_indices.insert(new_duplicate);
current_duplicates.push(new_duplicate);
} else {
if !current_duplicates.is_empty() {
if duplicate_sets.is_empty() {
duplicate_sets = (0..elements.len()).collect::<Vec<_>>();
}
for dupe in current_duplicates.iter() {
duplicate_sets[*dupe] = current_min_duplicate;
}
}
current_duplicates.clear();
last_position = Some(position);
current_min_duplicate = index;
};
if !current_duplicates.is_empty() {
if duplicate_sets.is_empty() {
duplicate_sets = (0..elements.len()).collect::<Vec<_>>();
}
for dupe in current_duplicates.iter() {
duplicate_sets[*dupe] = current_min_duplicate;
}
}
retain
});
if !removed_indices.is_empty() {
let mut old_to_new = vec![usize::MAX; elements.len()];
let mut index_counter = 0;
let mut i = 0;
elements.retain(|_| {
old_to_new[i] = index_counter;
if removed_indices.contains(&i) {
old_to_new[i] = old_to_new[duplicate_sets[i]];
i += 1;
return false;
}
old_to_new[i] = index_counter;
i += 1;
index_counter += 1;
true
});
for elem in &mut element_indices {
*elem = old_to_new[*elem];
}
for &mut [ref mut from, ref mut to] in edges {
*from = old_to_new[*from];
*to = old_to_new[*to];
}
}
Ok(element_indices)
}
fn try_get_hull_center<V, T>(
result: &T,
three_vertices: [FixedVertexHandle; 3],
) -> Option<Point2<f64>>
where
V: HasPosition,
T: Triangulation<Vertex = V>,
{
if result.all_vertices_on_line() {
return None;
}
let zero = <V as HasPosition>::Scalar::zero();
let mut sum_x = zero;
let mut sum_y = zero;
for handle in three_vertices.iter() {
let position = result.vertex(*handle).position();
sum_x = sum_x + position.x;
sum_y = sum_y + position.y;
}
let center = Point2::new(sum_x, sum_y).mul(<V as HasPosition>::Scalar::from(1f32 / 3f32));
if let crate::PositionInTriangulation::OnFace(_) =
result.locate_with_hint(center, three_vertices[0])
{
return Some(center.to_f64());
}
None
}
#[inline(never)] fn single_bulk_insertion_step<TR, T>(
result: &mut TR,
require_convexity: bool,
hull: &mut Hull,
handle: VertexToInsert<TR::Vertex>,
buffer_for_edge_legalization: &mut Vec<FixedUndirectedEdgeHandle>,
) -> Option<VertexToInsert<TR::Vertex>>
where
T: HasPosition,
TR: Triangulation<Vertex = T>,
{
let next_position = handle.position(result.s());
let current_angle = pseudo_angle(next_position.to_f64(), hull.center);
let edge = result.directed_edge(hull.get(current_angle));
let [from, to] = edge.positions();
if next_position == from || next_position == to {
return None;
}
if edge.side_query(next_position).is_on_right_side_or_on_line() {
return Some(handle);
}
assert!(edge.is_outer_edge());
let edge = edge.fix();
let handle = handle.resolve(result.s_mut());
let new_vertex =
dcel_operations::create_new_face_adjacent_to_edge(result.s_mut(), edge, handle);
let ccw_walk_start = result.directed_edge(edge).prev().rev().fix();
let cw_walk_start = result.directed_edge(edge).next().rev().fix();
result.legalize_edge(edge, false);
let mut current_edge = ccw_walk_start;
loop {
let handle = result.directed_edge(current_edge);
let prev = handle.prev();
let handle = handle.fix();
let [prev_from, prev_to] = prev.positions().map(|p| p.to_f64());
let angle_condition = require_convexity
|| !math::is_behind_edge(next_position.to_f64(), prev_to, prev_from)
|| current_angle == pseudo_angle(prev_to, hull.center);
current_edge = prev.fix();
if angle_condition && prev.side_query(next_position).is_on_left_side() {
let new_edge = dcel_operations::create_single_face_between_edge_and_next(
result.s_mut(),
current_edge,
);
buffer_for_edge_legalization.clear();
buffer_for_edge_legalization.push(handle.as_undirected());
buffer_for_edge_legalization.push(current_edge.as_undirected());
result.legalize_edges_after_removal(buffer_for_edge_legalization, |_| false);
current_edge = new_edge;
} else {
break;
}
}
let mut current_edge = cw_walk_start;
loop {
let handle = result.directed_edge(current_edge);
let next = handle.next();
let handle = handle.fix();
let middle_position = next.from().position().to_f64();
let angle_condition = require_convexity
|| !super::math::is_behind_edge(
middle_position,
next_position.to_f64(),
next.to().position().to_f64(),
)
|| current_angle == pseudo_angle(middle_position, hull.center);
let next_fix = next.fix();
let is_on_left_side = next.side_query(next_position).is_on_left_side();
if angle_condition && is_on_left_side {
let new_edge = dcel_operations::create_single_face_between_edge_and_next(
result.s_mut(),
current_edge,
);
buffer_for_edge_legalization.clear();
buffer_for_edge_legalization.push(handle.as_undirected());
buffer_for_edge_legalization.push(next_fix.as_undirected());
result.legalize_edges_after_removal(buffer_for_edge_legalization, |_| false);
current_edge = new_edge;
} else {
break;
}
}
let new_vertex = result.vertex(new_vertex);
let outgoing_ch_edge = new_vertex.out_edges().find(|edge| edge.is_outer_edge());
if let Some(second_edge) = outgoing_ch_edge {
let first_edge = second_edge.prev();
let first_angle = pseudo_angle(first_edge.from().position().to_f64(), hull.center);
let second_angle = pseudo_angle(second_edge.to().position().to_f64(), hull.center);
hull.insert(
first_angle,
current_angle,
second_angle,
first_edge.fix(),
second_edge.fix(),
);
if cfg!(any(fuzzing, test)) {
hull_sanity_check(result, hull);
}
}
None
}
fn fix_convexity<TR>(triangulation: &mut TR)
where
TR: Triangulation,
{
let mut edges_to_validate = Vec::with_capacity(2);
let mut convex_edges: Vec<FixedDirectedEdgeHandle> = Vec::with_capacity(64);
let mut current_fixed = triangulation.outer_face().adjacent_edge().unwrap().fix();
loop {
let current_handle = triangulation.directed_edge(current_fixed);
let next_handle = current_handle.next().fix();
convex_edges.push(current_fixed);
current_fixed = next_handle;
while let &[.., edge1_fixed, edge2_fixed] = &*convex_edges {
let edge1 = triangulation.directed_edge(edge1_fixed);
let edge2 = triangulation.directed_edge(edge2_fixed);
let target_position = edge2.to().position();
if edge1.side_query(target_position).is_on_left_side() {
edges_to_validate.push(edge1.fix().as_undirected());
edges_to_validate.push(edge2.fix().as_undirected());
let new_edge = dcel_operations::create_single_face_between_edge_and_next(
triangulation.s_mut(),
edge1_fixed,
);
convex_edges.pop();
convex_edges.pop();
convex_edges.push(new_edge);
triangulation.legalize_edges_after_removal(&mut edges_to_validate, |_| false);
} else {
break;
}
}
if Some(¤t_fixed) == convex_edges.get(1) {
break;
}
}
}
#[derive(Debug, Copy, Clone)]
struct Segment {
from: FloatOrd<f64>,
to: FloatOrd<f64>,
}
impl Segment {
fn new(from: FloatOrd<f64>, to: FloatOrd<f64>) -> Self {
assert_ne!(from, to);
Self { from, to }
}
fn is_non_wrapping_segment(&self) -> bool {
self.from < self.to
}
fn contains_angle(&self, angle: FloatOrd<f64>) -> bool {
if self.is_non_wrapping_segment() {
self.from <= angle && angle < self.to
} else {
self.from <= angle || angle < self.to
}
}
}
#[derive(Clone, Copy, Debug)]
struct Node {
angle: FloatOrd<f64>,
edge: FixedDirectedEdgeHandle,
left: usize,
right: usize,
}
#[derive(Debug)]
pub struct Hull {
buckets: Vec<usize>,
data: Vec<Node>,
center: Point2<f64>,
empty: Vec<usize>,
}
impl Hull {
pub fn from_triangulation<T>(triangulation: &T, center: Point2<f64>) -> Option<Self>
where
T: Triangulation,
{
assert!(!triangulation.all_vertices_on_line());
let hull_size = triangulation.convex_hull_size();
let mut data = Vec::with_capacity(hull_size);
let mut prev_index = hull_size - 1;
let mut last_segment: Option<Segment> = None;
for (current_index, edge) in triangulation.convex_hull().enumerate() {
let angle_from = pseudo_angle(edge.from().position().to_f64(), center);
let angle_to = pseudo_angle(edge.to().position().to_f64(), center);
if let Some(segment) = last_segment {
if segment.contains_angle(angle_to) {
return None;
}
}
if angle_from == angle_to || angle_from.0.is_nan() || angle_to.0.is_nan() {
return None;
}
last_segment = Some(Segment::new(angle_from, angle_to));
let next_index = (current_index + 1) % hull_size;
data.push(Node {
angle: angle_from,
edge: edge.fix(),
left: prev_index,
right: next_index,
});
prev_index = current_index;
}
let mut result = Self {
buckets: Vec::new(),
center,
data,
empty: Vec::new(),
};
const INITIAL_NUMBER_OF_BUCKETS: usize = 8;
result.initialize_buckets(INITIAL_NUMBER_OF_BUCKETS);
Some(result)
}
fn initialize_buckets(&mut self, target_size: usize) {
self.buckets.clear();
self.buckets.reserve(target_size);
const INVALID: usize = usize::MAX;
self.buckets
.extend(core::iter::repeat_n(INVALID, target_size));
let (first_index, current_node) = self
.data
.iter()
.enumerate()
.find(|(index, _)| !self.empty.contains(index))
.unwrap();
let mut current_index = first_index;
let first_bucket = self.ceiled_bucket(current_node.angle);
self.buckets[first_bucket] = current_index;
loop {
let current_node = self.data[current_index];
let segment = self.segment(¤t_node);
let start_bucket = self.ceiled_bucket(segment.from);
let end_bucket = self.ceiled_bucket(segment.to);
self.update_bucket_segment(start_bucket, end_bucket, current_index);
current_index = current_node.right;
if current_index == first_index {
break;
}
}
}
fn insert(
&mut self,
left_angle: FloatOrd<f64>,
middle_angle: FloatOrd<f64>,
mut right_angle: FloatOrd<f64>,
left_edge: FixedDirectedEdgeHandle,
mut right_edge: FixedDirectedEdgeHandle,
) {
let left_bucket = self.floored_bucket(left_angle);
let mut left_index = self.buckets[left_bucket];
loop {
let current_node = self.data[left_index];
if current_node.angle == left_angle {
break;
}
left_index = current_node.right;
}
let mut right_index = self.data[left_index].right;
loop {
let current_node = self.data[right_index];
if current_node.angle == right_angle {
break;
}
if cfg!(any(fuzzing, test)) {
assert!(!self.empty.contains(&right_index));
}
self.empty.push(right_index);
self.data[current_node.left].right = current_node.right;
self.data[current_node.right].left = current_node.left;
right_index = current_node.right;
}
let new_index = self.get_next_index();
if left_angle == middle_angle {
self.empty.push(left_index);
left_index = self.data[left_index].left;
} else {
self.data[left_index].edge = left_edge;
}
if right_angle == middle_angle {
if left_angle != right_angle {
self.empty.push(right_index);
}
right_edge = self.data[right_index].edge;
right_index = self.data[right_index].right;
right_angle = self.data[right_index].angle;
}
self.data[left_index].right = new_index;
self.data[right_index].left = new_index;
let new_node = Node {
angle: middle_angle,
edge: right_edge,
left: left_index,
right: right_index,
};
self.push_or_update_node(new_node, new_index);
let left_bucket = self.ceiled_bucket(left_angle);
let middle_bucket = self.ceiled_bucket(middle_angle);
let right_bucket = self.ceiled_bucket(right_angle);
self.update_bucket_segment(left_bucket, middle_bucket, left_index);
self.update_bucket_segment(middle_bucket, right_bucket, new_index);
self.adjust_bucket_size_if_necessary();
}
fn get_next_index(&mut self) -> usize {
self.empty.pop().unwrap_or(self.data.len())
}
fn update_bucket_segment(&mut self, left_bucket: usize, right_bucket: usize, new_value: usize) {
if left_bucket <= right_bucket {
for current_bucket in &mut self.buckets[left_bucket..right_bucket] {
*current_bucket = new_value;
}
} else {
for current_bucket in &mut self.buckets[left_bucket..] {
*current_bucket = new_value;
}
for current_bucket in &mut self.buckets[..right_bucket] {
*current_bucket = new_value;
}
}
}
fn push_or_update_node(&mut self, node: Node, index: usize) {
if let Some(existing_node) = self.data.get_mut(index) {
*existing_node = node;
} else {
assert_eq!(self.data.len(), index);
self.data.push(node);
}
}
fn get(&self, angle: FloatOrd<f64>) -> FixedDirectedEdgeHandle {
let mut current_handle = self.buckets[self.floored_bucket(angle)];
loop {
let current_node = self.data[current_handle];
let left_angle = current_node.angle;
let next_angle = self.data[current_node.right].angle;
if Segment::new(left_angle, next_angle).contains_angle(angle) {
return current_node.edge;
}
current_handle = current_node.right;
}
}
fn floored_bucket(&self, angle: FloatOrd<f64>) -> usize {
((angle.0 * self.buckets.len() as f64).floor() as usize) % self.buckets.len()
}
fn ceiled_bucket(&self, angle: FloatOrd<f64>) -> usize {
((angle.0 * self.buckets.len() as f64).ceil() as usize) % self.buckets.len()
}
fn segment(&self, node: &Node) -> Segment {
Segment::new(node.angle, self.data[node.right].angle)
}
fn adjust_bucket_size_if_necessary(&mut self) {
let size = self.data.len() - self.empty.len();
let num_buckets = self.buckets.len();
const MIN_NUMBER_OF_BUCKETS: usize = 16;
if num_buckets * 2 < size {
self.initialize_buckets(num_buckets * 2);
} else if num_buckets > size * 4 && num_buckets > MIN_NUMBER_OF_BUCKETS {
let new_size = num_buckets / 4;
if new_size >= MIN_NUMBER_OF_BUCKETS {
self.initialize_buckets(new_size);
}
}
}
}
#[inline]
fn pseudo_angle(a: Point2<f64>, center: Point2<f64>) -> FloatOrd<f64> {
let diff = a.sub(center);
let p = diff.x / (diff.x.abs() + diff.y.abs());
FloatOrd(1.0 - (if diff.y > 0.0 { 3.0 - p } else { 1.0 + p }) * 0.25)
}
fn hull_sanity_check(triangulation: &impl Triangulation, hull: &Hull) {
let non_empty_nodes: Vec<_> = hull
.data
.iter()
.enumerate()
.filter(|(index, _)| !hull.empty.contains(index))
.collect();
for (index, node) in &non_empty_nodes {
let left_node = hull.data[node.left];
let right_node = hull.data[node.right];
let edge = triangulation.directed_edge(node.edge);
assert!(edge.is_outer_edge());
assert!(!hull.empty.contains(&node.left));
assert!(!hull.empty.contains(&node.right));
assert_eq!(left_node.right, *index);
assert_eq!(right_node.left, *index);
}
for (bucket_index, bucket_node) in hull.buckets.iter().enumerate() {
assert!(!hull.empty.contains(bucket_node));
let bucket_start_angle = FloatOrd(bucket_index as f64 / hull.buckets.len() as f64);
for (node_index, node) in &non_empty_nodes {
let segment = hull.segment(node);
if segment.contains_angle(bucket_start_angle) {
assert_eq!(node_index, bucket_node);
}
}
}
}
#[cfg(test)]
mod test {
use float_next_after::NextAfter;
use rand::{seq::SliceRandom, SeedableRng};
use crate::delaunay_core::triangulation_ext::VertexToInsert;
use crate::handles::FixedVertexHandle;
use crate::test_utilities::{random_points_with_seed, SEED2};
use crate::{
ConstrainedDelaunayTriangulation, DelaunayTriangulation, InsertionError, Point2,
Triangulation, TriangulationExt,
};
use super::Hull;
use alloc::vec;
use alloc::vec::Vec;
#[test]
fn test_bulk_load_with_small_number_of_vertices() -> Result<(), InsertionError> {
for size in 0..10 {
let triangulation =
DelaunayTriangulation::<_>::bulk_load(random_points_with_seed(size, SEED2))?;
assert_eq!(triangulation.num_vertices(), size);
triangulation.sanity_check();
}
Ok(())
}
#[test]
fn test_bulk_load_on_grid() -> Result<(), InsertionError> {
let mut rng = rand::rngs::StdRng::from_seed(*SEED2);
const TEST_REPETITIONS: usize = 30;
const GRID_SIZE: usize = 20;
for _ in 0..TEST_REPETITIONS {
let mut vertices = Vec::with_capacity(GRID_SIZE * GRID_SIZE);
for x in 0..GRID_SIZE {
for y in 0..GRID_SIZE {
vertices.push(Point2::new(x as f64, y as f64));
}
}
vertices.shuffle(&mut rng);
let triangulation = DelaunayTriangulation::<_>::bulk_load(vertices)?;
assert_eq!(triangulation.num_vertices(), GRID_SIZE * GRID_SIZE);
triangulation.sanity_check();
}
Ok(())
}
fn get_epsilon_grid(grid_size: usize) -> Vec<Point2<f64>> {
let mut possible_f64: Vec<_> = Vec::with_capacity(grid_size);
let mut current_float = crate::MIN_ALLOWED_VALUE;
for _ in 0..grid_size / 2 {
possible_f64.push(current_float);
possible_f64.push(-current_float);
current_float = current_float.next_after(f64::INFINITY);
}
possible_f64.sort_by(|l, r| l.partial_cmp(r).unwrap());
let mut vertices = Vec::with_capacity(grid_size * grid_size);
for x in 0..grid_size {
for y in 0..grid_size {
vertices.push(Point2::new(possible_f64[x], possible_f64[y]));
}
}
vertices
}
#[test]
fn test_bulk_load_on_epsilon_grid() -> Result<(), InsertionError> {
const GRID_SIZE: usize = 20;
let mut rng = rand::rngs::StdRng::from_seed(*SEED2);
const TEST_REPETITIONS: usize = 30;
let vertices = get_epsilon_grid(GRID_SIZE);
for _ in 0..TEST_REPETITIONS {
let mut vertices = vertices.clone();
vertices.shuffle(&mut rng);
let triangulation = DelaunayTriangulation::<_>::bulk_load(vertices)?;
triangulation.sanity_check();
assert_eq!(triangulation.num_vertices(), GRID_SIZE * GRID_SIZE);
}
Ok(())
}
#[test]
fn test_cdt_bulk_load_on_epsilon_grid() -> Result<(), InsertionError> {
const GRID_SIZE: usize = 20;
let vertices = get_epsilon_grid(GRID_SIZE);
let edges = (0..vertices.len() - 1).map(|i| [i, i + 1]).collect();
let triangulation = ConstrainedDelaunayTriangulation::<_>::bulk_load_cdt(vertices, edges)?;
triangulation.cdt_sanity_check();
assert_eq!(triangulation.num_vertices(), GRID_SIZE * GRID_SIZE);
assert_eq!(triangulation.num_constraints(), GRID_SIZE * GRID_SIZE - 1);
Ok(())
}
#[test]
fn test_bulk_load_stable() -> Result<(), InsertionError> {
const SIZE: usize = 200;
let mut vertices = random_points_with_seed(SIZE, SEED2);
vertices.push(Point2::new(4.0, 4.0));
vertices.push(Point2::new(4.0, -4.0));
vertices.push(Point2::new(-4.0, 4.0));
vertices.push(Point2::new(-4.0, -4.0));
vertices.push(Point2::new(5.0, 5.0));
vertices.push(Point2::new(5.0, -5.0));
vertices.push(Point2::new(-5.0, 5.0));
vertices.push(Point2::new(-5.0, -5.0));
vertices.push(Point2::new(6.0, 6.0));
vertices.push(Point2::new(6.0, -6.0));
vertices.push(Point2::new(-6.0, 6.0));
vertices.push(Point2::new(-6.0, -6.0));
let num_vertices = vertices.len();
let triangulation = DelaunayTriangulation::<_>::bulk_load_stable(vertices.clone())?;
triangulation.sanity_check();
assert_eq!(triangulation.num_vertices(), num_vertices);
for (inserted, original) in triangulation.vertices().zip(vertices) {
assert_eq!(inserted.data(), &original);
}
triangulation.sanity_check();
Ok(())
}
fn small_cdt_vertices() -> Vec<Point2<f64>> {
vec![
Point2::new(1.0, 1.0),
Point2::new(1.0, -1.0),
Point2::new(-1.0, 0.0),
Point2::new(-0.9, -0.9),
Point2::new(0.0, 2.0),
Point2::new(2.0, 0.4),
Point2::new(-0.2, -1.9),
Point2::new(-2.0, 0.1),
]
}
fn check_bulk_load_cdt(edges: Vec<[usize; 2]>) -> Result<(), InsertionError> {
let vertices = small_cdt_vertices();
let num_constraints = edges.len();
let num_vertices = vertices.len();
let cdt = ConstrainedDelaunayTriangulation::<_>::bulk_load_cdt(vertices, edges.clone())?;
cdt.cdt_sanity_check();
assert_eq!(cdt.num_vertices(), num_vertices);
assert_eq!(cdt.num_constraints(), num_constraints);
for [from, to] in edges {
let from = FixedVertexHandle::from_index(from);
let to = FixedVertexHandle::from_index(to);
assert_eq!(
cdt.get_edge_from_neighbors(from, to)
.map(|h| h.is_constraint_edge()),
Some(true)
);
}
Ok(())
}
#[test]
fn test_cdt_bulk_load_small() -> Result<(), InsertionError> {
let edges = vec![[4, 5], [5, 6], [6, 7], [7, 4]];
check_bulk_load_cdt(edges)
}
#[test]
fn test_cdt_bulk_load_with_constraint_edges_in_center() -> Result<(), InsertionError> {
let edges = vec![[0, 1], [1, 3], [3, 2], [2, 0]];
check_bulk_load_cdt(edges)
}
#[test]
fn test_cdt_bulk_load_with_duplicates() -> Result<(), InsertionError> {
let mut vertices = small_cdt_vertices();
vertices.extend(small_cdt_vertices());
let edges = vec![[0, 1], [9, 3], [11, 2], [10, 0]];
let num_constraints = edges.len();
let cdt = ConstrainedDelaunayTriangulation::<_>::bulk_load_cdt(vertices, edges)?;
assert_eq!(cdt.num_constraints(), num_constraints);
Ok(())
}
#[test]
fn test_cdt_bulk_load() -> Result<(), InsertionError> {
const SIZE: usize = 500;
let vertices = random_points_with_seed(SIZE, SEED2);
let edge_vertices = vertices[0..SIZE / 10].to_vec();
let edge_triangulation = DelaunayTriangulation::<_>::bulk_load_stable(edge_vertices)?;
let edges = edge_triangulation
.undirected_edges()
.step_by(edge_triangulation.num_undirected_edges() * 20 / SIZE)
.map(|edge| edge.vertices().map(|v| v.index()))
.collect::<Vec<_>>();
let num_constraints = edges.len();
let cdt = ConstrainedDelaunayTriangulation::<_>::bulk_load_cdt(vertices, edges)?;
cdt.cdt_sanity_check();
assert_eq!(cdt.num_vertices(), SIZE);
assert_eq!(cdt.num_constraints(), num_constraints);
Ok(())
}
#[test]
fn test_bulk_load_stable_with_duplicates() -> Result<(), InsertionError> {
const SIZE: usize = 200;
let mut vertices = random_points_with_seed(SIZE, SEED2);
let original = vertices.clone();
let duplicates = vertices.iter().copied().take(SIZE / 10).collect::<Vec<_>>();
for (index, d) in duplicates.into_iter().enumerate() {
vertices.insert(index * 2, d);
}
let triangulation = DelaunayTriangulation::<_>::bulk_load_stable(vertices)?;
triangulation.sanity_check();
assert_eq!(triangulation.num_vertices(), SIZE);
for (inserted, original) in triangulation.vertices().zip(original) {
assert_eq!(inserted.data(), &original);
}
triangulation.sanity_check();
Ok(())
}
#[test]
fn test_empty() -> Result<(), InsertionError> {
let cdt =
ConstrainedDelaunayTriangulation::<Point2<f64>>::bulk_load_cdt(Vec::new(), Vec::new())?;
assert_eq!(cdt.num_vertices(), 0);
assert_eq!(cdt.num_constraints(), 0);
let dt = DelaunayTriangulation::<Point2<f64>>::bulk_load_stable(Vec::new())?;
assert_eq!(dt.num_vertices(), 0);
Ok(())
}
#[test]
fn test_bulk_load() -> Result<(), InsertionError> {
const SIZE: usize = 9000;
let mut vertices = random_points_with_seed(SIZE, SEED2);
vertices.push(Point2::new(4.0, 4.0));
vertices.push(Point2::new(4.0, -4.0));
vertices.push(Point2::new(-4.0, 4.0));
vertices.push(Point2::new(-4.0, -4.0));
vertices.push(Point2::new(5.0, 5.0));
vertices.push(Point2::new(5.0, -5.0));
vertices.push(Point2::new(-5.0, 5.0));
vertices.push(Point2::new(-5.0, -5.0));
vertices.push(Point2::new(6.0, 6.0));
vertices.push(Point2::new(6.0, -6.0));
vertices.push(Point2::new(-6.0, 6.0));
vertices.push(Point2::new(-6.0, -6.0));
let num_vertices = vertices.len();
let triangulation = DelaunayTriangulation::<Point2<f64>>::bulk_load(vertices)?;
triangulation.sanity_check();
assert_eq!(triangulation.num_vertices(), num_vertices);
Ok(())
}
#[test]
fn test_same_vertex_bulk_load() -> Result<(), InsertionError> {
const SIZE: usize = 100;
let mut vertices = random_points_with_seed(SIZE, SEED2);
for i in 0..SIZE - 5 {
vertices.insert(i * 2, Point2::new(0.5, 0.2));
}
let triangulation = DelaunayTriangulation::<Point2<f64>>::bulk_load(vertices)?;
triangulation.sanity_check();
assert_eq!(triangulation.num_vertices(), SIZE + 1);
Ok(())
}
#[test]
fn test_hull() -> Result<(), InsertionError> {
let mut triangulation = DelaunayTriangulation::<_>::new();
triangulation.insert(Point2::new(1.0, 1.0))?; triangulation.insert(Point2::new(1.0, -1.0))?; triangulation.insert(Point2::new(-1.0, 1.0))?; triangulation.insert(Point2::new(-1.0, -1.0))?;
let mut hull = Hull::from_triangulation(&triangulation, Point2::new(0.0, 0.0)).unwrap();
super::hull_sanity_check(&triangulation, &hull);
let additional_elements = [
Point2::new(0.4, 2.0),
Point2::new(-0.4, 3.0),
Point2::new(-0.4, -4.0),
Point2::new(3.0, 5.0),
];
for (index, element) in additional_elements.iter().enumerate() {
assert!(super::single_bulk_insertion_step(
&mut triangulation,
false,
&mut hull,
VertexToInsert::NewVertex(*element),
&mut Vec::new(),
)
.is_none());
if index != 0 {
super::hull_sanity_check(&triangulation, &hull)
}
}
Ok(())
}
#[test]
fn test_cdt_fuzz_1() -> Result<(), InsertionError> {
let data = vec![
Point2::new(-2.7049442424493675e-11f64, -2.7049442424493268e-11),
Point2::new(-2.7049442424493268e-11, -2.704944239760038e-11),
Point2::new(-2.704944242438945e-11, -2.704943553980988e-11),
Point2::new(-2.7049442424493675e-11, -2.7049442424388623e-11),
Point2::new(-2.7049442424493268e-11, -2.704944239760038e-11),
Point2::new(-2.7049442424493675e-11, 0.0),
];
let mut edges = Vec::<[usize; 2]>::new();
for p in &data {
if crate::validate_coordinate(p.x).is_err() || crate::validate_coordinate(p.y).is_err()
{
return Ok(());
}
if p.x.abs() > 20.0 || p.y.abs() > 20.0 {
return Ok(());
}
}
for &[from, to] in &edges {
if from >= data.len() || to >= data.len() || from == to {
return Ok(());
}
}
let mut reference_cdt =
ConstrainedDelaunayTriangulation::<Point2<f64>>::bulk_load(data.clone()).unwrap();
let mut last_index = 0;
for (index, [from, to]) in edges
.iter()
.copied()
.map(|e| e.map(FixedVertexHandle::from_index))
.enumerate()
{
if reference_cdt.can_add_constraint(from, to) {
reference_cdt.add_constraint(from, to);
} else {
last_index = index;
break;
}
}
edges.truncate(last_index);
let bulk_loaded =
ConstrainedDelaunayTriangulation::<Point2<f64>>::bulk_load_cdt(data, edges).unwrap();
bulk_loaded.cdt_sanity_check();
Ok(())
}
#[test]
fn test_bulk_load_with_flat_triangle() -> Result<(), InsertionError> {
let dt = DelaunayTriangulation::<Point2<f64>>::bulk_load(vec![
Point2::new(-0.4583333333333335, 0.0035353982507333875),
Point2::new(-0.44401041666666685, 0.09000381880347848),
Point2::new(-0.4296875000000002, 0.17647223935622358),
Point2::new(-0.4153645833333336, 0.26294065990896864),
Point2::new(-0.40104166666666696, 0.34940908046171376),
Point2::new(-0.34375, 0.4242340611633537),
Point2::new(-0.2864583333333335, 0.48354314550173816),
Point2::new(-0.22916666666666696, 0.5220359027883882),
Point2::new(-0.171875, 0.5605286600750382),
Point2::new(-0.11458333333333348, 0.5743482879175245),
Point2::new(-0.05729166666666696, 0.5864208547026089),
])?;
dt.sanity_check();
let tri = dt.inner_faces().nth(4).unwrap();
let [p0, p1, p2] = tri.positions();
assert!(crate::delaunay_core::math::side_query(p0, p1, p2).is_on_left_side());
Ok(())
}
}