147 lines
4.6 KiB
JavaScript
147 lines
4.6 KiB
JavaScript
// ==================== ФИЗИКА 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 };
|