use crate::kernel::arrangement::{boolean, box_mesh, difference_all, union_all, BoolOp, Tri};
use crate::kernel::mesh_bridge::orient_outward;
use crate::kernel::signed_volume::signed_volume_of;
#[derive(Clone, Copy, Debug)]
pub struct ZoneBox {
pub center: [f64; 3],
pub size: [f64; 3],
pub rotation_y: f64,
}
#[derive(Clone, Debug)]
pub enum ZoneShape {
Box(ZoneBox),
Prism {
footprint: Vec<[f64; 2]>,
min_y: f64,
max_y: f64,
},
}
impl ZoneShape {
fn to_tris(&self) -> Vec<Tri> {
match self {
ZoneShape::Box(b) => b.to_tris(),
ZoneShape::Prism { footprint, min_y, max_y } => prism_tris(footprint, *min_y, *max_y),
}
}
fn world_aabb(&self) -> ([f64; 3], [f64; 3]) {
match self {
ZoneShape::Box(b) => b.world_aabb(),
ZoneShape::Prism { footprint, min_y, max_y } => {
let mut lo = [f64::INFINITY, *min_y, f64::INFINITY];
let mut hi = [f64::NEG_INFINITY, *max_y, f64::NEG_INFINITY];
for p in footprint {
lo[0] = lo[0].min(p[0]);
hi[0] = hi[0].max(p[0]);
lo[2] = lo[2].min(p[1]);
hi[2] = hi[2].max(p[1]);
}
(lo, hi)
}
}
}
}
fn prism_tris(footprint: &[[f64; 2]], min_y: f64, max_y: f64) -> Vec<Tri> {
let n = footprint.len();
if n < 3 {
return Vec::new();
}
let lo = |i: usize| [footprint[i][0], min_y, footprint[i][1]];
let hi = |i: usize| [footprint[i][0], max_y, footprint[i][1]];
let mut tris = Vec::with_capacity(4 * n);
for i in 1..n - 1 {
tris.push([lo(0), lo(i + 1), lo(i)]);
tris.push([hi(0), hi(i), hi(i + 1)]);
}
for i in 0..n {
let j = (i + 1) % n;
tris.push([lo(i), lo(j), hi(j)]);
tris.push([lo(i), hi(j), hi(i)]);
}
tris
}
impl ZoneBox {
fn to_tris(self) -> Vec<Tri> {
let h = [self.size[0] / 2.0, self.size[1] / 2.0, self.size[2] / 2.0];
let local = box_mesh([-h[0], -h[1], -h[2]], [h[0], h[1], h[2]]);
let (sin, cos) = self.rotation_y.sin_cos();
local
.into_iter()
.map(|t| {
t.map(|p| {
[
self.center[0] + p[0] * cos - p[2] * sin,
self.center[1] + p[1],
self.center[2] + p[0] * sin + p[2] * cos,
]
})
})
.collect()
}
fn world_aabb(self) -> ([f64; 3], [f64; 3]) {
let mut lo = [f64::INFINITY; 3];
let mut hi = [f64::NEG_INFINITY; 3];
for t in self.to_tris() {
for p in t {
for k in 0..3 {
lo[k] = lo[k].min(p[k]);
hi[k] = hi[k].max(p[k]);
}
}
}
(lo, hi)
}
}
#[derive(Clone, Debug)]
pub struct ZonePiece {
pub zone: Option<usize>,
pub tris: Vec<Tri>,
pub volume: f64,
}
#[derive(Clone, Debug)]
pub struct ZoneSplit {
pub pieces: Vec<ZonePiece>,
pub whole_volume: f64,
pub remainder_failed: bool,
}
impl ZoneSplit {
pub fn sum_error_rel(&self) -> f64 {
if self.whole_volume.abs() <= f64::MIN_POSITIVE {
return 0.0;
}
let sum: f64 = self.pieces.iter().map(|p| p.volume).sum();
((sum - self.whole_volume) / self.whole_volume).abs()
}
}
pub const NEGLIGIBLE_PIECE_REL: f64 = 1e-9;
pub fn split_mesh_by_zones(host: &[Tri], zones: &[ZoneShape]) -> ZoneSplit {
let host = orient_outward(host.to_vec());
let whole_volume = signed_volume_of(&host);
let negligible = whole_volume.abs() * NEGLIGIBLE_PIECE_REL;
let (host_lo, host_hi) = tris_aabb(&host);
let mut pieces = Vec::new();
let mut reached: Vec<Vec<Tri>> = Vec::new();
let mut remainder_failed = false;
for (index, zone) in zones.iter().enumerate() {
let (lo, hi) = zone.world_aabb();
if (0..3).any(|k| lo[k] > host_hi[k] || hi[k] < host_lo[k]) {
continue;
}
let box_tris = orient_outward(zone.to_tris());
let piece = boolean(&host, &box_tris, BoolOp::Intersection);
let volume = signed_volume_of(&piece);
if piece.is_empty() || volume <= negligible {
continue;
}
reached.push(box_tris);
pieces.push(ZonePiece { zone: Some(index), tris: piece, volume });
}
if !reached.is_empty() {
let operands: Vec<&[Tri]> = reached.iter().map(|t| t.as_slice()).collect();
let (cutter, conforming) = union_all(&operands);
let rest = if conforming { difference_all(&host, &[&cutter]) } else { None };
match rest {
Some(rest) => {
let volume = signed_volume_of(&rest);
if !rest.is_empty() && volume > negligible {
pieces.push(ZonePiece { zone: None, tris: rest, volume });
}
}
None => remainder_failed = true,
}
} else {
let volume = whole_volume;
pieces.push(ZonePiece { zone: None, tris: host, volume });
}
ZoneSplit { pieces, whole_volume, remainder_failed }
}
fn tris_aabb(tris: &[Tri]) -> ([f64; 3], [f64; 3]) {
let mut lo = [f64::INFINITY; 3];
let mut hi = [f64::NEG_INFINITY; 3];
for t in tris {
for p in t {
for k in 0..3 {
lo[k] = lo[k].min(p[k]);
hi[k] = hi[k].max(p[k]);
}
}
}
(lo, hi)
}
#[cfg(test)]
#[path = "zone_split_tests.rs"]
mod tests;