use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::Topology;
use brepkit_topology::face::{FaceId, FaceSurface};
use brepkit_topology::solid::SolidId;
use crate::tessellate;
use super::helpers::{collect_solid_face_ids, collect_wire_positions, compute_angular_range};
pub fn face_area(
topo: &Topology,
face_id: FaceId,
deflection: f64,
) -> Result<f64, crate::OperationsError> {
let face = topo.face(face_id)?;
match face.surface() {
FaceSurface::Plane { .. } => planar_face_area(topo, face_id),
FaceSurface::Cylinder(cyl) => {
let r = cyl.radius();
let positions = crate::boolean::face_polygon(topo, face_id)?;
if positions.len() >= 2 {
let axis = cyl.axis();
let origin = cyl.origin();
let v_vals: Vec<f64> = positions
.iter()
.map(|p| {
axis.dot(Vec3::new(
p.x() - origin.x(),
p.y() - origin.y(),
p.z() - origin.z(),
))
})
.collect();
let v_min = v_vals.iter().copied().fold(f64::INFINITY, f64::min);
let v_max = v_vals.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let height = (v_max - v_min).abs();
let sweep = if let Some(s) = cylinder_arc_sweep(topo, face_id, axis, origin)? {
s
} else {
let u_vals: Vec<f64> = positions
.iter()
.map(|p| {
let rel = *p - origin;
let along = axis.dot(rel);
let radial = rel - axis * along;
radial.y().atan2(radial.x())
})
.collect();
let u_min = u_vals.iter().copied().fold(f64::INFINITY, f64::min);
let u_max = u_vals.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let angular_span = u_max - u_min;
if angular_span > 330.0_f64.to_radians() {
std::f64::consts::TAU
} else {
angular_span
}
};
Ok(sweep * r * height)
} else {
let mesh = tessellate::tessellate(topo, face_id, deflection)?;
Ok(triangle_mesh_area(&mesh))
}
}
FaceSurface::Sphere(sph) => {
let r = sph.radius();
let positions = crate::boolean::face_polygon(topo, face_id)?;
if positions.len() >= 3 {
let v_vals: Vec<f64> = positions.iter().map(|p| sph.project_point(*p).1).collect();
let avg_v: f64 = v_vals.iter().sum::<f64>() / v_vals.len() as f64;
let signed_area = newell_signed_z_area(&positions);
let (v_min, v_max) = if signed_area > 0.0 {
(avg_v, std::f64::consts::FRAC_PI_2)
} else {
(-std::f64::consts::FRAC_PI_2, avg_v)
};
Ok(2.0 * std::f64::consts::PI * r * r * (v_max.sin() - v_min.sin()))
} else {
Ok(4.0 * std::f64::consts::PI * r * r)
}
}
FaceSurface::Cone(_) => analytic_cone_face_area(topo, face_id),
FaceSurface::Torus(_) => analytic_torus_face_area(topo, face_id),
FaceSurface::Nurbs(_) => {
let mesh = tessellate::tessellate(topo, face_id, deflection)?;
Ok(triangle_mesh_area(&mesh))
}
}
}
fn cylinder_arc_sweep(
topo: &Topology,
face_id: FaceId,
axis: Vec3,
origin: Point3,
) -> Result<Option<f64>, crate::OperationsError> {
use brepkit_topology::edge::EdgeCurve;
const LEVEL_MERGE_TOL: f64 = 1e-6;
let face = topo.face(face_id)?;
let wire = topo.wire(face.outer_wire())?;
let mut levels: Vec<(f64, f64)> = Vec::new();
for oe in wire.edges() {
let edge = topo.edge(oe.edge())?;
let EdgeCurve::Circle(circle) = edge.curve() else {
continue;
};
if edge.start() == edge.end() {
return Ok(Some(std::f64::consts::TAU));
}
let sp = topo.vertex(edge.start())?.point();
let ep = topo.vertex(edge.end())?.point();
let ts = circle.project(sp);
let mut te = circle.project(ep);
if te <= ts {
te += std::f64::consts::TAU;
}
let span = te - ts;
let v = axis.dot(circle.center() - origin);
if let Some(entry) = levels
.iter_mut()
.find(|(lv, _)| (*lv - v).abs() < LEVEL_MERGE_TOL)
{
entry.1 += span;
} else {
levels.push((v, span));
}
}
Ok(levels
.iter()
.map(|&(_, span)| span.min(std::f64::consts::TAU))
.fold(None, |acc: Option<f64>, span| {
Some(acc.map_or(span, |a| a.max(span)))
}))
}
fn analytic_cone_face_area(
topo: &Topology,
face_id: FaceId,
) -> Result<f64, crate::OperationsError> {
let face = topo.face(face_id)?;
let cone = match face.surface() {
FaceSurface::Cone(c) => c,
_ => {
return Err(crate::OperationsError::InvalidInput {
reason: "analytic_cone_face_area requires a cone face".into(),
});
}
};
let wire = topo.wire(face.outer_wire())?;
let mut u_vals = Vec::new();
let mut v_vals = Vec::new();
for oe in wire.edges() {
if let Ok(edge) = topo.edge(oe.edge()) {
for &vid in &[edge.start(), edge.end()] {
if let Ok(vtx) = topo.vertex(vid) {
let (u, v) = cone.project_point(vtx.point());
u_vals.push(u);
v_vals.push(v);
}
}
if !edge.is_closed()
&& let brepkit_topology::edge::EdgeCurve::Circle(circle) = edge.curve()
&& let (Ok(sv), Ok(ev)) = (topo.vertex(edge.start()), topo.vertex(edge.end()))
{
let ts = circle.project(sv.point());
let te = circle.project(ev.point());
let fwd = (te - ts).rem_euclid(std::f64::consts::TAU);
let mid_t = if fwd <= std::f64::consts::PI {
ts + fwd * 0.5
} else {
ts - (std::f64::consts::TAU - fwd) * 0.5
};
let mid = circle.evaluate(mid_t);
let (u, _) = cone.project_point(mid);
u_vals.push(u);
}
}
}
if v_vals.is_empty() {
return Ok(0.0);
}
let v_min = v_vals.iter().copied().fold(f64::INFINITY, f64::min);
let v_max = v_vals.iter().copied().fold(f64::NEG_INFINITY, f64::max);
if (v_max - v_min).abs() < 1e-15 {
return Ok(0.0);
}
let u_range = compute_angular_range(&mut u_vals);
let (u0, u1) = u_range;
let cos_a = cone.half_angle().cos();
let area = cos_a * (u1 - u0) * (v_max * v_max - v_min * v_min) / 2.0;
Ok(area.abs())
}
fn analytic_torus_face_area(
topo: &Topology,
face_id: FaceId,
) -> Result<f64, crate::OperationsError> {
let face = topo.face(face_id)?;
let tor = match face.surface() {
FaceSurface::Torus(t) => t,
_ => {
return Err(crate::OperationsError::InvalidInput {
reason: "analytic_torus_face_area requires a torus face".into(),
});
}
};
let wire = topo.wire(face.outer_wire())?;
let mut u_vals = Vec::new();
let mut v_vals = Vec::new();
for oe in wire.edges() {
if let Ok(edge) = topo.edge(oe.edge()) {
for &vid in &[edge.start(), edge.end()] {
if let Ok(vtx) = topo.vertex(vid) {
let (u, v) = tor.project_point(vtx.point());
u_vals.push(u);
v_vals.push(v);
}
}
if !edge.is_closed()
&& let brepkit_topology::edge::EdgeCurve::Circle(circle) = edge.curve()
&& let (Ok(sv), Ok(ev)) = (topo.vertex(edge.start()), topo.vertex(edge.end()))
{
let ts = circle.project(sv.point());
let te = circle.project(ev.point());
let fwd = (te - ts).rem_euclid(std::f64::consts::TAU);
let mid_t = if fwd <= std::f64::consts::PI {
ts + fwd * 0.5
} else {
ts - (std::f64::consts::TAU - fwd) * 0.5
};
let mid = circle.evaluate(mid_t);
let (u, _) = tor.project_point(mid);
u_vals.push(u);
}
}
}
if v_vals.is_empty() {
return Ok(0.0);
}
let mut v_min = v_vals.iter().copied().fold(f64::INFINITY, f64::min);
let mut v_max = v_vals.iter().copied().fold(f64::NEG_INFINITY, f64::max);
if (v_max - v_min).abs() < 1e-15 {
let u_range = compute_angular_range(&mut u_vals);
let (u0, u1) = u_range;
let big_r = tor.major_radius();
let small_r = tor.minor_radius();
let dv = std::f64::consts::TAU;
let area = small_r * (u1 - u0) * (big_r * dv + small_r * 0.0);
return Ok(area.abs());
}
if v_max - v_min > std::f64::consts::PI {
let new_min = v_max;
let new_max = v_min + std::f64::consts::TAU;
v_min = new_min;
v_max = new_max;
}
let u_range = compute_angular_range(&mut u_vals);
let (u0, u1) = u_range;
let big_r = tor.major_radius();
let small_r = tor.minor_radius();
let area =
small_r * (u1 - u0) * (big_r * (v_max - v_min) + small_r * (v_max.sin() - v_min.sin()));
Ok(area.abs())
}
fn planar_face_area(topo: &Topology, face_id: FaceId) -> Result<f64, crate::OperationsError> {
let face = topo.face(face_id)?;
let outer_wire = topo.wire(face.outer_wire())?;
let outer_positions = collect_wire_positions(topo, outer_wire)?;
let outer_area = newell_area(&outer_positions);
let mut hole_area = 0.0;
for &inner_wid in face.inner_wires() {
let inner_wire = topo.wire(inner_wid)?;
let inner_positions = collect_wire_positions(topo, inner_wire)?;
hole_area += newell_area(&inner_positions);
}
Ok((outer_area - hole_area).abs())
}
fn newell_area(positions: &[Point3]) -> f64 {
let n = positions.len();
if n < 3 {
return 0.0;
}
let mut sx = 0.0;
let mut sy = 0.0;
let mut sz = 0.0;
for i in 0..n {
let j = (i + 1) % n;
let vi = positions[i];
let vj = positions[j];
sx = vi.z().mul_add(-vj.y(), vi.y().mul_add(vj.z(), sx));
sy = vi.x().mul_add(-vj.z(), vi.z().mul_add(vj.x(), sy));
sz = vi.y().mul_add(-vj.x(), vi.x().mul_add(vj.y(), sz));
}
0.5 * sz.mul_add(sz, sx.mul_add(sx, sy * sy)).sqrt()
}
fn newell_signed_z_area(pts: &[Point3]) -> f64 {
let n = pts.len();
let mut area = 0.0;
for i in 0..n {
let j = (i + 1) % n;
area += pts[i].x() * pts[j].y() - pts[j].x() * pts[i].y();
}
area * 0.5
}
fn triangle_mesh_area(mesh: &tessellate::TriangleMesh) -> f64 {
let mut area = 0.0;
let idx = &mesh.indices;
let pos = &mesh.positions;
let tri_count = idx.len() / 3;
for t in 0..tri_count {
let i0 = idx[t * 3] as usize;
let i1 = idx[t * 3 + 1] as usize;
let i2 = idx[t * 3 + 2] as usize;
let a = pos[i1] - pos[i0];
let b = pos[i2] - pos[i0];
area += 0.5 * a.cross(b).length();
}
area
}
pub fn solid_surface_area(
topo: &Topology,
solid: SolidId,
deflection: f64,
) -> Result<f64, crate::OperationsError> {
let mut total = 0.0;
for fid in collect_solid_face_ids(topo, solid)? {
total += face_area(topo, fid, deflection)?;
}
Ok(total)
}