use std::borrow::Cow;
use super::adaptive_tessellation_option::AdaptiveTessellationOptions;
use super::adaptive_tessellation_processor::AdaptiveTessellationProcessor;
use super::boundary_constraints::BoundaryConstraints;
use super::surface_tessellation::SurfaceTessellation;
use super::{adaptive_tessellation_node::AdaptiveTessellationNode, Tessellation};
use super::{ConstrainedTessellation, SurfacePoint};
use itertools::Itertools;
use nalgebra::{
allocator::Allocator, DefaultAllocator, DimName, DimNameDiff, DimNameSub, Vector2, U1,
};
use crate::tessellation::DividableDirection;
use crate::{misc::FloatingPoint, surface::NurbsSurface};
impl<T: FloatingPoint, D: DimName, F> Tessellation<Option<AdaptiveTessellationOptions<T, D, F>>>
for NurbsSurface<T, D>
where
D: DimNameSub<U1>,
DefaultAllocator: Allocator<D>,
DefaultAllocator: Allocator<DimNameDiff<D, U1>>,
F: Fn(&AdaptiveTessellationNode<T, D>) -> Option<DividableDirection> + Copy,
{
type Output = SurfaceTessellation<T, D>;
fn tessellate(&self, options: Option<AdaptiveTessellationOptions<T, D, F>>) -> Self::Output {
let nodes = surface_adaptive_tessellate(self, None, options);
SurfaceTessellation::new(self, &nodes, None)
}
}
impl<T: FloatingPoint, D: DimName, F>
ConstrainedTessellation<Option<AdaptiveTessellationOptions<T, D, F>>> for NurbsSurface<T, D>
where
D: DimNameSub<U1>,
DefaultAllocator: Allocator<D>,
DefaultAllocator: Allocator<DimNameDiff<D, U1>>,
F: Fn(&AdaptiveTessellationNode<T, D>) -> Option<DividableDirection> + Copy,
{
type Constraint = BoundaryConstraints<T>;
type Output = SurfaceTessellation<T, D>;
fn constrained_tessellate(
&self,
constraints: Self::Constraint,
options: Option<AdaptiveTessellationOptions<T, D, F>>,
) -> Self::Output {
let nodes = surface_adaptive_tessellate(self, Some(Cow::Borrowed(&constraints)), options);
SurfaceTessellation::new(self, &nodes, Some(constraints))
}
}
fn surface_adaptive_tessellate<T: FloatingPoint, D, F>(
s: &NurbsSurface<T, D>,
constraints: Option<Cow<'_, BoundaryConstraints<T>>>,
options: Option<AdaptiveTessellationOptions<T, D, F>>,
) -> Vec<AdaptiveTessellationNode<T, D>>
where
D: DimName,
D: DimNameSub<U1>,
DefaultAllocator: Allocator<D>,
DefaultAllocator: Allocator<DimNameDiff<D, U1>>,
F: Fn(&AdaptiveTessellationNode<T, D>) -> Option<DividableDirection> + Copy,
{
let is_adaptive = options.is_some();
let options = options.unwrap_or_default();
let min_divs_multiplier = if constraints.is_some() { 1 } else { 2 };
let us: Vec<_> = if s.u_degree() <= 1 {
s.u_knots()
.iter()
.skip(1)
.take(s.u_knots().len() - 2)
.cloned()
.collect()
} else {
let min_u = (s.control_points().len() - 1) * min_divs_multiplier;
let divs_u = options.min_divs_u.max(min_u);
let (umin, umax) = s.u_knots_domain();
let du = (umax - umin) / T::from_usize(divs_u).unwrap();
(0..=divs_u)
.map(|i| umin + du * T::from_usize(i).unwrap())
.collect()
};
let vs: Vec<_> = if s.v_degree() <= 1 {
s.v_knots()
.iter()
.skip(1)
.take(s.v_knots().len() - 2)
.cloned()
.collect()
} else {
let min_v = (s.control_points()[0].len() - 1) * min_divs_multiplier;
let divs_v = options.min_divs_v.max(min_v);
let (vmin, vmax) = s.v_knots_domain();
let dv = (vmax - vmin) / T::from_usize(divs_v).unwrap();
(0..=divs_v)
.map(|i| vmin + dv * T::from_usize(i).unwrap())
.collect()
};
let (us, vs) = if let Some(c) = constraints.as_ref() {
let us = if let Some(iu) = c.u_parameters() {
iu
} else {
us
};
let vs = if let Some(iv) = c.v_parameters() {
iv
} else {
vs
};
(us, vs)
} else {
(us, vs)
};
let (u_min_constraint, u_max_constraint, v_min_constraint, v_max_constraint) = constraints
.as_ref()
.map(|c| {
let u_in_v_min = c.u_parameters_at_v_min().is_some();
let u_in_v_max = c.u_parameters_at_v_max().is_some();
let v_in_u_min = c.v_parameters_at_u_min().is_some();
let v_in_u_max = c.v_parameters_at_u_max().is_some();
(u_in_v_min, u_in_v_max, v_in_u_min, v_in_u_max)
})
.unwrap_or((false, false, false, false));
let divs_u = us.len() - 1;
let divs_v = vs.len() - 1;
let eps = T::from_f64(1e-8).unwrap();
let pts = vs
.iter()
.enumerate()
.map(|(iv, v)| {
let u_min = iv == 0;
let u_max = iv == divs_v;
let u_constraint = (u_min && u_min_constraint) || (u_max && u_max_constraint);
let row = us.iter().enumerate().map(|(iu, u)| {
let v_min = iu == 0;
let v_max = iu == divs_u;
let v_constraint = (v_min && v_min_constraint) || (v_max && v_max_constraint);
let ds = s.rational_derivatives(*u, *v, 1);
let n = ds[1][0].cross(&ds[0][1]);
let l = n.norm();
let norm = if l.is_zero() || !l.is_finite() {
let u_p = if v_min {
*u + eps
} else if v_max {
*u - eps
} else {
*u
};
let v_p = if u_min {
*v + eps
} else if u_max {
*v - eps
} else {
*v
};
s.normal_at(u_p, v_p).normalize()
} else {
n / l
};
SurfacePoint::new(Vector2::new(*u, *v), ds[0][0].clone().into(), norm, false)
.with_constraints(u_constraint, v_constraint)
.with_boundary(u_min, u_max, v_min, v_max)
});
row.collect_vec()
})
.collect_vec();
let pts = &pts;
let nodes = (0..divs_v)
.flat_map(|iv: usize| {
let iv_r = divs_v - iv;
(0..divs_u).map(move |iu| {
let corners = [
pts[iv_r - 1][iu].clone(),
pts[iv_r - 1][iu + 1].clone(),
pts[iv_r][iu + 1].clone(),
pts[iv_r][iu].clone(),
];
let index = iv * divs_u + iu;
let s = south(index, iv, divs_u, divs_v);
let e = east(index, iu, divs_u);
let n = north(index, iv, divs_u);
let w = west(index, iu);
AdaptiveTessellationNode::new(index, corners, [s, e, n, w])
})
})
.collect_vec();
let nodes = if !is_adaptive {
nodes
} else {
let mut processor = AdaptiveTessellationProcessor::new(s, nodes);
for iv in 0..divs_v {
for iu in 0..divs_u {
let index = iv * divs_u + iu;
processor.divide(index, &options);
}
}
processor.into_nodes()
};
nodes
}
fn north(index: usize, iv: usize, divs_u: usize) -> Option<usize> {
if iv == 0 {
None
} else {
Some(index - divs_u)
}
}
fn south(index: usize, iv: usize, divs_u: usize, divs_v: usize) -> Option<usize> {
if iv == divs_v - 1 {
None
} else {
Some(index + divs_u)
}
}
fn east(index: usize, iu: usize, divs_u: usize) -> Option<usize> {
if iu == divs_u - 1 {
None
} else {
Some(index + 1)
}
}
fn west(index: usize, iu: usize) -> Option<usize> {
if iu == 0 {
None
} else {
Some(index - 1)
}
}
#[allow(dead_code)]
fn merge_sorted_parameters<T: FloatingPoint>(p0: Vec<T>, p1: Vec<T>) -> Vec<T> {
let mut c0 = 0;
let mut c1 = 0;
let mut res = vec![];
while c0 < p0.len() && c1 < p1.len() {
if p0[c0] < p1[c1] {
res.push(p0[c0]);
c0 += 1;
} else if p0[c0] > p1[c1] {
res.push(p1[c1]);
c1 += 1;
} else {
res.push(p0[c0]);
c0 += 1;
c1 += 1;
}
}
if c0 < p0.len() {
res.extend_from_slice(&p0[c0..]);
} else if c1 < p1.len() {
res.extend_from_slice(&p1[c1..]);
}
res
}
#[cfg(test)]
mod tests {
use std::f64::consts::FRAC_PI_2;
use nalgebra::{Point3, Rotation3, Translation3, Vector3};
use crate::{curve::NurbsCurve3D, misc::Transformable, prelude::Interpolation};
use super::*;
#[test]
fn surface_constrained_tessellation() {
let interpolation_target = vec![
Point3::new(-1.0, -1.0, 0.),
Point3::new(1.0, -1.0, 0.),
Point3::new(1.0, 1.0, 0.),
Point3::new(-1.0, 1.0, 0.),
Point3::new(-1.0, 2.0, 0.),
Point3::new(1.0, 2.5, 0.),
];
let interpolated = NurbsCurve3D::<f64>::interpolate(&interpolation_target, 3).unwrap();
let rotation = Rotation3::from_axis_angle(&Vector3::z_axis(), FRAC_PI_2);
let translation = Translation3::new(0., 0., 1.5);
let m = translation * rotation;
let front = interpolated.transformed(&(translation.inverse()).into());
let back = interpolated.transformed(&m.into());
let (umin, umax) = front.knots_domain();
let surface = NurbsSurface::try_loft(&[front.clone(), back.clone()], Some(3)).unwrap();
let u_parameters = (0..=8)
.map(|i| umin + (umax - umin) * i as f64 / 8.)
.collect_vec();
let boundary = BoundaryConstraints::default()
.with_u_parameters_at_v_min(u_parameters.clone())
.with_u_parameters_at_v_max(u_parameters.clone());
let option = AdaptiveTessellationOptions::<f64>::default().with_norm_tolerance(1e-3);
let tess = surface.constrained_tessellate(boundary, Some(option));
let vertices = tess.points().clone();
let front_points = u_parameters
.iter()
.map(|u| front.point_at(*u))
.collect_vec();
let back_points = u_parameters.iter().map(|u| back.point_at(*u)).collect_vec();
assert!(front_points.iter().all(|p| vertices.contains(p)));
assert!(back_points.iter().all(|p| vertices.contains(p)));
}
}