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_obb_overlaps_terminal(scope: &Scope, terminal: &Terminal) -> bool {
222 obb_overlap(scope, &terminal.scope)
223}
224
225pub(crate) fn register_scope_snap_planes(
229 scope: &Scope,
230 label: &str,
231 out: &mut Vec<crate::model::SnapPlane>,
232) {
233 let cx = scope.size.x * 0.5;
236 let cy = scope.size.y * 0.5;
237 let cz = scope.size.z * 0.5;
238 let local_face_centres = [
239 (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), ];
246 for (local_pt, local_normal) in local_face_centres {
247 let world_pt = scope.position + scope.rotation * local_pt;
248 let world_normal = (scope.rotation * local_normal).normalize();
249 out.push(crate::model::SnapPlane {
250 point: world_pt,
251 normal: world_normal,
252 label: label.to_string(),
253 });
254 }
255}
256
257pub(crate) fn snap_split_boundaries(
265 scope: &Scope,
266 axis: crate::ops::Axis,
267 sizes: &mut [f64],
268 label: &str,
269 tolerance: f64,
270 snap_planes: &[crate::model::SnapPlane],
271) {
272 if sizes.len() < 2 || tolerance <= 0.0 || snap_planes.is_empty() {
273 return;
274 }
275 let (local_axis_vec, total) = match axis {
277 crate::ops::Axis::X => (Vec3::X, scope.size.x),
278 crate::ops::Axis::Y => (Vec3::Y, scope.size.y),
279 crate::ops::Axis::Z => (Vec3::Z, scope.size.z),
280 };
281 if total <= 0.0 {
282 return;
283 }
284 let world_axis = scope.rotation * local_axis_vec;
286
287 let mut planes_local: Vec<f64> = Vec::new();
291 let scope_local_origin = scope.position;
292 for plane in snap_planes {
293 if plane.label != label {
294 continue;
295 }
296 let parallel = plane.normal.dot(world_axis).abs();
297 if parallel < 0.9 {
298 continue;
300 }
301 let local_pos = (plane.point - scope_local_origin).dot(world_axis);
303 if local_pos < -tolerance || local_pos > total + tolerance {
304 continue;
305 }
306 planes_local.push(local_pos);
307 }
308 if planes_local.is_empty() {
309 return;
310 }
311
312 let n = sizes.len();
314 let mut cumulative: Vec<f64> = Vec::with_capacity(n);
315 let mut acc = 0.0;
316 for s in sizes.iter() {
317 acc += *s;
318 cumulative.push(acc);
319 }
320 for boundary in 0..(n - 1) {
322 let pos = cumulative[boundary];
323 let mut best: Option<f64> = None;
325 let mut best_dist = tolerance;
326 for &p in &planes_local {
327 let d = (p - pos).abs();
328 if d <= best_dist {
329 best = Some(p);
330 best_dist = d;
331 }
332 }
333 let Some(target) = best else { continue };
334 let lower = if boundary == 0 {
336 0.0
337 } else {
338 cumulative[boundary - 1]
339 };
340 let upper = cumulative[boundary + 1];
341 if target <= lower + 1e-9 || target >= upper - 1e-9 {
342 continue;
343 }
344 cumulative[boundary] = target;
345 }
346
347 let mut prev = 0.0;
349 for (i, c) in cumulative.iter().enumerate() {
350 sizes[i] = c - prev;
351 prev = *c;
352 }
353}
354
355const _: fn() = || {
357 let _: Quat = Quat::IDENTITY;
358};