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,115 @@
|
||||
/** Keep pointer capture retryable after browser errors and ordinary unlocks. */
|
||||
export class PointerLockController {
|
||||
constructor(canvas, { onChange = () => {}, onError = () => {} } = {}) {
|
||||
this.canvas = canvas;
|
||||
this.document = canvas.ownerDocument;
|
||||
this.onChange = onChange;
|
||||
this.onError = onError;
|
||||
this.status = this.locked ? "locked" : "idle";
|
||||
this.lastError = null;
|
||||
this.generation = 0;
|
||||
this.disposed = false;
|
||||
this.cancelled = false;
|
||||
this.observedLocked = this.locked;
|
||||
this.changeListener = () => this.changed();
|
||||
this.errorListener = (event) =>
|
||||
this.failed(
|
||||
event.error ||
|
||||
new Error(event.message || "Pointer lock was denied by the browser."),
|
||||
this.generation,
|
||||
);
|
||||
this.document.addEventListener("pointerlockchange", this.changeListener);
|
||||
this.document.addEventListener("pointerlockerror", this.errorListener);
|
||||
}
|
||||
|
||||
get locked() {
|
||||
return this.document.pointerLockElement === this.canvas;
|
||||
}
|
||||
|
||||
request() {
|
||||
if (this.disposed || this.locked || this.status === "requesting")
|
||||
return false;
|
||||
const generation = ++this.generation;
|
||||
this.cancelled = false;
|
||||
this.lastError = null;
|
||||
this.status = "requesting";
|
||||
if (typeof this.canvas.requestPointerLock !== "function") {
|
||||
const error = new Error("This browser does not support pointer lock.");
|
||||
error.name = "NotSupportedError";
|
||||
this.failed(error, generation, "unsupported");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (this.canvas.tabIndex < 0) this.canvas.tabIndex = 0;
|
||||
this.canvas.focus({ preventScroll: true });
|
||||
// Start synchronously: awaiting anything here loses the user's gesture.
|
||||
const result = this.canvas.requestPointerLock();
|
||||
result?.then?.(
|
||||
() => {
|
||||
if (this.cancelled && this.locked) this.document.exitPointerLock?.();
|
||||
else if (
|
||||
!this.disposed &&
|
||||
generation === this.generation &&
|
||||
this.locked
|
||||
)
|
||||
this.changed();
|
||||
},
|
||||
(error) => this.failed(error, generation),
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.failed(error, generation);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
failed(error, generation, status = "error") {
|
||||
if (
|
||||
this.disposed ||
|
||||
generation !== this.generation ||
|
||||
this.locked ||
|
||||
this.status !== "requesting"
|
||||
)
|
||||
return;
|
||||
this.generation++;
|
||||
this.status = status;
|
||||
this.lastError = error?.message
|
||||
? error
|
||||
: new Error(String(error || "Pointer lock failed."));
|
||||
this.onError(this.lastError);
|
||||
}
|
||||
|
||||
changed() {
|
||||
if (this.disposed) return;
|
||||
if (this.locked && this.cancelled) {
|
||||
this.document.exitPointerLock?.();
|
||||
return;
|
||||
}
|
||||
const locked = this.locked;
|
||||
this.generation++;
|
||||
this.status = locked ? "locked" : "idle";
|
||||
this.lastError = null;
|
||||
if (locked !== this.observedLocked) {
|
||||
this.observedLocked = locked;
|
||||
this.onChange(locked);
|
||||
}
|
||||
}
|
||||
|
||||
release() {
|
||||
if (this.disposed) return;
|
||||
const requesting = this.status === "requesting";
|
||||
this.generation++;
|
||||
this.cancelled = true;
|
||||
this.status = "idle";
|
||||
this.lastError = null;
|
||||
if (this.locked || requesting) this.document.exitPointerLock?.();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.disposed) return;
|
||||
this.release();
|
||||
this.disposed = true;
|
||||
this.document.removeEventListener("pointerlockchange", this.changeListener);
|
||||
this.document.removeEventListener("pointerlockerror", this.errorListener);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user