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,104 @@
|
||||
import { attachBlockLightSamplers, buildBlockLight } from "./block-light.js";
|
||||
import { applyLightProperties, loadLightProperties } from "./light-properties.js";
|
||||
|
||||
/** Only one build runs at a time. Edits arriving during a build replace its result. */
|
||||
export class BlockLightController {
|
||||
constructor(publish) {
|
||||
this.publish = publish;
|
||||
this.generation = 0;
|
||||
this.busy = false;
|
||||
this.pending = null;
|
||||
this.status = "loading";
|
||||
this.properties = null;
|
||||
this.worker = null;
|
||||
if (typeof Worker === "function") {
|
||||
try {
|
||||
this.worker = new Worker(new URL("./block-light-worker.js", import.meta.url), { type: "module" });
|
||||
} catch {
|
||||
// Restricted browser contexts can reject workers before any error event.
|
||||
// The same solver remains available on the main thread.
|
||||
}
|
||||
}
|
||||
if (this.worker) {
|
||||
this.worker.onmessage = ({ data }) => this.receive(data);
|
||||
this.worker.onerror = (event) => {
|
||||
event.preventDefault();
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
this.busy = false;
|
||||
// Preserve the latest request and recover with the same implementation.
|
||||
this.pending ||= this.current;
|
||||
this.start();
|
||||
};
|
||||
}
|
||||
loadLightProperties().then(properties => {
|
||||
this.properties = properties;
|
||||
this.start();
|
||||
}).catch(error => {
|
||||
this.status = `error: ${error.message}`;
|
||||
console.error("Shacraft light metadata", error);
|
||||
});
|
||||
}
|
||||
request(blocks, materials, bounds) {
|
||||
this.generation++;
|
||||
this.pending = { blocks, materials, bounds, generation: this.generation };
|
||||
this.status = "pending";
|
||||
if (!this.timer) this.timer = setTimeout(() => { this.timer = null; this.start(); }, 25);
|
||||
}
|
||||
start() {
|
||||
if (this.busy || !this.pending || !this.properties) return;
|
||||
const request = this.pending;
|
||||
this.pending = null;
|
||||
this.busy = true;
|
||||
this.current = request;
|
||||
this.status = "building";
|
||||
const materials = new Map([...request.materials].map(([id, mat]) => [id,
|
||||
applyLightProperties({
|
||||
minecraft_id: mat.minecraft_id, state: mat.state, light: mat.light,
|
||||
opacity: mat.opacity, render: mat.render, transparent: mat.transparent,
|
||||
light_dampening: mat.light_dampening, light_occlusion: mat.light_occlusion,
|
||||
}, this.properties)]));
|
||||
const packet = { generation: request.generation, bounds: request.bounds,
|
||||
blocks: [...request.blocks], materials: [...materials] };
|
||||
this.current = { ...request, blocks: new Map(packet.blocks), materials };
|
||||
if (this.worker) this.worker.postMessage(packet);
|
||||
else setTimeout(() => {
|
||||
const start = performance.now();
|
||||
try {
|
||||
this.receive({ generation: request.generation,
|
||||
field: buildBlockLight(this.current.blocks, materials, request.bounds),
|
||||
milliseconds: performance.now() - start });
|
||||
} catch (error) { this.receive({ generation: request.generation, error: error.message }); }
|
||||
}, 0);
|
||||
}
|
||||
receive(message) {
|
||||
const current = this.current;
|
||||
if (!this.busy || message.generation !== current?.generation) return;
|
||||
this.busy = false;
|
||||
if (message.generation === this.generation) {
|
||||
if (message.error) {
|
||||
this.status = `error: ${message.error}`;
|
||||
console.error("Shacraft block lighting", message.error);
|
||||
} else {
|
||||
this.status = "ready";
|
||||
this.milliseconds = message.milliseconds;
|
||||
this.publish(attachBlockLightSamplers(message.field, current.blocks, current.materials));
|
||||
}
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/** In a standalone scene, keep a full light-radius margin around its geometry. */
|
||||
export function inferLightBounds(blocks) {
|
||||
const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
|
||||
for (const key of blocks.keys()) {
|
||||
const pos = key.split(",").map(Number);
|
||||
for (let axis = 0; axis < 3; axis++) {
|
||||
min[axis] = Math.min(min[axis], pos[axis]);
|
||||
max[axis] = Math.max(max[axis], pos[axis]);
|
||||
}
|
||||
}
|
||||
if (!Number.isFinite(min[0])) return { min: [-16, -16, -16], max: [16, 16, 16] };
|
||||
return { min: min.map(v => v - 15), max: max.map(v => v + 15) };
|
||||
}
|
||||
Reference in New Issue
Block a user