// ==================== ФИЗИКА 3D — GrechkaCraft v3 ==================== // Осевое разрешение столкновений по X → Z → Y, как в Minecraft. // Автошаг на 1 блок, прыжок, лестницы, плавание, лодка. import { TILE } from '../core/constants.js'; import { BLOCKS } from '../data/blocks.js'; import { state } from '../core/state.js'; import { getBlock, isSolid } from '../world/world-storage3d.js'; const EPS = 0.001; // Собственная AABB-коробка сущности: x/z — центр, y — низ (как в 2D-игре: e.x, e.y = левый-ниний) function boxOf(e) { return { x0: e.x, x1: e.x + e.w, y0: e.y, y1: e.y + e.h, z0: e.z, z1: e.z + e.d, }; } function solidAt(gx, gy, gz) { const b = getBlock(gx, gy, gz); if (!b) return false; const def = BLOCKS[b.t]; if (!def) return false; if (def.fluid) return false; if (def.decor || def.climbable) return false; return !!def.solid; } function climbableAt(gx, gy, gz) { const b = getBlock(gx, gy, gz); return !!(b && BLOCKS[b.t] && BLOCKS[b.t].climbable); } function fluidAt(gx, gy, gz) { const b = getBlock(gx, gy, gz); return !!(b && BLOCKS[b.t] && BLOCKS[b.t].fluid); } // Пересекает ли коробка сущности твёрдый блок function hitsSolid(bx) { const x0 = Math.floor(bx.x0 / TILE), x1 = Math.floor((bx.x1 - EPS) / TILE); const y0 = Math.floor(bx.y0 / TILE), y1 = Math.floor((bx.y1 - EPS) / TILE); const z0 = Math.floor(bx.z0 / TILE), z1 = Math.floor((bx.z1 - EPS) / TILE); for (let gx = x0; gx <= x1; gx++) { for (let gy = y0; gy <= y1; gy++) { for (let gz = z0; gz <= z1; gz++) { if (solidAt(gx, gy, gz)) return true; } } } return false; } // Стоит ли сущность на чём-то (для grounded) function checkGrounded(e) { const bx = boxOf(e); const gy = Math.floor((bx.y1 + 1) / TILE); const x0 = Math.floor(bx.x0 / TILE), x1 = Math.floor((bx.x1 - EPS) / TILE); const z0 = Math.floor(bx.z0 / TILE), z1 = Math.floor((bx.z1 - EPS) / TILE); for (let gx = x0; gx <= x1; gx++) { for (let gz = z0; gz <= z1; gz++) { if (solidAt(gx, gy, gz)) return true; } } return false; } function onLadder(e) { const cx = Math.floor((e.x + e.w / 2) / TILE); const cy = Math.floor((e.y + e.h / 2) / TILE); const cz = Math.floor((e.z + e.d / 2) / TILE); return climbableAt(cx, cy, cz); } function inFluid(e) { const cx = Math.floor((e.x + e.w / 2) / TILE); const cy = Math.floor((e.y + e.h * 0.4) / TILE); const cz = Math.floor((e.z + e.d / 2) / TILE); return fluidAt(cx, cy, cz); } // Разрешение одной оси. delta — смещение за кадр. function moveAxis(e, axis, delta) { if (delta === 0) return false; const bx = boxOf(e); if (axis === 'x') { bx.x0 += delta; bx.x1 += delta; } else if (axis === 'z') { bx.z0 += delta; bx.z1 += delta; } else { bx.y0 += delta; bx.y1 += delta; } if (!hitsSolid(bx)) { if (axis === 'x') e.x += delta; else if (axis === 'z') e.z += delta; else e.y += delta; return false; } const dir = Math.sign(delta); // Автошаг вверх на 1 блок if ((axis === 'x' || axis === 'z') && e.grounded) { const savedX = e.x, savedY = e.y, savedZ = e.z; if (axis === 'x') e.x += delta; else e.z += delta; e.y -= TILE; if (!hitsSolid(boxOf(e))) { return true; // шаг удался, продолжим движение } e.x = savedX; e.y = savedY; e.z = savedZ; } // Откат к границе блока // шаг за блок, пока не пересечение const stepPx = dir * 0.5; let moved = 0; for (let i = 0; i < TILE * 4; i++) { const test = boxOf(e); if (axis === 'x') { test.x0 += stepPx; test.x1 += stepPx; } else if (axis === 'z') { test.z0 += stepPx; test.z1 += stepPx; } else { test.y0 += stepPx; test.y1 += stepPx; } if (hitsSolid(test)) break; moved += stepPx; if (axis === 'x') e.x += stepPx; else if (axis === 'z') e.z += stepPx; else e.y += stepPx; } if (axis === 'x') e.vx = 0; else if (axis === 'z') e.vz = 0; else e.vy = 0; return true; } export function resolveX(e, dt) { return moveAxis(e, 'x', e.vx * dt); } export function resolveZ(e, dt) { return moveAxis(e, 'z', (e.vz || 0) * dt); } export function resolveY(e, dt) { const hit = moveAxis(e, 'y', e.vy * dt); e.grounded = false; if (hit && e.vy >= 0) { e.grounded = checkGrounded(e); } else if (!hit) { e.grounded = checkGrounded(e); } return hit; } export { boxOf, hitsSolid, checkGrounded, onLadder, inFluid, solidAt, fluidAt, climbableAt };