1use crate::model::{ShapeModel, Terminal};
14use crate::scope::{Quat, Scope, Vec3};
15
16pub struct TerminalQuery<'a> {
18 terminals: &'a [Terminal],
19}
20
21impl<'a> TerminalQuery<'a> {
22 pub(crate) fn new(terminals: &'a [Terminal]) -> Self {
23 Self { terminals }
24 }
25
26 pub fn overlaps(&self, scope: &Scope) -> bool {
28 self.terminals
29 .iter()
30 .any(|t| scope_obb_overlaps_terminal(scope, t))
31 }
32
33 pub fn overlapping(&self, scope: &'a Scope) -> impl Iterator<Item = &'a Terminal> {
35 self.terminals
36 .iter()
37 .filter(move |t| scope_obb_overlaps_terminal(scope, t))
38 }
39
40 pub fn terminals(&self) -> &'a [Terminal] {
43 self.terminals
44 }
45}
46
47impl ShapeModel {
48 pub fn query(&self) -> TerminalQuery<'_> {
50 TerminalQuery::new(&self.terminals)
51 }
52}
53
54const THIN_HALF_EXTENT: f64 = 0.5e-6;
61
62struct Obb {
65 centre: Vec3,
66 half: Vec3,
67 axes: [Vec3; 3],
68}
69
70fn obb_from_scope(scope: &Scope) -> Obb {
71 let hx = (scope.size.x * 0.5).max(THIN_HALF_EXTENT);
74 let hy = (scope.size.y * 0.5).max(THIN_HALF_EXTENT);
75 let hz = (scope.size.z * 0.5).max(THIN_HALF_EXTENT);
76 let local_centre = Vec3::new(scope.size.x * 0.5, scope.size.y * 0.5, scope.size.z * 0.5);
77 let centre = scope.position + scope.rotation * local_centre;
78 let axes = [
79 scope.rotation * Vec3::X,
80 scope.rotation * Vec3::Y,
81 scope.rotation * Vec3::Z,
82 ];
83 Obb {
84 centre,
85 half: Vec3::new(hx, hy, hz),
86 axes,
87 }
88}
89
90pub fn obb_overlap(a: &Scope, b: &Scope) -> bool {
97 let a = obb_from_scope(a);
98 let b = obb_from_scope(b);
99 obb_overlap_impl(&a, &b)
100}
101
102fn obb_overlap_impl(a: &Obb, b: &Obb) -> bool {
103 const EPS: f64 = 1e-9;
107 let mut r = [[0.0_f64; 3]; 3];
108 let mut abs_r = [[0.0_f64; 3]; 3];
109 for i in 0..3 {
110 for j in 0..3 {
111 r[i][j] = a.axes[i].dot(b.axes[j]);
112 abs_r[i][j] = r[i][j].abs() + EPS;
113 }
114 }
115 let t_world = b.centre - a.centre;
117 let t = [
118 t_world.dot(a.axes[0]),
119 t_world.dot(a.axes[1]),
120 t_world.dot(a.axes[2]),
121 ];
122 let a_h = [a.half.x, a.half.y, a.half.z];
123 let b_h = [b.half.x, b.half.y, b.half.z];
124
125 for i in 0..3 {
127 let ra = a_h[i];
128 let rb = b_h[0] * abs_r[i][0] + b_h[1] * abs_r[i][1] + b_h[2] * abs_r[i][2];
129 if t[i].abs() > ra + rb {
130 return false;
131 }
132 }
133 for j in 0..3 {
135 let ra = a_h[0] * abs_r[0][j] + a_h[1] * abs_r[1][j] + a_h[2] * abs_r[2][j];
136 let rb = b_h[j];
137 let proj = t[0] * r[0][j] + t[1] * r[1][j] + t[2] * r[2][j];
138 if proj.abs() > ra + rb {
139 return false;
140 }
141 }
142 macro_rules! cross_test {
145 ($i:expr, $j:expr, $ra:expr, $rb:expr, $tt:expr) => {
146 if ($tt).abs() > $ra + $rb {
147 return false;
148 }
149 };
150 }
151 cross_test!(
153 0,
154 0,
155 a_h[1] * abs_r[2][0] + a_h[2] * abs_r[1][0],
156 b_h[1] * abs_r[0][2] + b_h[2] * abs_r[0][1],
157 t[2] * r[1][0] - t[1] * r[2][0]
158 );
159 cross_test!(
160 0,
161 1,
162 a_h[1] * abs_r[2][1] + a_h[2] * abs_r[1][1],
163 b_h[0] * abs_r[0][2] + b_h[2] * abs_r[0][0],
164 t[2] * r[1][1] - t[1] * r[2][1]
165 );
166 cross_test!(
167 0,
168 2,
169 a_h[1] * abs_r[2][2] + a_h[2] * abs_r[1][2],
170 b_h[0] * abs_r[0][1] + b_h[1] * abs_r[0][0],
171 t[2] * r[1][2] - t[1] * r[2][2]
172 );
173 cross_test!(
175 1,
176 0,
177 a_h[0] * abs_r[2][0] + a_h[2] * abs_r[0][0],
178 b_h[1] * abs_r[1][2] + b_h[2] * abs_r[1][1],
179 t[0] * r[2][0] - t[2] * r[0][0]
180 );
181 cross_test!(
182 1,
183 1,
184 a_h[0] * abs_r[2][1] + a_h[2] * abs_r[0][1],
185 b_h[0] * abs_r[1][2] + b_h[2] * abs_r[1][0],
186 t[0] * r[2][1] - t[2] * r[0][1]
187 );
188 cross_test!(
189 1,
190 2,
191 a_h[0] * abs_r[2][2] + a_h[2] * abs_r[0][2],
192 b_h[0] * abs_r[1][1] + b_h[1] * abs_r[1][0],
193 t[0] * r[2][2] - t[2] * r[0][2]
194 );
195 cross_test!(
197 2,
198 0,
199 a_h[0] * abs_r[1][0] + a_h[1] * abs_r[0][0],
200 b_h[1] * abs_r[2][2] + b_h[2] * abs_r[2][1],
201 t[1] * r[0][0] - t[0] * r[1][0]
202 );
203 cross_test!(
204 2,
205 1,
206 a_h[0] * abs_r[1][1] + a_h[1] * abs_r[0][1],
207 b_h[0] * abs_r[2][2] + b_h[2] * abs_r[2][0],
208 t[1] * r[0][1] - t[0] * r[1][1]
209 );
210 cross_test!(
211 2,
212 2,
213 a_h[0] * abs_r[1][2] + a_h[1] * abs_r[0][2],
214 b_h[0] * abs_r[2][1] + b_h[1] * abs_r[2][0],
215 t[1] * r[0][2] - t[0] * r[1][2]
216 );
217 true
218}
219
220pub(crate) fn scope_inside_terminal(scope: &Scope, terminal: &Terminal) -> bool {
224 const EPS: f64 = 1e-9;
225 let t = &terminal.scope;
226 let inv = t.rotation.inverse();
227 let half = t.size / 2.0;
228 let t_center = t.position + t.rotation * half;
229 for ix in 0..2 {
230 for iy in 0..2 {
231 for iz in 0..2 {
232 let corner_local = crate::scope::Vec3::new(
233 ix as f64 * scope.size.x,
234 iy as f64 * scope.size.y,
235 iz as f64 * scope.size.z,
236 );
237 let world = scope.position + scope.rotation * corner_local;
238 let in_t = inv * (world - t_center);
239 if in_t.x.abs() > half.x + EPS
240 || in_t.y.abs() > half.y + EPS
241 || in_t.z.abs() > half.z + EPS
242 {
243 return false;
244 }
245 }
246 }
247 }
248 true
249}
250
251pub(crate) fn scope_touches_terminal(scope: &Scope, terminal: &Terminal) -> bool {
254 const HAIR: f64 = 1e-6;
255 let grown = inflate_scope(scope, HAIR);
256 let shrunk = inflate_scope(scope, -HAIR);
257 scope_obb_overlaps_terminal(&grown, terminal) && !scope_obb_overlaps_terminal(&shrunk, terminal)
258}
259
260fn inflate_scope(scope: &Scope, d: f64) -> Scope {
263 let delta = crate::scope::Vec3::splat(d);
264 let new_size = (scope.size + delta * 2.0).max(crate::scope::Vec3::ZERO);
265 let shift = (scope.size - new_size) / 2.0;
266 Scope::new(
267 scope.position + scope.rotation * shift,
268 scope.rotation,
269 new_size,
270 )
271}
272
273pub(crate) fn scope_obb_overlaps_terminal(scope: &Scope, terminal: &Terminal) -> bool {
274 obb_overlap(scope, &terminal.scope)
275}
276
277pub(crate) fn register_scope_snap_planes(
281 scope: &Scope,
282 label: &str,
283 out: &mut Vec<crate::model::SnapPlane>,
284) {
285 let cx = scope.size.x * 0.5;
288 let cy = scope.size.y * 0.5;
289 let cz = scope.size.z * 0.5;
290 let local_face_centres = [
291 (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), ];
298 for (local_pt, local_normal) in local_face_centres {
299 let world_pt = scope.position + scope.rotation * local_pt;
300 let world_normal = (scope.rotation * local_normal).normalize();
301 out.push(crate::model::SnapPlane {
302 point: world_pt,
303 normal: world_normal,
304 label: label.to_string(),
305 });
306 }
307}
308
309pub(crate) fn snap_split_boundaries(
317 scope: &Scope,
318 axis: crate::ops::Axis,
319 sizes: &mut [f64],
320 label: &str,
321 tolerance: f64,
322 snap_planes: &[crate::model::SnapPlane],
323) {
324 if sizes.len() < 2 || tolerance <= 0.0 || snap_planes.is_empty() {
325 return;
326 }
327 let (local_axis_vec, total) = match axis {
329 crate::ops::Axis::X => (Vec3::X, scope.size.x),
330 crate::ops::Axis::Y => (Vec3::Y, scope.size.y),
331 crate::ops::Axis::Z => (Vec3::Z, scope.size.z),
332 };
333 if total <= 0.0 {
334 return;
335 }
336 let world_axis = scope.rotation * local_axis_vec;
338
339 let mut planes_local: Vec<f64> = Vec::new();
343 let scope_local_origin = scope.position;
344 for plane in snap_planes {
345 if plane.label != label {
346 continue;
347 }
348 let parallel = plane.normal.dot(world_axis).abs();
349 if parallel < 0.9 {
350 continue;
352 }
353 let local_pos = (plane.point - scope_local_origin).dot(world_axis);
355 if local_pos < -tolerance || local_pos > total + tolerance {
356 continue;
357 }
358 planes_local.push(local_pos);
359 }
360 if planes_local.is_empty() {
361 return;
362 }
363
364 let n = sizes.len();
366 let mut cumulative: Vec<f64> = Vec::with_capacity(n);
367 let mut acc = 0.0;
368 for s in sizes.iter() {
369 acc += *s;
370 cumulative.push(acc);
371 }
372 for boundary in 0..(n - 1) {
374 let pos = cumulative[boundary];
375 let mut best: Option<f64> = None;
377 let mut best_dist = tolerance;
378 for &p in &planes_local {
379 let d = (p - pos).abs();
380 if d <= best_dist {
381 best = Some(p);
382 best_dist = d;
383 }
384 }
385 let Some(target) = best else { continue };
386 let lower = if boundary == 0 {
388 0.0
389 } else {
390 cumulative[boundary - 1]
391 };
392 let upper = cumulative[boundary + 1];
393 if target <= lower + 1e-9 || target >= upper - 1e-9 {
394 continue;
395 }
396 cumulative[boundary] = target;
397 }
398
399 let mut prev = 0.0;
401 for (i, c) in cumulative.iter().enumerate() {
402 sizes[i] = c - prev;
403 prev = *c;
404 }
405}
406
407const _: fn() = || {
409 let _: Quat = Quat::IDENTITY;
410};