Files
Emil c7e86663d8
MVP checks / mvp (push) Canceled after 0s
Expand voxel gameplay, lighting, full-height streaming and world imports
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
2026-09-17 02:10:53 +03:00

102 lines
4.7 KiB
JavaScript

import { BLOCK_FACES } from "./texture-pack.js";
const WHITE = Object.freeze([1, 1, 1]);
const GRASS = Object.freeze([0.58, 0.8, 0.34]);
const LEAVES = Object.freeze([0.46, 0.7, 0.28]);
// These aliases share a surface material. Similar block names are deliberately
// not enough: stripped logs, other wood species and deepslate ores need assets.
const materials = new Map([
["stone", ["stone", "stone_stairs", "stone_slab", "stone_button", "stone_pressure_plate"]],
["cobblestone", ["cobblestone", "cobblestone_stairs", "cobblestone_slab", "cobblestone_wall"]],
["oak_planks", ["oak_planks", "oak_stairs", "oak_slab", "oak_fence", "oak_fence_gate", "oak_button", "oak_pressure_plate"]],
["bricks", ["bricks", "brick_stairs", "brick_slab", "brick_wall"]],
["stone_bricks", ["stone_bricks", "stone_brick_stairs", "stone_brick_slab", "stone_brick_wall"]],
["glass", ["glass", "glass_pane"]],
["snow", ["snow", "snow_block"]],
...["dirt", "sand", "gravel", "diamond_ore", "iron_ore", "coal_ore", "gold_ore", "redstone_ore", "deepslate", "obsidian", "netherrack"].map((name) => [name, [name]]),
].flatMap(([texture, blocks]) => blocks.map((block) => [block, texture])));
/** Face order matches renderer.js: east, west, up, down, south, north. */
export function getBlockFaceTextures(state, layers) {
const empty = () => Array(6).fill(null);
const match = /^(?:minecraft:)?([a-z0-9_]+)(?:\[([^\]]*)\])?$/.exec(state ?? "");
if (!match) return empty();
const [, name, properties = ""] = match;
const props = Object.fromEntries(
properties.split(",").filter(Boolean).map((entry) => entry.split("=")),
);
const mapping = layers.get(BLOCK_FACES);
if (mapping) {
const base = `minecraft:${name}`;
const canonical = base + (properties ? `[${Object.entries(props).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",")}]` : "");
const index = Object.hasOwn(mapping.states, canonical) ? mapping.states[canonical] : mapping.defaults[base];
if (Number.isInteger(index)) return mapping.sets[index];
}
const face = (texture, tint = WHITE, cutout = false) => {
const layer = layers.get(texture);
return Number.isInteger(layer) && layer >= 0 ? { layer, tint, cutout } : null;
};
const all = (texture, tint = WHITE, cutout = false) =>
Array.from({ length: 6 }, () => face(texture, tint, cutout));
if (name === "grass_block") {
const side = props.snowy === "true" ? null : "grass_block_side";
return [side, side, "grass_block_top", "dirt", side, side].map(
(texture, index) => texture ? face(texture, index === 2 ? GRASS : WHITE) : null,
);
}
if (name === "oak_log") {
const caps = { x: [0, 1], y: [2, 3], z: [4, 5] }[props.axis ?? "y"];
if (!caps) return empty();
return Array.from({ length: 6 }, (_, index) =>
face(caps.includes(index) ? "oak_log_top" : "oak_log"),
);
}
if (name === "oak_wood") return all("oak_log");
if (name === "oak_leaves") return all("oak_leaves", LEAVES, true);
if (name === "oak_door") {
if ((props.half ?? "lower") !== "lower") return empty();
let alongX = ["east", "west"].includes(props.facing ?? "north");
if (props.open === "true") alongX = !alongX;
const broadFaces = alongX ? [0, 1] : [4, 5];
return Array.from({ length: 6 }, (_, index) =>
face(broadFaces.includes(index) ? "oak_door_bottom" : "oak_planks"),
);
}
const texture = materials.get(name);
return texture ? all(texture, WHITE, texture === "glass") : empty();
}
/**
* UVs address an unflipped bitmap with its first row at V=0. Coordinates stay
* relative to the whole block, so slabs and other boxes crop instead of stretch.
* Horizontal logs rotate back to the vertical-log frame before projection,
* aligning the bark grain with the log axis without mirroring any face.
*/
export function getFaceUV(faceIndex, localPos, state = "", textureFace = null) {
if (textureFace?.uv) {
const uv = textureFace.uv, [x, y, z] = localPos;
return [uv[0]*x + uv[1]*y + uv[2]*z + uv[3], uv[4]*x + uv[5]*y + uv[6]*z + uv[7]];
}
let [x, y, z] = localPos;
const log = /^(?:minecraft:)?oak_(?:log|wood)\[([^\]]*)\]$/.exec(state);
const axis = log && /(?:^|,)axis=([xz])(?:,|$)/.exec(log[1])?.[1];
if (axis === "x") {
[x, y, z] = [1 - y, x, z];
faceIndex = [2, 3, 1, 0, 4, 5][faceIndex];
} else if (axis === "z") {
[x, y, z] = [x, z, 1 - y];
faceIndex = [0, 1, 5, 4, 2, 3][faceIndex];
}
switch (faceIndex) {
case 0: return [1 - z, 1 - y];
case 1: return [z, 1 - y];
case 2: return [x, z];
case 3: return [x, 1 - z];
case 4: return [x, 1 - y];
case 5: return [1 - x, 1 - y];
default: throw new RangeError(`Unknown block face: ${faceIndex}`);
}
}