Files
shacraft-core/client/block-light-controller.js
Emil c7e86663d8
MVP checks / mvp (push) Waiting to run
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

105 lines
4.1 KiB
JavaScript

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