#![allow(dead_code)]
use super::types::{FaceData, FaceFragment};
use brepkit_algo::FaceClass;
use brepkit_math::aabb::Aabb3;
use brepkit_math::bvh::Bvh;
use brepkit_math::predicates::point_in_polygon;
use brepkit_math::tolerance::Tolerance;
use brepkit_math::vec::{Point2, Point3, Vec3};
use crate::dot_normal_point;
pub(super) fn build_face_bvh(faces: &FaceData) -> Option<Bvh> {
if faces.len() < 32 {
return None;
}
let aabbs: Vec<(usize, Aabb3)> = faces
.iter()
.enumerate()
.map(|(i, (_, verts, _, _))| {
let bb = Aabb3::from_points(verts.iter().copied());
(i, bb)
})
.collect();
Some(Bvh::build(&aabbs))
}
pub(super) fn classify_fragment(
frag: &FaceFragment,
opposite: &FaceData,
bvh: Option<&Bvh>,
tol: Tolerance,
) -> FaceClass {
let centroid = polygon_centroid(&frag.vertices);
let class = classify_point(centroid, frag.normal, opposite, bvh, tol);
guard_tangent_coplanar(class, &frag.vertices, frag.normal, opposite, bvh, tol)
}
pub(super) fn guard_tangent_coplanar(
class: FaceClass,
vertices: &[Point3],
normal: Vec3,
opposite: &FaceData,
bvh: Option<&Bvh>,
tol: Tolerance,
) -> FaceClass {
if !matches!(
class,
FaceClass::CoplanarSame | FaceClass::CoplanarOpposite | FaceClass::On
) || vertices.len() < 3
{
return class;
}
let mut on_plane_count = 0usize;
for v in vertices {
let on_any_plane = opposite.iter().any(|(_, _verts, n_opp, d_opp)| {
let dist = dot_normal_point(*n_opp, *v) - d_opp;
dist.abs() < tol.linear * 10.0
});
if on_any_plane {
on_plane_count += 1;
}
}
if on_plane_count <= 1 {
for v in vertices {
let on_any = opposite.iter().any(|(_, _verts, n_opp, d_opp)| {
let dist = dot_normal_point(*n_opp, *v) - d_opp;
dist.abs() < tol.linear * 10.0
});
if !on_any {
return classify_point(*v, normal, opposite, bvh, tol);
}
}
let centroid = polygon_centroid(vertices);
return multiray_classify(centroid, normal, opposite, bvh, tol);
}
class
}
pub(super) fn classify_point(
centroid: Point3,
normal: Vec3,
opposite: &FaceData,
bvh: Option<&Bvh>,
tol: Tolerance,
) -> FaceClass {
let coplanar_indices: Vec<usize> = if let Some(bvh) = bvh {
let probe = Aabb3 {
min: centroid + Vec3::new(-tol.linear, -tol.linear, -tol.linear),
max: centroid + Vec3::new(tol.linear, tol.linear, tol.linear),
};
bvh.query_overlap(&probe)
} else {
(0..opposite.len()).collect()
};
for &i in &coplanar_indices {
let (_, ref verts, n_opp, d_opp) = opposite[i];
let near_vertex = verts.iter().any(|v| {
let dx = centroid.x() - v.x();
let dy = centroid.y() - v.y();
let dz = centroid.z() - v.z();
dx * dx + dy * dy + dz * dz < tol.linear * 10.0 * tol.linear * 10.0
});
if near_vertex {
continue;
}
let dist = dot_normal_point(n_opp, centroid) - d_opp;
if dist.abs() < tol.linear && point_in_face_3d(centroid, verts, &n_opp) {
let dot = normal.dot(n_opp);
return if dot > tol.angular {
FaceClass::CoplanarSame
} else if dot < -tol.angular {
FaceClass::CoplanarOpposite
} else {
continue;
};
}
}
multiray_classify(centroid, normal, opposite, bvh, tol)
}
#[inline]
fn point_along_line(pt: &Point3, dir: &Vec3, t: f64) -> Point3 {
Point3::new(
dir.x().mul_add(t, pt.x()),
dir.y().mul_add(t, pt.y()),
dir.z().mul_add(t, pt.z()),
)
}
#[inline]
fn ray_face_crossing(
centroid: Point3,
ray_dir: Vec3,
verts: &[Point3],
n_opp: Vec3,
d_opp: f64,
tol: Tolerance,
) -> i32 {
let denom = n_opp.dot(ray_dir);
if denom.abs() < tol.angular {
return 0;
}
let numer = d_opp - dot_normal_point(n_opp, centroid);
let t = numer / denom;
if t <= tol.linear {
return 0;
}
let hit = point_along_line(¢roid, &ray_dir, t);
if point_in_face_3d(hit, verts, &n_opp) {
if denom > 0.0 { -1 } else { 1 }
} else {
0
}
}
fn multiray_classify(
point: Point3,
normal: Vec3,
opposite: &FaceData,
bvh: Option<&Bvh>,
tol: Tolerance,
) -> FaceClass {
let ray_dirs = {
let perp = if normal.x().abs() < 0.9 {
Vec3::new(1.0, 0.0, 0.0)
} else {
Vec3::new(0.0, 1.0, 0.0)
};
let cross_vec = normal.cross(perp);
let axis_len = cross_vec.length();
if axis_len < 1e-12 {
[normal, normal, normal]
} else {
let inv = 1.0 / axis_len;
let axis = Vec3::new(
cross_vec.x() * inv,
cross_vec.y() * inv,
cross_vec.z() * inv,
);
let rodrigues = |cos_a: f64, sin_a: f64| -> Vec3 {
let dot = axis.dot(normal);
let cross = axis.cross(normal);
Vec3::new(
normal.x().mul_add(
cos_a,
cross.x().mul_add(sin_a, axis.x() * dot * (1.0 - cos_a)),
),
normal.y().mul_add(
cos_a,
cross.y().mul_add(sin_a, axis.y() * dot * (1.0 - cos_a)),
),
normal.z().mul_add(
cos_a,
cross.z().mul_add(sin_a, axis.z() * dot * (1.0 - cos_a)),
),
)
};
[normal, rodrigues(0.574, 0.819), rodrigues(0.574, -0.819)]
}
};
let mut inside_votes = 0u8;
let mut candidates = Vec::new();
for ray_dir in &ray_dirs {
let mut crossings = 0i32;
if let Some(bvh) = bvh {
bvh.query_ray_into(point, *ray_dir, &mut candidates);
for &idx in &candidates {
let (_, ref verts, n_opp, d_opp) = opposite[idx];
crossings += ray_face_crossing(point, *ray_dir, verts, n_opp, d_opp, tol);
}
} else {
for &(_, ref verts, n_opp, d_opp) in opposite {
crossings += ray_face_crossing(point, *ray_dir, verts, n_opp, d_opp, tol);
}
}
if crossings != 0 {
inside_votes += 1;
}
}
if inside_votes >= 2 {
FaceClass::Inside
} else {
FaceClass::Outside
}
}
#[inline]
pub(super) fn polygon_centroid(vertices: &[Point3]) -> Point3 {
if vertices.is_empty() {
return Point3::new(0.0, 0.0, 0.0);
}
#[allow(clippy::cast_precision_loss)] let inv_n = 1.0 / vertices.len() as f64;
let (sx, sy, sz) = vertices.iter().fold((0.0, 0.0, 0.0), |(ax, ay, az), v| {
(ax + v.x(), ay + v.y(), az + v.z())
});
Point3::new(sx * inv_n, sy * inv_n, sz * inv_n)
}
#[inline]
pub(super) fn point_in_face_3d(point: Point3, polygon: &[Point3], normal: &Vec3) -> bool {
if polygon.len() < 3 {
return false;
}
let ax = normal.x().abs();
let ay = normal.y().abs();
let az = normal.z().abs();
let (project_point, project_polygon): (Point2, Vec<Point2>) = if az >= ax && az >= ay {
(
Point2::new(point.x(), point.y()),
polygon.iter().map(|p| Point2::new(p.x(), p.y())).collect(),
)
} else if ay >= ax {
(
Point2::new(point.x(), point.z()),
polygon.iter().map(|p| Point2::new(p.x(), p.z())).collect(),
)
} else {
(
Point2::new(point.y(), point.z()),
polygon.iter().map(|p| Point2::new(p.y(), p.z())).collect(),
)
};
point_in_polygon(project_point, &project_polygon)
}