use core::cell::RefCell;
use crate::{
delaunay_core::math,
handles::{FixedDirectedEdgeHandle, FixedVertexHandle},
DelaunayTriangulation, HasPosition, HintGenerator, Point2, PositionInTriangulation,
Triangulation,
};
use num_traits::{one, zero, Float};
use alloc::vec::Vec;
use super::VertexHandle;
#[doc = include_str!("../../images/interpolation_nearest_neighbor.img")]
#[doc = include_str!("../../images/interpolation_barycentric.img")]
#[doc = include_str!("../../images/interpolation_nn_c0.img")]
#[doc = include_str!("../../images/interpolation_nn_c1_flatness_05.img")]
#[doc = include_str!("../../images/interpolation_nn_c1_flatness_1.img")]
#[doc(alias = "Interpolation")]
pub struct NaturalNeighbor<'a, T>
where
T: Triangulation,
T::Vertex: HasPosition,
{
triangulation: &'a T,
inspect_edges_buffer: RefCell<Vec<FixedDirectedEdgeHandle>>,
natural_neighbor_buffer: RefCell<Vec<FixedDirectedEdgeHandle>>,
insert_cell_buffer: RefCell<Vec<Point2<<T::Vertex as HasPosition>::Scalar>>>,
weight_buffer: RefCell<Vec<(FixedVertexHandle, <T::Vertex as HasPosition>::Scalar)>>,
}
#[doc(alias = "Interpolation")]
pub struct Barycentric<'a, T>
where
T: Triangulation,
{
triangulation: &'a T,
weight_buffer: RefCell<Vec<(FixedVertexHandle, <T::Vertex as HasPosition>::Scalar)>>,
}
impl<'a, T> Barycentric<'a, T>
where
T: Triangulation,
<T::Vertex as HasPosition>::Scalar: Float,
{
pub(crate) fn new(triangulation: &'a T) -> Self {
Self {
triangulation,
weight_buffer: Default::default(),
}
}
pub fn get_weights(
&self,
position: Point2<<T::Vertex as HasPosition>::Scalar>,
result: &mut Vec<(FixedVertexHandle, <T::Vertex as HasPosition>::Scalar)>,
) {
result.clear();
match self.triangulation.locate(position) {
PositionInTriangulation::OnVertex(vertex) => {
result.push((vertex, <T::Vertex as HasPosition>::Scalar::from(1.0)))
}
PositionInTriangulation::OnEdge(edge) => {
let [v0, v1] = self.triangulation.directed_edge(edge).vertices();
let [w0, w1] = two_point_interpolation::<T>(v0, v1, position);
result.push((v0.fix(), w0));
result.push((v1.fix(), w1));
}
PositionInTriangulation::OnFace(face) => {
let face = self.triangulation.face(face);
let [v0, v1, v2] = face.vertices();
let [c0, c1, c2] = face.barycentric_interpolation(position);
result.extend([(v0.fix(), c0), (v1.fix(), c1), (v2.fix(), c2)]);
}
_ => {}
}
}
pub fn interpolate<I>(
&self,
i: I,
position: Point2<<T::Vertex as HasPosition>::Scalar>,
) -> Option<<T::Vertex as HasPosition>::Scalar>
where
I: Fn(
VertexHandle<T::Vertex, T::DirectedEdge, T::UndirectedEdge, T::Face>,
) -> <T::Vertex as HasPosition>::Scalar,
{
let nns = &mut *self.weight_buffer.borrow_mut();
self.get_weights(position, nns);
if nns.is_empty() {
return None;
}
let mut total_sum = zero();
for (vertex, weight) in nns {
total_sum = total_sum + i(self.triangulation.vertex(*vertex)) * *weight;
}
Some(total_sum)
}
}
impl<'a, V, DE, UE, F, L> NaturalNeighbor<'a, DelaunayTriangulation<V, DE, UE, F, L>>
where
V: HasPosition,
DE: Default,
UE: Default,
F: Default,
L: HintGenerator<<V as HasPosition>::Scalar>,
<V as HasPosition>::Scalar: Float,
{
pub(crate) fn new(triangulation: &'a DelaunayTriangulation<V, DE, UE, F, L>) -> Self {
Self {
triangulation,
inspect_edges_buffer: Default::default(),
insert_cell_buffer: Default::default(),
natural_neighbor_buffer: Default::default(),
weight_buffer: Default::default(),
}
}
#[doc = include_str!("../../images/natural_neighbor_scenario.svg")]
pub fn get_weights(
&self,
position: Point2<<V as HasPosition>::Scalar>,
result: &mut Vec<(FixedVertexHandle, <V as HasPosition>::Scalar)>,
) {
let nns = &mut *self.natural_neighbor_buffer.borrow_mut();
get_natural_neighbor_edges(
self.triangulation,
&mut self.inspect_edges_buffer.borrow_mut(),
position,
nns,
);
self.get_natural_neighbor_weights(position, nns, result);
}
pub fn interpolate<I>(
&self,
i: I,
position: Point2<<V as HasPosition>::Scalar>,
) -> Option<<V as HasPosition>::Scalar>
where
I: Fn(VertexHandle<V, DE, UE, F>) -> <V as HasPosition>::Scalar,
{
let nns = &mut *self.weight_buffer.borrow_mut();
self.get_weights(position, nns);
if nns.is_empty() {
return None;
}
let mut total_sum = zero();
for (vertex, weight) in nns {
total_sum = total_sum + i(self.triangulation.vertex(*vertex)) * *weight;
}
Some(total_sum)
}
pub fn interpolate_gradient<I, G>(
&self,
i: I,
g: G,
flatness: <V as HasPosition>::Scalar,
position: Point2<<V as HasPosition>::Scalar>,
) -> Option<<V as HasPosition>::Scalar>
where
I: Fn(VertexHandle<V, DE, UE, F>) -> <V as HasPosition>::Scalar,
G: Fn(VertexHandle<V, DE, UE, F>) -> [<V as HasPosition>::Scalar; 2],
{
let nns = &mut *self.weight_buffer.borrow_mut();
self.get_weights(position, nns);
if nns.is_empty() {
return None;
}
let mut sum_c0 = zero();
let mut sum_c1 = zero();
let mut sum_c1_weights = zero();
let mut alpha: <V as HasPosition>::Scalar = zero();
let mut beta: <V as HasPosition>::Scalar = zero();
for (handle, weight) in nns {
let handle = self.triangulation.vertex(*handle);
let pos_i = handle.position();
let h_i = i(handle);
let diff = position.sub(pos_i);
let r_i2 = diff.length2();
if r_i2 == zero() {
return Some(h_i);
}
let r_i = r_i2.powf(flatness);
let c1_weight_i = *weight / r_i;
let grad_i = g(handle);
let zeta_i = h_i + diff.dot(grad_i.into());
alpha = alpha + c1_weight_i * r_i2;
beta = beta + *weight * r_i2;
sum_c1_weights = sum_c1_weights + c1_weight_i;
sum_c1 = sum_c1 + zeta_i * c1_weight_i;
sum_c0 = sum_c0 + h_i * *weight;
}
alpha = alpha / sum_c1_weights;
sum_c1 = sum_c1 / sum_c1_weights;
let result = (alpha * sum_c0 + beta * sum_c1) / (alpha + beta);
Some(result)
}
#[doc = include_str!("../../images/natural_neighbor_insertion_cell.svg")]
#[doc = include_str!("../../images/natural_neighbor_polygon.svg")]
fn get_natural_neighbor_weights(
&self,
position: Point2<<V as HasPosition>::Scalar>,
nns: &[FixedDirectedEdgeHandle],
result: &mut Vec<(FixedVertexHandle, <V as HasPosition>::Scalar)>,
) {
result.clear();
if nns.is_empty() {
return;
}
if nns.len() == 1 {
let edge = self.triangulation.directed_edge(nns[0]);
result.push((edge.from().fix(), one()));
return;
}
if nns.len() == 2 {
let [e0, e1] = [
self.triangulation.directed_edge(nns[0]),
self.triangulation.directed_edge(nns[1]),
];
let [v0, v1] = [e0.from(), e1.from()];
let [w0, w1] =
two_point_interpolation::<DelaunayTriangulation<V, DE, UE, F>>(v0, v1, position);
result.push((v0.fix(), w0));
result.push((v1.fix(), w1));
return;
}
let mut insertion_cell = self.insert_cell_buffer.borrow_mut();
insertion_cell.clear();
for cur_nn in nns {
let cur_nn = self.triangulation.directed_edge(*cur_nn);
let [from, to] = cur_nn.positions();
insertion_cell.push(
math::circumcenter([
to.sub(position),
from.sub(position),
Point2::new(zero(), zero()),
])
.0,
);
}
let mut total_area = zero();
let mut last_edge = self.triangulation.directed_edge(*nns.last().unwrap());
let mut last = *insertion_cell.last().unwrap();
for (stop_edge, first) in core::iter::zip(nns.iter(), &*insertion_cell) {
let stop_edge = self.triangulation.directed_edge(*stop_edge);
assert!(!stop_edge.is_outer_edge());
let mut positive_area = first.x * last.y;
let mut negative_area = first.y * last.x;
loop {
let vertices = last_edge
.face()
.as_inner()
.unwrap()
.positions()
.map(|p| p.sub(position));
let current = math::circumcenter(vertices).0;
positive_area = positive_area + last.x * current.y;
negative_area = negative_area + last.y * current.x;
last_edge = last_edge.next().rev();
last = current;
if last_edge == stop_edge.rev() {
positive_area = positive_area + current.x * first.y;
negative_area = negative_area + current.y * first.x;
break;
}
}
let polygon_area = positive_area - negative_area;
total_area = total_area + polygon_area;
result.push((stop_edge.from().fix(), polygon_area));
last = *first;
last_edge = stop_edge;
}
for tuple in result {
tuple.1 = tuple.1 / total_area;
}
}
pub fn estimate_gradients<I>(
&self,
i: I,
) -> impl Fn(VertexHandle<V, DE, UE, F>) -> [<V as HasPosition>::Scalar; 2]
where
I: Fn(VertexHandle<V, DE, UE, F>) -> <V as HasPosition>::Scalar,
{
let grads = self
.triangulation
.vertices()
.map(|v| self.estimate_gradient(v, &i))
.collect::<Vec<_>>();
move |v: VertexHandle<V, DE, UE, F>| grads[v.index()]
}
pub fn estimate_gradient<I>(
&self,
v: VertexHandle<V, DE, UE, F>,
i: I,
) -> [<V as HasPosition>::Scalar; 2]
where
I: Fn(VertexHandle<V, DE, UE, F>) -> <V as HasPosition>::Scalar,
{
let v_2d = v.position();
let v_pos = [v_2d.x, v_2d.y, i(v)];
let neighbor_positions = {
v.out_edges()
.map(|e| {
let pos = e.to().position();
[pos.x, pos.y, i(e.to())]
})
.collect::<Vec<_>>()
};
let mut final_normal: [<V as HasPosition>::Scalar; 3] = [zero(); 3];
for index in 0..neighbor_positions.len() {
let p0 = neighbor_positions[index];
let p1 = neighbor_positions[(index + 1) % neighbor_positions.len()];
let d0 = [p0[0] - v_pos[0], p0[1] - v_pos[1], p0[2] - v_pos[2]];
let d1 = [p1[0] - v_pos[0], p1[1] - v_pos[1], p1[2] - v_pos[2]];
let normal = [
d0[1] * d1[2] - d0[2] * d1[1],
d0[2] * d1[0] - d0[0] * d1[2],
d0[0] * d1[1] - d0[1] * d1[0],
];
if normal[2] > zero() {
final_normal = [
final_normal[0] + normal[0],
final_normal[1] + normal[1],
final_normal[2] + normal[2],
];
}
}
if final_normal[2] != zero() {
[
-final_normal[0] / final_normal[2],
-final_normal[1] / final_normal[2],
]
} else {
[zero(), zero()]
}
}
}
fn get_natural_neighbor_edges<T>(
triangulation: &T,
inspect_buffer: &mut Vec<FixedDirectedEdgeHandle>,
position: Point2<<T::Vertex as HasPosition>::Scalar>,
result: &mut Vec<FixedDirectedEdgeHandle>,
) where
T: Triangulation,
<T::Vertex as HasPosition>::Scalar: Float,
{
inspect_buffer.clear();
result.clear();
match triangulation.locate(position) {
PositionInTriangulation::OnFace(face) => {
for edge in triangulation
.face(face)
.adjacent_edges()
.into_iter()
.rev()
.map(|e| e.rev())
{
inspect_flips(triangulation, result, inspect_buffer, edge.fix(), position);
}
}
PositionInTriangulation::OnEdge(edge) => {
let edge = triangulation.directed_edge(edge);
if edge.is_part_of_convex_hull() {
result.extend([edge.fix(), edge.fix().rev()]);
return;
}
for edge in [edge, edge.rev()] {
inspect_flips(triangulation, result, inspect_buffer, edge.fix(), position);
}
}
PositionInTriangulation::OnVertex(fixed_handle) => {
let vertex = triangulation.vertex(fixed_handle);
result.push(
vertex
.out_edge()
.map(|e| e.fix())
.unwrap_or(FixedDirectedEdgeHandle::new(0)),
)
}
_ => {}
}
result.reverse();
}
fn inspect_flips<T>(
triangulation: &T,
result: &mut Vec<FixedDirectedEdgeHandle>,
buffer: &mut Vec<FixedDirectedEdgeHandle>,
edge_to_validate: FixedDirectedEdgeHandle,
position: Point2<<T::Vertex as HasPosition>::Scalar>,
) where
T: Triangulation,
{
buffer.clear();
buffer.push(edge_to_validate);
while let Some(edge) = buffer.pop() {
let edge = triangulation.directed_edge(edge);
let v2 = edge.opposite_vertex();
let v1 = edge.from();
let mut should_flip = false;
if let Some(v2) = v2 {
let v0 = edge.to().position();
let v1 = v1.position();
let v3 = position;
debug_assert!(math::is_ordered_ccw(v2.position(), v1, v0));
should_flip = math::contained_in_circumference(v2.position(), v1, v0, v3);
if should_flip {
let e1 = edge.next().fix().rev();
let e2 = edge.prev().fix().rev();
buffer.push(e1);
buffer.push(e2);
}
}
if !should_flip {
result.push(edge.fix().rev());
}
}
}
fn two_point_interpolation<'a, T>(
v0: VertexHandle<'a, T::Vertex, T::DirectedEdge, T::UndirectedEdge, T::Face>,
v1: VertexHandle<'a, T::Vertex, T::DirectedEdge, T::UndirectedEdge, T::Face>,
position: Point2<<T::Vertex as HasPosition>::Scalar>,
) -> [<T::Vertex as HasPosition>::Scalar; 2]
where
T: Triangulation,
<T::Vertex as HasPosition>::Scalar: Float,
{
let projection = math::project_point(v0.position(), v1.position(), position);
let rel = projection.relative_position();
let one: <T::Vertex as HasPosition>::Scalar = 1.0.into();
[one - rel, rel]
}
#[cfg(test)]
mod test {
use approx::assert_ulps_eq;
use crate::test_utilities::{random_points_in_range, random_points_with_seed, SEED, SEED2};
use crate::{DelaunayTriangulation, HasPosition, InsertionError, Point2, Triangulation};
use alloc::vec;
use alloc::vec::Vec;
struct PointWithHeight {
position: Point2<f64>,
height: f64,
}
impl PointWithHeight {
fn new(position: Point2<f64>, height: f64) -> Self {
Self { position, height }
}
}
impl HasPosition for PointWithHeight {
type Scalar = f64;
fn position(&self) -> Point2<Self::Scalar> {
self.position
}
}
#[test]
fn test_natural_neighbors() -> Result<(), InsertionError> {
let vertices = random_points_with_seed(50, SEED);
let t = DelaunayTriangulation::<_>::bulk_load(vertices)?;
let mut nn_edges = Vec::new();
let mut buffer = Vec::new();
let query_point = Point2::new(0.5, 0.2);
super::get_natural_neighbor_edges(&t, &mut buffer, query_point, &mut nn_edges);
assert!(nn_edges.len() >= 3);
for edge in &nn_edges {
let edge = t.directed_edge(*edge);
assert!(edge.side_query(query_point).is_on_left_side());
}
let mut nns = Vec::new();
let natural_neighbor = &t.natural_neighbor();
for v in t.vertices() {
natural_neighbor.get_weights(v.position(), &mut nns);
let expected = vec![(v.fix(), 1.0f64)];
assert_eq!(nns, expected);
}
Ok(())
}
#[test]
fn test_small_interpolation() -> Result<(), InsertionError> {
let mut t = DelaunayTriangulation::<_>::new();
t.insert(PointWithHeight::new(Point2::new(0.0, 2.0f64.sqrt()), 0.0))?;
let v1 = t.insert(PointWithHeight::new(Point2::new(-1.0, -1.0), 0.0))?;
let v2 = t.insert(PointWithHeight::new(Point2::new(1.0, -1.0), 0.0))?;
let mut result = Vec::new();
t.natural_neighbor()
.get_weights(Point2::new(0.0, 0.0), &mut result);
assert_eq!(result.len(), 3);
let mut v1_weight = None;
let mut v2_weight = None;
for (v, weight) in result {
if v == v1 {
v1_weight = Some(weight);
}
if v == v2 {
v2_weight = Some(weight);
} else {
assert!(weight > 0.0);
}
}
assert!(v1_weight.is_some());
assert!(v2_weight.is_some());
assert_ulps_eq!(v1_weight.unwrap(), v2_weight.unwrap());
Ok(())
}
#[test]
fn test_quad_interpolation() -> Result<(), InsertionError> {
let mut t = DelaunayTriangulation::<_>::new();
t.insert(Point2::new(1.0, 1.0))?;
t.insert(Point2::new(1.0, -1.0))?;
t.insert(Point2::new(-1.0, 1.0))?;
t.insert(Point2::new(-1.0, -1.0))?;
let mut result = Vec::new();
t.natural_neighbor()
.get_weights(Point2::new(0.0, 0.0), &mut result);
assert_eq!(result.len(), 4);
for (_, weight) in result {
assert_ulps_eq!(weight, 0.25);
}
Ok(())
}
#[test]
fn test_constant_height_interpolation() -> Result<(), InsertionError> {
let mut t = DelaunayTriangulation::<_>::new();
let vertices = random_points_with_seed(50, SEED);
let fixed_height = 1.5;
for v in vertices {
t.insert(PointWithHeight::new(v, fixed_height))?;
}
for v in random_points_in_range(1.5, 50, SEED2) {
let value = t.natural_neighbor().interpolate(|p| p.data().height, v);
if let Some(value) = value {
assert_ulps_eq!(value, 1.5);
}
}
Ok(())
}
#[test]
fn test_slope_interpolation() -> Result<(), InsertionError> {
let mut t = DelaunayTriangulation::<_>::new();
let grid_size = 32;
let scale = 1.0 / grid_size as f64;
for x in -grid_size..=grid_size {
for y in -grid_size..=grid_size {
let coords = Point2::new(x as f64, y as f64).mul(scale);
t.insert(PointWithHeight::new(coords, coords.x))?;
}
}
let query_points = random_points_with_seed(50, SEED);
let check = |point, expected| {
let value = t
.natural_neighbor()
.interpolate(|v| v.data().height, point)
.unwrap();
assert_ulps_eq!(value, expected, epsilon = 0.001);
};
for point in query_points {
check(point, point.x);
}
check(Point2::new(-1.0, 0.0), -1.0);
check(Point2::new(-1.0, 0.2), -1.0);
check(Point2::new(-1.0, 0.5), -1.0);
check(Point2::new(-1.0, -1.0), -1.0);
check(Point2::new(1.0, 0.0), 1.0);
check(Point2::new(1.0, 0.2), 1.0);
check(Point2::new(1.0, 0.5), 1.0);
check(Point2::new(1.0, -1.0), 1.0);
for x in [-1.0, -0.8, -0.5, 0.0, 0.3, 0.7, 1.0] {
check(Point2::new(x, 1.0), x);
check(Point2::new(x, -1.0), x);
}
let expect_none = |point| {
assert!(t
.natural_neighbor()
.interpolate(|v| v.data().height, point)
.is_none());
};
for x in [-5.0f64, -4.0, 3.0, 2.0] {
expect_none(Point2::new(x, 0.5));
expect_none(Point2::new(x, -0.5));
expect_none(Point2::new(x, 0.1));
}
Ok(())
}
#[test]
fn test_parabola_interpolation() -> Result<(), InsertionError> {
let mut t = DelaunayTriangulation::<_>::new();
let grid_size = 32;
let scale = 1.0 / grid_size as f64;
for x in -grid_size..=grid_size {
for y in -grid_size..=grid_size {
let coords = Point2::new(x as f64, y as f64).mul(scale);
t.insert(PointWithHeight::new(coords, coords.length2()))?;
}
}
let query_points = random_points_with_seed(50, SEED);
for point in query_points {
let value = t
.natural_neighbor()
.interpolate(|v| v.data().height, point)
.unwrap();
let r2 = point.length2();
assert_ulps_eq!(value, r2, epsilon = 0.001);
}
Ok(())
}
#[test]
fn test_nan_issue() -> Result<(), InsertionError> {
let vertices = vec![
Point2 {
x: 2273118.4147972693,
y: 6205168.575915335,
},
Point2 {
x: 2273118.2119929367,
y: 6205168.6854697745,
},
Point2 {
x: 2273118.1835989403,
y: 6205168.653506873,
},
Point2 {
x: 2273118.2647345643,
y: 6205169.082690307,
},
Point2 {
x: 2273118.105938253,
y: 6205168.163882839,
},
Point2 {
x: 2273117.7264146665,
y: 6205168.718028998,
},
Point2 {
x: 2273118.0946678743,
y: 6205169.148656867,
},
Point2 {
x: 2273118.3779900977,
y: 6205168.1644559605,
},
Point2 {
x: 2273117.71105166,
y: 6205168.459413756,
},
Point2 {
x: 2273118.30732556,
y: 6205169.130634655,
},
];
let delaunay = DelaunayTriangulation::<_>::bulk_load(vertices)?;
let nns = delaunay.natural_neighbor();
let result: f64 = nns
.interpolate(|_| 1.0, Point2::new(2273118.2, 6205168.666666667))
.unwrap();
assert!(!result.is_nan());
Ok(())
}
#[test]
fn test_gradient_estimation_planar() -> Result<(), InsertionError> {
let points = vec![
PointWithHeight::new(Point2::new(0., 0.), 0.),
PointWithHeight::new(Point2::new(1., 0.), 0.),
PointWithHeight::new(Point2::new(1., 1.), 1.),
PointWithHeight::new(Point2::new(0., 1.), 1.),
PointWithHeight::new(Point2::new(0.5, 0.5), 0.5),
];
let t = DelaunayTriangulation::<_>::bulk_load_stable(points).unwrap();
let nn = t.natural_neighbor();
let v = nn
.triangulation
.locate_vertex(Point2::new(0.5, 0.5))
.unwrap();
let grad = nn.estimate_gradient(v, |v| v.data().height);
assert!(grad == [0., 1.0]);
Ok(())
}
#[test]
fn test_gradient_estimation_flat() -> Result<(), InsertionError> {
let points = vec![
PointWithHeight::new(Point2::new(0., 0.), 1.),
PointWithHeight::new(Point2::new(1., 0.), 1.),
PointWithHeight::new(Point2::new(1., 1.), 1.),
PointWithHeight::new(Point2::new(0., 1.), 1.),
PointWithHeight::new(Point2::new(0.5, 0.5), 0.),
];
let t = DelaunayTriangulation::<_>::bulk_load_stable(points).unwrap();
let nn = t.natural_neighbor();
let v = nn
.triangulation
.locate_vertex(Point2::new(0.5, 0.5))
.unwrap();
let grad = nn.estimate_gradient(v, |v| v.data().height);
assert!(grad == [0., 0.]);
Ok(())
}
}