Expand voxel gameplay, lighting, full-height streaming and world imports
MVP checks / mvp (push) Canceled after 0s
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.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
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"]);
|
||||
});
|
||||
Reference in New Issue
Block a user