Item entities

This commit is contained in:
2026-09-25 16:26:45 -03:00
parent 5fb23cf404
commit a254b96f65
18 changed files with 797 additions and 94 deletions
+96
View File
@@ -0,0 +1,96 @@
// collision against blocks, shared by the client's entities and the server's, so both move things the same way
import { TICK_DELTA } from "./constants.ts";
// how far inside a block face still counts as touching it, so boxes resting exactly on a face don't snag
const EPSILON = 1e-7;
// the two axes that aren't the one being moved along
const OTHER_AXES = [[1, 2], [0, 2], [0, 1]] as const;
// something with a box that moves. position is the middle of its feet, velocity is in blocks per second
export interface Body {
x: number;
y: number;
z: number;
vx: number;
vy: number;
vz: number;
// width is used for both x and z
width: number;
height: number;
}
// which way the body hit something on each axis: 1 or -1, 0 for nothing. 1 on y means it landed on something
export interface Collisions {
x: number;
y: number;
z: number;
}
// falls and moves a body by its velocity for one tick. like minecraft it moves along y, then x, then z, each
// time only as far as it can before touching a block, and stops its velocity on the axes it hit something.
// blocks it's already inside don't stop it, so it can get out of them
export function move_body(body: Body, gravity: number, is_solid: (x: number, y: number, z: number) => boolean) {
// the average of this tick's start and end speed, so the arc is the same at any tick rate
const wanted_y = (body.vy + gravity * TICK_DELTA / 2) * TICK_DELTA;
body.vy += gravity * TICK_DELTA;
const wanted = [body.vx * TICK_DELTA, wanted_y, body.vz * TICK_DELTA];
const half = body.width / 2;
const min = [body.x - half, body.y, body.z - half];
const max = [body.x + half, body.y + body.height, body.z + half];
const moved = [0, 0, 0];
for (const axis of [1, 0, 2]) {
const distance = clip(min, max, axis, wanted[axis], is_solid);
min[axis] += distance;
max[axis] += distance;
moved[axis] = distance;
}
const hit = (axis: number) => moved[axis] !== wanted[axis] ? -Math.sign(wanted[axis]) : 0;
const collisions: Collisions = { x: hit(0), y: hit(1), z: hit(2) };
if (collisions.x !== 0) body.vx = 0;
if (collisions.y !== 0) body.vy = 0;
if (collisions.z !== 0) body.vz = 0;
body.x += moved[0];
body.y += moved[1];
body.z += moved[2];
return collisions;
}
// how far the box can go along an axis before it runs into a block
function clip(
min: number[],
max: number[],
axis: number,
distance: number,
is_solid: (x: number, y: number, z: number) => boolean,
) {
if (distance === 0) {
return 0;
}
const [a, b] = OTHER_AXES[axis];
const from = Math.floor(Math.min(min[axis], min[axis] + distance));
const to = Math.floor(Math.max(max[axis], max[axis] + distance));
const position = [0, 0, 0];
for (let i = from; i <= to; i++) {
for (let j = Math.floor(min[a] + EPSILON); j <= Math.floor(max[a] - EPSILON); j++) {
for (let k = Math.floor(min[b] + EPSILON); k <= Math.floor(max[b] - EPSILON); k++) {
position[axis] = i;
position[a] = j;
position[b] = k;
if (!is_solid(position[0], position[1], position[2])) {
continue;
}
if (distance > 0 && i >= max[axis] - EPSILON) {
distance = Math.min(distance, i - max[axis]);
} else if (distance < 0 && i + 1 <= min[axis] + EPSILON) {
distance = Math.max(distance, i + 1 - min[axis]);
}
}
}
}
return distance;
}