Files
shacraft-core/client/tests/ambient-occlusion.test.js
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

145 lines
6.0 KiB
JavaScript

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"]);
});