use std::sync::OnceLock;
use curvo::prelude::Boolean;
use curvo::prelude::operation::BooleanOperation;
use curvo::prelude::nurbs_curve;
use curvo::prelude::NurbsCurve2D;
use curvo::prelude::KnotStyle;
use crate::float_types::{parry3d::bounding_volume::Aabb, Real};
use curvo::prelude::FloatingPoint;
use curvo::region::{CompoundCurve, Region};
use crate::traits::CSGOps;
use nalgebra::{Matrix4, Point3, Translation3, Vector3};
#[derive(Clone, Debug)]
pub struct Nurbs<S: Clone + Send + Sync + std::fmt::Debug = ()> {
region: Region<Real>,
bbox: OnceLock<Aabb>,
pub metadata: Option<S>,
}
impl<S: Clone + Send + Sync + std::fmt::Debug> Nurbs<S> {
pub fn rectangle(width: Real, height: Real, metadata: Option<S>) -> Self {
let hw = width * 0.5;
let hh = height * 0.5;
let pts = [
Point3::new(-hw, -hh, 0.0),
Point3::new(hw, -hh, 0.0),
Point3::new(hw, hh, 0.0),
Point3::new(-hw, hh, 0.0),
Point3::new(-hw, -hh, 0.0),
];
let rect = NurbsCurve2D::polyline(&pts, true, );
Self::from_exterior(rect, metadata)
}
pub fn circle(radius: Real, segments: usize) -> Self {
use nalgebra::{Point2, Vector2};
let center = Point2::origin();
let x_axis = Vector2::x();
let y_axis = Vector2::y();
let circle =
NurbsCurve2D::try_circle(¢er, &x_axis, &y_axis, radius).expect("circle");
Self::from_exterior(circle, None)
}
pub fn from_exterior(exterior: NurbsCurve2D<Real>, metadata: Option<S>) -> Self {
let region = Region::new(CompoundCurve::from(exterior), vec![]);
Self {
region,
bbox: OnceLock::new(),
metadata,
}
}
fn from_region(region: Region<Real>, metadata: Option<S>) -> Self {
Self {
region,
bbox: OnceLock::new(),
metadata,
}
}
fn points_3d(&self) -> Vec<Point3<Real>> {
self.region
.exterior()
.spans()
.iter()
.flat_map(|span| span.dehomogenized_control_points())
.map(|p| Point3::new(p.x, p.y, 0.0))
.collect()
}
}
impl<S: Clone + Send + Sync + std::fmt::Debug> CSGOps for Nurbs<S> {
fn new() -> Self {
Self::rectangle(0.0, 0.0, None)
}
fn union(&self, other: &Self) -> Self {
let clip = self
.region
.boolean(BooleanOperation::Union, &other.region, None)
.expect("boolean union failed");
let mut regions = clip.into_regions();
let region = regions
.pop()
.unwrap_or_else(|| Region::new(CompoundCurve::new(vec![]), vec![]));
Self::from_region(region, self.metadata.clone())
}
fn difference(&self, other: &Self) -> Self {
let clip = self
.region
.boolean(BooleanOperation::Difference, &other.region, None)
.expect("boolean difference failed");
let mut regions = clip.into_regions();
let region = regions
.pop()
.unwrap_or_else(|| Region::new(CompoundCurve::new(vec![]), vec![]));
Self::from_region(region, self.metadata.clone())
}
fn intersection(&self, other: &Self) -> Self {
let clip = self
.region
.boolean(BooleanOperation::Intersection, &other.region, None)
.expect("boolean intersection failed");
let mut regions = clip.into_regions();
let region = regions
.pop()
.unwrap_or_else(|| Region::new(CompoundCurve::new(vec![]), vec![]));
Self::from_region(region, self.metadata.clone())
}
fn xor(&self, other: &Self) -> Self {
let a_sub_b = self.difference(other);
let b_sub_a = other.difference(self);
a_sub_b.union(&b_sub_a)
}
fn transform(&self, mat: &Matrix4<Real>) -> Self {
use curvo::prelude::Transformable;
let mut region = self.region.clone();
region.transform(mat);
Self::from_region(region, self.metadata.clone())
}
fn bounding_box(&self) -> Aabb {
*self.bbox.get_or_init(|| {
let points = self.points_3d();
if points.is_empty() {
return Aabb::new(Point3::origin(), Point3::origin());
}
let (mut min_x, mut min_y, mut max_x, mut max_y) =
(Real::MAX, Real::MAX, -Real::MAX, -Real::MAX);
for p in &points {
if p.x < min_x {
min_x = p.x;
}
if p.y < min_y {
min_y = p.y;
}
if p.x > max_x {
max_x = p.x;
}
if p.y > max_y {
max_y = p.y;
}
}
let mins = Point3::new(min_x, min_y, 0.0);
let maxs = Point3::new(max_x, max_y, 0.0);
Aabb::new(mins, maxs)
})
}
fn invalidate_bounding_box(&mut self) {
self.bbox = OnceLock::new();
}
fn inverse(&self) -> Self {
use curvo::prelude::Invertible;
let mut region = self.region.clone();
region.invert();
Self::from_region(region, self.metadata.clone())
}
}