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}`); } }