use crate::model::{ShapeModel, Terminal};
use crate::scope::{Quat, Scope, Vec3};
pub struct TerminalQuery<'a> {
terminals: &'a [Terminal],
}
impl<'a> TerminalQuery<'a> {
pub(crate) fn new(terminals: &'a [Terminal]) -> Self {
Self { terminals }
}
pub fn overlaps(&self, scope: &Scope) -> bool {
self.terminals
.iter()
.any(|t| scope_obb_overlaps_terminal(scope, t))
}
pub fn overlapping(&self, scope: &'a Scope) -> impl Iterator<Item = &'a Terminal> {
self.terminals
.iter()
.filter(move |t| scope_obb_overlaps_terminal(scope, t))
}
pub fn terminals(&self) -> &'a [Terminal] {
self.terminals
}
}
impl ShapeModel {
pub fn query(&self) -> TerminalQuery<'_> {
TerminalQuery::new(&self.terminals)
}
}
const THIN_HALF_EXTENT: f64 = 0.5e-6;
struct Obb {
centre: Vec3,
half: Vec3,
axes: [Vec3; 3],
}
fn obb_from_scope(scope: &Scope) -> Obb {
let hx = (scope.size.x * 0.5).max(THIN_HALF_EXTENT);
let hy = (scope.size.y * 0.5).max(THIN_HALF_EXTENT);
let hz = (scope.size.z * 0.5).max(THIN_HALF_EXTENT);
let local_centre = Vec3::new(scope.size.x * 0.5, scope.size.y * 0.5, scope.size.z * 0.5);
let centre = scope.position + scope.rotation * local_centre;
let axes = [
scope.rotation * Vec3::X,
scope.rotation * Vec3::Y,
scope.rotation * Vec3::Z,
];
Obb {
centre,
half: Vec3::new(hx, hy, hz),
axes,
}
}
pub fn obb_overlap(a: &Scope, b: &Scope) -> bool {
let a = obb_from_scope(a);
let b = obb_from_scope(b);
obb_overlap_impl(&a, &b)
}
fn obb_overlap_impl(a: &Obb, b: &Obb) -> bool {
const EPS: f64 = 1e-9;
let mut r = [[0.0_f64; 3]; 3];
let mut abs_r = [[0.0_f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
r[i][j] = a.axes[i].dot(b.axes[j]);
abs_r[i][j] = r[i][j].abs() + EPS;
}
}
let t_world = b.centre - a.centre;
let t = [
t_world.dot(a.axes[0]),
t_world.dot(a.axes[1]),
t_world.dot(a.axes[2]),
];
let a_h = [a.half.x, a.half.y, a.half.z];
let b_h = [b.half.x, b.half.y, b.half.z];
for i in 0..3 {
let ra = a_h[i];
let rb = b_h[0] * abs_r[i][0] + b_h[1] * abs_r[i][1] + b_h[2] * abs_r[i][2];
if t[i].abs() > ra + rb {
return false;
}
}
for j in 0..3 {
let ra = a_h[0] * abs_r[0][j] + a_h[1] * abs_r[1][j] + a_h[2] * abs_r[2][j];
let rb = b_h[j];
let proj = t[0] * r[0][j] + t[1] * r[1][j] + t[2] * r[2][j];
if proj.abs() > ra + rb {
return false;
}
}
macro_rules! cross_test {
($i:expr, $j:expr, $ra:expr, $rb:expr, $tt:expr) => {
if ($tt).abs() > $ra + $rb {
return false;
}
};
}
cross_test!(
0,
0,
a_h[1] * abs_r[2][0] + a_h[2] * abs_r[1][0],
b_h[1] * abs_r[0][2] + b_h[2] * abs_r[0][1],
t[2] * r[1][0] - t[1] * r[2][0]
);
cross_test!(
0,
1,
a_h[1] * abs_r[2][1] + a_h[2] * abs_r[1][1],
b_h[0] * abs_r[0][2] + b_h[2] * abs_r[0][0],
t[2] * r[1][1] - t[1] * r[2][1]
);
cross_test!(
0,
2,
a_h[1] * abs_r[2][2] + a_h[2] * abs_r[1][2],
b_h[0] * abs_r[0][1] + b_h[1] * abs_r[0][0],
t[2] * r[1][2] - t[1] * r[2][2]
);
cross_test!(
1,
0,
a_h[0] * abs_r[2][0] + a_h[2] * abs_r[0][0],
b_h[1] * abs_r[1][2] + b_h[2] * abs_r[1][1],
t[0] * r[2][0] - t[2] * r[0][0]
);
cross_test!(
1,
1,
a_h[0] * abs_r[2][1] + a_h[2] * abs_r[0][1],
b_h[0] * abs_r[1][2] + b_h[2] * abs_r[1][0],
t[0] * r[2][1] - t[2] * r[0][1]
);
cross_test!(
1,
2,
a_h[0] * abs_r[2][2] + a_h[2] * abs_r[0][2],
b_h[0] * abs_r[1][1] + b_h[1] * abs_r[1][0],
t[0] * r[2][2] - t[2] * r[0][2]
);
cross_test!(
2,
0,
a_h[0] * abs_r[1][0] + a_h[1] * abs_r[0][0],
b_h[1] * abs_r[2][2] + b_h[2] * abs_r[2][1],
t[1] * r[0][0] - t[0] * r[1][0]
);
cross_test!(
2,
1,
a_h[0] * abs_r[1][1] + a_h[1] * abs_r[0][1],
b_h[0] * abs_r[2][2] + b_h[2] * abs_r[2][0],
t[1] * r[0][1] - t[0] * r[1][1]
);
cross_test!(
2,
2,
a_h[0] * abs_r[1][2] + a_h[1] * abs_r[0][2],
b_h[0] * abs_r[2][1] + b_h[1] * abs_r[2][0],
t[1] * r[0][2] - t[0] * r[1][2]
);
true
}
pub(crate) fn scope_obb_overlaps_terminal(scope: &Scope, terminal: &Terminal) -> bool {
obb_overlap(scope, &terminal.scope)
}
pub(crate) fn register_scope_snap_planes(
scope: &Scope,
label: &str,
out: &mut Vec<crate::model::SnapPlane>,
) {
let cx = scope.size.x * 0.5;
let cy = scope.size.y * 0.5;
let cz = scope.size.z * 0.5;
let local_face_centres = [
(Vec3::new(0.0, cy, cz), -Vec3::X), (Vec3::new(scope.size.x, cy, cz), Vec3::X), (Vec3::new(cx, 0.0, cz), -Vec3::Y), (Vec3::new(cx, scope.size.y, cz), Vec3::Y), (Vec3::new(cx, cy, 0.0), -Vec3::Z), (Vec3::new(cx, cy, scope.size.z), Vec3::Z), ];
for (local_pt, local_normal) in local_face_centres {
let world_pt = scope.position + scope.rotation * local_pt;
let world_normal = (scope.rotation * local_normal).normalize();
out.push(crate::model::SnapPlane {
point: world_pt,
normal: world_normal,
label: label.to_string(),
});
}
}
pub(crate) fn snap_split_boundaries(
scope: &Scope,
axis: crate::ops::Axis,
sizes: &mut [f64],
label: &str,
tolerance: f64,
snap_planes: &[crate::model::SnapPlane],
) {
if sizes.len() < 2 || tolerance <= 0.0 || snap_planes.is_empty() {
return;
}
let (local_axis_vec, total) = match axis {
crate::ops::Axis::X => (Vec3::X, scope.size.x),
crate::ops::Axis::Y => (Vec3::Y, scope.size.y),
crate::ops::Axis::Z => (Vec3::Z, scope.size.z),
};
if total <= 0.0 {
return;
}
let world_axis = scope.rotation * local_axis_vec;
let mut planes_local: Vec<f64> = Vec::new();
let scope_local_origin = scope.position;
for plane in snap_planes {
if plane.label != label {
continue;
}
let parallel = plane.normal.dot(world_axis).abs();
if parallel < 0.9 {
continue;
}
let local_pos = (plane.point - scope_local_origin).dot(world_axis);
if local_pos < -tolerance || local_pos > total + tolerance {
continue;
}
planes_local.push(local_pos);
}
if planes_local.is_empty() {
return;
}
let n = sizes.len();
let mut cumulative: Vec<f64> = Vec::with_capacity(n);
let mut acc = 0.0;
for s in sizes.iter() {
acc += *s;
cumulative.push(acc);
}
for boundary in 0..(n - 1) {
let pos = cumulative[boundary];
let mut best: Option<f64> = None;
let mut best_dist = tolerance;
for &p in &planes_local {
let d = (p - pos).abs();
if d <= best_dist {
best = Some(p);
best_dist = d;
}
}
let Some(target) = best else { continue };
let lower = if boundary == 0 {
0.0
} else {
cumulative[boundary - 1]
};
let upper = cumulative[boundary + 1];
if target <= lower + 1e-9 || target >= upper - 1e-9 {
continue;
}
cumulative[boundary] = target;
}
let mut prev = 0.0;
for (i, c) in cumulative.iter().enumerate() {
sizes[i] = c - prev;
prev = *c;
}
}
const _: fn() = || {
let _: Quat = Quat::IDENTITY;
};