minerender 2.1.0-alpha.86 → 2.1.0-alpha.88
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 +194 -3
- package/dist/browser/index.d.ts +194 -3
- package/dist/browser/index.js +806 -184
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +806 -185
- package/dist/browser/index.mjs.map +1 -1
- package/dist/browser/section.worker.js +119 -0
- package/dist/browser/section.worker.js.map +1 -0
- package/dist/browser/section.worker.mjs +119 -0
- package/dist/browser/section.worker.mjs.map +1 -0
- package/dist/bundle.js +826 -187
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +193 -3
- package/dist/node/index.d.ts +193 -3
- package/dist/node/index.js +804 -184
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +798 -184
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node/index.js
CHANGED
|
@@ -192,6 +192,23 @@ async function sleep(timeout) {
|
|
|
192
192
|
setTimeout(() => resolve(), timeout);
|
|
193
193
|
});
|
|
194
194
|
}
|
|
195
|
+
function yieldToEventLoop() {
|
|
196
|
+
const scheduler = globalThis.scheduler;
|
|
197
|
+
if (scheduler?.yield) return scheduler.yield();
|
|
198
|
+
return new Promise((resolve) => {
|
|
199
|
+
if (typeof MessageChannel !== "undefined") {
|
|
200
|
+
const channel = new MessageChannel();
|
|
201
|
+
channel.port1.onmessage = () => {
|
|
202
|
+
channel.port1.close();
|
|
203
|
+
channel.port2.close();
|
|
204
|
+
resolve();
|
|
205
|
+
};
|
|
206
|
+
channel.port2.postMessage(void 0);
|
|
207
|
+
} else {
|
|
208
|
+
setTimeout(resolve, 0);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
195
212
|
function clampRotationDegrees(deg) {
|
|
196
213
|
return deg % 360;
|
|
197
214
|
}
|
|
@@ -18780,6 +18797,7 @@ var index_node_exports = {};
|
|
|
18780
18797
|
__export(index_node_exports, {
|
|
18781
18798
|
AXES: () => AXES,
|
|
18782
18799
|
AnvilParser: () => AnvilParser,
|
|
18800
|
+
AnvilWorldSource: () => AnvilWorldSource,
|
|
18783
18801
|
ArchiveAssetSource: () => ArchiveAssetSource,
|
|
18784
18802
|
AssetKey: () => AssetKey,
|
|
18785
18803
|
AssetLoadError: () => AssetLoadError,
|
|
@@ -18869,6 +18887,7 @@ __export(index_node_exports, {
|
|
|
18869
18887
|
SchematicParser: () => SchematicParser,
|
|
18870
18888
|
SectionMesh: () => SectionMesh,
|
|
18871
18889
|
SectionModels: () => SectionModels,
|
|
18890
|
+
SectionWorker: () => SectionWorker,
|
|
18872
18891
|
SkinImage: () => SkinImage,
|
|
18873
18892
|
SkinObject: () => SkinObject,
|
|
18874
18893
|
SkinPart: () => SkinPart,
|
|
@@ -18883,6 +18902,7 @@ __export(index_node_exports, {
|
|
|
18883
18902
|
TintColors: () => TintColors,
|
|
18884
18903
|
UVMapper: () => UVMapper,
|
|
18885
18904
|
VideoExporter: () => VideoExporter,
|
|
18905
|
+
WorldStreamer: () => WorldStreamer,
|
|
18886
18906
|
WrappedImage: () => WrappedImage,
|
|
18887
18907
|
addBox3WireframeToObject: () => addBox3WireframeToObject,
|
|
18888
18908
|
addWireframeToMesh: () => addWireframeToMesh,
|
|
@@ -18892,6 +18912,7 @@ __export(index_node_exports, {
|
|
|
18892
18912
|
axisToVec3: () => axisToVec3,
|
|
18893
18913
|
base64decode: () => base64decode,
|
|
18894
18914
|
base64encode: () => base64encode,
|
|
18915
|
+
buildSectionGeometry: () => buildSectionGeometry,
|
|
18895
18916
|
capeTextureSizes: () => capeTextureSizes,
|
|
18896
18917
|
captureException: () => captureException,
|
|
18897
18918
|
changeEvent: () => changeEvent,
|
|
@@ -18943,6 +18964,7 @@ __export(index_node_exports, {
|
|
|
18943
18964
|
sampleEntityAnimation: () => sampleEntityAnimation,
|
|
18944
18965
|
sampleEntityKeyframes: () => sampleEntityKeyframes,
|
|
18945
18966
|
sampleFluid: () => sampleFluid,
|
|
18967
|
+
sectionGeometryTransferables: () => sectionGeometryTransferables,
|
|
18946
18968
|
serializeBoxGeometryKey: () => serializeBoxGeometryKey,
|
|
18947
18969
|
serializeImageKey: () => serializeImageKey,
|
|
18948
18970
|
serializeMaterialKey: () => serializeMaterialKey,
|
|
@@ -18960,7 +18982,8 @@ __export(index_node_exports, {
|
|
|
18960
18982
|
toDegrees: () => toDegrees,
|
|
18961
18983
|
toRadians: () => toRadians,
|
|
18962
18984
|
toTrianglesDrawMode: () => toTrianglesDrawMode,
|
|
18963
|
-
trimCanvas: () => trimCanvas
|
|
18985
|
+
trimCanvas: () => trimCanvas,
|
|
18986
|
+
yieldToEventLoop: () => yieldToEventLoop
|
|
18964
18987
|
});
|
|
18965
18988
|
module.exports = __toCommonJS(index_node_exports);
|
|
18966
18989
|
|
|
@@ -28690,6 +28713,16 @@ init_NBTHelper();
|
|
|
28690
28713
|
|
|
28691
28714
|
// src/world/ChunkData.ts
|
|
28692
28715
|
init_AssetKey();
|
|
28716
|
+
var blockKeys = /* @__PURE__ */ new Map();
|
|
28717
|
+
function blockKey(type) {
|
|
28718
|
+
let key = blockKeys.get(type);
|
|
28719
|
+
if (!key) {
|
|
28720
|
+
key = AssetKey.parse("blockstates", type);
|
|
28721
|
+
if (blockKeys.size >= 4096) blockKeys.clear();
|
|
28722
|
+
blockKeys.set(type, key);
|
|
28723
|
+
}
|
|
28724
|
+
return key;
|
|
28725
|
+
}
|
|
28693
28726
|
var ChunkData = class _ChunkData {
|
|
28694
28727
|
constructor() {
|
|
28695
28728
|
this.indices = new Uint16Array(4096);
|
|
@@ -28717,10 +28750,10 @@ var ChunkData = class _ChunkData {
|
|
|
28717
28750
|
let key;
|
|
28718
28751
|
let nbt;
|
|
28719
28752
|
if (block && !_ChunkData.isAir(block)) {
|
|
28720
|
-
const type =
|
|
28753
|
+
const type = blockKey(block.type).toNamespacedString();
|
|
28721
28754
|
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28722
28755
|
state = Object.freeze(Object.keys(properties).length ? { type, properties: Object.freeze(properties) } : { type });
|
|
28723
|
-
key =
|
|
28756
|
+
key = _ChunkData.paletteKey(block);
|
|
28724
28757
|
if (block.nbt !== void 0) nbt = structuredClone(block.nbt);
|
|
28725
28758
|
}
|
|
28726
28759
|
const previousId = this.indices[index];
|
|
@@ -28757,9 +28790,14 @@ var ChunkData = class _ChunkData {
|
|
|
28757
28790
|
}
|
|
28758
28791
|
static isAir(block) {
|
|
28759
28792
|
if (typeof block === "undefined" || typeof block.type === "undefined") return true;
|
|
28760
|
-
const key =
|
|
28793
|
+
const key = blockKey(block.type);
|
|
28761
28794
|
return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
|
|
28762
28795
|
}
|
|
28796
|
+
/** Identifies a normalized block type and its sorted properties, excluding per-cell NBT. */
|
|
28797
|
+
static paletteKey(block) {
|
|
28798
|
+
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28799
|
+
return JSON.stringify([blockKey(block.type).toNamespacedString(), properties]);
|
|
28800
|
+
}
|
|
28763
28801
|
static copyBlock(state, nbt) {
|
|
28764
28802
|
const block = { type: state.type };
|
|
28765
28803
|
if (state.properties) block.properties = { ...state.properties };
|
|
@@ -28939,19 +28977,150 @@ var AnvilParser = class {
|
|
|
28939
28977
|
const perLong = Math.floor(64 / bits);
|
|
28940
28978
|
const expected = padded ? Math.ceil(4096 / perLong) : Math.ceil(4096 * bits / 64);
|
|
28941
28979
|
if (tag.value.length !== expected) throw new MineRenderError(`Invalid Anvil block-state array length: expected ${expected}`);
|
|
28942
|
-
const words = tag.value.map(([high, low]) =>
|
|
28943
|
-
const mask = (
|
|
28980
|
+
const words = tag.value.map(([high, low]) => [high >>> 0, low >>> 0]);
|
|
28981
|
+
const mask = (1 << bits) - 1;
|
|
28944
28982
|
for (let index = 0; index < 4096; index++) {
|
|
28945
|
-
const
|
|
28946
|
-
const
|
|
28947
|
-
|
|
28948
|
-
|
|
28949
|
-
|
|
28983
|
+
const bitPos = index * bits;
|
|
28984
|
+
const word = padded ? Math.floor(index / perLong) : bitPos >>> 6;
|
|
28985
|
+
const shift = padded ? index % perLong * bits : bitPos & 63;
|
|
28986
|
+
const [high, low] = words[word];
|
|
28987
|
+
let value = shift < 32 ? low >>> shift : high >>> shift - 32;
|
|
28988
|
+
if (shift < 32 && shift + bits > 32) value += high * 2 ** (32 - shift);
|
|
28989
|
+
if (!padded && shift + bits > 64) value += words[word + 1][1] * 2 ** (64 - shift);
|
|
28990
|
+
indices[index] = value & mask;
|
|
28950
28991
|
}
|
|
28951
28992
|
return indices;
|
|
28952
28993
|
}
|
|
28953
28994
|
};
|
|
28954
28995
|
|
|
28996
|
+
// src/world/AnvilWorldSource.ts
|
|
28997
|
+
init_MineRenderError();
|
|
28998
|
+
async function abortable2(promise, signal) {
|
|
28999
|
+
if (!signal) return promise;
|
|
29000
|
+
let abort;
|
|
29001
|
+
const cancelled = new Promise((_, reject) => {
|
|
29002
|
+
abort = () => reject(signal.reason);
|
|
29003
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
29004
|
+
if (signal.aborted) abort();
|
|
29005
|
+
});
|
|
29006
|
+
try {
|
|
29007
|
+
return await Promise.race([promise, cancelled]);
|
|
29008
|
+
} finally {
|
|
29009
|
+
signal.removeEventListener("abort", abort);
|
|
29010
|
+
}
|
|
29011
|
+
}
|
|
29012
|
+
var AnvilWorldSource = class {
|
|
29013
|
+
constructor(readRegion, options = {}) {
|
|
29014
|
+
this.readRegion = readRegion;
|
|
29015
|
+
this.regions = /* @__PURE__ */ new Map();
|
|
29016
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
29017
|
+
this.cachedBytes = 0;
|
|
29018
|
+
this.maxCachedRegions = options.maxCachedRegions ?? 4;
|
|
29019
|
+
this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
|
|
29020
|
+
for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
|
|
29021
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
|
|
29022
|
+
}
|
|
29023
|
+
}
|
|
29024
|
+
/** Decodes one column at absolute chunk coordinates. Aborting a caller leaves other shared readers active. */
|
|
29025
|
+
async getChunk(x, z, signal) {
|
|
29026
|
+
if (!Number.isSafeInteger(x) || !Number.isSafeInteger(z)) {
|
|
29027
|
+
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
29028
|
+
}
|
|
29029
|
+
const regionX = Math.floor(x / 32), regionZ = Math.floor(z / 32);
|
|
29030
|
+
const data = await this.getRegion(regionX, regionZ, signal);
|
|
29031
|
+
signal?.throwIfAborted();
|
|
29032
|
+
if (!data) return void 0;
|
|
29033
|
+
const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
|
|
29034
|
+
signal?.throwIfAborted();
|
|
29035
|
+
if (chunk && (chunk.x !== x || chunk.z !== z)) {
|
|
29036
|
+
throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
|
|
29037
|
+
}
|
|
29038
|
+
return chunk;
|
|
29039
|
+
}
|
|
29040
|
+
/** Releases cached regions. Reads already in progress finish without repopulating this cache. */
|
|
29041
|
+
clearCache() {
|
|
29042
|
+
this.regions.clear();
|
|
29043
|
+
this.pending.clear();
|
|
29044
|
+
this.cachedBytes = 0;
|
|
29045
|
+
}
|
|
29046
|
+
getRegion(x, z, signal) {
|
|
29047
|
+
signal?.throwIfAborted();
|
|
29048
|
+
const key = `${x},${z}`;
|
|
29049
|
+
const cached2 = this.regions.get(key);
|
|
29050
|
+
if (cached2) {
|
|
29051
|
+
this.regions.delete(key);
|
|
29052
|
+
this.regions.set(key, cached2);
|
|
29053
|
+
return Promise.resolve(cached2.data);
|
|
29054
|
+
}
|
|
29055
|
+
let loading = this.pending.get(key);
|
|
29056
|
+
if (!loading) {
|
|
29057
|
+
const controller = new AbortController();
|
|
29058
|
+
loading = {
|
|
29059
|
+
controller,
|
|
29060
|
+
waiters: 0,
|
|
29061
|
+
settled: false,
|
|
29062
|
+
promise: Promise.resolve().then(() => {
|
|
29063
|
+
controller.signal.throwIfAborted();
|
|
29064
|
+
return this.readRegion(x, z, controller.signal);
|
|
29065
|
+
}).then((data) => {
|
|
29066
|
+
controller.signal.throwIfAborted();
|
|
29067
|
+
let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
|
|
29068
|
+
if (bytes && bytes.byteLength !== bytes.buffer.byteLength) bytes = new Uint8Array(bytes);
|
|
29069
|
+
if (bytes) AnvilParser.getChunkList(bytes);
|
|
29070
|
+
loading.settled = true;
|
|
29071
|
+
if (this.pending.get(key) === loading) this.cacheRegion(key, bytes);
|
|
29072
|
+
return bytes;
|
|
29073
|
+
}).finally(() => {
|
|
29074
|
+
loading.settled = true;
|
|
29075
|
+
if (this.pending.get(key) === loading) this.pending.delete(key);
|
|
29076
|
+
})
|
|
29077
|
+
};
|
|
29078
|
+
this.pending.set(key, loading);
|
|
29079
|
+
}
|
|
29080
|
+
const read = loading;
|
|
29081
|
+
read.waiters++;
|
|
29082
|
+
return new Promise((resolve, reject) => {
|
|
29083
|
+
let finished = false;
|
|
29084
|
+
const release = () => {
|
|
29085
|
+
if (finished) return false;
|
|
29086
|
+
finished = true;
|
|
29087
|
+
signal?.removeEventListener("abort", abort);
|
|
29088
|
+
read.waiters--;
|
|
29089
|
+
return true;
|
|
29090
|
+
};
|
|
29091
|
+
const abort = () => {
|
|
29092
|
+
if (!release()) return;
|
|
29093
|
+
if (!read.waiters && !read.settled) {
|
|
29094
|
+
if (this.pending.get(key) === read) this.pending.delete(key);
|
|
29095
|
+
read.controller.abort(signal.reason);
|
|
29096
|
+
}
|
|
29097
|
+
reject(signal.reason);
|
|
29098
|
+
};
|
|
29099
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
29100
|
+
read.promise.then(
|
|
29101
|
+
(data) => {
|
|
29102
|
+
if (release()) resolve(data);
|
|
29103
|
+
},
|
|
29104
|
+
(error) => {
|
|
29105
|
+
if (release()) reject(error);
|
|
29106
|
+
}
|
|
29107
|
+
);
|
|
29108
|
+
if (signal?.aborted) abort();
|
|
29109
|
+
});
|
|
29110
|
+
}
|
|
29111
|
+
cacheRegion(key, data) {
|
|
29112
|
+
const size = data?.byteLength ?? 0;
|
|
29113
|
+
if (!this.maxCachedRegions || size > this.maxCachedBytes) return;
|
|
29114
|
+
this.regions.set(key, { data, size });
|
|
29115
|
+
this.cachedBytes += size;
|
|
29116
|
+
while (this.regions.size > this.maxCachedRegions || this.cachedBytes > this.maxCachedBytes) {
|
|
29117
|
+
const oldest = this.regions.keys().next().value;
|
|
29118
|
+
this.cachedBytes -= this.regions.get(oldest).size;
|
|
29119
|
+
this.regions.delete(oldest);
|
|
29120
|
+
}
|
|
29121
|
+
}
|
|
29122
|
+
};
|
|
29123
|
+
|
|
28955
29124
|
// src/world/Chunk.ts
|
|
28956
29125
|
var import_three42 = require("three");
|
|
28957
29126
|
init_BlockStates();
|
|
@@ -28960,7 +29129,7 @@ init_AssetKey();
|
|
|
28960
29129
|
// src/world/MineRenderWorld.ts
|
|
28961
29130
|
var import_three40 = require("three");
|
|
28962
29131
|
init_Model();
|
|
28963
|
-
|
|
29132
|
+
init_util();
|
|
28964
29133
|
init_AssetKey();
|
|
28965
29134
|
init_BlockStates();
|
|
28966
29135
|
init_CubeFace();
|
|
@@ -29105,7 +29274,7 @@ var MineRenderWorld = class {
|
|
|
29105
29274
|
* Places a structure's blocks at their stored positions, then refreshes neighboring faces.
|
|
29106
29275
|
* Entity NBT is retained by the structure but is not rendered.
|
|
29107
29276
|
*
|
|
29108
|
-
* @param useBatches -
|
|
29277
|
+
* @param useBatches - Yields between chunks by default. Set to `false` for sequential placement.
|
|
29109
29278
|
* @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
|
|
29110
29279
|
*/
|
|
29111
29280
|
async placeMultiBlock(multiblock, useBatches = true, executor) {
|
|
@@ -29122,8 +29291,10 @@ var MineRenderWorld = class {
|
|
|
29122
29291
|
};
|
|
29123
29292
|
const place = (block) => this.placeBlock(new import_three40.Vector3(...block.position), block, collect);
|
|
29124
29293
|
const keys = /* @__PURE__ */ new Map();
|
|
29294
|
+
const types = /* @__PURE__ */ new Set();
|
|
29125
29295
|
for (const block of multiblock.blocks) {
|
|
29126
|
-
if (Chunk.isAir(block)) continue;
|
|
29296
|
+
if (Chunk.isAir(block) || types.has(block.type)) continue;
|
|
29297
|
+
types.add(block.type);
|
|
29127
29298
|
const key = AssetKey.parse("blockstates", block.type);
|
|
29128
29299
|
keys.set(key.serialize(), key);
|
|
29129
29300
|
}
|
|
@@ -29132,6 +29303,23 @@ var MineRenderWorld = class {
|
|
|
29132
29303
|
for (const block of multiblock.blocks) await place(block);
|
|
29133
29304
|
return;
|
|
29134
29305
|
}
|
|
29306
|
+
if (!executor) {
|
|
29307
|
+
const groups2 = /* @__PURE__ */ new Map();
|
|
29308
|
+
const position = new import_three40.Vector3();
|
|
29309
|
+
for (const block of multiblock.blocks) {
|
|
29310
|
+
const [x, y, z] = block.position;
|
|
29311
|
+
this.validatePosBounds(position.set(x, y, z));
|
|
29312
|
+
const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
|
|
29313
|
+
const key = `${cx}_${cy}_${cz}`;
|
|
29314
|
+
const chunk = this._chunks.get(key) ?? (Chunk.isAir(block) ? void 0 : this.getOrCreateChunkAt(position));
|
|
29315
|
+
if (!chunk) continue;
|
|
29316
|
+
let group = groups2.get(chunk);
|
|
29317
|
+
if (!group) groups2.set(chunk, group = []);
|
|
29318
|
+
group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
|
|
29319
|
+
}
|
|
29320
|
+
await this.placeChunkGroups(groups2, changes);
|
|
29321
|
+
return;
|
|
29322
|
+
}
|
|
29135
29323
|
const positions = /* @__PURE__ */ new Map();
|
|
29136
29324
|
for (const block of multiblock.blocks) {
|
|
29137
29325
|
const key = block.position.join(",");
|
|
@@ -29140,19 +29328,34 @@ var MineRenderWorld = class {
|
|
|
29140
29328
|
else positions.set(key, [block]);
|
|
29141
29329
|
}
|
|
29142
29330
|
const groups = [...positions.values()];
|
|
29143
|
-
|
|
29144
|
-
|
|
29145
|
-
|
|
29146
|
-
|
|
29147
|
-
|
|
29148
|
-
|
|
29149
|
-
|
|
29331
|
+
for (let i = 0; i < groups.length; i += executor.batch) {
|
|
29332
|
+
const results = await Promise.allSettled(groups.slice(i, i + executor.batch).map(
|
|
29333
|
+
(blocks) => executor.submit(async () => {
|
|
29334
|
+
for (const block of blocks) await place(block);
|
|
29335
|
+
})
|
|
29336
|
+
));
|
|
29337
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
29338
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
29339
|
+
}
|
|
29340
|
+
}
|
|
29341
|
+
async placeChunkGroups(groups, changes) {
|
|
29342
|
+
let sliceStart = performance.now();
|
|
29343
|
+
for (const [chunk, blocks] of groups) {
|
|
29344
|
+
let positions;
|
|
29345
|
+
try {
|
|
29346
|
+
positions = await chunk.placeBlocks(blocks);
|
|
29347
|
+
} finally {
|
|
29348
|
+
positions ??= blocks.map(({ index }) => new import_three40.Vector3(
|
|
29349
|
+
chunk.x * 16 + index % 16,
|
|
29350
|
+
chunk.y * 16 + Math.floor(index / 256),
|
|
29351
|
+
chunk.z * 16 + Math.floor(index / 16) % 16
|
|
29150
29352
|
));
|
|
29151
|
-
const
|
|
29152
|
-
|
|
29353
|
+
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
29354
|
+
}
|
|
29355
|
+
if (performance.now() - sliceStart > 8) {
|
|
29356
|
+
await yieldToEventLoop();
|
|
29357
|
+
sliceStart = performance.now();
|
|
29153
29358
|
}
|
|
29154
|
-
} finally {
|
|
29155
|
-
if (!executor) queue.stop();
|
|
29156
29359
|
}
|
|
29157
29360
|
}
|
|
29158
29361
|
/** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
|
|
@@ -29160,7 +29363,24 @@ var MineRenderWorld = class {
|
|
|
29160
29363
|
const changes = /* @__PURE__ */ new Map();
|
|
29161
29364
|
try {
|
|
29162
29365
|
await this.clearChunkColumn(chunk.x, chunk.z, changes);
|
|
29366
|
+
const groups = /* @__PURE__ */ new Map();
|
|
29163
29367
|
for (const section of chunk.sections) {
|
|
29368
|
+
if (!executor) {
|
|
29369
|
+
const blocks2 = [];
|
|
29370
|
+
for (let index = 0; index < 4096; index++) {
|
|
29371
|
+
const block = section.data.get(index);
|
|
29372
|
+
if (block) blocks2.push({ index, block });
|
|
29373
|
+
}
|
|
29374
|
+
if (blocks2.length) {
|
|
29375
|
+
const position = new import_three40.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
|
|
29376
|
+
this.validatePosBounds(position);
|
|
29377
|
+
const target = this.getOrCreateChunkAt(position);
|
|
29378
|
+
const previous = groups.get(target);
|
|
29379
|
+
if (previous) previous.push(...blocks2);
|
|
29380
|
+
else groups.set(target, blocks2);
|
|
29381
|
+
}
|
|
29382
|
+
continue;
|
|
29383
|
+
}
|
|
29164
29384
|
const blocks = [];
|
|
29165
29385
|
for (let index = 0; index < 4096; index++) {
|
|
29166
29386
|
const block = section.data.get(index);
|
|
@@ -29175,6 +29395,7 @@ var MineRenderWorld = class {
|
|
|
29175
29395
|
}
|
|
29176
29396
|
await this.placeBlocks({ size: [16, 16, 16], blocks }, true, executor, changes);
|
|
29177
29397
|
}
|
|
29398
|
+
if (!executor) await this.placeChunkGroups(groups, changes);
|
|
29178
29399
|
} finally {
|
|
29179
29400
|
await this.updateCulling([...changes.values()]);
|
|
29180
29401
|
}
|
|
@@ -29215,19 +29436,23 @@ var MineRenderWorld = class {
|
|
|
29215
29436
|
this.sectionModels?.clear();
|
|
29216
29437
|
}
|
|
29217
29438
|
updateCulling(positions) {
|
|
29439
|
+
const neighbor = new import_three40.Vector3();
|
|
29218
29440
|
for (const pos of positions) {
|
|
29219
|
-
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
|
|
29223
|
-
}
|
|
29441
|
+
const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
|
|
29442
|
+
const chunk = this._chunks.get(`${cx}_${cy}_${cz}`);
|
|
29443
|
+
const lx = pos.x - cx * 16, ly = pos.y - cy * 16, lz = pos.z - cz * 16;
|
|
29224
29444
|
for (let y = -1; y <= 1; y++) {
|
|
29225
29445
|
for (let z = -1; z <= 1; z++) {
|
|
29226
29446
|
for (let x = -1; x <= 1; x++) {
|
|
29227
|
-
const
|
|
29228
|
-
|
|
29229
|
-
|
|
29230
|
-
|
|
29447
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29448
|
+
const section = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16 ? chunk : this._chunks.get(`${cx + Math.floor(nx / 16)}_${cy + Math.floor(ny / 16)}_${cz + Math.floor(nz / 16)}`);
|
|
29449
|
+
if (!section) continue;
|
|
29450
|
+
const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29451
|
+
let pending = this.pendingCulling.get(section);
|
|
29452
|
+
if (pending?.has(index)) continue;
|
|
29453
|
+
if (Math.abs(x) + Math.abs(y) + Math.abs(z) > 1 && !section.getBlockAt(neighbor.set(pos.x + x, pos.y + y, pos.z + z))?.object?.fluidKind) continue;
|
|
29454
|
+
if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
|
|
29455
|
+
pending.add(index);
|
|
29231
29456
|
}
|
|
29232
29457
|
}
|
|
29233
29458
|
}
|
|
@@ -29235,39 +29460,38 @@ var MineRenderWorld = class {
|
|
|
29235
29460
|
return this.culling ??= Promise.resolve().then(async () => {
|
|
29236
29461
|
try {
|
|
29237
29462
|
while (this.pendingCulling.size) {
|
|
29238
|
-
const batch = [...this.pendingCulling
|
|
29463
|
+
const batch = [...this.pendingCulling];
|
|
29239
29464
|
this.pendingCulling.clear();
|
|
29240
|
-
const
|
|
29241
|
-
|
|
29242
|
-
|
|
29243
|
-
|
|
29244
|
-
|
|
29245
|
-
|
|
29246
|
-
|
|
29247
|
-
|
|
29248
|
-
|
|
29249
|
-
|
|
29250
|
-
|
|
29251
|
-
|
|
29252
|
-
|
|
29253
|
-
|
|
29254
|
-
|
|
29255
|
-
|
|
29256
|
-
|
|
29257
|
-
|
|
29258
|
-
|
|
29259
|
-
|
|
29260
|
-
|
|
29261
|
-
|
|
29262
|
-
|
|
29263
|
-
|
|
29264
|
-
|
|
29265
|
-
mask |= 1 << face;
|
|
29465
|
+
for (const [chunk, indices] of batch) {
|
|
29466
|
+
for (const index of indices) {
|
|
29467
|
+
if (!chunk.isBlockVisibleIndex(index)) continue;
|
|
29468
|
+
const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
|
|
29469
|
+
const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
|
|
29470
|
+
const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
|
|
29471
|
+
if (block.object?.fluidKind) {
|
|
29472
|
+
await block.object.updateFluid((x, y, z) => {
|
|
29473
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29474
|
+
const section = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16 ? chunk : this._chunks.get(`${chunk.x + Math.floor(nx / 16)}_${chunk.y + Math.floor(ny / 16)}_${chunk.z + Math.floor(nz / 16)}`);
|
|
29475
|
+
const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29476
|
+
const object3 = section?.isBlockVisibleIndex(neighborIndex) ? section.getBlockAt(neighbor.set(wx + x, wy + y, wz + z))?.object : void 0;
|
|
29477
|
+
return {
|
|
29478
|
+
fluid: object3?.fluidKind,
|
|
29479
|
+
level: object3?.fluidLevel,
|
|
29480
|
+
solid: section?.isOccludingIndex(neighborIndex) ?? false
|
|
29481
|
+
};
|
|
29482
|
+
});
|
|
29483
|
+
}
|
|
29484
|
+
let mask = 0;
|
|
29485
|
+
for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
|
|
29486
|
+
const offset = CUBE_FACE_OFFSETS[face];
|
|
29487
|
+
const nx = lx + offset[0], ny = ly + offset[1], nz = lz + offset[2];
|
|
29488
|
+
const section = nx >= 0 && nx < 16 && ny >= 0 && ny < 16 && nz >= 0 && nz < 16 ? chunk : this._chunks.get(`${chunk.x + Math.floor(nx / 16)}_${chunk.y + Math.floor(ny / 16)}_${chunk.z + Math.floor(nz / 16)}`);
|
|
29489
|
+
if (section?.isOccludingIndex((ny & 15) * 256 + (nz & 15) * 16 + (nx & 15))) mask |= 1 << face;
|
|
29266
29490
|
}
|
|
29491
|
+
await chunk.setCullMaskIndex(index, mask);
|
|
29267
29492
|
}
|
|
29268
|
-
await chunk.setCullMaskAt(pos, mask);
|
|
29269
29493
|
}
|
|
29270
|
-
|
|
29494
|
+
await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
|
|
29271
29495
|
}
|
|
29272
29496
|
} finally {
|
|
29273
29497
|
this.culling = void 0;
|
|
@@ -29329,7 +29553,229 @@ init_Model();
|
|
|
29329
29553
|
// src/world/SectionMesh.ts
|
|
29330
29554
|
var import_three41 = require("three");
|
|
29331
29555
|
init_CanvasCompat();
|
|
29556
|
+
init_CubeFace();
|
|
29332
29557
|
init_Materials();
|
|
29558
|
+
|
|
29559
|
+
// src/world/SectionGeometry.ts
|
|
29560
|
+
init_CubeFace();
|
|
29561
|
+
function buildSectionGeometry(input) {
|
|
29562
|
+
const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize } = input;
|
|
29563
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29564
|
+
if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
|
|
29565
|
+
const visible = [];
|
|
29566
|
+
const visibleAtlases = /* @__PURE__ */ new Set();
|
|
29567
|
+
for (let entry = 0; entry < count; entry++) {
|
|
29568
|
+
const template = templateData[templates[entry]];
|
|
29569
|
+
if (!template.cullFaces.some((direction) => !(cullMasks[entry] & direction))) continue;
|
|
29570
|
+
visible.push(entry);
|
|
29571
|
+
visibleAtlases.add(template.atlas);
|
|
29572
|
+
}
|
|
29573
|
+
const pages = [];
|
|
29574
|
+
const locations = /* @__PURE__ */ new Map();
|
|
29575
|
+
for (const atlas of [...visibleAtlases].sort((a, b) => atlases[b].height - atlases[a].height)) {
|
|
29576
|
+
const { width, height } = atlases[atlas];
|
|
29577
|
+
if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
|
|
29578
|
+
let selected = -1;
|
|
29579
|
+
for (let i = 0; i < pages.length; i++) {
|
|
29580
|
+
const page2 = pages[i];
|
|
29581
|
+
const nextRow = page2.x + width > maxAtlasSize;
|
|
29582
|
+
if ((nextRow ? page2.y + page2.rowHeight : page2.y) + height > maxAtlasSize) continue;
|
|
29583
|
+
if (nextRow) {
|
|
29584
|
+
page2.y += page2.rowHeight;
|
|
29585
|
+
page2.x = 0;
|
|
29586
|
+
page2.rowHeight = 0;
|
|
29587
|
+
}
|
|
29588
|
+
selected = i;
|
|
29589
|
+
break;
|
|
29590
|
+
}
|
|
29591
|
+
if (selected < 0) {
|
|
29592
|
+
selected = pages.length;
|
|
29593
|
+
pages.push({ placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 });
|
|
29594
|
+
}
|
|
29595
|
+
const page = pages[selected];
|
|
29596
|
+
page.placements.push({ atlas, x: page.x, y: page.y });
|
|
29597
|
+
locations.set(atlas, { page: selected, x: page.x, y: page.y });
|
|
29598
|
+
page.x += width;
|
|
29599
|
+
page.rowHeight = Math.max(page.rowHeight, height);
|
|
29600
|
+
page.width = Math.max(page.width, page.x);
|
|
29601
|
+
page.height = Math.max(page.height, page.y + height);
|
|
29602
|
+
}
|
|
29603
|
+
for (const entry of visible) pages[locations.get(templateData[templates[entry]].atlas).page].entries.push(entry);
|
|
29604
|
+
return pages.map((page) => {
|
|
29605
|
+
let faces = 0;
|
|
29606
|
+
for (const entry of page.entries) {
|
|
29607
|
+
const template = templateData[templates[entry]];
|
|
29608
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29609
|
+
if (!(cullMasks[entry] & template.cullFaces[face])) faces++;
|
|
29610
|
+
}
|
|
29611
|
+
}
|
|
29612
|
+
const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
|
|
29613
|
+
const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
|
|
29614
|
+
const outputIndices = new Uint32Array(faces * 6);
|
|
29615
|
+
let vertex = 0, indexOffset = 0;
|
|
29616
|
+
for (const entry of page.entries) {
|
|
29617
|
+
const template = templateData[templates[entry]];
|
|
29618
|
+
const atlas = atlases[template.atlas];
|
|
29619
|
+
const placement = locations.get(template.atlas);
|
|
29620
|
+
const mapU = (u) => (placement.x + u * atlas.width) / page.width;
|
|
29621
|
+
const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
|
|
29622
|
+
const offsetX = indices[entry] % 16 * 16;
|
|
29623
|
+
const offsetY = Math.floor(indices[entry] / 256) * 16;
|
|
29624
|
+
const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
|
|
29625
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29626
|
+
if (cullMasks[entry] & template.cullFaces[face]) continue;
|
|
29627
|
+
const start = vertex;
|
|
29628
|
+
for (let corner = 0; corner < 4; corner++, vertex++) {
|
|
29629
|
+
const source = face * 4 + corner;
|
|
29630
|
+
positions[vertex * 3] = template.positions[source * 3] + offsetX;
|
|
29631
|
+
positions[vertex * 3 + 1] = template.positions[source * 3 + 1] + offsetY;
|
|
29632
|
+
positions[vertex * 3 + 2] = template.positions[source * 3 + 2] + offsetZ;
|
|
29633
|
+
normals[vertex * 3] = template.normals[source * 3];
|
|
29634
|
+
normals[vertex * 3 + 1] = template.normals[source * 3 + 1];
|
|
29635
|
+
normals[vertex * 3 + 2] = template.normals[source * 3 + 2];
|
|
29636
|
+
uvs[vertex * 2] = mapU(template.uvs[source * 2]);
|
|
29637
|
+
uvs[vertex * 2 + 1] = mapV(template.uvs[source * 2 + 1]);
|
|
29638
|
+
uvBounds[vertex * 4] = mapU(template.uvBounds[source * 4]);
|
|
29639
|
+
uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
|
|
29640
|
+
uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
|
|
29641
|
+
uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
|
|
29642
|
+
colors[vertex * 3] = template.colors[source * 3];
|
|
29643
|
+
colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
|
|
29644
|
+
colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
|
|
29645
|
+
}
|
|
29646
|
+
for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[face * 6 + corner] - face * 4;
|
|
29647
|
+
}
|
|
29648
|
+
}
|
|
29649
|
+
return { width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
|
|
29650
|
+
});
|
|
29651
|
+
}
|
|
29652
|
+
function sectionGeometryTransferables(pages) {
|
|
29653
|
+
return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
|
|
29654
|
+
}
|
|
29655
|
+
|
|
29656
|
+
// src/world/SectionWorker.ts
|
|
29657
|
+
init_Env();
|
|
29658
|
+
var SectionWorker = class _SectionWorker {
|
|
29659
|
+
constructor(worker) {
|
|
29660
|
+
this.worker = worker;
|
|
29661
|
+
this.nextId = 0;
|
|
29662
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
29663
|
+
worker.addEventListener("message", (event) => {
|
|
29664
|
+
const reply = event.data;
|
|
29665
|
+
const pending = this.pending.get(reply.id);
|
|
29666
|
+
if (!pending) return;
|
|
29667
|
+
this.pending.delete(reply.id);
|
|
29668
|
+
if (reply.type === "pages") pending.resolve(reply.pages);
|
|
29669
|
+
else pending.reject(new Error(reply.message));
|
|
29670
|
+
});
|
|
29671
|
+
worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
|
|
29672
|
+
worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
|
|
29673
|
+
}
|
|
29674
|
+
static {
|
|
29675
|
+
this.initialized = false;
|
|
29676
|
+
}
|
|
29677
|
+
/** The shared worker, or undefined when the environment has none or a previous worker failed. */
|
|
29678
|
+
static shared() {
|
|
29679
|
+
if (!this.initialized) {
|
|
29680
|
+
this.initialized = true;
|
|
29681
|
+
const worker = Env.provider.createWorker?.("section");
|
|
29682
|
+
if (worker) this.instance = new _SectionWorker(worker);
|
|
29683
|
+
}
|
|
29684
|
+
return this.instance;
|
|
29685
|
+
}
|
|
29686
|
+
/** Builds geometry without transferring the input buffers away from the caller. */
|
|
29687
|
+
build(input) {
|
|
29688
|
+
if (!this.worker) return Promise.reject(new Error("Section worker terminated"));
|
|
29689
|
+
return new Promise((resolve, reject) => {
|
|
29690
|
+
const id = this.nextId++;
|
|
29691
|
+
this.pending.set(id, { resolve, reject });
|
|
29692
|
+
this.worker.postMessage({ id, type: "build", input });
|
|
29693
|
+
});
|
|
29694
|
+
}
|
|
29695
|
+
/** Rejects pending builds, terminates the worker, and makes shared() return undefined. */
|
|
29696
|
+
terminate() {
|
|
29697
|
+
this.fail("Section worker terminated");
|
|
29698
|
+
}
|
|
29699
|
+
fail(message) {
|
|
29700
|
+
const error = new Error(message);
|
|
29701
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
29702
|
+
this.pending.clear();
|
|
29703
|
+
this.worker?.terminate();
|
|
29704
|
+
this.worker = void 0;
|
|
29705
|
+
_SectionWorker.instance = void 0;
|
|
29706
|
+
}
|
|
29707
|
+
};
|
|
29708
|
+
|
|
29709
|
+
// src/world/SectionMesh.ts
|
|
29710
|
+
var templateCache = /* @__PURE__ */ new WeakMap();
|
|
29711
|
+
function toInput(entries, maxAtlasSize) {
|
|
29712
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29713
|
+
const atlases = [];
|
|
29714
|
+
const atlasIds = /* @__PURE__ */ new Map();
|
|
29715
|
+
const templateIds = /* @__PURE__ */ new Map();
|
|
29716
|
+
const input = {
|
|
29717
|
+
count: entries.length,
|
|
29718
|
+
indices: new Uint16Array(entries.length),
|
|
29719
|
+
templates: new Uint16Array(entries.length),
|
|
29720
|
+
cullMasks: new Uint8Array(entries.length),
|
|
29721
|
+
templateData: [],
|
|
29722
|
+
atlases: [],
|
|
29723
|
+
maxAtlasSize
|
|
29724
|
+
};
|
|
29725
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29726
|
+
const { index, template, cullMask } = entries[i];
|
|
29727
|
+
let id = templateIds.get(template);
|
|
29728
|
+
if (id === void 0) {
|
|
29729
|
+
let atlas = atlasIds.get(template.atlas);
|
|
29730
|
+
if (atlas === void 0) {
|
|
29731
|
+
atlas = atlases.length;
|
|
29732
|
+
atlasIds.set(template.atlas, atlas);
|
|
29733
|
+
atlases.push(template.atlas);
|
|
29734
|
+
input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height });
|
|
29735
|
+
}
|
|
29736
|
+
let data = templateCache.get(template);
|
|
29737
|
+
if (!data) {
|
|
29738
|
+
data = {
|
|
29739
|
+
positions: new Float32Array(72),
|
|
29740
|
+
normals: new Float32Array(72),
|
|
29741
|
+
uvs: new Float32Array(48),
|
|
29742
|
+
uvBounds: new Float32Array(96),
|
|
29743
|
+
colors: new Float32Array(72),
|
|
29744
|
+
indices: new Uint16Array(36),
|
|
29745
|
+
cullFaces: new Uint8Array(template.cullFaces),
|
|
29746
|
+
atlas: 0
|
|
29747
|
+
};
|
|
29748
|
+
const position = template.geometry.getAttribute("position");
|
|
29749
|
+
const normal = template.geometry.getAttribute("normal");
|
|
29750
|
+
const uv = template.geometry.getAttribute("uv");
|
|
29751
|
+
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29752
|
+
const color2 = template.geometry.getAttribute("color");
|
|
29753
|
+
for (let vertex = 0; vertex < CUBE_FACES.length * 4; vertex++) {
|
|
29754
|
+
data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
|
|
29755
|
+
data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
|
|
29756
|
+
data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
|
|
29757
|
+
data.uvBounds.set([
|
|
29758
|
+
bounds?.getX(vertex) ?? 0,
|
|
29759
|
+
bounds?.getY(vertex) ?? 0,
|
|
29760
|
+
bounds?.getZ(vertex) ?? 1,
|
|
29761
|
+
bounds?.getW(vertex) ?? 1
|
|
29762
|
+
], vertex * 4);
|
|
29763
|
+
data.colors.set([color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1], vertex * 3);
|
|
29764
|
+
}
|
|
29765
|
+
const indices = template.geometry.getIndex();
|
|
29766
|
+
for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2);
|
|
29767
|
+
templateCache.set(template, data);
|
|
29768
|
+
}
|
|
29769
|
+
id = input.templateData.length;
|
|
29770
|
+
templateIds.set(template, id);
|
|
29771
|
+
input.templateData.push({ ...data, atlas });
|
|
29772
|
+
}
|
|
29773
|
+
input.indices[i] = index;
|
|
29774
|
+
input.templates[i] = id;
|
|
29775
|
+
input.cullMasks[i] = cullMask;
|
|
29776
|
+
}
|
|
29777
|
+
return { input, atlases };
|
|
29778
|
+
}
|
|
29333
29779
|
var SectionMesh = class _SectionMesh extends import_three41.Group {
|
|
29334
29780
|
constructor() {
|
|
29335
29781
|
super(...arguments);
|
|
@@ -29337,86 +29783,31 @@ var SectionMesh = class _SectionMesh extends import_three41.Group {
|
|
|
29337
29783
|
}
|
|
29338
29784
|
/** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
|
|
29339
29785
|
static build(entries, maxAtlasSize = 2048) {
|
|
29340
|
-
|
|
29341
|
-
|
|
29342
|
-
|
|
29343
|
-
|
|
29344
|
-
|
|
29345
|
-
|
|
29346
|
-
|
|
29347
|
-
|
|
29348
|
-
|
|
29349
|
-
|
|
29350
|
-
|
|
29351
|
-
if ((nextRow ? page.y + page.rowHeight : page.y) + height > maxAtlasSize) continue;
|
|
29352
|
-
if (nextRow) {
|
|
29353
|
-
page.y += page.rowHeight;
|
|
29354
|
-
page.x = 0;
|
|
29355
|
-
page.rowHeight = 0;
|
|
29356
|
-
}
|
|
29357
|
-
selected = page;
|
|
29358
|
-
break;
|
|
29359
|
-
}
|
|
29360
|
-
if (!selected) {
|
|
29361
|
-
selected = { placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 };
|
|
29362
|
-
pages.push(selected);
|
|
29363
|
-
}
|
|
29364
|
-
const placement = { atlas, x: selected.x, y: selected.y };
|
|
29365
|
-
selected.placements.push(placement);
|
|
29366
|
-
selected.x += width;
|
|
29367
|
-
selected.rowHeight = Math.max(selected.rowHeight, height);
|
|
29368
|
-
selected.width = Math.max(selected.width, selected.x);
|
|
29369
|
-
selected.height = Math.max(selected.height, selected.y + height);
|
|
29370
|
-
locations.set(atlas, { page: selected, placement });
|
|
29371
|
-
}
|
|
29372
|
-
for (const entry of visible) locations.get(entry.template.atlas).page.entries.push(entry);
|
|
29786
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29787
|
+
return this.fromPages(buildSectionGeometry(input), atlases);
|
|
29788
|
+
}
|
|
29789
|
+
/** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
|
|
29790
|
+
static async buildAsync(entries, maxAtlasSize = 2048) {
|
|
29791
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29792
|
+
const worker = SectionWorker.shared();
|
|
29793
|
+
const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
|
|
29794
|
+
return this.fromPages(pages, atlases);
|
|
29795
|
+
}
|
|
29796
|
+
static fromPages(pages, atlases) {
|
|
29373
29797
|
const section = new _SectionMesh();
|
|
29374
29798
|
for (const page of pages) {
|
|
29375
29799
|
const canvas = createCanvas2(page.width, page.height);
|
|
29376
29800
|
const context = canvas.getContext("2d");
|
|
29377
29801
|
for (const { atlas, x, y } of page.placements) {
|
|
29378
|
-
context.drawImage(atlas.image.canvas, x, y);
|
|
29379
|
-
}
|
|
29380
|
-
const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
|
|
29381
|
-
for (const { index, template, cullMask } of page.entries) {
|
|
29382
|
-
const position = template.geometry.getAttribute("position");
|
|
29383
|
-
const normal = template.geometry.getAttribute("normal");
|
|
29384
|
-
const uv = template.geometry.getAttribute("uv");
|
|
29385
|
-
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29386
|
-
const color2 = template.geometry.getAttribute("color");
|
|
29387
|
-
const sourceIndices = template.geometry.getIndex();
|
|
29388
|
-
const placement = locations.get(template.atlas).placement;
|
|
29389
|
-
const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
|
|
29390
|
-
const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
|
|
29391
|
-
const offsetX = index % 16 * 16;
|
|
29392
|
-
const offsetY = Math.floor(index / 256) * 16;
|
|
29393
|
-
const offsetZ = Math.floor(index / 16) % 16 * 16;
|
|
29394
|
-
for (let face = 0; face < 6; face++) {
|
|
29395
|
-
if (cullMask & template.cullFaces[face]) continue;
|
|
29396
|
-
const start = positions.length / 3;
|
|
29397
|
-
for (let corner = 0; corner < 4; corner++) {
|
|
29398
|
-
const vertex = face * 4 + corner;
|
|
29399
|
-
positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
|
|
29400
|
-
normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
|
|
29401
|
-
uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
|
|
29402
|
-
uvBounds.push(
|
|
29403
|
-
mapU(bounds?.getX(vertex) ?? 0),
|
|
29404
|
-
mapV(bounds?.getY(vertex) ?? 0),
|
|
29405
|
-
mapU(bounds?.getZ(vertex) ?? 1),
|
|
29406
|
-
mapV(bounds?.getW(vertex) ?? 1)
|
|
29407
|
-
);
|
|
29408
|
-
colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
|
|
29409
|
-
}
|
|
29410
|
-
for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
|
|
29411
|
-
}
|
|
29802
|
+
context.drawImage(atlases[atlas].image.canvas, x, y);
|
|
29412
29803
|
}
|
|
29413
29804
|
const geometry = new import_three41.BufferGeometry();
|
|
29414
|
-
geometry.setAttribute("position", new import_three41.Float32BufferAttribute(positions, 3));
|
|
29415
|
-
geometry.setAttribute("normal", new import_three41.Float32BufferAttribute(normals, 3));
|
|
29416
|
-
geometry.setAttribute("uv", new import_three41.Float32BufferAttribute(uvs, 2));
|
|
29417
|
-
geometry.setAttribute("uvBounds", new import_three41.Float32BufferAttribute(uvBounds, 4));
|
|
29418
|
-
geometry.setAttribute("color", new import_three41.Float32BufferAttribute(colors, 3));
|
|
29419
|
-
geometry.setIndex(indices);
|
|
29805
|
+
geometry.setAttribute("position", new import_three41.Float32BufferAttribute(page.positions.buffer, 3));
|
|
29806
|
+
geometry.setAttribute("normal", new import_three41.Float32BufferAttribute(page.normals.buffer, 3));
|
|
29807
|
+
geometry.setAttribute("uv", new import_three41.Float32BufferAttribute(page.uvs.buffer, 2));
|
|
29808
|
+
geometry.setAttribute("uvBounds", new import_three41.Float32BufferAttribute(page.uvBounds.buffer, 4));
|
|
29809
|
+
geometry.setAttribute("color", new import_three41.Float32BufferAttribute(page.colors.buffer, 3));
|
|
29810
|
+
geometry.setIndex(new import_three41.Uint32BufferAttribute(page.indices.buffer, 1));
|
|
29420
29811
|
geometry.computeBoundingBox();
|
|
29421
29812
|
geometry.computeBoundingSphere();
|
|
29422
29813
|
const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
|
|
@@ -29450,7 +29841,9 @@ var Chunk = class _Chunk {
|
|
|
29450
29841
|
this.renderedBlocks = /* @__PURE__ */ new Map();
|
|
29451
29842
|
this.sectionBlocks = /* @__PURE__ */ new Map();
|
|
29452
29843
|
this.hiddenBlocks = /* @__PURE__ */ new Set();
|
|
29844
|
+
this.blockPosition = new import_three42.Vector3();
|
|
29453
29845
|
this.meshDirty = false;
|
|
29846
|
+
this.meshGeneration = 0;
|
|
29454
29847
|
this.scene = scene;
|
|
29455
29848
|
this.x = x;
|
|
29456
29849
|
this.y = y;
|
|
@@ -29463,7 +29856,7 @@ var Chunk = class _Chunk {
|
|
|
29463
29856
|
if (isTripleArray(posOrX)) {
|
|
29464
29857
|
return this.getBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]));
|
|
29465
29858
|
}
|
|
29466
|
-
const pos = this.
|
|
29859
|
+
const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
|
|
29467
29860
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29468
29861
|
return this.renderedBlocks.get(index);
|
|
29469
29862
|
}
|
|
@@ -29487,48 +29880,81 @@ var Chunk = class _Chunk {
|
|
|
29487
29880
|
worldPos = this.chunkPosToWorldPos(pos);
|
|
29488
29881
|
}
|
|
29489
29882
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29490
|
-
this.hiddenBlocks.delete(index);
|
|
29491
|
-
this.data.set(index, block);
|
|
29492
|
-
const readBlock = this.data.snapshot(index);
|
|
29493
|
-
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29494
|
-
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29495
|
-
this.renderedBlocks.delete(index);
|
|
29496
|
-
let object3;
|
|
29497
29883
|
try {
|
|
29498
|
-
|
|
29499
|
-
|
|
29500
|
-
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29501
|
-
if (!blockState) {
|
|
29502
|
-
this.data.set(index, void 0);
|
|
29503
|
-
return void 0;
|
|
29504
|
-
}
|
|
29505
|
-
const perBlock = !this.sectionModels || getFluidKind(blockState.key, stored.properties) || blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString());
|
|
29506
|
-
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29507
|
-
if (template) {
|
|
29508
|
-
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29509
|
-
this.meshDirty = true;
|
|
29510
|
-
} else {
|
|
29511
|
-
object3 = await this.scene.addBlock(blockState, {
|
|
29512
|
-
mergeMeshes: true,
|
|
29513
|
-
instanceMeshes: true,
|
|
29514
|
-
maxInstanceCount: 2e3,
|
|
29515
|
-
initialState: stored.properties
|
|
29516
|
-
});
|
|
29517
|
-
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29518
|
-
}
|
|
29519
|
-
const info = { get block() {
|
|
29520
|
-
return readBlock();
|
|
29521
|
-
}, object: object3 };
|
|
29522
|
-
this.renderedBlocks.set(index, info);
|
|
29523
|
-
return info;
|
|
29524
|
-
} catch (error) {
|
|
29525
|
-
this.data.set(index, void 0);
|
|
29526
|
-
object3?.removeFromScene();
|
|
29527
|
-
throw error;
|
|
29884
|
+
await this.placeBlocks([{ index, block }]);
|
|
29885
|
+
return this.renderedBlocks.get(index);
|
|
29528
29886
|
} finally {
|
|
29529
29887
|
await onBlocksChanged?.([worldPos]);
|
|
29530
29888
|
}
|
|
29531
29889
|
}
|
|
29890
|
+
/** Places cells in input order and returns changed world positions without refreshing neighbors. */
|
|
29891
|
+
async placeBlocks(blocks) {
|
|
29892
|
+
const resolutions = /* @__PURE__ */ new Map();
|
|
29893
|
+
for (const { block } of blocks) {
|
|
29894
|
+
if (!block || ChunkData.isAir(block)) continue;
|
|
29895
|
+
const key = ChunkData.paletteKey(block);
|
|
29896
|
+
if (resolutions.has(key)) continue;
|
|
29897
|
+
const stored = { type: block.type, properties: block.properties ? { ...block.properties } : void 0 };
|
|
29898
|
+
const resolved = (async () => {
|
|
29899
|
+
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29900
|
+
const perBlock = !blockState || !this.sectionModels || !!getFluidKind(blockState.key, stored.properties) || !!(blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString()));
|
|
29901
|
+
if (!perBlock) await this.sectionModels.get(blockState, stored.properties);
|
|
29902
|
+
return { blockState, perBlock };
|
|
29903
|
+
})();
|
|
29904
|
+
resolutions.set(key, resolved);
|
|
29905
|
+
void resolved.catch(() => void 0);
|
|
29906
|
+
}
|
|
29907
|
+
const positions = [];
|
|
29908
|
+
let failed = false, failure;
|
|
29909
|
+
for (const { index, block } of blocks) {
|
|
29910
|
+
this.hiddenBlocks.delete(index);
|
|
29911
|
+
this.data.set(index, block);
|
|
29912
|
+
const readBlock = this.data.snapshot(index);
|
|
29913
|
+
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29914
|
+
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29915
|
+
this.renderedBlocks.delete(index);
|
|
29916
|
+
const worldPos = new import_three42.Vector3(
|
|
29917
|
+
this.x * 16 + index % 16,
|
|
29918
|
+
this.y * 16 + Math.floor(index / 256),
|
|
29919
|
+
this.z * 16 + Math.floor(index / 16) % 16
|
|
29920
|
+
);
|
|
29921
|
+
positions.push(worldPos);
|
|
29922
|
+
let object3;
|
|
29923
|
+
try {
|
|
29924
|
+
if (!readBlock) continue;
|
|
29925
|
+
const stored = readBlock();
|
|
29926
|
+
const key = ChunkData.paletteKey(stored);
|
|
29927
|
+
const { blockState, perBlock } = await resolutions.get(key);
|
|
29928
|
+
if (!blockState) {
|
|
29929
|
+
this.data.set(index, void 0);
|
|
29930
|
+
continue;
|
|
29931
|
+
}
|
|
29932
|
+
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29933
|
+
if (template) {
|
|
29934
|
+
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29935
|
+
this.meshDirty = true;
|
|
29936
|
+
} else {
|
|
29937
|
+
object3 = await this.scene.addBlock(blockState, {
|
|
29938
|
+
mergeMeshes: true,
|
|
29939
|
+
instanceMeshes: true,
|
|
29940
|
+
maxInstanceCount: 2e3,
|
|
29941
|
+
initialState: stored.properties
|
|
29942
|
+
});
|
|
29943
|
+
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29944
|
+
}
|
|
29945
|
+
this.renderedBlocks.set(index, { get block() {
|
|
29946
|
+
return readBlock();
|
|
29947
|
+
}, object: object3 });
|
|
29948
|
+
} catch (error) {
|
|
29949
|
+
this.data.set(index, void 0);
|
|
29950
|
+
object3?.removeFromScene();
|
|
29951
|
+
if (!failed) failure = error;
|
|
29952
|
+
failed = true;
|
|
29953
|
+
}
|
|
29954
|
+
}
|
|
29955
|
+
if (failed) throw failure;
|
|
29956
|
+
return positions;
|
|
29957
|
+
}
|
|
29532
29958
|
/** Changes visibility at world block coordinates while preserving the block's data. */
|
|
29533
29959
|
async setBlockVisibleAt(pos, visible) {
|
|
29534
29960
|
const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
|
|
@@ -29549,15 +29975,24 @@ var Chunk = class _Chunk {
|
|
|
29549
29975
|
await this.onBlocksChanged?.([pos]);
|
|
29550
29976
|
}
|
|
29551
29977
|
isBlockVisibleAt(pos) {
|
|
29552
|
-
|
|
29978
|
+
return this.isBlockVisibleIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29979
|
+
}
|
|
29980
|
+
/** Reports whether a cell has a visible render object or section entry. */
|
|
29981
|
+
isBlockVisibleIndex(index) {
|
|
29553
29982
|
return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
|
|
29554
29983
|
}
|
|
29555
29984
|
isOccludingAt(pos) {
|
|
29556
|
-
|
|
29985
|
+
return this.isOccludingIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29986
|
+
}
|
|
29987
|
+
/** Reports whether a visible cell hides neighboring cube faces. */
|
|
29988
|
+
isOccludingIndex(index) {
|
|
29557
29989
|
return !this.hiddenBlocks.has(index) && (this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false));
|
|
29558
29990
|
}
|
|
29559
29991
|
async setCullMaskAt(pos, mask) {
|
|
29560
|
-
|
|
29992
|
+
await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
|
|
29993
|
+
}
|
|
29994
|
+
/** Updates hidden faces for a cell's section entry or individual render object. */
|
|
29995
|
+
async setCullMaskIndex(index, mask) {
|
|
29561
29996
|
const entry = this.sectionBlocks.get(index);
|
|
29562
29997
|
if (entry) {
|
|
29563
29998
|
if (entry.cullMask !== mask) {
|
|
@@ -29568,12 +30003,17 @@ var Chunk = class _Chunk {
|
|
|
29568
30003
|
await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
|
|
29569
30004
|
}
|
|
29570
30005
|
}
|
|
29571
|
-
|
|
30006
|
+
/** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
|
|
30007
|
+
async rebuildSectionMesh() {
|
|
29572
30008
|
if (!this.meshDirty) return;
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
) : void 0;
|
|
30009
|
+
this.meshDirty = false;
|
|
30010
|
+
const generation = ++this.meshGeneration;
|
|
30011
|
+
const entries = [...this.sectionBlocks.values()].filter((entry) => !this.hiddenBlocks.has(entry.index));
|
|
30012
|
+
const next = entries.length ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize) : void 0;
|
|
30013
|
+
if (generation !== this.meshGeneration) {
|
|
30014
|
+
next?.dispose();
|
|
30015
|
+
return;
|
|
30016
|
+
}
|
|
29577
30017
|
this.sectionMesh?.dispose();
|
|
29578
30018
|
this.sectionMesh = next;
|
|
29579
30019
|
if (next) {
|
|
@@ -29581,7 +30021,6 @@ var Chunk = class _Chunk {
|
|
|
29581
30021
|
this.scene.add(next);
|
|
29582
30022
|
}
|
|
29583
30023
|
this.scene.dirty = true;
|
|
29584
|
-
this.meshDirty = false;
|
|
29585
30024
|
}
|
|
29586
30025
|
/** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
|
|
29587
30026
|
async clear(onBlocksChanged = this.onBlocksChanged) {
|
|
@@ -29590,6 +30029,7 @@ var Chunk = class _Chunk {
|
|
|
29590
30029
|
));
|
|
29591
30030
|
for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
|
|
29592
30031
|
this.sectionBlocks.clear();
|
|
30032
|
+
this.meshGeneration++;
|
|
29593
30033
|
this.sectionMesh?.dispose();
|
|
29594
30034
|
this.sectionMesh = void 0;
|
|
29595
30035
|
this.meshDirty = false;
|
|
@@ -29626,10 +30066,185 @@ var Chunk = class _Chunk {
|
|
|
29626
30066
|
);
|
|
29627
30067
|
}
|
|
29628
30068
|
};
|
|
30069
|
+
|
|
30070
|
+
// src/world/WorldStreamer.ts
|
|
30071
|
+
var WorldStreamer = class {
|
|
30072
|
+
constructor(world, source, options = {}) {
|
|
30073
|
+
this.world = world;
|
|
30074
|
+
this.source = source;
|
|
30075
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
30076
|
+
this.owned = /* @__PURE__ */ new Map();
|
|
30077
|
+
this.missing = /* @__PURE__ */ new Map();
|
|
30078
|
+
this.failed = /* @__PURE__ */ new Map();
|
|
30079
|
+
this.desired = [];
|
|
30080
|
+
this.disposed = false;
|
|
30081
|
+
this.loadRadius = options.loadRadius ?? 2;
|
|
30082
|
+
this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
|
|
30083
|
+
if (![this.loadRadius, this.unloadRadius].every((value) => Number.isInteger(value) && value >= 0 && value <= 32) || this.unloadRadius < this.loadRadius) {
|
|
30084
|
+
throw new RangeError("Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius");
|
|
30085
|
+
}
|
|
30086
|
+
}
|
|
30087
|
+
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
30088
|
+
get loadedChunks() {
|
|
30089
|
+
return [...this.loaded.values()].map((position) => ({ ...position }));
|
|
30090
|
+
}
|
|
30091
|
+
/** Returns source failures retained within the unload radius. Failed columns do not block other loads. */
|
|
30092
|
+
get failedChunks() {
|
|
30093
|
+
return [...this.failed.values()].map((failure) => ({ ...failure }));
|
|
30094
|
+
}
|
|
30095
|
+
/** Counts desired columns still waiting for a source read or placement, including the active column. */
|
|
30096
|
+
get pendingChunks() {
|
|
30097
|
+
return this.desired.filter((position) => this.needsLoad(position)).length;
|
|
30098
|
+
}
|
|
30099
|
+
/**
|
|
30100
|
+
* Updates the center in absolute chunk coordinates. Repeated calls share the active drain.
|
|
30101
|
+
* Source failures are recorded in failedChunks; other columns continue loading. Missing and failed
|
|
30102
|
+
* columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.
|
|
30103
|
+
* Placement and unload failures reject the drain; call update again to retry those operations.
|
|
30104
|
+
* Reads outside the latest load radius receive an abort signal; placement finishes before unloading.
|
|
30105
|
+
* Sources should represent an unchanged world during streaming.
|
|
30106
|
+
*/
|
|
30107
|
+
async update(x, z) {
|
|
30108
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
30109
|
+
if (![x, z].every((value) => Number.isSafeInteger(value) && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {
|
|
30110
|
+
throw new RangeError("Chunk coordinates must be safe integers within the streaming radius");
|
|
30111
|
+
}
|
|
30112
|
+
if (!this.center || this.center.x !== x || this.center.z !== z) {
|
|
30113
|
+
this.center = { x, z };
|
|
30114
|
+
this.desired = [];
|
|
30115
|
+
for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {
|
|
30116
|
+
for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {
|
|
30117
|
+
this.desired.push({ x: x + dx, z: z + dz });
|
|
30118
|
+
}
|
|
30119
|
+
}
|
|
30120
|
+
this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);
|
|
30121
|
+
if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
|
|
30122
|
+
this.loading.controller.abort();
|
|
30123
|
+
}
|
|
30124
|
+
}
|
|
30125
|
+
return this.running ??= Promise.resolve().then(() => this.drain());
|
|
30126
|
+
}
|
|
30127
|
+
/** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */
|
|
30128
|
+
async updatePosition(position) {
|
|
30129
|
+
if (![position.x, position.z].every(Number.isFinite)) throw new RangeError("Scene coordinates must be finite");
|
|
30130
|
+
return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));
|
|
30131
|
+
}
|
|
30132
|
+
/** Forgets recorded source failures and retries desired columns at the latest center. */
|
|
30133
|
+
async retryFailedChunks() {
|
|
30134
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
30135
|
+
this.failed.clear();
|
|
30136
|
+
if (this.center) await this.update(this.center.x, this.center.z);
|
|
30137
|
+
}
|
|
30138
|
+
/** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
|
|
30139
|
+
dispose() {
|
|
30140
|
+
this.disposed = true;
|
|
30141
|
+
this.desired = [];
|
|
30142
|
+
this.center = void 0;
|
|
30143
|
+
this.loading?.controller.abort();
|
|
30144
|
+
return this.disposal ??= Promise.resolve().then(async () => {
|
|
30145
|
+
await this.running?.catch(() => void 0);
|
|
30146
|
+
let failure;
|
|
30147
|
+
let failed = false;
|
|
30148
|
+
for (const position of this.owned.values()) {
|
|
30149
|
+
try {
|
|
30150
|
+
await this.unload(position);
|
|
30151
|
+
} catch (error) {
|
|
30152
|
+
failed = true;
|
|
30153
|
+
failure ??= error;
|
|
30154
|
+
}
|
|
30155
|
+
}
|
|
30156
|
+
this.missing.clear();
|
|
30157
|
+
this.failed.clear();
|
|
30158
|
+
if (failed) throw failure;
|
|
30159
|
+
}).catch((error) => {
|
|
30160
|
+
this.disposal = void 0;
|
|
30161
|
+
throw error;
|
|
30162
|
+
});
|
|
30163
|
+
}
|
|
30164
|
+
async drain() {
|
|
30165
|
+
try {
|
|
30166
|
+
while (!this.disposed) {
|
|
30167
|
+
for (const [key, position2] of this.missing) {
|
|
30168
|
+
if (!this.within(position2, this.unloadRadius)) this.missing.delete(key);
|
|
30169
|
+
}
|
|
30170
|
+
for (const [key, position2] of this.failed) {
|
|
30171
|
+
if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
|
|
30172
|
+
}
|
|
30173
|
+
const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
|
|
30174
|
+
if (obsolete) {
|
|
30175
|
+
await this.unload(obsolete);
|
|
30176
|
+
continue;
|
|
30177
|
+
}
|
|
30178
|
+
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
30179
|
+
if (!position) return;
|
|
30180
|
+
let chunk;
|
|
30181
|
+
const controller = new AbortController();
|
|
30182
|
+
this.loading = { position, controller };
|
|
30183
|
+
try {
|
|
30184
|
+
chunk = await this.source.getChunk(position.x, position.z, controller.signal);
|
|
30185
|
+
if (chunk) this.validateChunk(chunk, position);
|
|
30186
|
+
} catch (error) {
|
|
30187
|
+
if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {
|
|
30188
|
+
this.failed.set(this.key(position), { ...position, error });
|
|
30189
|
+
}
|
|
30190
|
+
continue;
|
|
30191
|
+
} finally {
|
|
30192
|
+
this.loading = void 0;
|
|
30193
|
+
}
|
|
30194
|
+
if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;
|
|
30195
|
+
if (!chunk) {
|
|
30196
|
+
this.missing.set(this.key(position), position);
|
|
30197
|
+
continue;
|
|
30198
|
+
}
|
|
30199
|
+
this.owned.set(this.key(position), position);
|
|
30200
|
+
try {
|
|
30201
|
+
await this.world.placeChunk(chunk);
|
|
30202
|
+
this.loaded.set(this.key(position), position);
|
|
30203
|
+
} catch (error) {
|
|
30204
|
+
try {
|
|
30205
|
+
await this.unload(position);
|
|
30206
|
+
} catch (cleanupError) {
|
|
30207
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
30208
|
+
try {
|
|
30209
|
+
Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
|
|
30210
|
+
} catch {
|
|
30211
|
+
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
30212
|
+
}
|
|
30213
|
+
throw failure;
|
|
30214
|
+
}
|
|
30215
|
+
throw error;
|
|
30216
|
+
}
|
|
30217
|
+
}
|
|
30218
|
+
} finally {
|
|
30219
|
+
this.running = void 0;
|
|
30220
|
+
}
|
|
30221
|
+
}
|
|
30222
|
+
needsLoad(position) {
|
|
30223
|
+
const key = this.key(position);
|
|
30224
|
+
return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
|
|
30225
|
+
}
|
|
30226
|
+
async unload(position) {
|
|
30227
|
+
await this.world.unloadChunkColumn(position.x, position.z);
|
|
30228
|
+
this.owned.delete(this.key(position));
|
|
30229
|
+
this.loaded.delete(this.key(position));
|
|
30230
|
+
}
|
|
30231
|
+
validateChunk(chunk, position) {
|
|
30232
|
+
if (chunk.x !== position.x || chunk.z !== position.z) {
|
|
30233
|
+
throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);
|
|
30234
|
+
}
|
|
30235
|
+
}
|
|
30236
|
+
within(position, radius) {
|
|
30237
|
+
return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;
|
|
30238
|
+
}
|
|
30239
|
+
key(position) {
|
|
30240
|
+
return `${position.x},${position.z}`;
|
|
30241
|
+
}
|
|
30242
|
+
};
|
|
29629
30243
|
// Annotate the CommonJS export names for ESM import in node:
|
|
29630
30244
|
0 && (module.exports = {
|
|
29631
30245
|
AXES,
|
|
29632
30246
|
AnvilParser,
|
|
30247
|
+
AnvilWorldSource,
|
|
29633
30248
|
ArchiveAssetSource,
|
|
29634
30249
|
AssetKey,
|
|
29635
30250
|
AssetLoadError,
|
|
@@ -29719,6 +30334,7 @@ var Chunk = class _Chunk {
|
|
|
29719
30334
|
SchematicParser,
|
|
29720
30335
|
SectionMesh,
|
|
29721
30336
|
SectionModels,
|
|
30337
|
+
SectionWorker,
|
|
29722
30338
|
SkinImage,
|
|
29723
30339
|
SkinObject,
|
|
29724
30340
|
SkinPart,
|
|
@@ -29733,6 +30349,7 @@ var Chunk = class _Chunk {
|
|
|
29733
30349
|
TintColors,
|
|
29734
30350
|
UVMapper,
|
|
29735
30351
|
VideoExporter,
|
|
30352
|
+
WorldStreamer,
|
|
29736
30353
|
WrappedImage,
|
|
29737
30354
|
addBox3WireframeToObject,
|
|
29738
30355
|
addWireframeToMesh,
|
|
@@ -29742,6 +30359,7 @@ var Chunk = class _Chunk {
|
|
|
29742
30359
|
axisToVec3,
|
|
29743
30360
|
base64decode,
|
|
29744
30361
|
base64encode,
|
|
30362
|
+
buildSectionGeometry,
|
|
29745
30363
|
capeTextureSizes,
|
|
29746
30364
|
captureException,
|
|
29747
30365
|
changeEvent,
|
|
@@ -29793,6 +30411,7 @@ var Chunk = class _Chunk {
|
|
|
29793
30411
|
sampleEntityAnimation,
|
|
29794
30412
|
sampleEntityKeyframes,
|
|
29795
30413
|
sampleFluid,
|
|
30414
|
+
sectionGeometryTransferables,
|
|
29796
30415
|
serializeBoxGeometryKey,
|
|
29797
30416
|
serializeImageKey,
|
|
29798
30417
|
serializeMaterialKey,
|
|
@@ -29810,6 +30429,7 @@ var Chunk = class _Chunk {
|
|
|
29810
30429
|
toDegrees,
|
|
29811
30430
|
toRadians,
|
|
29812
30431
|
toTrianglesDrawMode,
|
|
29813
|
-
trimCanvas
|
|
30432
|
+
trimCanvas,
|
|
30433
|
+
yieldToEventLoop
|
|
29814
30434
|
});
|
|
29815
30435
|
//# sourceMappingURL=index.js.map
|