MVP checks / mvp (push) Canceled after 0s
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.
102 lines
4.7 KiB
JavaScript
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}`);
|
|
}
|
|
}
|