#![allow(clippy::needless_range_loop)]
use super::types::{Obb, SatResult};
#[allow(dead_code)]
pub fn obb_obb_sat_test(a: &Obb, b: &Obb) -> bool {
obb_obb_test(a, b).is_some()
}
#[allow(dead_code)]
pub fn obb_obb_contact_normal(a: &Obb, b: &Obb) -> Option<[f64; 3]> {
obb_obb_test(a, b).map(|r| r.contact_normal)
}
#[allow(dead_code)]
pub fn obb_obb_penetration_depth(a: &Obb, b: &Obb) -> Option<f64> {
obb_obb_test(a, b).map(|r| r.penetration_depth)
}
#[allow(dead_code)]
pub fn aabb_to_obb(min: [f64; 3], max: [f64; 3]) -> Obb {
let center = [
(min[0] + max[0]) * 0.5,
(min[1] + max[1]) * 0.5,
(min[2] + max[2]) * 0.5,
];
let half_extents = [
(max[0] - min[0]) * 0.5,
(max[1] - min[1]) * 0.5,
(max[2] - min[2]) * 0.5,
];
Obb::axis_aligned(center, half_extents)
}
#[allow(dead_code)]
pub fn project_obb_onto_axis(obb: &Obb, axis: [f64; 3]) -> (f64, f64) {
let center_proj = dot3_raw(obb.center, axis);
let mut radius = 0.0_f64;
for i in 0..3 {
radius += obb.half_extents[i] * dot3_raw(obb.rotation[i], axis).abs();
}
(center_proj - radius, center_proj + radius)
}
#[allow(dead_code)]
pub fn obb_obb_test(a: &Obb, b: &Obb) -> Option<SatResult> {
let diff = sub3_raw(b.center, a.center);
let mut min_depth = f64::INFINITY;
let mut best_axis = [0.0, 1.0, 0.0];
let mut axes: Vec<[f64; 3]> = Vec::with_capacity(15);
for i in 0..3 {
axes.push(a.rotation[i]);
}
for i in 0..3 {
axes.push(b.rotation[i]);
}
for i in 0..3 {
for j in 0..3 {
let c = cross3_raw(a.rotation[i], b.rotation[j]);
axes.push(c);
}
}
for axis in &axes {
let len_sq = dot3_raw(*axis, *axis);
if len_sq < 1e-12 {
continue;
}
let inv_len = 1.0 / len_sq.sqrt();
let norm_axis = scale3_raw(*axis, inv_len);
let (min_a, max_a) = project_obb_onto_axis(a, norm_axis);
let (min_b, max_b) = project_obb_onto_axis(b, norm_axis);
let overlap = (max_a.min(max_b)) - (min_a.max(min_b));
if overlap < 0.0 {
return None;
}
if overlap < min_depth {
min_depth = overlap;
best_axis = if dot3_raw(diff, norm_axis) >= 0.0 {
norm_axis
} else {
negate3_raw(norm_axis)
};
}
}
let support_a = obb_support(a, best_axis);
let support_b = obb_support(b, negate3_raw(best_axis));
let contact_point = scale3_raw(add3_raw(support_a, support_b), 0.5);
Some(SatResult {
penetration_depth: min_depth,
contact_normal: best_axis,
contact_point,
})
}
#[allow(dead_code)]
pub fn compute_contact_points(a: &Obb, b: &Obb, sat_result: &SatResult) -> Vec<[f64; 3]> {
let normal = sat_result.contact_normal;
let mut best_face_idx = 0usize;
let mut best_dot = 0.0_f64;
let mut ref_is_a = true;
for i in 0..3 {
let d = dot3_raw(a.rotation[i], normal).abs();
if d > best_dot {
best_dot = d;
best_face_idx = i;
ref_is_a = true;
}
let d2 = dot3_raw(b.rotation[i], normal).abs();
if d2 > best_dot {
best_dot = d2;
best_face_idx = i;
ref_is_a = false;
}
}
let cp1 = obb_support(a, normal);
let cp2 = obb_support(b, negate3_raw(normal));
let mut contacts = vec![scale3_raw(add3_raw(cp1, cp2), 0.5)];
let ref_obb = if ref_is_a { a } else { b };
let ref_axis = ref_obb.rotation[best_face_idx];
let sign = if dot3_raw(ref_axis, normal) > 0.0 {
1.0
} else {
-1.0
};
let face_center = add3_raw(
ref_obb.center,
scale3_raw(ref_axis, sign * ref_obb.half_extents[best_face_idx]),
);
let inc_obb = if ref_is_a { b } else { a };
let to_inc = sub3_raw(inc_obb.center, face_center);
let dist_to_plane = dot3_raw(to_inc, ref_axis) * sign;
if dist_to_plane.abs() < sat_result.penetration_depth + 0.01 {
let projected = sub3_raw(inc_obb.center, scale3_raw(ref_axis, dist_to_plane * sign));
contacts.push(projected);
}
contacts
}
#[allow(dead_code)]
pub fn obb_sphere_test(obb: &Obb, sphere_center: [f64; 3], radius: f64) -> Option<SatResult> {
let diff = sub3_raw(sphere_center, obb.center);
let mut local = [0.0; 3];
for i in 0..3 {
local[i] = dot3_raw(diff, obb.rotation[i]);
}
let mut clamped = [0.0; 3];
for i in 0..3 {
clamped[i] = local[i].clamp(-obb.half_extents[i], obb.half_extents[i]);
}
let delta = sub3_raw(local, clamped);
let dist_sq = dot3_raw(delta, delta);
if dist_sq > radius * radius {
return None;
}
let dist = dist_sq.sqrt();
let penetration = radius - dist;
let mut closest_world = obb.center;
for i in 0..3 {
closest_world = add3_raw(closest_world, scale3_raw(obb.rotation[i], clamped[i]));
}
let normal = if dist > 1e-10 {
let n = sub3_raw(sphere_center, closest_world);
let n_len = len3_raw(n);
if n_len > 1e-10 {
scale3_raw(n, 1.0 / n_len)
} else {
[0.0, 1.0, 0.0]
}
} else {
let mut best_i = 0;
let mut best_sep = f64::INFINITY;
for i in 0..3 {
let s = obb.half_extents[i] - local[i].abs();
if s < best_sep {
best_sep = s;
best_i = i;
}
}
let sign = if local[best_i] >= 0.0 { 1.0 } else { -1.0 };
scale3_raw(obb.rotation[best_i], sign)
};
let contact_point = add3_raw(closest_world, scale3_raw(normal, penetration * 0.5));
Some(SatResult {
penetration_depth: penetration,
contact_normal: normal,
contact_point,
})
}
#[allow(dead_code)]
pub fn obb_capsule_test(obb: &Obb, p0: [f64; 3], p1: [f64; 3], radius: f64) -> Option<SatResult> {
let seg = sub3_raw(p1, p0);
let to_center = sub3_raw(obb.center, p0);
let seg_len_sq = dot3_raw(seg, seg);
let t = if seg_len_sq > 1e-10 {
(dot3_raw(to_center, seg) / seg_len_sq).clamp(0.0, 1.0)
} else {
0.0
};
let closest_on_seg = add3_raw(p0, scale3_raw(seg, t));
obb_sphere_test(obb, closest_on_seg, radius)
}
#[allow(dead_code)]
pub(super) fn obb_support(obb: &Obb, direction: [f64; 3]) -> [f64; 3] {
let mut p = obb.center;
for i in 0..3 {
let sign = if dot3_raw(obb.rotation[i], direction) >= 0.0 {
1.0
} else {
-1.0
};
p = add3_raw(p, scale3_raw(obb.rotation[i], sign * obb.half_extents[i]));
}
p
}
#[allow(dead_code)]
pub(super) fn dot3_raw(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[allow(dead_code)]
pub(super) fn sub3_raw(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[allow(dead_code)]
pub(super) fn add3_raw(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
#[allow(dead_code)]
pub(super) fn scale3_raw(a: [f64; 3], s: f64) -> [f64; 3] {
[a[0] * s, a[1] * s, a[2] * s]
}
#[allow(dead_code)]
pub(super) fn negate3_raw(a: [f64; 3]) -> [f64; 3] {
[-a[0], -a[1], -a[2]]
}
#[allow(dead_code)]
pub(super) fn cross3_raw(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[allow(dead_code)]
pub(super) fn len3_raw(a: [f64; 3]) -> f64 {
(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
}
#[allow(dead_code)]
pub fn obb_frustum_cull(obb: &Obb, planes: &[[f64; 4]; 6]) -> bool {
for plane in planes {
let n = [plane[0], plane[1], plane[2]];
let d = plane[3];
let r = obb.half_extents[0] * dot3_raw(obb.rotation[0], n).abs()
+ obb.half_extents[1] * dot3_raw(obb.rotation[1], n).abs()
+ obb.half_extents[2] * dot3_raw(obb.rotation[2], n).abs();
let dist = dot3_raw(obb.center, n) + d;
if dist + r < 0.0 {
return false;
}
}
true
}
#[allow(dead_code)]
pub fn obb_triangle_test(obb: &Obb, v0: [f64; 3], v1: [f64; 3], v2: [f64; 3]) -> bool {
let t0 = sub3_raw(v0, obb.center);
let t1 = sub3_raw(v1, obb.center);
let t2 = sub3_raw(v2, obb.center);
let p0 = [
dot3_raw(t0, obb.rotation[0]),
dot3_raw(t0, obb.rotation[1]),
dot3_raw(t0, obb.rotation[2]),
];
let p1 = [
dot3_raw(t1, obb.rotation[0]),
dot3_raw(t1, obb.rotation[1]),
dot3_raw(t1, obb.rotation[2]),
];
let p2 = [
dot3_raw(t2, obb.rotation[0]),
dot3_raw(t2, obb.rotation[1]),
dot3_raw(t2, obb.rotation[2]),
];
let he = obb.half_extents;
let test_axis = |axis: [f64; 3]| -> bool {
let len_sq = dot3_raw(axis, axis);
if len_sq < 1e-14 {
return true;
}
let p_a = dot3_raw(p0, axis);
let p_b = dot3_raw(p1, axis);
let p_c = dot3_raw(p2, axis);
let tri_min = p_a.min(p_b).min(p_c);
let tri_max = p_a.max(p_b).max(p_c);
let obb_r = he[0] * axis[0].abs() + he[1] * axis[1].abs() + he[2] * axis[2].abs();
tri_max >= -obb_r && tri_min <= obb_r
};
for i in 0..3 {
let mut axis = [0.0; 3];
axis[i] = 1.0;
if !test_axis(axis) {
return false;
}
}
let e0 = sub3_raw(p1, p0);
let e1 = sub3_raw(p2, p1);
let e2 = sub3_raw(p0, p2);
for &edge in &[e0, e1, e2] {
for i in 0..3 {
let mut obb_edge = [0.0; 3];
obb_edge[i] = 1.0;
let axis = cross3_raw(obb_edge, edge);
if !test_axis(axis) {
return false;
}
}
}
let tri_normal = cross3_raw(e0, e1);
if !test_axis(tri_normal) {
return false;
}
true
}
#[allow(dead_code)]
pub fn obb_support_point(obb: &Obb, direction: [f64; 3]) -> [f64; 3] {
obb_support(obb, direction)
}
#[allow(dead_code)]
pub fn obb_vertices(obb: &Obb) -> [[f64; 3]; 8] {
let hx = obb.half_extents[0];
let hy = obb.half_extents[1];
let hz = obb.half_extents[2];
let signs: [[f64; 3]; 8] = [
[-1.0, -1.0, -1.0],
[1.0, -1.0, -1.0],
[-1.0, 1.0, -1.0],
[1.0, 1.0, -1.0],
[-1.0, -1.0, 1.0],
[1.0, -1.0, 1.0],
[-1.0, 1.0, 1.0],
[1.0, 1.0, 1.0],
];
let extents = [hx, hy, hz];
let mut verts = [[0.0; 3]; 8];
for (k, s) in signs.iter().enumerate() {
let mut v = obb.center;
for i in 0..3 {
v = add3_raw(v, scale3_raw(obb.rotation[i], s[i] * extents[i]));
}
verts[k] = v;
}
verts
}
#[allow(dead_code)]
pub fn obb_edges(obb: &Obb) -> [([f64; 3], [f64; 3]); 12] {
let verts = obb_vertices(obb);
let edge_indices: [(usize, usize); 12] = [
(0, 1),
(2, 3),
(4, 5),
(6, 7),
(0, 2),
(1, 3),
(4, 6),
(5, 7),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
];
let mut edges = [([0.0; 3], [0.0; 3]); 12];
for (k, &(i, j)) in edge_indices.iter().enumerate() {
edges[k] = (verts[i], verts[j]);
}
edges
}
#[allow(dead_code)]
pub fn obb_face_centers(obb: &Obb) -> [[f64; 3]; 6] {
let mut centers = [[0.0; 3]; 6];
for i in 0..3 {
let axis = obb.rotation[i];
let he = obb.half_extents[i];
centers[2 * i] = add3_raw(obb.center, scale3_raw(axis, he));
centers[2 * i + 1] = add3_raw(obb.center, scale3_raw(axis, -he));
}
centers
}
#[allow(dead_code)]
pub(super) fn points_mean(pts: &[[f64; 3]]) -> [f64; 3] {
if pts.is_empty() {
return [0.0; 3];
}
let n = pts.len() as f64;
let mut s = [0.0; 3];
for p in pts {
s[0] += p[0];
s[1] += p[1];
s[2] += p[2];
}
[s[0] / n, s[1] / n, s[2] / n]
}
#[allow(dead_code)]
pub(super) fn covariance_matrix(pts: &[[f64; 3]], mean: [f64; 3]) -> [[f64; 3]; 3] {
let mut cov = [[0.0_f64; 3]; 3];
let n = pts.len() as f64;
if n < 2.0 {
return cov;
}
for p in pts {
let d = [p[0] - mean[0], p[1] - mean[1], p[2] - mean[2]];
for i in 0..3 {
for j in 0..3 {
cov[i][j] += d[i] * d[j];
}
}
}
let inv = 1.0 / (n - 1.0);
for row in &mut cov {
for v in row.iter_mut() {
*v *= inv;
}
}
cov
}
#[allow(dead_code)]
pub(super) fn jacobi_sweep(a: &mut [[f64; 3]; 3], v: &mut [[f64; 3]; 3]) -> f64 {
let mut off = 0.0_f64;
for p in 0..3 {
for q in (p + 1)..3 {
if a[p][q].abs() < 1e-15 {
continue;
}
let theta = (a[q][q] - a[p][p]) / (2.0 * a[p][q]);
let t = if theta >= 0.0 {
1.0 / (theta + (1.0 + theta * theta).sqrt())
} else {
1.0 / (theta - (1.0 + theta * theta).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
let s = t * c;
let app = a[p][p] - t * a[p][q];
let aqq = a[q][q] + t * a[p][q];
a[p][p] = app;
a[q][q] = aqq;
a[p][q] = 0.0;
a[q][p] = 0.0;
for r in 0..3 {
if r == p || r == q {
continue;
}
let arp = c * a[r][p] - s * a[r][q];
let arq = s * a[r][p] + c * a[r][q];
a[r][p] = arp;
a[p][r] = arp;
a[r][q] = arq;
a[q][r] = arq;
}
for r in 0..3 {
let vrp = c * v[r][p] - s * v[r][q];
let vrq = s * v[r][p] + c * v[r][q];
v[r][p] = vrp;
v[r][q] = vrq;
}
}
}
for p in 0..3 {
for q in (p + 1)..3 {
off += a[p][q] * a[p][q];
}
}
off
}
#[allow(dead_code)]
pub(super) fn eigen_symmetric3(mat: [[f64; 3]; 3]) -> ([f64; 3], [[f64; 3]; 3]) {
let mut a = mat;
let mut v = [[0.0_f64; 3]; 3];
v[0][0] = 1.0;
v[1][1] = 1.0;
v[2][2] = 1.0;
for _ in 0..50 {
let off = jacobi_sweep(&mut a, &mut v);
if off < 1e-20 {
break;
}
}
let eigenvalues = [a[0][0], a[1][1], a[2][2]];
let eigenvectors = [
[v[0][0], v[1][0], v[2][0]],
[v[0][1], v[1][1], v[2][1]],
[v[0][2], v[1][2], v[2][2]],
];
(eigenvalues, eigenvectors)
}
#[allow(dead_code)]
pub(super) fn normalize3_raw(a: [f64; 3]) -> [f64; 3] {
let len = len3_raw(a);
if len > 1e-10 {
[a[0] / len, a[1] / len, a[2] / len]
} else {
[1.0, 0.0, 0.0]
}
}
#[allow(dead_code)]
pub fn obb_from_points(pts: &[[f64; 3]]) -> Option<Obb> {
if pts.len() < 2 {
return None;
}
let mean = points_mean(pts);
let cov = covariance_matrix(pts, mean);
let (_eigenvalues, eigenvectors) = eigen_symmetric3(cov);
let axis0 = normalize3_raw(eigenvectors[0]);
let axis1 = normalize3_raw(eigenvectors[1]);
let axis2 = normalize3_raw(cross3_raw(axis0, axis1));
let axes = [axis0, axis1, axis2];
let mut mins = [f64::INFINITY; 3];
let mut maxs = [f64::NEG_INFINITY; 3];
for p in pts {
let d = sub3_raw(*p, mean);
for i in 0..3 {
let proj = dot3_raw(d, axes[i]);
if proj < mins[i] {
mins[i] = proj;
}
if proj > maxs[i] {
maxs[i] = proj;
}
}
}
let half_extents = [
(maxs[0] - mins[0]) * 0.5,
(maxs[1] - mins[1]) * 0.5,
(maxs[2] - mins[2]) * 0.5,
];
let mut center = mean;
for i in 0..3 {
let offset = (maxs[i] + mins[i]) * 0.5;
center = add3_raw(center, scale3_raw(axes[i], offset));
}
Some(Obb::from_center_axes(center, axes, half_extents))
}
#[allow(dead_code)]
pub fn obb_transform(obb: &Obb, rotation: [[f64; 3]; 3], translation: [f64; 3]) -> Obb {
let mut new_center = translation;
for i in 0..3 {
new_center[i] += dot3_raw(rotation[i], obb.center);
}
let mut new_rotation = [[0.0_f64; 3]; 3];
for k in 0..3 {
for i in 0..3 {
new_rotation[k][i] = dot3_raw(rotation[i], obb.rotation[k]);
}
}
Obb {
center: new_center,
half_extents: obb.half_extents,
rotation: new_rotation,
}
}
#[allow(dead_code)]
pub fn obb_closest_point(obb: &Obb, query: [f64; 3]) -> [f64; 3] {
let diff = sub3_raw(query, obb.center);
let mut result = obb.center;
for i in 0..3 {
let dist = dot3_raw(diff, obb.rotation[i]);
let clamped = dist.clamp(-obb.half_extents[i], obb.half_extents[i]);
result = add3_raw(result, scale3_raw(obb.rotation[i], clamped));
}
result
}
#[allow(dead_code)]
pub fn obb_point_sq_dist(obb: &Obb, query: [f64; 3]) -> f64 {
let closest = obb_closest_point(obb, query);
let d = sub3_raw(query, closest);
dot3_raw(d, d)
}
#[allow(dead_code)]
pub fn obb_segment_test(obb: &Obb, p0: [f64; 3], p1: [f64; 3]) -> bool {
let mid = scale3_raw(add3_raw(p0, p1), 0.5);
let half_seg = scale3_raw(sub3_raw(p1, p0), 0.5);
let d = sub3_raw(mid, obb.center);
for i in 0..3 {
let e = obb.half_extents[i];
let t = dot3_raw(d, obb.rotation[i]);
let r = dot3_raw(half_seg, obb.rotation[i]).abs();
if t.abs() > e + r {
return false;
}
}
for i in 0..3 {
let cross_axis = cross3_raw(half_seg, obb.rotation[i]);
let len_sq = dot3_raw(cross_axis, cross_axis);
if len_sq < 1e-14 {
continue;
}
let t = dot3_raw(d, cross_axis);
let r_a = obb.half_extents[(i + 1) % 3]
* dot3_raw(obb.rotation[(i + 1) % 3], cross_axis).abs()
+ obb.half_extents[(i + 2) % 3] * dot3_raw(obb.rotation[(i + 2) % 3], cross_axis).abs();
let r_b = dot3_raw(half_seg, cross_axis).abs();
if t.abs() > r_a + r_b {
return false;
}
}
true
}
#[allow(dead_code)]
pub fn obb_ray_cast(obb: &Obb, origin: [f64; 3], direction: [f64; 3]) -> Option<f64> {
let d = sub3_raw(origin, obb.center);
let mut t_min = f64::NEG_INFINITY;
let mut t_max = f64::INFINITY;
for i in 0..3 {
let e = obb.half_extents[i];
let proj_d = dot3_raw(d, obb.rotation[i]);
let proj_dir = dot3_raw(direction, obb.rotation[i]);
if proj_dir.abs() < 1e-12 {
if proj_d.abs() > e {
return None;
}
} else {
let inv = 1.0 / proj_dir;
let t1 = (-e - proj_d) * inv;
let t2 = (e - proj_d) * inv;
let (t1, t2) = if t1 < t2 { (t1, t2) } else { (t2, t1) };
t_min = t_min.max(t1);
t_max = t_max.min(t2);
if t_min > t_max {
return None;
}
}
}
if t_max < 0.0 {
return None;
}
let t = if t_min >= 0.0 { t_min } else { t_max };
Some(t)
}
#[allow(dead_code)]
pub fn sat_test_axis(a: &Obb, b: &Obb, axis: [f64; 3]) -> Option<f64> {
let len_sq = dot3_raw(axis, axis);
if len_sq < 1e-14 {
return Some(0.0);
}
let inv = 1.0 / len_sq.sqrt();
let n = scale3_raw(axis, inv);
let (min_a, max_a) = project_obb_onto_axis(a, n);
let (min_b, max_b) = project_obb_onto_axis(b, n);
let overlap = max_a.min(max_b) - min_a.max(min_b);
if overlap < 0.0 { None } else { Some(overlap) }
}
#[allow(dead_code)]
pub fn sat_obb_obb(a: &Obb, b: &Obb) -> Option<([f64; 3], f64)> {
let result = obb_obb_test(a, b)?;
Some((result.contact_normal, result.penetration_depth))
}
#[allow(dead_code)]
pub fn obb_contact_point(a: &Obb, b: &Obb, normal: [f64; 3]) -> [f64; 3] {
let sa = obb_support(a, normal);
let sb = obb_support(b, negate3_raw(normal));
scale3_raw(add3_raw(sa, sb), 0.5)
}
#[allow(dead_code)]
pub fn obb_merge_aabb(a: &Obb, b: &Obb) -> Obb {
let va = obb_vertices(a);
let vb = obb_vertices(b);
let mut min_pt = [f64::INFINITY; 3];
let mut max_pt = [f64::NEG_INFINITY; 3];
for v in va.iter().chain(vb.iter()) {
for i in 0..3 {
if v[i] < min_pt[i] {
min_pt[i] = v[i];
}
if v[i] > max_pt[i] {
max_pt[i] = v[i];
}
}
}
aabb_to_obb(min_pt, max_pt)
}
#[allow(dead_code)]
pub fn obb_volume(obb: &Obb) -> f64 {
8.0 * obb.half_extents[0] * obb.half_extents[1] * obb.half_extents[2]
}
#[allow(dead_code)]
pub fn obb_surface_area(obb: &Obb) -> f64 {
let [hx, hy, hz] = obb.half_extents;
8.0 * (hx * hy + hy * hz + hz * hx)
}
#[allow(dead_code)]
pub fn obb_contains_point(obb: &Obb, point: [f64; 3]) -> bool {
let d = sub3_raw(point, obb.center);
for i in 0..3 {
if dot3_raw(d, obb.rotation[i]).abs() > obb.half_extents[i] {
return false;
}
}
true
}
#[allow(dead_code)]
pub fn obb_contains_obb(outer: &Obb, inner: &Obb) -> bool {
for v in &obb_vertices(inner) {
if !obb_contains_point(outer, *v) {
return false;
}
}
true
}