minerender 2.1.0-alpha.85 → 2.1.0-alpha.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.d.mts +161 -3
- package/dist/browser/index.d.ts +161 -3
- package/dist/browser/index.js +469 -4
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +469 -4
- package/dist/browser/index.mjs.map +1 -1
- package/dist/bundle.js +483 -4
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +161 -3
- package/dist/node/index.d.ts +161 -3
- package/dist/node/index.js +472 -4
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +469 -4
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node/index.js
CHANGED
|
@@ -18780,6 +18780,7 @@ var index_node_exports = {};
|
|
|
18780
18780
|
__export(index_node_exports, {
|
|
18781
18781
|
AXES: () => AXES,
|
|
18782
18782
|
AnvilParser: () => AnvilParser,
|
|
18783
|
+
AnvilWorldSource: () => AnvilWorldSource,
|
|
18783
18784
|
ArchiveAssetSource: () => ArchiveAssetSource,
|
|
18784
18785
|
AssetKey: () => AssetKey,
|
|
18785
18786
|
AssetLoadError: () => AssetLoadError,
|
|
@@ -18882,6 +18883,8 @@ __export(index_node_exports, {
|
|
|
18882
18883
|
Ticker: () => Ticker,
|
|
18883
18884
|
TintColors: () => TintColors,
|
|
18884
18885
|
UVMapper: () => UVMapper,
|
|
18886
|
+
VideoExporter: () => VideoExporter,
|
|
18887
|
+
WorldStreamer: () => WorldStreamer,
|
|
18885
18888
|
WrappedImage: () => WrappedImage,
|
|
18886
18889
|
addBox3WireframeToObject: () => addBox3WireframeToObject,
|
|
18887
18890
|
addWireframeToMesh: () => addWireframeToMesh,
|
|
@@ -22218,6 +22221,131 @@ function copyDataTexture(source) {
|
|
|
22218
22221
|
return new import_three19.Texture().copy(copySource);
|
|
22219
22222
|
}
|
|
22220
22223
|
|
|
22224
|
+
// src/export/VideoExporter.ts
|
|
22225
|
+
var VideoExporter = class {
|
|
22226
|
+
constructor(source, options) {
|
|
22227
|
+
this.source = source;
|
|
22228
|
+
this.chunks = [];
|
|
22229
|
+
this.settled = false;
|
|
22230
|
+
this.stopping = false;
|
|
22231
|
+
this.onStart = () => {
|
|
22232
|
+
this.timer = setTimeout(() => {
|
|
22233
|
+
this.stopping = true;
|
|
22234
|
+
try {
|
|
22235
|
+
this.recorder.stop();
|
|
22236
|
+
} catch (error) {
|
|
22237
|
+
this.cancel(error);
|
|
22238
|
+
}
|
|
22239
|
+
}, this.duration * 1e3);
|
|
22240
|
+
};
|
|
22241
|
+
this.onData = (event) => {
|
|
22242
|
+
if (event.data.size) this.chunks.push(event.data);
|
|
22243
|
+
};
|
|
22244
|
+
this.onStop = () => {
|
|
22245
|
+
if (!this.stopping) return this.cancel(new Error("Video recording stopped before its duration elapsed"));
|
|
22246
|
+
if (!this.chunks.length) return this.cancel(new Error("The browser produced no video data"));
|
|
22247
|
+
const mimeType = this.chunks.find((chunk) => chunk.type)?.type || this.recorder.mimeType;
|
|
22248
|
+
const blob = new Blob(this.chunks, { type: mimeType });
|
|
22249
|
+
this.settled = true;
|
|
22250
|
+
this.cleanup();
|
|
22251
|
+
this.resolve(blob);
|
|
22252
|
+
};
|
|
22253
|
+
this.onError = (event) => {
|
|
22254
|
+
this.cancel(event.error ?? new Error("Video recording failed"));
|
|
22255
|
+
};
|
|
22256
|
+
this.onAbort = () => {
|
|
22257
|
+
this.cancel(this.signal.reason);
|
|
22258
|
+
};
|
|
22259
|
+
this.onTrackEnded = () => {
|
|
22260
|
+
this.cancel(new Error("The video capture stream stopped producing frames"));
|
|
22261
|
+
};
|
|
22262
|
+
const fps = options.fps ?? 30;
|
|
22263
|
+
if (!Number.isFinite(options.duration) || options.duration <= 0 || options.duration * 1e3 > 2147483647) {
|
|
22264
|
+
throw new RangeError("Video duration must be greater than zero and at most 2147483.647 seconds");
|
|
22265
|
+
}
|
|
22266
|
+
if (!Number.isFinite(fps) || fps <= 0) throw new RangeError("Video fps must be a finite positive number");
|
|
22267
|
+
const bitrate = options.videoBitsPerSecond;
|
|
22268
|
+
if (bitrate !== void 0 && (!Number.isInteger(bitrate) || bitrate <= 0 || bitrate > 4294967295)) {
|
|
22269
|
+
throw new RangeError("Video bitrate must be a positive unsigned 32-bit integer");
|
|
22270
|
+
}
|
|
22271
|
+
if (!source.width || !source.height) throw new Error("Cannot export video from an empty canvas");
|
|
22272
|
+
options.signal?.throwIfAborted();
|
|
22273
|
+
if (typeof document === "undefined" || typeof MediaRecorder === "undefined" || typeof MediaRecorder.isTypeSupported !== "function") {
|
|
22274
|
+
throw new Error("Video export requires a browser with MediaRecorder support");
|
|
22275
|
+
}
|
|
22276
|
+
const mimeTypes = options.mimeType === void 0 ? ["video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm", "video/mp4"] : [options.mimeType];
|
|
22277
|
+
const mimeType = mimeTypes.find((type) => type.startsWith("video/") && MediaRecorder.isTypeSupported(type));
|
|
22278
|
+
if (!mimeType) throw new Error("The browser does not support the requested video format");
|
|
22279
|
+
this.canvas = document.createElement("canvas");
|
|
22280
|
+
this.canvas.width = source.width;
|
|
22281
|
+
this.canvas.height = source.height;
|
|
22282
|
+
if (typeof this.canvas.captureStream !== "function") throw new Error("Video export requires canvas captureStream support");
|
|
22283
|
+
const context = this.canvas.getContext("2d", { alpha: false });
|
|
22284
|
+
if (!context) throw new Error("Could not create a canvas for video export");
|
|
22285
|
+
this.context = context;
|
|
22286
|
+
this.signal = options.signal;
|
|
22287
|
+
this.duration = options.duration;
|
|
22288
|
+
this.result = new Promise((resolve, reject) => {
|
|
22289
|
+
this.resolve = resolve;
|
|
22290
|
+
this.reject = reject;
|
|
22291
|
+
});
|
|
22292
|
+
try {
|
|
22293
|
+
this.stream = this.canvas.captureStream(fps);
|
|
22294
|
+
this.recorder = new MediaRecorder(this.stream, { mimeType, videoBitsPerSecond: bitrate });
|
|
22295
|
+
this.recorder.addEventListener("start", this.onStart);
|
|
22296
|
+
this.recorder.addEventListener("dataavailable", this.onData);
|
|
22297
|
+
this.recorder.addEventListener("stop", this.onStop);
|
|
22298
|
+
this.recorder.addEventListener("error", this.onError);
|
|
22299
|
+
for (const track of this.stream.getTracks()) {
|
|
22300
|
+
track.addEventListener("ended", this.onTrackEnded);
|
|
22301
|
+
}
|
|
22302
|
+
this.signal?.addEventListener("abort", this.onAbort, { once: true });
|
|
22303
|
+
this.recorder.start();
|
|
22304
|
+
} catch (error) {
|
|
22305
|
+
this.cancel(error);
|
|
22306
|
+
}
|
|
22307
|
+
}
|
|
22308
|
+
/** Copies a freshly drawn source frame before WebGL can clear its drawing buffer. */
|
|
22309
|
+
capture() {
|
|
22310
|
+
if (this.settled || this.stopping) return;
|
|
22311
|
+
try {
|
|
22312
|
+
this.context.fillStyle = "#000000";
|
|
22313
|
+
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
22314
|
+
this.context.drawImage(this.source, 0, 0, this.canvas.width, this.canvas.height);
|
|
22315
|
+
} catch (error) {
|
|
22316
|
+
this.cancel(error);
|
|
22317
|
+
}
|
|
22318
|
+
}
|
|
22319
|
+
/** Cancels recording, releases its capture stream, and rejects {@link result}. */
|
|
22320
|
+
cancel(reason = new DOMException("Video export cancelled", "AbortError")) {
|
|
22321
|
+
if (this.settled) return;
|
|
22322
|
+
this.settled = true;
|
|
22323
|
+
this.cleanup();
|
|
22324
|
+
this.reject(reason);
|
|
22325
|
+
}
|
|
22326
|
+
cleanup() {
|
|
22327
|
+
if (this.timer !== void 0) clearTimeout(this.timer);
|
|
22328
|
+
this.signal?.removeEventListener("abort", this.onAbort);
|
|
22329
|
+
if (this.recorder) {
|
|
22330
|
+
this.recorder.removeEventListener("start", this.onStart);
|
|
22331
|
+
this.recorder.removeEventListener("dataavailable", this.onData);
|
|
22332
|
+
this.recorder.removeEventListener("stop", this.onStop);
|
|
22333
|
+
this.recorder.removeEventListener("error", this.onError);
|
|
22334
|
+
if (this.recorder.state !== "inactive") {
|
|
22335
|
+
try {
|
|
22336
|
+
this.recorder.stop();
|
|
22337
|
+
} catch {
|
|
22338
|
+
}
|
|
22339
|
+
}
|
|
22340
|
+
}
|
|
22341
|
+
for (const track of this.stream?.getTracks() ?? []) {
|
|
22342
|
+
track.removeEventListener("ended", this.onTrackEnded);
|
|
22343
|
+
track.stop();
|
|
22344
|
+
}
|
|
22345
|
+
this.chunks.length = 0;
|
|
22346
|
+
}
|
|
22347
|
+
};
|
|
22348
|
+
|
|
22221
22349
|
// src/gui/GuiHelper.ts
|
|
22222
22350
|
init_AssetKey();
|
|
22223
22351
|
init_ModelTextures();
|
|
@@ -27529,6 +27657,9 @@ var Renderer = class _Renderer {
|
|
|
27529
27657
|
this._inAnimationLoop = true;
|
|
27530
27658
|
try {
|
|
27531
27659
|
this.animate(time);
|
|
27660
|
+
} catch (error) {
|
|
27661
|
+
this._videoExporter?.cancel(error);
|
|
27662
|
+
throw error;
|
|
27532
27663
|
} finally {
|
|
27533
27664
|
this._inAnimationLoop = false;
|
|
27534
27665
|
}
|
|
@@ -27763,16 +27894,18 @@ var Renderer = class _Renderer {
|
|
|
27763
27894
|
if (this._frameCallbacks.get(callback) === subscription) this._frameCallbacks.delete(callback);
|
|
27764
27895
|
};
|
|
27765
27896
|
}
|
|
27766
|
-
/** Starts or resumes the animation loop and registered frame callbacks. */
|
|
27897
|
+
/** Starts or resumes the animation loop and registered frame callbacks. Has no effect while running. */
|
|
27767
27898
|
start() {
|
|
27768
|
-
if (this._disposed) return;
|
|
27899
|
+
if (this._disposed || this._running) return;
|
|
27769
27900
|
this.stop();
|
|
27770
27901
|
this._running = true;
|
|
27771
27902
|
if (!this._inAnimationLoop) this.renderer.setAnimationLoop(this._animationLoop);
|
|
27772
27903
|
}
|
|
27773
|
-
/** Pauses rendering and frame callbacks. Call {@link start} to resume. */
|
|
27904
|
+
/** Pauses rendering and frame callbacks, and cancels video export. Call {@link start} to resume. */
|
|
27774
27905
|
stop() {
|
|
27775
27906
|
if (this._disposed) return;
|
|
27907
|
+
this._videoExporter?.cancel();
|
|
27908
|
+
this._videoExporter = void 0;
|
|
27776
27909
|
this._running = false;
|
|
27777
27910
|
if (this._inAnimationLoop) {
|
|
27778
27911
|
queueMicrotask(() => {
|
|
@@ -27834,7 +27967,7 @@ var Renderer = class _Renderer {
|
|
|
27834
27967
|
this._controls.update();
|
|
27835
27968
|
}
|
|
27836
27969
|
if (!this._running) return;
|
|
27837
|
-
if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size) return;
|
|
27970
|
+
if (!this.dirty && !this.options.render.renderAlways && !this._frameCallbacks.size && !this._videoExporter) return;
|
|
27838
27971
|
const interval = this._frameInterval;
|
|
27839
27972
|
if (interval) {
|
|
27840
27973
|
const next = this._nextFrameTime;
|
|
@@ -27859,6 +27992,7 @@ var Renderer = class _Renderer {
|
|
|
27859
27992
|
} else {
|
|
27860
27993
|
this.renderer.render(this.scene, this.camera);
|
|
27861
27994
|
}
|
|
27995
|
+
this._videoExporter?.capture();
|
|
27862
27996
|
this.dirty = false;
|
|
27863
27997
|
if (this._stats) {
|
|
27864
27998
|
this._stats.end();
|
|
@@ -27879,6 +28013,35 @@ var Renderer = class _Renderer {
|
|
|
27879
28013
|
const canvas = trim ? trimCanvas(this.renderer.domElement) : this.renderer.domElement;
|
|
27880
28014
|
return canvas.toDataURL(mime, quality);
|
|
27881
28015
|
}
|
|
28016
|
+
/**
|
|
28017
|
+
* Records a silent browser video in real time, including frame callbacks and animated textures.
|
|
28018
|
+
* Keep the tab visible; browser scheduling and `render.fpsLimit` limit the capture rate.
|
|
28019
|
+
* The video keeps the initial drawing-buffer size and fills transparent pixels with black.
|
|
28020
|
+
* Temporarily starts a stopped renderer. Stop, disposal, or `options.signal` cancels the export.
|
|
28021
|
+
* Only one export can run at a time. Use the returned Blob's type to select a file extension.
|
|
28022
|
+
*/
|
|
28023
|
+
async toVideo(options) {
|
|
28024
|
+
if (this._disposed) throw new Error("Cannot export a video from a disposed renderer");
|
|
28025
|
+
if (this._videoExporter) throw new Error("A video export is already running");
|
|
28026
|
+
const exporter = new VideoExporter(this.renderer.domElement, options);
|
|
28027
|
+
const wasRunning = this._running;
|
|
28028
|
+
try {
|
|
28029
|
+
try {
|
|
28030
|
+
if (!wasRunning) this.start();
|
|
28031
|
+
this._videoExporter = exporter;
|
|
28032
|
+
if (this._controls?.enabled) this._controls.update();
|
|
28033
|
+
this.drawFrame();
|
|
28034
|
+
} catch (error) {
|
|
28035
|
+
exporter.cancel(error);
|
|
28036
|
+
}
|
|
28037
|
+
return await exporter.result;
|
|
28038
|
+
} finally {
|
|
28039
|
+
if (this._videoExporter === exporter) {
|
|
28040
|
+
this._videoExporter = void 0;
|
|
28041
|
+
if (!wasRunning) this.stop();
|
|
28042
|
+
}
|
|
28043
|
+
}
|
|
28044
|
+
}
|
|
27882
28045
|
/** Exports visible scene meshes as OBJ text, without texture images. */
|
|
27883
28046
|
toObj() {
|
|
27884
28047
|
return SceneExporter.toObj(this.scene);
|
|
@@ -28791,6 +28954,134 @@ var AnvilParser = class {
|
|
|
28791
28954
|
}
|
|
28792
28955
|
};
|
|
28793
28956
|
|
|
28957
|
+
// src/world/AnvilWorldSource.ts
|
|
28958
|
+
init_MineRenderError();
|
|
28959
|
+
async function abortable2(promise, signal) {
|
|
28960
|
+
if (!signal) return promise;
|
|
28961
|
+
let abort;
|
|
28962
|
+
const cancelled = new Promise((_, reject) => {
|
|
28963
|
+
abort = () => reject(signal.reason);
|
|
28964
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
28965
|
+
if (signal.aborted) abort();
|
|
28966
|
+
});
|
|
28967
|
+
try {
|
|
28968
|
+
return await Promise.race([promise, cancelled]);
|
|
28969
|
+
} finally {
|
|
28970
|
+
signal.removeEventListener("abort", abort);
|
|
28971
|
+
}
|
|
28972
|
+
}
|
|
28973
|
+
var AnvilWorldSource = class {
|
|
28974
|
+
constructor(readRegion, options = {}) {
|
|
28975
|
+
this.readRegion = readRegion;
|
|
28976
|
+
this.regions = /* @__PURE__ */ new Map();
|
|
28977
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
28978
|
+
this.cachedBytes = 0;
|
|
28979
|
+
this.maxCachedRegions = options.maxCachedRegions ?? 4;
|
|
28980
|
+
this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
|
|
28981
|
+
for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
|
|
28982
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
|
|
28983
|
+
}
|
|
28984
|
+
}
|
|
28985
|
+
/** Decodes one column at absolute chunk coordinates. Aborting a caller leaves other shared readers active. */
|
|
28986
|
+
async getChunk(x, z, signal) {
|
|
28987
|
+
if (!Number.isSafeInteger(x) || !Number.isSafeInteger(z)) {
|
|
28988
|
+
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
28989
|
+
}
|
|
28990
|
+
const regionX = Math.floor(x / 32), regionZ = Math.floor(z / 32);
|
|
28991
|
+
const data = await this.getRegion(regionX, regionZ, signal);
|
|
28992
|
+
signal?.throwIfAborted();
|
|
28993
|
+
if (!data) return void 0;
|
|
28994
|
+
const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
|
|
28995
|
+
signal?.throwIfAborted();
|
|
28996
|
+
if (chunk && (chunk.x !== x || chunk.z !== z)) {
|
|
28997
|
+
throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
|
|
28998
|
+
}
|
|
28999
|
+
return chunk;
|
|
29000
|
+
}
|
|
29001
|
+
/** Releases cached regions. Reads already in progress finish without repopulating this cache. */
|
|
29002
|
+
clearCache() {
|
|
29003
|
+
this.regions.clear();
|
|
29004
|
+
this.pending.clear();
|
|
29005
|
+
this.cachedBytes = 0;
|
|
29006
|
+
}
|
|
29007
|
+
getRegion(x, z, signal) {
|
|
29008
|
+
signal?.throwIfAborted();
|
|
29009
|
+
const key = `${x},${z}`;
|
|
29010
|
+
const cached2 = this.regions.get(key);
|
|
29011
|
+
if (cached2) {
|
|
29012
|
+
this.regions.delete(key);
|
|
29013
|
+
this.regions.set(key, cached2);
|
|
29014
|
+
return Promise.resolve(cached2.data);
|
|
29015
|
+
}
|
|
29016
|
+
let loading = this.pending.get(key);
|
|
29017
|
+
if (!loading) {
|
|
29018
|
+
const controller = new AbortController();
|
|
29019
|
+
loading = {
|
|
29020
|
+
controller,
|
|
29021
|
+
waiters: 0,
|
|
29022
|
+
settled: false,
|
|
29023
|
+
promise: Promise.resolve().then(() => {
|
|
29024
|
+
controller.signal.throwIfAborted();
|
|
29025
|
+
return this.readRegion(x, z, controller.signal);
|
|
29026
|
+
}).then((data) => {
|
|
29027
|
+
controller.signal.throwIfAborted();
|
|
29028
|
+
let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
|
|
29029
|
+
if (bytes && bytes.byteLength !== bytes.buffer.byteLength) bytes = new Uint8Array(bytes);
|
|
29030
|
+
if (bytes) AnvilParser.getChunkList(bytes);
|
|
29031
|
+
loading.settled = true;
|
|
29032
|
+
if (this.pending.get(key) === loading) this.cacheRegion(key, bytes);
|
|
29033
|
+
return bytes;
|
|
29034
|
+
}).finally(() => {
|
|
29035
|
+
loading.settled = true;
|
|
29036
|
+
if (this.pending.get(key) === loading) this.pending.delete(key);
|
|
29037
|
+
})
|
|
29038
|
+
};
|
|
29039
|
+
this.pending.set(key, loading);
|
|
29040
|
+
}
|
|
29041
|
+
const read = loading;
|
|
29042
|
+
read.waiters++;
|
|
29043
|
+
return new Promise((resolve, reject) => {
|
|
29044
|
+
let finished = false;
|
|
29045
|
+
const release = () => {
|
|
29046
|
+
if (finished) return false;
|
|
29047
|
+
finished = true;
|
|
29048
|
+
signal?.removeEventListener("abort", abort);
|
|
29049
|
+
read.waiters--;
|
|
29050
|
+
return true;
|
|
29051
|
+
};
|
|
29052
|
+
const abort = () => {
|
|
29053
|
+
if (!release()) return;
|
|
29054
|
+
if (!read.waiters && !read.settled) {
|
|
29055
|
+
if (this.pending.get(key) === read) this.pending.delete(key);
|
|
29056
|
+
read.controller.abort(signal.reason);
|
|
29057
|
+
}
|
|
29058
|
+
reject(signal.reason);
|
|
29059
|
+
};
|
|
29060
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
29061
|
+
read.promise.then(
|
|
29062
|
+
(data) => {
|
|
29063
|
+
if (release()) resolve(data);
|
|
29064
|
+
},
|
|
29065
|
+
(error) => {
|
|
29066
|
+
if (release()) reject(error);
|
|
29067
|
+
}
|
|
29068
|
+
);
|
|
29069
|
+
if (signal?.aborted) abort();
|
|
29070
|
+
});
|
|
29071
|
+
}
|
|
29072
|
+
cacheRegion(key, data) {
|
|
29073
|
+
const size = data?.byteLength ?? 0;
|
|
29074
|
+
if (!this.maxCachedRegions || size > this.maxCachedBytes) return;
|
|
29075
|
+
this.regions.set(key, { data, size });
|
|
29076
|
+
this.cachedBytes += size;
|
|
29077
|
+
while (this.regions.size > this.maxCachedRegions || this.cachedBytes > this.maxCachedBytes) {
|
|
29078
|
+
const oldest = this.regions.keys().next().value;
|
|
29079
|
+
this.cachedBytes -= this.regions.get(oldest).size;
|
|
29080
|
+
this.regions.delete(oldest);
|
|
29081
|
+
}
|
|
29082
|
+
}
|
|
29083
|
+
};
|
|
29084
|
+
|
|
28794
29085
|
// src/world/Chunk.ts
|
|
28795
29086
|
var import_three42 = require("three");
|
|
28796
29087
|
init_BlockStates();
|
|
@@ -29465,10 +29756,185 @@ var Chunk = class _Chunk {
|
|
|
29465
29756
|
);
|
|
29466
29757
|
}
|
|
29467
29758
|
};
|
|
29759
|
+
|
|
29760
|
+
// src/world/WorldStreamer.ts
|
|
29761
|
+
var WorldStreamer = class {
|
|
29762
|
+
constructor(world, source, options = {}) {
|
|
29763
|
+
this.world = world;
|
|
29764
|
+
this.source = source;
|
|
29765
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
29766
|
+
this.owned = /* @__PURE__ */ new Map();
|
|
29767
|
+
this.missing = /* @__PURE__ */ new Map();
|
|
29768
|
+
this.failed = /* @__PURE__ */ new Map();
|
|
29769
|
+
this.desired = [];
|
|
29770
|
+
this.disposed = false;
|
|
29771
|
+
this.loadRadius = options.loadRadius ?? 2;
|
|
29772
|
+
this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
|
|
29773
|
+
if (![this.loadRadius, this.unloadRadius].every((value) => Number.isInteger(value) && value >= 0 && value <= 32) || this.unloadRadius < this.loadRadius) {
|
|
29774
|
+
throw new RangeError("Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius");
|
|
29775
|
+
}
|
|
29776
|
+
}
|
|
29777
|
+
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
29778
|
+
get loadedChunks() {
|
|
29779
|
+
return [...this.loaded.values()].map((position) => ({ ...position }));
|
|
29780
|
+
}
|
|
29781
|
+
/** Returns source failures retained within the unload radius. Failed columns do not block other loads. */
|
|
29782
|
+
get failedChunks() {
|
|
29783
|
+
return [...this.failed.values()].map((failure) => ({ ...failure }));
|
|
29784
|
+
}
|
|
29785
|
+
/** Counts desired columns still waiting for a source read or placement, including the active column. */
|
|
29786
|
+
get pendingChunks() {
|
|
29787
|
+
return this.desired.filter((position) => this.needsLoad(position)).length;
|
|
29788
|
+
}
|
|
29789
|
+
/**
|
|
29790
|
+
* Updates the center in absolute chunk coordinates. Repeated calls share the active drain.
|
|
29791
|
+
* Source failures are recorded in failedChunks; other columns continue loading. Missing and failed
|
|
29792
|
+
* columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.
|
|
29793
|
+
* Placement and unload failures reject the drain; call update again to retry those operations.
|
|
29794
|
+
* Reads outside the latest load radius receive an abort signal; placement finishes before unloading.
|
|
29795
|
+
* Sources should represent an unchanged world during streaming.
|
|
29796
|
+
*/
|
|
29797
|
+
async update(x, z) {
|
|
29798
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
29799
|
+
if (![x, z].every((value) => Number.isSafeInteger(value) && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {
|
|
29800
|
+
throw new RangeError("Chunk coordinates must be safe integers within the streaming radius");
|
|
29801
|
+
}
|
|
29802
|
+
if (!this.center || this.center.x !== x || this.center.z !== z) {
|
|
29803
|
+
this.center = { x, z };
|
|
29804
|
+
this.desired = [];
|
|
29805
|
+
for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {
|
|
29806
|
+
for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {
|
|
29807
|
+
this.desired.push({ x: x + dx, z: z + dz });
|
|
29808
|
+
}
|
|
29809
|
+
}
|
|
29810
|
+
this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);
|
|
29811
|
+
if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
|
|
29812
|
+
this.loading.controller.abort();
|
|
29813
|
+
}
|
|
29814
|
+
}
|
|
29815
|
+
return this.running ??= Promise.resolve().then(() => this.drain());
|
|
29816
|
+
}
|
|
29817
|
+
/** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */
|
|
29818
|
+
async updatePosition(position) {
|
|
29819
|
+
if (![position.x, position.z].every(Number.isFinite)) throw new RangeError("Scene coordinates must be finite");
|
|
29820
|
+
return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));
|
|
29821
|
+
}
|
|
29822
|
+
/** Forgets recorded source failures and retries desired columns at the latest center. */
|
|
29823
|
+
async retryFailedChunks() {
|
|
29824
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
29825
|
+
this.failed.clear();
|
|
29826
|
+
if (this.center) await this.update(this.center.x, this.center.z);
|
|
29827
|
+
}
|
|
29828
|
+
/** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
|
|
29829
|
+
dispose() {
|
|
29830
|
+
this.disposed = true;
|
|
29831
|
+
this.desired = [];
|
|
29832
|
+
this.center = void 0;
|
|
29833
|
+
this.loading?.controller.abort();
|
|
29834
|
+
return this.disposal ??= Promise.resolve().then(async () => {
|
|
29835
|
+
await this.running?.catch(() => void 0);
|
|
29836
|
+
let failure;
|
|
29837
|
+
let failed = false;
|
|
29838
|
+
for (const position of this.owned.values()) {
|
|
29839
|
+
try {
|
|
29840
|
+
await this.unload(position);
|
|
29841
|
+
} catch (error) {
|
|
29842
|
+
failed = true;
|
|
29843
|
+
failure ??= error;
|
|
29844
|
+
}
|
|
29845
|
+
}
|
|
29846
|
+
this.missing.clear();
|
|
29847
|
+
this.failed.clear();
|
|
29848
|
+
if (failed) throw failure;
|
|
29849
|
+
}).catch((error) => {
|
|
29850
|
+
this.disposal = void 0;
|
|
29851
|
+
throw error;
|
|
29852
|
+
});
|
|
29853
|
+
}
|
|
29854
|
+
async drain() {
|
|
29855
|
+
try {
|
|
29856
|
+
while (!this.disposed) {
|
|
29857
|
+
for (const [key, position2] of this.missing) {
|
|
29858
|
+
if (!this.within(position2, this.unloadRadius)) this.missing.delete(key);
|
|
29859
|
+
}
|
|
29860
|
+
for (const [key, position2] of this.failed) {
|
|
29861
|
+
if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
|
|
29862
|
+
}
|
|
29863
|
+
const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
|
|
29864
|
+
if (obsolete) {
|
|
29865
|
+
await this.unload(obsolete);
|
|
29866
|
+
continue;
|
|
29867
|
+
}
|
|
29868
|
+
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
29869
|
+
if (!position) return;
|
|
29870
|
+
let chunk;
|
|
29871
|
+
const controller = new AbortController();
|
|
29872
|
+
this.loading = { position, controller };
|
|
29873
|
+
try {
|
|
29874
|
+
chunk = await this.source.getChunk(position.x, position.z, controller.signal);
|
|
29875
|
+
if (chunk) this.validateChunk(chunk, position);
|
|
29876
|
+
} catch (error) {
|
|
29877
|
+
if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {
|
|
29878
|
+
this.failed.set(this.key(position), { ...position, error });
|
|
29879
|
+
}
|
|
29880
|
+
continue;
|
|
29881
|
+
} finally {
|
|
29882
|
+
this.loading = void 0;
|
|
29883
|
+
}
|
|
29884
|
+
if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;
|
|
29885
|
+
if (!chunk) {
|
|
29886
|
+
this.missing.set(this.key(position), position);
|
|
29887
|
+
continue;
|
|
29888
|
+
}
|
|
29889
|
+
this.owned.set(this.key(position), position);
|
|
29890
|
+
try {
|
|
29891
|
+
await this.world.placeChunk(chunk);
|
|
29892
|
+
this.loaded.set(this.key(position), position);
|
|
29893
|
+
} catch (error) {
|
|
29894
|
+
try {
|
|
29895
|
+
await this.unload(position);
|
|
29896
|
+
} catch (cleanupError) {
|
|
29897
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
29898
|
+
try {
|
|
29899
|
+
Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
|
|
29900
|
+
} catch {
|
|
29901
|
+
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
29902
|
+
}
|
|
29903
|
+
throw failure;
|
|
29904
|
+
}
|
|
29905
|
+
throw error;
|
|
29906
|
+
}
|
|
29907
|
+
}
|
|
29908
|
+
} finally {
|
|
29909
|
+
this.running = void 0;
|
|
29910
|
+
}
|
|
29911
|
+
}
|
|
29912
|
+
needsLoad(position) {
|
|
29913
|
+
const key = this.key(position);
|
|
29914
|
+
return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
|
|
29915
|
+
}
|
|
29916
|
+
async unload(position) {
|
|
29917
|
+
await this.world.unloadChunkColumn(position.x, position.z);
|
|
29918
|
+
this.owned.delete(this.key(position));
|
|
29919
|
+
this.loaded.delete(this.key(position));
|
|
29920
|
+
}
|
|
29921
|
+
validateChunk(chunk, position) {
|
|
29922
|
+
if (chunk.x !== position.x || chunk.z !== position.z) {
|
|
29923
|
+
throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);
|
|
29924
|
+
}
|
|
29925
|
+
}
|
|
29926
|
+
within(position, radius) {
|
|
29927
|
+
return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;
|
|
29928
|
+
}
|
|
29929
|
+
key(position) {
|
|
29930
|
+
return `${position.x},${position.z}`;
|
|
29931
|
+
}
|
|
29932
|
+
};
|
|
29468
29933
|
// Annotate the CommonJS export names for ESM import in node:
|
|
29469
29934
|
0 && (module.exports = {
|
|
29470
29935
|
AXES,
|
|
29471
29936
|
AnvilParser,
|
|
29937
|
+
AnvilWorldSource,
|
|
29472
29938
|
ArchiveAssetSource,
|
|
29473
29939
|
AssetKey,
|
|
29474
29940
|
AssetLoadError,
|
|
@@ -29571,6 +30037,8 @@ var Chunk = class _Chunk {
|
|
|
29571
30037
|
Ticker,
|
|
29572
30038
|
TintColors,
|
|
29573
30039
|
UVMapper,
|
|
30040
|
+
VideoExporter,
|
|
30041
|
+
WorldStreamer,
|
|
29574
30042
|
WrappedImage,
|
|
29575
30043
|
addBox3WireframeToObject,
|
|
29576
30044
|
addWireframeToMesh,
|