use crate::{KernelRefusal, KernelStage, OrRefuse};
use crate::topology::{EdgeRecord, FaceRecord};
use crate::{
build_pcurve_on_surface, build_pcurve_on_surface_range, make_plane, BrepSolid, KnotVector,
NurbsSurface, Vec3,
};
use rustc_hash::FxHashMap as HashMap;
pub(crate) const PARALLEL_EPS: f64 = 1e-9;
#[derive(Clone, Copy)]
pub(crate) struct Plane {
pub(crate) origin: Vec3,
pub(crate) u_dir: Vec3,
pub(crate) v_dir: Vec3,
pub(crate) normal: Vec3,
}
fn surface_domain_mid(surface: &NurbsSurface) -> Result<(f64, f64), KernelRefusal> {
let ku = KnotVector::new(surface.knots_u.clone(), surface.degree_u).or_refuse(KernelStage::Refine, "new")?;
let kv = KnotVector::new(surface.knots_v.clone(), surface.degree_v).or_refuse(KernelStage::Refine, "new")?;
let [u0, u1] = ku.domain();
let [v0, v1] = kv.domain();
Ok(((u0 + u1) * 0.5, (v0 + v1) * 0.5))
}
pub(crate) fn plane_of_surface(
surface: &NurbsSurface,
tolerance: f64,
op: &str,
) -> Result<Plane, KernelRefusal> {
let (u_mid, v_mid) = surface_domain_mid(surface)?;
let derivatives = surface.derivatives(u_mid, v_mid, 1).or_refuse(KernelStage::Refine, "derivatives")?;
let origin = derivatives[0][0];
let du = derivatives[1][0];
let dv = derivatives[0][1];
let raw_normal = du.cross(dv);
if raw_normal.length() <= PARALLEL_EPS {
return Err(KernelRefusal::unsupported(KernelStage::Refine, "retrim_degenerate_surface", format!("{op}: degenerate face surface")));
}
let normal = raw_normal.normalized().or_refuse(KernelStage::Refine, "normalized")?;
for row in &surface.control_points {
for control in row {
let point = control.point().or_refuse(KernelStage::Refine, "point")?;
if point.sub(origin).dot(normal).abs() > tolerance {
return Err(KernelRefusal::unsupported(KernelStage::Refine, "retrim_curved_face", format!(
"{op}: face is not planar (curved neighbours are deferred in this slice)"
)));
}
}
}
let u_dir = du.normalized().or_refuse(KernelStage::Refine, "normalized")?;
let v_dir = normal.cross(u_dir).normalized().or_refuse(KernelStage::Refine, "normalized")?;
Ok(Plane {
origin,
u_dir,
v_dir,
normal,
})
}
pub(crate) fn boundary_samples(
face: &FaceRecord,
edges: &HashMap<u64, EdgeRecord>,
op: &str,
) -> Result<Vec<Vec3>, KernelRefusal> {
let mut points: Vec<Vec3> = Vec::new();
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
let edge = edges
.get(&coedge.edge_id)
.ok_or_else(|| format!("{op}: missing edge {}", coedge.edge_id)).or_refuse(KernelStage::Refine, "retrim_missing_edge")?;
for step in 0..=4 {
let t = edge.t0 + (edge.t1 - edge.t0) * (step as f64 / 4.0);
points.push(edge.curve.evaluate(t).or_refuse(KernelStage::Refine, "evaluate")?);
}
}
}
Ok(points)
}
pub(crate) fn rebuild_loop_pcurves(
face: &mut FaceRecord,
edges: &HashMap<u64, EdgeRecord>,
surface: &NurbsSurface,
tolerance: f64,
op: &str,
) -> Result<(), KernelRefusal> {
let closed = surface
.closed_directions()
.or_refuse(KernelStage::Refine, "closed_directions")?;
let periods = [
if closed.0 {
let [a, b] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
b - a
} else {
0.0
},
if closed.1 {
let [a, b] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
b - a
} else {
0.0
},
];
for loop_record in &mut face.loops {
for coedge in &mut loop_record.coedges {
let edge = edges
.get(&coedge.edge_id)
.ok_or_else(|| format!("{op}: missing edge {}", coedge.edge_id)).or_refuse(KernelStage::Refine, "retrim_missing_edge")?;
let [d0, d1] = edge.curve.domain().or_refuse(KernelStage::Refine, "domain")?;
let span = (d1 - d0).max(1e-12);
let is_subrange =
(edge.t0 - d0).abs() > 1e-9 * span || (edge.t1 - d1).abs() > 1e-9 * span;
let mut pcurve = if is_subrange {
build_pcurve_on_surface_range(
surface,
&edge.curve,
edge.t0,
edge.t1,
coedge.forward,
tolerance,
).or_refuse(KernelStage::Refine, "build_pcurve_on_surface_range")?
} else {
let mut pcurve = build_pcurve_on_surface(surface, &edge.curve).or_refuse(KernelStage::Refine, "build_pcurve_on_surface")?;
if !coedge.forward {
pcurve = pcurve.reversed().or_refuse(KernelStage::Refine, "reversed")?;
}
pcurve
};
if closed.0 || closed.1 {
let [old_t0, _] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let [new_t0, _] = pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let old_start = coedge.pcurve.evaluate(old_t0)
.or_refuse(KernelStage::Refine, "evaluate")?;
let new_start = pcurve.evaluate(new_t0)
.or_refuse(KernelStage::Refine, "evaluate")?;
let shift = |old: f64, new: f64, period: f64| {
if period > 0.0 {
((old - new) / period).round() * period
} else {
0.0
}
};
let du = shift(old_start.x, new_start.x, periods[0]);
let dv = shift(old_start.y, new_start.y, periods[1]);
for control in &mut pcurve.control_points {
control.x += du * control.w;
control.y += dv * control.w;
}
}
coedge.pcurve = pcurve;
}
}
Ok(())
}
pub(crate) fn retrim_planar_face(
face: &mut FaceRecord,
plane: &Plane,
edges: &HashMap<u64, EdgeRecord>,
scale: f64,
op: &str,
) -> Result<(), KernelRefusal> {
let mut u_min = f64::INFINITY;
let mut u_max = f64::NEG_INFINITY;
let mut v_min = f64::INFINITY;
let mut v_max = f64::NEG_INFINITY;
for point in boundary_samples(face, edges, op)? {
let delta = point.sub(plane.origin);
let u = delta.dot(plane.u_dir);
let v = delta.dot(plane.v_dir);
u_min = u_min.min(u);
u_max = u_max.max(u);
v_min = v_min.min(v);
v_max = v_max.max(v);
}
if !(u_min.is_finite() && u_max.is_finite() && v_min.is_finite() && v_max.is_finite()) {
return Err(KernelRefusal::internal(KernelStage::Refine, "retrim_empty_boundary", format!("{op}: empty face boundary")));
}
let margin = ((u_max - u_min).max(v_max - v_min) * 0.25).max(scale * 1e-3);
let new_origin = plane
.origin
.add(plane.u_dir.scale(u_min - margin))
.add(plane.v_dir.scale(v_min - margin));
let width = (u_max - u_min) + 2.0 * margin;
let height = (v_max - v_min) + 2.0 * margin;
let surface = make_plane(new_origin, plane.u_dir, plane.v_dir, width, height).or_refuse(KernelStage::Refine, "make_plane")?;
rebuild_loop_pcurves(face, edges, &surface, (scale * 1e-7).max(1e-9), op)?;
face.surface = surface;
Ok(())
}
pub(crate) fn retrim_face_in_solid<G>(
solid: &mut BrepSolid,
shell: usize,
face_pos: usize,
edges: &HashMap<u64, EdgeRecord>,
grow: G,
tolerance: f64,
op: &str,
) -> Result<(), KernelRefusal>
where
G: FnOnce(&mut BrepSolid, &[Vec3]) -> Result<(), KernelRefusal>,
{
let points = boundary_samples(&solid.shells[shell].faces[face_pos], edges, op)?;
grow(solid, &points)?;
let surface = solid.shells[shell].faces[face_pos].surface.clone();
rebuild_loop_pcurves(
&mut solid.shells[shell].faces[face_pos],
edges,
&surface,
tolerance,
op,
)
}
#[path = "retrim/analytic.rs"]
mod analytic;
pub(crate) use analytic::reconstruct_analytic_shell;