use std::f64::consts::PI;
use brepkit_math::vec::Point3;
use brepkit_topology::Topology;
use brepkit_topology::face::FaceId;
use brepkit_topology::solid::SolidId;
use crate::CheckError;
pub fn winding_number(topo: &Topology, solid: SolidId, point: Point3) -> Result<f64, CheckError> {
let solid_data = topo.solid(solid)?;
let shell = topo.shell(solid_data.outer_shell())?;
let faces = shell.faces().to_vec();
let mut total = 0.0;
for fid in &faces {
total += face_winding_contribution(topo, *fid, point)?;
}
Ok(total / (4.0 * PI))
}
fn face_winding_contribution(
topo: &Topology,
face_id: FaceId,
point: Point3,
) -> Result<f64, CheckError> {
let reversed = topo.face(face_id)?.is_reversed();
let polygon = crate::util::face_polygon(topo, face_id)?;
if polygon.len() < 3 {
return Ok(0.0);
}
let mut contribution = 0.0;
for i in 1..polygon.len() - 1 {
let omega = solid_angle(point, polygon[0], polygon[i], polygon[i + 1]);
contribution += if reversed { -omega } else { omega };
}
Ok(contribution)
}
fn solid_angle(p: Point3, a: Point3, b: Point3, c: Point3) -> f64 {
let pa = a - p;
let pb = b - p;
let pc = c - p;
let la = pa.length();
let lb = pb.length();
let lc = pc.length();
if la < 1e-15 || lb < 1e-15 || lc < 1e-15 {
return 0.0;
}
let num = pa.x() * (pb.y() * pc.z() - pb.z() * pc.y())
+ pa.y() * (pb.z() * pc.x() - pb.x() * pc.z())
+ pa.z() * (pb.x() * pc.y() - pb.y() * pc.x());
let den = la
.mul_add(lb * lc, pa.dot(pb) * lc)
.mul_add(1.0, pa.dot(pc) * lb + pb.dot(pc) * la);
2.0 * num.atan2(den)
}