/** * OBB (Oriented Bounding Box) — AABB пересечение через SAT. * * Используется для столкновения игрока (AABB) с повёрнутыми примитивами (OBB). * * OBB задаётся: * - центр (cx, cy, cz) * - полуразмеры по локальным осям (hx, hy, hz) * - 3 ортонормированных локальных оси (axes), задающих ориентацию * * AABB задаётся: * - центр (cx, cy, cz) * - полуразмеры (hw, hh, hd) — по мировым X/Y/Z * * Возвращает true если есть пересечение. * * Алгоритм SAT (Separating Axis Theorem): два выпуклых тела не пересекаются * тогда и только тогда, когда есть ось, на проекции которой их интервалы * не пересекаются. Для AABB×OBB достаточно проверить 15 осей: * - 3 оси AABB (X/Y/Z) * - 3 оси OBB (axes[0..2]) * - 9 cross-products пар осей */ const EPS = 1e-6; /** * Построить axes для OBB с произвольным rotation (Euler XYZ). * Возвращает 3 единичных вектора локальных осей в мировых координатах. */ export function buildOBBAxes(rotX, rotY, rotZ) { const cx = Math.cos(rotX), sx = Math.sin(rotX); const cy = Math.cos(rotY), sy = Math.sin(rotY); const cz = Math.cos(rotZ), sz = Math.sin(rotZ); // Babylon использует Euler XYZ: M = Rx * Ry * Rz (Right-handed Y-up). // Локальная ось X в мире: const axisX = [ cy * cz, cy * sz, -sy, ]; const axisY = [ sx * sy * cz - cx * sz, sx * sy * sz + cx * cz, sx * cy, ]; const axisZ = [ cx * sy * cz + sx * sz, cx * sy * sz - sx * cz, cx * cy, ]; return [axisX, axisY, axisZ]; } /** * AABB (cx,cy,cz, hw,hh,hd) пересекается с OBB (ocx,ocy,ocz, hx,hy,hz, axes). */ export function aabbIntersectsOBB( cx, cy, cz, hw, hh, hd, ocx, ocy, ocz, hx, hy, hz, axes ) { // Вектор от центра OBB к центру AABB const dx = cx - ocx, dy = cy - ocy, dz = cz - ocz; // 3 оси AABB — стандартные X/Y/Z const aabbAxes = [ [1, 0, 0], [0, 1, 0], [0, 0, 1], ]; // Проекция AABB на ось — это всегда hw*|ax| + hh*|ay| + hd*|az| const projectAABB = (a) => hw * Math.abs(a[0]) + hh * Math.abs(a[1]) + hd * Math.abs(a[2]); // Проекция OBB на ось — h0*|axis0·a| + h1*|axis1·a| + h2*|axis2·a| const obbHalves = [hx, hy, hz]; const projectOBB = (a) => obbHalves[0] * Math.abs(dot(axes[0], a)) + obbHalves[1] * Math.abs(dot(axes[1], a)) + obbHalves[2] * Math.abs(dot(axes[2], a)); // Расстояние между центрами по оси const distOnAxis = (a) => Math.abs(dx * a[0] + dy * a[1] + dz * a[2]); // Проверяем 3 + 3 + 9 = 15 осей const allAxes = []; allAxes.push(aabbAxes[0], aabbAxes[1], aabbAxes[2]); allAxes.push(axes[0], axes[1], axes[2]); for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { const c = cross(aabbAxes[i], axes[j]); if (lenSq(c) > EPS) allAxes.push(c); } } for (const ax of allAxes) { const ra = projectAABB(ax); const rb = projectOBB(ax); const d = distOnAxis(ax); if (d > ra + rb + EPS) return false; // нашли разделяющую ось } return true; } function dot(a, b) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; } function cross(a, b) { return [ a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0], ]; } function lenSq(a) { return a[0]*a[0] + a[1]*a[1] + a[2]*a[2]; }