import test from "node:test"; import assert from "node:assert/strict"; import { affectedSectionKeys, createAmbientOcclusionSampler, isAmbientOccluder, } from "../ambient-occlusion.js"; import { unitBox } from "../math.js"; const top = [[0, 1, 1], [1, 1, 1], [1, 1, 0], [0, 1, 0]]; const stone = { state: "minecraft:stone", render: [unitBox] }; const slab = { min: [0, 0, 0], max: [1, 0.5, 1] }; const materialMap = () => new Map([ [1, stone], [2, { state: "minecraft:stone_slab[type=bottom]", render: [slab] }], ]); const blockMap = (...cells) => new Map(cells.map(([pos, id = 1]) => [pos.join(","), id])); const sampleTop = (blocks, materials = materialMap(), box = unitBox, pos = [0, 0, 0]) => createAmbientOcclusionSampler(blocks, materials)(pos, box, [0, 1, 0], top); test("an exposed flat floor has no dark grid or dark silhouette corners", () => { const blocks = new Map(); for (let x = -2; x <= 2; x++) for (let z = -2; z <= 2; z++) blocks.set(`${x},0,${z}`, 1); assert.deepEqual(sampleTop(blocks), [1, 1, 1, 1]); assert.deepEqual(sampleTop(blockMap([[0, 0, 0], 1])), [1, 1, 1, 1]); }); test("one adjacent wall shades only its two touching vertices", () => { assert.deepEqual(sampleTop(blockMap([[-1, 1, 0], 1])), [0.92, 1, 1, 0.92]); }); test("diagonal contact shades one corner and two enclosing walls saturate it", () => { assert.deepEqual(sampleTop(blockMap([[-1, 1, -1], 1])), [1, 1, 1, 0.92]); const sides = [[[-1, 1, 0], 1], [[0, 1, -1], 1]]; assert.deepEqual(sampleTop(blockMap(...sides)), [0.92, 1, 0.92, 0.76]); assert.deepEqual(sampleTop(blockMap(...sides, [[-1, 1, -1], 1])), [0.92, 1, 0.92, 0.76]); }); test("all six face orientations sample the outward hemisphere", () => { for (let axis = 0; axis < 3; axis++) for (const sign of [-1, 1]) { const normal = [0, 0, 0]; normal[axis] = sign; const tangent = [0, 1, 2].filter((a) => a !== axis); const vertices = [[0, 0], [1, 0], [1, 1], [0, 1]].map(([u, v]) => { const result = [0, 0, 0]; result[axis] = sign > 0 ? 1 : 0; result[tangent[0]] = u; result[tangent[1]] = v; return result; }); const diagonal = [0, 0, 0]; diagonal[axis] = sign; diagonal[tangent[0]] = -1; diagonal[tangent[1]] = -1; const sample = createAmbientOcclusionSampler(blockMap([diagonal, 1]), materialMap()); assert.deepEqual(sample([0, 0, 0], unitBox, normal, vertices), [0.92, 1, 1, 1], `normal ${normal}`); } }); test("actual slab height controls occlusion rather than whole-cell occupancy", () => { assert.deepEqual(sampleTop(blockMap([[-1, 0, 0], 2]), materialMap(), slab), [1, 1, 1, 1]); assert.deepEqual(sampleTop(blockMap([[-1, 0, 0], 1]), materialMap(), slab), [0.92, 1, 1, 0.92]); assert.deepEqual(sampleTop(blockMap([[-1, 1, 0], 2])), [0.92, 1, 1, 0.92]); }); test("neighboring boxes within one stair model shade its recessed tread", () => { const tread = { min: [0, 0, 0], max: [0.5, 0.5, 1] }; const riser = { min: [0.5, 0, 0], max: [1, 1, 1] }; const materials = new Map([[1, { ...stone, render: [tread, riser] }]]); assert.deepEqual(sampleTop(blockMap([[0, 0, 0], 1]), materials, tread), [1, 0.92, 0.92, 1]); }); test("render boxes extending beyond their owner can shade a neighboring face", () => { const materials = new Map([[1, { ...stone, render: [{ min: [0, 0, 0], max: [1, 1.5, 1] }], }]]); assert.deepEqual(sampleTop(blockMap([[-1, 0, 0], 1]), materials), [0.92, 1, 1, 0.92]); }); test("transparent and cutout materials never act like opaque corner cubes", () => { for (const overrides of [ { transparent: true }, { opacity: 0.4 }, { cutout: true }, { state: "minecraft:oak_leaves[persistent=true]" }, { state: "minecraft:red_stained_glass" }, { state: "minecraft:water[level=0]" }, { state: "minecraft:cobweb" }, { state: "minecraft:short_grass" }, ]) { const material = { ...stone, ...overrides }; assert.equal(isAmbientOccluder(material), false); assert.deepEqual(sampleTop(blockMap([[-1, 1, 0], 1]), new Map([[1, material]])), [1, 1, 1, 1]); } assert.equal(isAmbientOccluder({ ...stone, state: "minecraft:grass_block" }), true); assert.equal(isAmbientOccluder({ ...stone, state: "minecraft:brown_mushroom_block" }), true); }); test("a texture-pack cutout predicate is cached per material", () => { let calls = 0; const sample = createAmbientOcclusionSampler(blockMap([[-1, 1, 0], 1]), materialMap(), (material, id) => { calls++; return id !== 1; }); assert.deepEqual(sample([0, 0, 0], unitBox, [0, 1, 0], top), [1, 1, 1, 1]); sample([1, 0, 0], unitBox, [0, 1, 0], top); assert.equal(calls, 2); }); test("new rebuild samplers observe edits without keeping stale cached air", () => { const blocks = blockMap(); assert.deepEqual(sampleTop(blocks), [1, 1, 1, 1]); blocks.set("-1,1,0", 1); assert.deepEqual(sampleTop(blocks), [0.92, 1, 1, 0.92]); blocks.delete("-1,1,0"); assert.deepEqual(sampleTop(blocks), [1, 1, 1, 1]); }); test("empty render geometry casts no AO and unknown blocks match placeholder cubes", () => { assert.deepEqual(sampleTop(blockMap([[-1, 1, 0], 1]), new Map([[1, { ...stone, render: [] }]])), [1, 1, 1, 1]); assert.deepEqual(sampleTop(blockMap([[-1, 1, 0], 99])), [0.92, 1, 1, 0.92]); }); test("section invalidation includes corners across positive and negative boundaries", () => { assert.deepEqual(affectedSectionKeys([5, 5, 5]), ["0,0,0"]); assert.deepEqual(affectedSectionKeys([16, 5, 5]), ["0,0,0", "1,0,0"]); assert.deepEqual(affectedSectionKeys([15, 15, 15]), [ "0,0,0", "0,0,1", "0,1,0", "0,1,1", "1,0,0", "1,0,1", "1,1,0", "1,1,1", ]); assert.equal(affectedSectionKeys([-1, -1, -1]).length, 8); assert.ok(affectedSectionKeys([-1, -1, -1]).includes("0,0,0")); assert.ok(affectedSectionKeys([0, 0, 0]).includes("-1,-1,-1")); }); test("extended render geometry widens the affected sections", () => { assert.deepEqual(affectedSectionKeys([5, 13, 5]), ["0,0,0"]); assert.deepEqual(affectedSectionKeys([5, 13, 5], [{ min: [0, 0, 0], max: [1, 3, 1] }]), ["0,0,0", "0,1,0"]); });