import test from "node:test"; import assert from "node:assert/strict"; import { boxVertices, buildSectionMesh, materialType, MESH_VERTEX_STRIDE } from "../mesh-geometry.js"; import { buildBlockLight } from "../block-light.js"; import { unitBox } from "../math.js"; const stone = { state: "minecraft:stone", color: [128, 144, 160], render: [unitBox], opacity: 1, light: 0 }; const materialMap = () => new Map([ [1, stone], [2, { ...stone, state: "minecraft:water[level=0]", color: [50, 90, 180], opacity: 0.55, transparent: true }], [3, { ...stone, state: "minecraft:glass", opacity: 0.35, transparent: true }], [4, { ...stone, state: "minecraft:oak_leaves", transparent: true }], [5, { ...stone, state: "minecraft:torch", light: 14, render: [], light_dampening: 0, light_occlusion: [] }], [6, { ...stone, state: "minecraft:redstone_lamp[lit=true]", light: 15 }], ]); const blockMap = (...cells) => new Map(cells.map(([pos, id = 1]) => [pos.join(","), id])); const build = (blocks, options = {}) => buildSectionMesh({ id: "0,0,0", blocks, materials: materialMap(), ...options }); const rows = (vertices) => Array.from({ length: vertices.length / MESH_VERTEX_STRIDE }, (_, index) => [...vertices.slice(index * MESH_VERTEX_STRIDE, (index + 1) * MESH_VERTEX_STRIDE)]); test("empty sections return transferable empty Float32 arrays", () => { const result = build(new Map()); assert.ok(result.vertices instanceof Float32Array); assert.ok(result.transparent instanceof Float32Array); assert.equal(result.vertices.length, 0); assert.equal(result.transparent.length, 0); const invisible = build(blockMap([[1, 1, 1], 5])); assert.equal(invisible.vertices.length, 0); }); test("an isolated cube keeps all six faces and the complete 20-float format", () => { const result = build(blockMap([[2, 3, 4], 1])), vertices = rows(result.vertices); assert.equal(MESH_VERTEX_STRIDE, 20); assert.equal(vertices.length, 36); assert.equal(result.transparent.length, 0); for (const vertex of vertices) { assert.ok(vertex.slice(0, 3).every((value, axis) => [2, 3, 4][axis] <= value && value <= [3, 4, 5][axis])); assert.deepEqual(vertex.slice(3, 6), [128, 144, 160].map((value) => Math.fround(value / 255))); assert.equal(Math.hypot(...vertex.slice(6, 9)), 1); assert.deepEqual(vertex.slice(13), [-1, 1, 0, 0, 0, 0, 1]); } }); test("opaque neighbors cull their shared faces, including across section boundaries", () => { assert.equal(build(blockMap([[1, 1, 1], 1], [[2, 1, 1], 1])).vertices.length / 20, 60); const result = build(blockMap([[15, 1, 1], 1], [[16, 1, 1], 1])); assert.equal(result.vertices.length / 20, 30); assert.ok(rows(result.vertices).every((vertex) => vertex[6] !== 1)); const negative = build(blockMap([[-1, 1, 1], 1], [[0, 1, 1], 1]), { id: "-1,0,0" }); assert.equal(negative.vertices.length / 20, 30); assert.ok(rows(negative.vertices).every((vertex) => vertex[0] <= 0)); }); test("water goes into the translucent buffer and does not hide a stone face", () => { const result = build(blockMap([[1, 1, 1], 1], [[2, 1, 1], 2])); assert.equal(result.vertices.length / 20, 36); assert.equal(result.transparent.length / 20, 36); for (const vertex of rows(result.transparent)) { assert.equal(vertex[9], 6); assert.equal(vertex[10], Math.fround(0.55)); } }); test("texture-pack glass and leaves retain cutout faces in the opaque pass", () => { const blocks = blockMap([[1, 1, 1], 3], [[2, 1, 1], 4]); const fallback = build(blocks); assert.equal(fallback.transparent.length / 20, 36); const result = build(blocks, { textureLayers: new Map([["glass", 2], ["oak_leaves", 3]]) }); assert.equal(result.vertices.length / 20, 72); assert.equal(result.transparent.length, 0); const vertices = rows(result.vertices); assert.ok(vertices.every((vertex) => vertex[10] === 1)); assert.equal(vertices.filter((vertex) => vertex[13] === 2).length, 36); assert.equal(vertices.filter((vertex) => vertex[13] === 3).length, 36); assert.ok(vertices.filter((vertex) => vertex[13] === 3).every((vertex) => vertex[4] > vertex[3])); }); test("partial geometry preserves its real height and crops texture coordinates", () => { const materials = materialMap(); materials.set(7, { ...stone, state: "minecraft:stone_slab[type=bottom]", render: [{ min: [0, 0, 0], max: [1, 0.5, 1] }] }); const result = build(blockMap([[1, 1, 1], 7]), { materials, textureLayers: new Map([["stone", 8]]) }); const vertices = rows(result.vertices); assert.equal(Math.max(...vertices.map((vertex) => vertex[1])), 1.5); assert.ok(vertices.every((vertex) => vertex[13] === 8)); const sides = vertices.filter((vertex) => vertex[7] === 0); assert.ok(sides.every((vertex) => vertex[12] >= 0.5)); }); test("baked AO shades contact corners while exposed faces remain bright", () => { const result = build(blockMap([[1, 0, 1], 1], [[0, 1, 1], 1], [[1, 1, 0], 1])); const top = rows(result.vertices).filter((vertex) => vertex[1] === 1 && vertex[7] === 1); assert.equal(top.length, 6); assert.ok(top.some((vertex) => vertex[14] === Math.fround(0.76))); assert.ok(top.some((vertex) => vertex[14] === 1)); }); test("actual propagated torch light and sky are baked without changing emission", () => { const blocks = blockMap([[4, 0, 4], 1], [[4, 2, 4], 5], [[10, 0, 10], 6]); const materials = materialMap(); const blockLightField = buildBlockLight(blocks, materials, { min: [-1, -1, -1], max: [16, 16, 16] }); const result = build(blocks, { materials, blockLightField }); const vertices = rows(result.vertices); const top = vertices.filter((vertex) => vertex[1] === 1 && vertex[7] === 1 && vertex[0] < 6); assert.ok(top.every((vertex) => vertex[16] > 0 && vertex[16] > vertex[17] && vertex[17] > vertex[18])); assert.ok(top.every((vertex) => vertex[19] === 1 && vertex[15] === 0)); assert.ok(vertices.some((vertex) => vertex[15] === 1)); }); test("geometry extraction keeps yaw and per-vertex dynamic lighting for actors", () => { const out = [], sampled = []; const field = { sample(point) { sampled.push(point); return { block: [0.1, 0.2, 0.3], sky: 0.4 }; } }; boxVertices(out, [5, 6, 7], unitBox, [1, 1, 1], 0, () => false, Math.PI / 2, 1, null, "", null, 0, field, true); const vertices = rows(new Float32Array(out)); assert.equal(sampled.length, 36); assert.ok(vertices.every((vertex) => vertex[0] >= 4 && vertex[0] <= 5 && vertex[2] >= 7 && vertex[2] <= 8)); assert.deepEqual(vertices[0].slice(16), [0.1, 0.2, 0.3, 0.4].map(Math.fround)); assert.equal(materialType("minecraft:water[level=0]"), 6); assert.equal(materialType("shacraft:trampoline", "bounce"), 5); }); test("reused texture caches keep section output byte-identical", () => { const blocks = blockMap([[1, 1, 1], 1], [[2, 1, 1], 3]); const textureLayers = new Map([["stone", 4], ["glass", 5]]), faceTextureCache = new Map(); const first = build(blocks, { textureLayers, faceTextureCache }); const second = build(blocks, { textureLayers, faceTextureCache }); assert.deepEqual(second.vertices, first.vertices); assert.deepEqual(second.transparent, first.transparent); assert.ok(faceTextureCache.size > 0); const transferred = structuredClone(first, { transfer: [first.vertices.buffer, first.transparent.buffer] }); assert.deepEqual(transferred.vertices, second.vertices); });