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.mjs
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
|
}
|
|
@@ -28557,6 +28574,16 @@ init_NBTHelper();
|
|
|
28557
28574
|
|
|
28558
28575
|
// src/world/ChunkData.ts
|
|
28559
28576
|
init_AssetKey();
|
|
28577
|
+
var blockKeys = /* @__PURE__ */ new Map();
|
|
28578
|
+
function blockKey(type) {
|
|
28579
|
+
let key = blockKeys.get(type);
|
|
28580
|
+
if (!key) {
|
|
28581
|
+
key = AssetKey.parse("blockstates", type);
|
|
28582
|
+
if (blockKeys.size >= 4096) blockKeys.clear();
|
|
28583
|
+
blockKeys.set(type, key);
|
|
28584
|
+
}
|
|
28585
|
+
return key;
|
|
28586
|
+
}
|
|
28560
28587
|
var ChunkData = class _ChunkData {
|
|
28561
28588
|
constructor() {
|
|
28562
28589
|
this.indices = new Uint16Array(4096);
|
|
@@ -28584,10 +28611,10 @@ var ChunkData = class _ChunkData {
|
|
|
28584
28611
|
let key;
|
|
28585
28612
|
let nbt;
|
|
28586
28613
|
if (block && !_ChunkData.isAir(block)) {
|
|
28587
|
-
const type =
|
|
28614
|
+
const type = blockKey(block.type).toNamespacedString();
|
|
28588
28615
|
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28589
28616
|
state = Object.freeze(Object.keys(properties).length ? { type, properties: Object.freeze(properties) } : { type });
|
|
28590
|
-
key =
|
|
28617
|
+
key = _ChunkData.paletteKey(block);
|
|
28591
28618
|
if (block.nbt !== void 0) nbt = structuredClone(block.nbt);
|
|
28592
28619
|
}
|
|
28593
28620
|
const previousId = this.indices[index];
|
|
@@ -28624,9 +28651,14 @@ var ChunkData = class _ChunkData {
|
|
|
28624
28651
|
}
|
|
28625
28652
|
static isAir(block) {
|
|
28626
28653
|
if (typeof block === "undefined" || typeof block.type === "undefined") return true;
|
|
28627
|
-
const key =
|
|
28654
|
+
const key = blockKey(block.type);
|
|
28628
28655
|
return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
|
|
28629
28656
|
}
|
|
28657
|
+
/** Identifies a normalized block type and its sorted properties, excluding per-cell NBT. */
|
|
28658
|
+
static paletteKey(block) {
|
|
28659
|
+
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28660
|
+
return JSON.stringify([blockKey(block.type).toNamespacedString(), properties]);
|
|
28661
|
+
}
|
|
28630
28662
|
static copyBlock(state, nbt) {
|
|
28631
28663
|
const block = { type: state.type };
|
|
28632
28664
|
if (state.properties) block.properties = { ...state.properties };
|
|
@@ -28806,19 +28838,150 @@ var AnvilParser = class {
|
|
|
28806
28838
|
const perLong = Math.floor(64 / bits);
|
|
28807
28839
|
const expected = padded ? Math.ceil(4096 / perLong) : Math.ceil(4096 * bits / 64);
|
|
28808
28840
|
if (tag.value.length !== expected) throw new MineRenderError(`Invalid Anvil block-state array length: expected ${expected}`);
|
|
28809
|
-
const words = tag.value.map(([high, low]) =>
|
|
28810
|
-
const mask = (
|
|
28841
|
+
const words = tag.value.map(([high, low]) => [high >>> 0, low >>> 0]);
|
|
28842
|
+
const mask = (1 << bits) - 1;
|
|
28811
28843
|
for (let index = 0; index < 4096; index++) {
|
|
28812
|
-
const
|
|
28813
|
-
const
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28844
|
+
const bitPos = index * bits;
|
|
28845
|
+
const word = padded ? Math.floor(index / perLong) : bitPos >>> 6;
|
|
28846
|
+
const shift = padded ? index % perLong * bits : bitPos & 63;
|
|
28847
|
+
const [high, low] = words[word];
|
|
28848
|
+
let value = shift < 32 ? low >>> shift : high >>> shift - 32;
|
|
28849
|
+
if (shift < 32 && shift + bits > 32) value += high * 2 ** (32 - shift);
|
|
28850
|
+
if (!padded && shift + bits > 64) value += words[word + 1][1] * 2 ** (64 - shift);
|
|
28851
|
+
indices[index] = value & mask;
|
|
28817
28852
|
}
|
|
28818
28853
|
return indices;
|
|
28819
28854
|
}
|
|
28820
28855
|
};
|
|
28821
28856
|
|
|
28857
|
+
// src/world/AnvilWorldSource.ts
|
|
28858
|
+
init_MineRenderError();
|
|
28859
|
+
async function abortable2(promise, signal) {
|
|
28860
|
+
if (!signal) return promise;
|
|
28861
|
+
let abort;
|
|
28862
|
+
const cancelled = new Promise((_, reject) => {
|
|
28863
|
+
abort = () => reject(signal.reason);
|
|
28864
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
28865
|
+
if (signal.aborted) abort();
|
|
28866
|
+
});
|
|
28867
|
+
try {
|
|
28868
|
+
return await Promise.race([promise, cancelled]);
|
|
28869
|
+
} finally {
|
|
28870
|
+
signal.removeEventListener("abort", abort);
|
|
28871
|
+
}
|
|
28872
|
+
}
|
|
28873
|
+
var AnvilWorldSource = class {
|
|
28874
|
+
constructor(readRegion, options = {}) {
|
|
28875
|
+
this.readRegion = readRegion;
|
|
28876
|
+
this.regions = /* @__PURE__ */ new Map();
|
|
28877
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
28878
|
+
this.cachedBytes = 0;
|
|
28879
|
+
this.maxCachedRegions = options.maxCachedRegions ?? 4;
|
|
28880
|
+
this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
|
|
28881
|
+
for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
|
|
28882
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
|
|
28883
|
+
}
|
|
28884
|
+
}
|
|
28885
|
+
/** Decodes one column at absolute chunk coordinates. Aborting a caller leaves other shared readers active. */
|
|
28886
|
+
async getChunk(x, z, signal) {
|
|
28887
|
+
if (!Number.isSafeInteger(x) || !Number.isSafeInteger(z)) {
|
|
28888
|
+
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
28889
|
+
}
|
|
28890
|
+
const regionX = Math.floor(x / 32), regionZ = Math.floor(z / 32);
|
|
28891
|
+
const data = await this.getRegion(regionX, regionZ, signal);
|
|
28892
|
+
signal?.throwIfAborted();
|
|
28893
|
+
if (!data) return void 0;
|
|
28894
|
+
const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
|
|
28895
|
+
signal?.throwIfAborted();
|
|
28896
|
+
if (chunk && (chunk.x !== x || chunk.z !== z)) {
|
|
28897
|
+
throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
|
|
28898
|
+
}
|
|
28899
|
+
return chunk;
|
|
28900
|
+
}
|
|
28901
|
+
/** Releases cached regions. Reads already in progress finish without repopulating this cache. */
|
|
28902
|
+
clearCache() {
|
|
28903
|
+
this.regions.clear();
|
|
28904
|
+
this.pending.clear();
|
|
28905
|
+
this.cachedBytes = 0;
|
|
28906
|
+
}
|
|
28907
|
+
getRegion(x, z, signal) {
|
|
28908
|
+
signal?.throwIfAborted();
|
|
28909
|
+
const key = `${x},${z}`;
|
|
28910
|
+
const cached2 = this.regions.get(key);
|
|
28911
|
+
if (cached2) {
|
|
28912
|
+
this.regions.delete(key);
|
|
28913
|
+
this.regions.set(key, cached2);
|
|
28914
|
+
return Promise.resolve(cached2.data);
|
|
28915
|
+
}
|
|
28916
|
+
let loading = this.pending.get(key);
|
|
28917
|
+
if (!loading) {
|
|
28918
|
+
const controller = new AbortController();
|
|
28919
|
+
loading = {
|
|
28920
|
+
controller,
|
|
28921
|
+
waiters: 0,
|
|
28922
|
+
settled: false,
|
|
28923
|
+
promise: Promise.resolve().then(() => {
|
|
28924
|
+
controller.signal.throwIfAborted();
|
|
28925
|
+
return this.readRegion(x, z, controller.signal);
|
|
28926
|
+
}).then((data) => {
|
|
28927
|
+
controller.signal.throwIfAborted();
|
|
28928
|
+
let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
|
|
28929
|
+
if (bytes && bytes.byteLength !== bytes.buffer.byteLength) bytes = new Uint8Array(bytes);
|
|
28930
|
+
if (bytes) AnvilParser.getChunkList(bytes);
|
|
28931
|
+
loading.settled = true;
|
|
28932
|
+
if (this.pending.get(key) === loading) this.cacheRegion(key, bytes);
|
|
28933
|
+
return bytes;
|
|
28934
|
+
}).finally(() => {
|
|
28935
|
+
loading.settled = true;
|
|
28936
|
+
if (this.pending.get(key) === loading) this.pending.delete(key);
|
|
28937
|
+
})
|
|
28938
|
+
};
|
|
28939
|
+
this.pending.set(key, loading);
|
|
28940
|
+
}
|
|
28941
|
+
const read = loading;
|
|
28942
|
+
read.waiters++;
|
|
28943
|
+
return new Promise((resolve, reject) => {
|
|
28944
|
+
let finished = false;
|
|
28945
|
+
const release = () => {
|
|
28946
|
+
if (finished) return false;
|
|
28947
|
+
finished = true;
|
|
28948
|
+
signal?.removeEventListener("abort", abort);
|
|
28949
|
+
read.waiters--;
|
|
28950
|
+
return true;
|
|
28951
|
+
};
|
|
28952
|
+
const abort = () => {
|
|
28953
|
+
if (!release()) return;
|
|
28954
|
+
if (!read.waiters && !read.settled) {
|
|
28955
|
+
if (this.pending.get(key) === read) this.pending.delete(key);
|
|
28956
|
+
read.controller.abort(signal.reason);
|
|
28957
|
+
}
|
|
28958
|
+
reject(signal.reason);
|
|
28959
|
+
};
|
|
28960
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
28961
|
+
read.promise.then(
|
|
28962
|
+
(data) => {
|
|
28963
|
+
if (release()) resolve(data);
|
|
28964
|
+
},
|
|
28965
|
+
(error) => {
|
|
28966
|
+
if (release()) reject(error);
|
|
28967
|
+
}
|
|
28968
|
+
);
|
|
28969
|
+
if (signal?.aborted) abort();
|
|
28970
|
+
});
|
|
28971
|
+
}
|
|
28972
|
+
cacheRegion(key, data) {
|
|
28973
|
+
const size = data?.byteLength ?? 0;
|
|
28974
|
+
if (!this.maxCachedRegions || size > this.maxCachedBytes) return;
|
|
28975
|
+
this.regions.set(key, { data, size });
|
|
28976
|
+
this.cachedBytes += size;
|
|
28977
|
+
while (this.regions.size > this.maxCachedRegions || this.cachedBytes > this.maxCachedBytes) {
|
|
28978
|
+
const oldest = this.regions.keys().next().value;
|
|
28979
|
+
this.cachedBytes -= this.regions.get(oldest).size;
|
|
28980
|
+
this.regions.delete(oldest);
|
|
28981
|
+
}
|
|
28982
|
+
}
|
|
28983
|
+
};
|
|
28984
|
+
|
|
28822
28985
|
// src/world/Chunk.ts
|
|
28823
28986
|
import { Vector3 as Vector317 } from "three";
|
|
28824
28987
|
init_BlockStates();
|
|
@@ -28826,8 +28989,8 @@ init_AssetKey();
|
|
|
28826
28989
|
|
|
28827
28990
|
// src/world/MineRenderWorld.ts
|
|
28828
28991
|
init_Model();
|
|
28992
|
+
init_util();
|
|
28829
28993
|
import { Vector3 as Vector316 } from "three";
|
|
28830
|
-
init_BatchedExecutor();
|
|
28831
28994
|
init_AssetKey();
|
|
28832
28995
|
init_BlockStates();
|
|
28833
28996
|
init_CubeFace();
|
|
@@ -28972,7 +29135,7 @@ var MineRenderWorld = class {
|
|
|
28972
29135
|
* Places a structure's blocks at their stored positions, then refreshes neighboring faces.
|
|
28973
29136
|
* Entity NBT is retained by the structure but is not rendered.
|
|
28974
29137
|
*
|
|
28975
|
-
* @param useBatches -
|
|
29138
|
+
* @param useBatches - Yields between chunks by default. Set to `false` for sequential placement.
|
|
28976
29139
|
* @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
|
|
28977
29140
|
*/
|
|
28978
29141
|
async placeMultiBlock(multiblock, useBatches = true, executor) {
|
|
@@ -28989,8 +29152,10 @@ var MineRenderWorld = class {
|
|
|
28989
29152
|
};
|
|
28990
29153
|
const place = (block) => this.placeBlock(new Vector316(...block.position), block, collect);
|
|
28991
29154
|
const keys = /* @__PURE__ */ new Map();
|
|
29155
|
+
const types = /* @__PURE__ */ new Set();
|
|
28992
29156
|
for (const block of multiblock.blocks) {
|
|
28993
|
-
if (Chunk.isAir(block)) continue;
|
|
29157
|
+
if (Chunk.isAir(block) || types.has(block.type)) continue;
|
|
29158
|
+
types.add(block.type);
|
|
28994
29159
|
const key = AssetKey.parse("blockstates", block.type);
|
|
28995
29160
|
keys.set(key.serialize(), key);
|
|
28996
29161
|
}
|
|
@@ -28999,6 +29164,23 @@ var MineRenderWorld = class {
|
|
|
28999
29164
|
for (const block of multiblock.blocks) await place(block);
|
|
29000
29165
|
return;
|
|
29001
29166
|
}
|
|
29167
|
+
if (!executor) {
|
|
29168
|
+
const groups2 = /* @__PURE__ */ new Map();
|
|
29169
|
+
const position = new Vector316();
|
|
29170
|
+
for (const block of multiblock.blocks) {
|
|
29171
|
+
const [x, y, z] = block.position;
|
|
29172
|
+
this.validatePosBounds(position.set(x, y, z));
|
|
29173
|
+
const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
|
|
29174
|
+
const key = `${cx}_${cy}_${cz}`;
|
|
29175
|
+
const chunk = this._chunks.get(key) ?? (Chunk.isAir(block) ? void 0 : this.getOrCreateChunkAt(position));
|
|
29176
|
+
if (!chunk) continue;
|
|
29177
|
+
let group = groups2.get(chunk);
|
|
29178
|
+
if (!group) groups2.set(chunk, group = []);
|
|
29179
|
+
group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
|
|
29180
|
+
}
|
|
29181
|
+
await this.placeChunkGroups(groups2, changes);
|
|
29182
|
+
return;
|
|
29183
|
+
}
|
|
29002
29184
|
const positions = /* @__PURE__ */ new Map();
|
|
29003
29185
|
for (const block of multiblock.blocks) {
|
|
29004
29186
|
const key = block.position.join(",");
|
|
@@ -29007,19 +29189,34 @@ var MineRenderWorld = class {
|
|
|
29007
29189
|
else positions.set(key, [block]);
|
|
29008
29190
|
}
|
|
29009
29191
|
const groups = [...positions.values()];
|
|
29010
|
-
|
|
29011
|
-
|
|
29012
|
-
|
|
29013
|
-
|
|
29014
|
-
|
|
29015
|
-
|
|
29016
|
-
|
|
29192
|
+
for (let i = 0; i < groups.length; i += executor.batch) {
|
|
29193
|
+
const results = await Promise.allSettled(groups.slice(i, i + executor.batch).map(
|
|
29194
|
+
(blocks) => executor.submit(async () => {
|
|
29195
|
+
for (const block of blocks) await place(block);
|
|
29196
|
+
})
|
|
29197
|
+
));
|
|
29198
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
29199
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
29200
|
+
}
|
|
29201
|
+
}
|
|
29202
|
+
async placeChunkGroups(groups, changes) {
|
|
29203
|
+
let sliceStart = performance.now();
|
|
29204
|
+
for (const [chunk, blocks] of groups) {
|
|
29205
|
+
let positions;
|
|
29206
|
+
try {
|
|
29207
|
+
positions = await chunk.placeBlocks(blocks);
|
|
29208
|
+
} finally {
|
|
29209
|
+
positions ??= blocks.map(({ index }) => new Vector316(
|
|
29210
|
+
chunk.x * 16 + index % 16,
|
|
29211
|
+
chunk.y * 16 + Math.floor(index / 256),
|
|
29212
|
+
chunk.z * 16 + Math.floor(index / 16) % 16
|
|
29017
29213
|
));
|
|
29018
|
-
const
|
|
29019
|
-
|
|
29214
|
+
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
29215
|
+
}
|
|
29216
|
+
if (performance.now() - sliceStart > 8) {
|
|
29217
|
+
await yieldToEventLoop();
|
|
29218
|
+
sliceStart = performance.now();
|
|
29020
29219
|
}
|
|
29021
|
-
} finally {
|
|
29022
|
-
if (!executor) queue.stop();
|
|
29023
29220
|
}
|
|
29024
29221
|
}
|
|
29025
29222
|
/** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
|
|
@@ -29027,7 +29224,24 @@ var MineRenderWorld = class {
|
|
|
29027
29224
|
const changes = /* @__PURE__ */ new Map();
|
|
29028
29225
|
try {
|
|
29029
29226
|
await this.clearChunkColumn(chunk.x, chunk.z, changes);
|
|
29227
|
+
const groups = /* @__PURE__ */ new Map();
|
|
29030
29228
|
for (const section of chunk.sections) {
|
|
29229
|
+
if (!executor) {
|
|
29230
|
+
const blocks2 = [];
|
|
29231
|
+
for (let index = 0; index < 4096; index++) {
|
|
29232
|
+
const block = section.data.get(index);
|
|
29233
|
+
if (block) blocks2.push({ index, block });
|
|
29234
|
+
}
|
|
29235
|
+
if (blocks2.length) {
|
|
29236
|
+
const position = new Vector316(chunk.x * 16, section.y * 16, chunk.z * 16);
|
|
29237
|
+
this.validatePosBounds(position);
|
|
29238
|
+
const target = this.getOrCreateChunkAt(position);
|
|
29239
|
+
const previous = groups.get(target);
|
|
29240
|
+
if (previous) previous.push(...blocks2);
|
|
29241
|
+
else groups.set(target, blocks2);
|
|
29242
|
+
}
|
|
29243
|
+
continue;
|
|
29244
|
+
}
|
|
29031
29245
|
const blocks = [];
|
|
29032
29246
|
for (let index = 0; index < 4096; index++) {
|
|
29033
29247
|
const block = section.data.get(index);
|
|
@@ -29042,6 +29256,7 @@ var MineRenderWorld = class {
|
|
|
29042
29256
|
}
|
|
29043
29257
|
await this.placeBlocks({ size: [16, 16, 16], blocks }, true, executor, changes);
|
|
29044
29258
|
}
|
|
29259
|
+
if (!executor) await this.placeChunkGroups(groups, changes);
|
|
29045
29260
|
} finally {
|
|
29046
29261
|
await this.updateCulling([...changes.values()]);
|
|
29047
29262
|
}
|
|
@@ -29082,19 +29297,23 @@ var MineRenderWorld = class {
|
|
|
29082
29297
|
this.sectionModels?.clear();
|
|
29083
29298
|
}
|
|
29084
29299
|
updateCulling(positions) {
|
|
29300
|
+
const neighbor = new Vector316();
|
|
29085
29301
|
for (const pos of positions) {
|
|
29086
|
-
|
|
29087
|
-
|
|
29088
|
-
|
|
29089
|
-
this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
|
|
29090
|
-
}
|
|
29302
|
+
const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
|
|
29303
|
+
const chunk = this._chunks.get(`${cx}_${cy}_${cz}`);
|
|
29304
|
+
const lx = pos.x - cx * 16, ly = pos.y - cy * 16, lz = pos.z - cz * 16;
|
|
29091
29305
|
for (let y = -1; y <= 1; y++) {
|
|
29092
29306
|
for (let z = -1; z <= 1; z++) {
|
|
29093
29307
|
for (let x = -1; x <= 1; x++) {
|
|
29094
|
-
const
|
|
29095
|
-
|
|
29096
|
-
|
|
29097
|
-
|
|
29308
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29309
|
+
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)}`);
|
|
29310
|
+
if (!section) continue;
|
|
29311
|
+
const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29312
|
+
let pending = this.pendingCulling.get(section);
|
|
29313
|
+
if (pending?.has(index)) continue;
|
|
29314
|
+
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;
|
|
29315
|
+
if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
|
|
29316
|
+
pending.add(index);
|
|
29098
29317
|
}
|
|
29099
29318
|
}
|
|
29100
29319
|
}
|
|
@@ -29102,39 +29321,38 @@ var MineRenderWorld = class {
|
|
|
29102
29321
|
return this.culling ??= Promise.resolve().then(async () => {
|
|
29103
29322
|
try {
|
|
29104
29323
|
while (this.pendingCulling.size) {
|
|
29105
|
-
const batch = [...this.pendingCulling
|
|
29324
|
+
const batch = [...this.pendingCulling];
|
|
29106
29325
|
this.pendingCulling.clear();
|
|
29107
|
-
const
|
|
29108
|
-
|
|
29109
|
-
|
|
29110
|
-
|
|
29111
|
-
|
|
29112
|
-
|
|
29113
|
-
|
|
29114
|
-
|
|
29115
|
-
|
|
29116
|
-
|
|
29117
|
-
|
|
29118
|
-
|
|
29119
|
-
|
|
29120
|
-
|
|
29121
|
-
|
|
29122
|
-
|
|
29123
|
-
|
|
29124
|
-
|
|
29125
|
-
|
|
29126
|
-
|
|
29127
|
-
|
|
29128
|
-
|
|
29129
|
-
|
|
29130
|
-
|
|
29131
|
-
|
|
29132
|
-
mask |= 1 << face;
|
|
29326
|
+
for (const [chunk, indices] of batch) {
|
|
29327
|
+
for (const index of indices) {
|
|
29328
|
+
if (!chunk.isBlockVisibleIndex(index)) continue;
|
|
29329
|
+
const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
|
|
29330
|
+
const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
|
|
29331
|
+
const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
|
|
29332
|
+
if (block.object?.fluidKind) {
|
|
29333
|
+
await block.object.updateFluid((x, y, z) => {
|
|
29334
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29335
|
+
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)}`);
|
|
29336
|
+
const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29337
|
+
const object3 = section?.isBlockVisibleIndex(neighborIndex) ? section.getBlockAt(neighbor.set(wx + x, wy + y, wz + z))?.object : void 0;
|
|
29338
|
+
return {
|
|
29339
|
+
fluid: object3?.fluidKind,
|
|
29340
|
+
level: object3?.fluidLevel,
|
|
29341
|
+
solid: section?.isOccludingIndex(neighborIndex) ?? false
|
|
29342
|
+
};
|
|
29343
|
+
});
|
|
29344
|
+
}
|
|
29345
|
+
let mask = 0;
|
|
29346
|
+
for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
|
|
29347
|
+
const offset = CUBE_FACE_OFFSETS[face];
|
|
29348
|
+
const nx = lx + offset[0], ny = ly + offset[1], nz = lz + offset[2];
|
|
29349
|
+
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)}`);
|
|
29350
|
+
if (section?.isOccludingIndex((ny & 15) * 256 + (nz & 15) * 16 + (nx & 15))) mask |= 1 << face;
|
|
29133
29351
|
}
|
|
29352
|
+
await chunk.setCullMaskIndex(index, mask);
|
|
29134
29353
|
}
|
|
29135
|
-
await chunk.setCullMaskAt(pos, mask);
|
|
29136
29354
|
}
|
|
29137
|
-
|
|
29355
|
+
await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
|
|
29138
29356
|
}
|
|
29139
29357
|
} finally {
|
|
29140
29358
|
this.culling = void 0;
|
|
@@ -29195,8 +29413,230 @@ init_Model();
|
|
|
29195
29413
|
|
|
29196
29414
|
// src/world/SectionMesh.ts
|
|
29197
29415
|
init_CanvasCompat();
|
|
29416
|
+
init_CubeFace();
|
|
29198
29417
|
init_Materials();
|
|
29199
|
-
import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group3, Mesh as Mesh9 } from "three";
|
|
29418
|
+
import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group3, Mesh as Mesh9, Uint32BufferAttribute } from "three";
|
|
29419
|
+
|
|
29420
|
+
// src/world/SectionGeometry.ts
|
|
29421
|
+
init_CubeFace();
|
|
29422
|
+
function buildSectionGeometry(input) {
|
|
29423
|
+
const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize } = input;
|
|
29424
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29425
|
+
if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
|
|
29426
|
+
const visible = [];
|
|
29427
|
+
const visibleAtlases = /* @__PURE__ */ new Set();
|
|
29428
|
+
for (let entry = 0; entry < count; entry++) {
|
|
29429
|
+
const template = templateData[templates[entry]];
|
|
29430
|
+
if (!template.cullFaces.some((direction) => !(cullMasks[entry] & direction))) continue;
|
|
29431
|
+
visible.push(entry);
|
|
29432
|
+
visibleAtlases.add(template.atlas);
|
|
29433
|
+
}
|
|
29434
|
+
const pages = [];
|
|
29435
|
+
const locations = /* @__PURE__ */ new Map();
|
|
29436
|
+
for (const atlas of [...visibleAtlases].sort((a, b) => atlases[b].height - atlases[a].height)) {
|
|
29437
|
+
const { width, height } = atlases[atlas];
|
|
29438
|
+
if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
|
|
29439
|
+
let selected = -1;
|
|
29440
|
+
for (let i = 0; i < pages.length; i++) {
|
|
29441
|
+
const page2 = pages[i];
|
|
29442
|
+
const nextRow = page2.x + width > maxAtlasSize;
|
|
29443
|
+
if ((nextRow ? page2.y + page2.rowHeight : page2.y) + height > maxAtlasSize) continue;
|
|
29444
|
+
if (nextRow) {
|
|
29445
|
+
page2.y += page2.rowHeight;
|
|
29446
|
+
page2.x = 0;
|
|
29447
|
+
page2.rowHeight = 0;
|
|
29448
|
+
}
|
|
29449
|
+
selected = i;
|
|
29450
|
+
break;
|
|
29451
|
+
}
|
|
29452
|
+
if (selected < 0) {
|
|
29453
|
+
selected = pages.length;
|
|
29454
|
+
pages.push({ placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 });
|
|
29455
|
+
}
|
|
29456
|
+
const page = pages[selected];
|
|
29457
|
+
page.placements.push({ atlas, x: page.x, y: page.y });
|
|
29458
|
+
locations.set(atlas, { page: selected, x: page.x, y: page.y });
|
|
29459
|
+
page.x += width;
|
|
29460
|
+
page.rowHeight = Math.max(page.rowHeight, height);
|
|
29461
|
+
page.width = Math.max(page.width, page.x);
|
|
29462
|
+
page.height = Math.max(page.height, page.y + height);
|
|
29463
|
+
}
|
|
29464
|
+
for (const entry of visible) pages[locations.get(templateData[templates[entry]].atlas).page].entries.push(entry);
|
|
29465
|
+
return pages.map((page) => {
|
|
29466
|
+
let faces = 0;
|
|
29467
|
+
for (const entry of page.entries) {
|
|
29468
|
+
const template = templateData[templates[entry]];
|
|
29469
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29470
|
+
if (!(cullMasks[entry] & template.cullFaces[face])) faces++;
|
|
29471
|
+
}
|
|
29472
|
+
}
|
|
29473
|
+
const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
|
|
29474
|
+
const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
|
|
29475
|
+
const outputIndices = new Uint32Array(faces * 6);
|
|
29476
|
+
let vertex = 0, indexOffset = 0;
|
|
29477
|
+
for (const entry of page.entries) {
|
|
29478
|
+
const template = templateData[templates[entry]];
|
|
29479
|
+
const atlas = atlases[template.atlas];
|
|
29480
|
+
const placement = locations.get(template.atlas);
|
|
29481
|
+
const mapU = (u) => (placement.x + u * atlas.width) / page.width;
|
|
29482
|
+
const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
|
|
29483
|
+
const offsetX = indices[entry] % 16 * 16;
|
|
29484
|
+
const offsetY = Math.floor(indices[entry] / 256) * 16;
|
|
29485
|
+
const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
|
|
29486
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29487
|
+
if (cullMasks[entry] & template.cullFaces[face]) continue;
|
|
29488
|
+
const start = vertex;
|
|
29489
|
+
for (let corner = 0; corner < 4; corner++, vertex++) {
|
|
29490
|
+
const source = face * 4 + corner;
|
|
29491
|
+
positions[vertex * 3] = template.positions[source * 3] + offsetX;
|
|
29492
|
+
positions[vertex * 3 + 1] = template.positions[source * 3 + 1] + offsetY;
|
|
29493
|
+
positions[vertex * 3 + 2] = template.positions[source * 3 + 2] + offsetZ;
|
|
29494
|
+
normals[vertex * 3] = template.normals[source * 3];
|
|
29495
|
+
normals[vertex * 3 + 1] = template.normals[source * 3 + 1];
|
|
29496
|
+
normals[vertex * 3 + 2] = template.normals[source * 3 + 2];
|
|
29497
|
+
uvs[vertex * 2] = mapU(template.uvs[source * 2]);
|
|
29498
|
+
uvs[vertex * 2 + 1] = mapV(template.uvs[source * 2 + 1]);
|
|
29499
|
+
uvBounds[vertex * 4] = mapU(template.uvBounds[source * 4]);
|
|
29500
|
+
uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
|
|
29501
|
+
uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
|
|
29502
|
+
uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
|
|
29503
|
+
colors[vertex * 3] = template.colors[source * 3];
|
|
29504
|
+
colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
|
|
29505
|
+
colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
|
|
29506
|
+
}
|
|
29507
|
+
for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[face * 6 + corner] - face * 4;
|
|
29508
|
+
}
|
|
29509
|
+
}
|
|
29510
|
+
return { width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
|
|
29511
|
+
});
|
|
29512
|
+
}
|
|
29513
|
+
function sectionGeometryTransferables(pages) {
|
|
29514
|
+
return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
|
|
29515
|
+
}
|
|
29516
|
+
|
|
29517
|
+
// src/world/SectionWorker.ts
|
|
29518
|
+
init_Env();
|
|
29519
|
+
var SectionWorker = class _SectionWorker {
|
|
29520
|
+
constructor(worker) {
|
|
29521
|
+
this.worker = worker;
|
|
29522
|
+
this.nextId = 0;
|
|
29523
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
29524
|
+
worker.addEventListener("message", (event) => {
|
|
29525
|
+
const reply = event.data;
|
|
29526
|
+
const pending = this.pending.get(reply.id);
|
|
29527
|
+
if (!pending) return;
|
|
29528
|
+
this.pending.delete(reply.id);
|
|
29529
|
+
if (reply.type === "pages") pending.resolve(reply.pages);
|
|
29530
|
+
else pending.reject(new Error(reply.message));
|
|
29531
|
+
});
|
|
29532
|
+
worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
|
|
29533
|
+
worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
|
|
29534
|
+
}
|
|
29535
|
+
static {
|
|
29536
|
+
this.initialized = false;
|
|
29537
|
+
}
|
|
29538
|
+
/** The shared worker, or undefined when the environment has none or a previous worker failed. */
|
|
29539
|
+
static shared() {
|
|
29540
|
+
if (!this.initialized) {
|
|
29541
|
+
this.initialized = true;
|
|
29542
|
+
const worker = Env.provider.createWorker?.("section");
|
|
29543
|
+
if (worker) this.instance = new _SectionWorker(worker);
|
|
29544
|
+
}
|
|
29545
|
+
return this.instance;
|
|
29546
|
+
}
|
|
29547
|
+
/** Builds geometry without transferring the input buffers away from the caller. */
|
|
29548
|
+
build(input) {
|
|
29549
|
+
if (!this.worker) return Promise.reject(new Error("Section worker terminated"));
|
|
29550
|
+
return new Promise((resolve, reject) => {
|
|
29551
|
+
const id = this.nextId++;
|
|
29552
|
+
this.pending.set(id, { resolve, reject });
|
|
29553
|
+
this.worker.postMessage({ id, type: "build", input });
|
|
29554
|
+
});
|
|
29555
|
+
}
|
|
29556
|
+
/** Rejects pending builds, terminates the worker, and makes shared() return undefined. */
|
|
29557
|
+
terminate() {
|
|
29558
|
+
this.fail("Section worker terminated");
|
|
29559
|
+
}
|
|
29560
|
+
fail(message) {
|
|
29561
|
+
const error = new Error(message);
|
|
29562
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
29563
|
+
this.pending.clear();
|
|
29564
|
+
this.worker?.terminate();
|
|
29565
|
+
this.worker = void 0;
|
|
29566
|
+
_SectionWorker.instance = void 0;
|
|
29567
|
+
}
|
|
29568
|
+
};
|
|
29569
|
+
|
|
29570
|
+
// src/world/SectionMesh.ts
|
|
29571
|
+
var templateCache = /* @__PURE__ */ new WeakMap();
|
|
29572
|
+
function toInput(entries, maxAtlasSize) {
|
|
29573
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29574
|
+
const atlases = [];
|
|
29575
|
+
const atlasIds = /* @__PURE__ */ new Map();
|
|
29576
|
+
const templateIds = /* @__PURE__ */ new Map();
|
|
29577
|
+
const input = {
|
|
29578
|
+
count: entries.length,
|
|
29579
|
+
indices: new Uint16Array(entries.length),
|
|
29580
|
+
templates: new Uint16Array(entries.length),
|
|
29581
|
+
cullMasks: new Uint8Array(entries.length),
|
|
29582
|
+
templateData: [],
|
|
29583
|
+
atlases: [],
|
|
29584
|
+
maxAtlasSize
|
|
29585
|
+
};
|
|
29586
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29587
|
+
const { index, template, cullMask } = entries[i];
|
|
29588
|
+
let id = templateIds.get(template);
|
|
29589
|
+
if (id === void 0) {
|
|
29590
|
+
let atlas = atlasIds.get(template.atlas);
|
|
29591
|
+
if (atlas === void 0) {
|
|
29592
|
+
atlas = atlases.length;
|
|
29593
|
+
atlasIds.set(template.atlas, atlas);
|
|
29594
|
+
atlases.push(template.atlas);
|
|
29595
|
+
input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height });
|
|
29596
|
+
}
|
|
29597
|
+
let data = templateCache.get(template);
|
|
29598
|
+
if (!data) {
|
|
29599
|
+
data = {
|
|
29600
|
+
positions: new Float32Array(72),
|
|
29601
|
+
normals: new Float32Array(72),
|
|
29602
|
+
uvs: new Float32Array(48),
|
|
29603
|
+
uvBounds: new Float32Array(96),
|
|
29604
|
+
colors: new Float32Array(72),
|
|
29605
|
+
indices: new Uint16Array(36),
|
|
29606
|
+
cullFaces: new Uint8Array(template.cullFaces),
|
|
29607
|
+
atlas: 0
|
|
29608
|
+
};
|
|
29609
|
+
const position = template.geometry.getAttribute("position");
|
|
29610
|
+
const normal = template.geometry.getAttribute("normal");
|
|
29611
|
+
const uv = template.geometry.getAttribute("uv");
|
|
29612
|
+
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29613
|
+
const color2 = template.geometry.getAttribute("color");
|
|
29614
|
+
for (let vertex = 0; vertex < CUBE_FACES.length * 4; vertex++) {
|
|
29615
|
+
data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
|
|
29616
|
+
data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
|
|
29617
|
+
data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
|
|
29618
|
+
data.uvBounds.set([
|
|
29619
|
+
bounds?.getX(vertex) ?? 0,
|
|
29620
|
+
bounds?.getY(vertex) ?? 0,
|
|
29621
|
+
bounds?.getZ(vertex) ?? 1,
|
|
29622
|
+
bounds?.getW(vertex) ?? 1
|
|
29623
|
+
], vertex * 4);
|
|
29624
|
+
data.colors.set([color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1], vertex * 3);
|
|
29625
|
+
}
|
|
29626
|
+
const indices = template.geometry.getIndex();
|
|
29627
|
+
for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2);
|
|
29628
|
+
templateCache.set(template, data);
|
|
29629
|
+
}
|
|
29630
|
+
id = input.templateData.length;
|
|
29631
|
+
templateIds.set(template, id);
|
|
29632
|
+
input.templateData.push({ ...data, atlas });
|
|
29633
|
+
}
|
|
29634
|
+
input.indices[i] = index;
|
|
29635
|
+
input.templates[i] = id;
|
|
29636
|
+
input.cullMasks[i] = cullMask;
|
|
29637
|
+
}
|
|
29638
|
+
return { input, atlases };
|
|
29639
|
+
}
|
|
29200
29640
|
var SectionMesh = class _SectionMesh extends Group3 {
|
|
29201
29641
|
constructor() {
|
|
29202
29642
|
super(...arguments);
|
|
@@ -29204,86 +29644,31 @@ var SectionMesh = class _SectionMesh extends Group3 {
|
|
|
29204
29644
|
}
|
|
29205
29645
|
/** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
|
|
29206
29646
|
static build(entries, maxAtlasSize = 2048) {
|
|
29207
|
-
|
|
29208
|
-
|
|
29209
|
-
|
|
29210
|
-
|
|
29211
|
-
|
|
29212
|
-
|
|
29213
|
-
|
|
29214
|
-
|
|
29215
|
-
|
|
29216
|
-
|
|
29217
|
-
|
|
29218
|
-
if ((nextRow ? page.y + page.rowHeight : page.y) + height > maxAtlasSize) continue;
|
|
29219
|
-
if (nextRow) {
|
|
29220
|
-
page.y += page.rowHeight;
|
|
29221
|
-
page.x = 0;
|
|
29222
|
-
page.rowHeight = 0;
|
|
29223
|
-
}
|
|
29224
|
-
selected = page;
|
|
29225
|
-
break;
|
|
29226
|
-
}
|
|
29227
|
-
if (!selected) {
|
|
29228
|
-
selected = { placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 };
|
|
29229
|
-
pages.push(selected);
|
|
29230
|
-
}
|
|
29231
|
-
const placement = { atlas, x: selected.x, y: selected.y };
|
|
29232
|
-
selected.placements.push(placement);
|
|
29233
|
-
selected.x += width;
|
|
29234
|
-
selected.rowHeight = Math.max(selected.rowHeight, height);
|
|
29235
|
-
selected.width = Math.max(selected.width, selected.x);
|
|
29236
|
-
selected.height = Math.max(selected.height, selected.y + height);
|
|
29237
|
-
locations.set(atlas, { page: selected, placement });
|
|
29238
|
-
}
|
|
29239
|
-
for (const entry of visible) locations.get(entry.template.atlas).page.entries.push(entry);
|
|
29647
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29648
|
+
return this.fromPages(buildSectionGeometry(input), atlases);
|
|
29649
|
+
}
|
|
29650
|
+
/** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
|
|
29651
|
+
static async buildAsync(entries, maxAtlasSize = 2048) {
|
|
29652
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29653
|
+
const worker = SectionWorker.shared();
|
|
29654
|
+
const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
|
|
29655
|
+
return this.fromPages(pages, atlases);
|
|
29656
|
+
}
|
|
29657
|
+
static fromPages(pages, atlases) {
|
|
29240
29658
|
const section = new _SectionMesh();
|
|
29241
29659
|
for (const page of pages) {
|
|
29242
29660
|
const canvas = createCanvas2(page.width, page.height);
|
|
29243
29661
|
const context = canvas.getContext("2d");
|
|
29244
29662
|
for (const { atlas, x, y } of page.placements) {
|
|
29245
|
-
context.drawImage(atlas.image.canvas, x, y);
|
|
29246
|
-
}
|
|
29247
|
-
const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
|
|
29248
|
-
for (const { index, template, cullMask } of page.entries) {
|
|
29249
|
-
const position = template.geometry.getAttribute("position");
|
|
29250
|
-
const normal = template.geometry.getAttribute("normal");
|
|
29251
|
-
const uv = template.geometry.getAttribute("uv");
|
|
29252
|
-
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29253
|
-
const color2 = template.geometry.getAttribute("color");
|
|
29254
|
-
const sourceIndices = template.geometry.getIndex();
|
|
29255
|
-
const placement = locations.get(template.atlas).placement;
|
|
29256
|
-
const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
|
|
29257
|
-
const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
|
|
29258
|
-
const offsetX = index % 16 * 16;
|
|
29259
|
-
const offsetY = Math.floor(index / 256) * 16;
|
|
29260
|
-
const offsetZ = Math.floor(index / 16) % 16 * 16;
|
|
29261
|
-
for (let face = 0; face < 6; face++) {
|
|
29262
|
-
if (cullMask & template.cullFaces[face]) continue;
|
|
29263
|
-
const start = positions.length / 3;
|
|
29264
|
-
for (let corner = 0; corner < 4; corner++) {
|
|
29265
|
-
const vertex = face * 4 + corner;
|
|
29266
|
-
positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
|
|
29267
|
-
normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
|
|
29268
|
-
uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
|
|
29269
|
-
uvBounds.push(
|
|
29270
|
-
mapU(bounds?.getX(vertex) ?? 0),
|
|
29271
|
-
mapV(bounds?.getY(vertex) ?? 0),
|
|
29272
|
-
mapU(bounds?.getZ(vertex) ?? 1),
|
|
29273
|
-
mapV(bounds?.getW(vertex) ?? 1)
|
|
29274
|
-
);
|
|
29275
|
-
colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
|
|
29276
|
-
}
|
|
29277
|
-
for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
|
|
29278
|
-
}
|
|
29663
|
+
context.drawImage(atlases[atlas].image.canvas, x, y);
|
|
29279
29664
|
}
|
|
29280
29665
|
const geometry = new BufferGeometry8();
|
|
29281
|
-
geometry.setAttribute("position", new Float32BufferAttribute8(positions, 3));
|
|
29282
|
-
geometry.setAttribute("normal", new Float32BufferAttribute8(normals, 3));
|
|
29283
|
-
geometry.setAttribute("uv", new Float32BufferAttribute8(uvs, 2));
|
|
29284
|
-
geometry.setAttribute("uvBounds", new Float32BufferAttribute8(uvBounds, 4));
|
|
29285
|
-
geometry.setAttribute("color", new Float32BufferAttribute8(colors, 3));
|
|
29286
|
-
geometry.setIndex(indices);
|
|
29666
|
+
geometry.setAttribute("position", new Float32BufferAttribute8(page.positions.buffer, 3));
|
|
29667
|
+
geometry.setAttribute("normal", new Float32BufferAttribute8(page.normals.buffer, 3));
|
|
29668
|
+
geometry.setAttribute("uv", new Float32BufferAttribute8(page.uvs.buffer, 2));
|
|
29669
|
+
geometry.setAttribute("uvBounds", new Float32BufferAttribute8(page.uvBounds.buffer, 4));
|
|
29670
|
+
geometry.setAttribute("color", new Float32BufferAttribute8(page.colors.buffer, 3));
|
|
29671
|
+
geometry.setIndex(new Uint32BufferAttribute(page.indices.buffer, 1));
|
|
29287
29672
|
geometry.computeBoundingBox();
|
|
29288
29673
|
geometry.computeBoundingSphere();
|
|
29289
29674
|
const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
|
|
@@ -29317,7 +29702,9 @@ var Chunk = class _Chunk {
|
|
|
29317
29702
|
this.renderedBlocks = /* @__PURE__ */ new Map();
|
|
29318
29703
|
this.sectionBlocks = /* @__PURE__ */ new Map();
|
|
29319
29704
|
this.hiddenBlocks = /* @__PURE__ */ new Set();
|
|
29705
|
+
this.blockPosition = new Vector317();
|
|
29320
29706
|
this.meshDirty = false;
|
|
29707
|
+
this.meshGeneration = 0;
|
|
29321
29708
|
this.scene = scene;
|
|
29322
29709
|
this.x = x;
|
|
29323
29710
|
this.y = y;
|
|
@@ -29330,7 +29717,7 @@ var Chunk = class _Chunk {
|
|
|
29330
29717
|
if (isTripleArray(posOrX)) {
|
|
29331
29718
|
return this.getBlockAt(new Vector317(posOrX[0], posOrX[1], posOrX[2]));
|
|
29332
29719
|
}
|
|
29333
|
-
const pos = this.
|
|
29720
|
+
const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
|
|
29334
29721
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29335
29722
|
return this.renderedBlocks.get(index);
|
|
29336
29723
|
}
|
|
@@ -29354,48 +29741,81 @@ var Chunk = class _Chunk {
|
|
|
29354
29741
|
worldPos = this.chunkPosToWorldPos(pos);
|
|
29355
29742
|
}
|
|
29356
29743
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29357
|
-
this.hiddenBlocks.delete(index);
|
|
29358
|
-
this.data.set(index, block);
|
|
29359
|
-
const readBlock = this.data.snapshot(index);
|
|
29360
|
-
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29361
|
-
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29362
|
-
this.renderedBlocks.delete(index);
|
|
29363
|
-
let object3;
|
|
29364
29744
|
try {
|
|
29365
|
-
|
|
29366
|
-
|
|
29367
|
-
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29368
|
-
if (!blockState) {
|
|
29369
|
-
this.data.set(index, void 0);
|
|
29370
|
-
return void 0;
|
|
29371
|
-
}
|
|
29372
|
-
const perBlock = !this.sectionModels || getFluidKind(blockState.key, stored.properties) || blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString());
|
|
29373
|
-
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29374
|
-
if (template) {
|
|
29375
|
-
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29376
|
-
this.meshDirty = true;
|
|
29377
|
-
} else {
|
|
29378
|
-
object3 = await this.scene.addBlock(blockState, {
|
|
29379
|
-
mergeMeshes: true,
|
|
29380
|
-
instanceMeshes: true,
|
|
29381
|
-
maxInstanceCount: 2e3,
|
|
29382
|
-
initialState: stored.properties
|
|
29383
|
-
});
|
|
29384
|
-
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29385
|
-
}
|
|
29386
|
-
const info = { get block() {
|
|
29387
|
-
return readBlock();
|
|
29388
|
-
}, object: object3 };
|
|
29389
|
-
this.renderedBlocks.set(index, info);
|
|
29390
|
-
return info;
|
|
29391
|
-
} catch (error) {
|
|
29392
|
-
this.data.set(index, void 0);
|
|
29393
|
-
object3?.removeFromScene();
|
|
29394
|
-
throw error;
|
|
29745
|
+
await this.placeBlocks([{ index, block }]);
|
|
29746
|
+
return this.renderedBlocks.get(index);
|
|
29395
29747
|
} finally {
|
|
29396
29748
|
await onBlocksChanged?.([worldPos]);
|
|
29397
29749
|
}
|
|
29398
29750
|
}
|
|
29751
|
+
/** Places cells in input order and returns changed world positions without refreshing neighbors. */
|
|
29752
|
+
async placeBlocks(blocks) {
|
|
29753
|
+
const resolutions = /* @__PURE__ */ new Map();
|
|
29754
|
+
for (const { block } of blocks) {
|
|
29755
|
+
if (!block || ChunkData.isAir(block)) continue;
|
|
29756
|
+
const key = ChunkData.paletteKey(block);
|
|
29757
|
+
if (resolutions.has(key)) continue;
|
|
29758
|
+
const stored = { type: block.type, properties: block.properties ? { ...block.properties } : void 0 };
|
|
29759
|
+
const resolved = (async () => {
|
|
29760
|
+
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29761
|
+
const perBlock = !blockState || !this.sectionModels || !!getFluidKind(blockState.key, stored.properties) || !!(blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString()));
|
|
29762
|
+
if (!perBlock) await this.sectionModels.get(blockState, stored.properties);
|
|
29763
|
+
return { blockState, perBlock };
|
|
29764
|
+
})();
|
|
29765
|
+
resolutions.set(key, resolved);
|
|
29766
|
+
void resolved.catch(() => void 0);
|
|
29767
|
+
}
|
|
29768
|
+
const positions = [];
|
|
29769
|
+
let failed = false, failure;
|
|
29770
|
+
for (const { index, block } of blocks) {
|
|
29771
|
+
this.hiddenBlocks.delete(index);
|
|
29772
|
+
this.data.set(index, block);
|
|
29773
|
+
const readBlock = this.data.snapshot(index);
|
|
29774
|
+
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29775
|
+
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29776
|
+
this.renderedBlocks.delete(index);
|
|
29777
|
+
const worldPos = new Vector317(
|
|
29778
|
+
this.x * 16 + index % 16,
|
|
29779
|
+
this.y * 16 + Math.floor(index / 256),
|
|
29780
|
+
this.z * 16 + Math.floor(index / 16) % 16
|
|
29781
|
+
);
|
|
29782
|
+
positions.push(worldPos);
|
|
29783
|
+
let object3;
|
|
29784
|
+
try {
|
|
29785
|
+
if (!readBlock) continue;
|
|
29786
|
+
const stored = readBlock();
|
|
29787
|
+
const key = ChunkData.paletteKey(stored);
|
|
29788
|
+
const { blockState, perBlock } = await resolutions.get(key);
|
|
29789
|
+
if (!blockState) {
|
|
29790
|
+
this.data.set(index, void 0);
|
|
29791
|
+
continue;
|
|
29792
|
+
}
|
|
29793
|
+
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29794
|
+
if (template) {
|
|
29795
|
+
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29796
|
+
this.meshDirty = true;
|
|
29797
|
+
} else {
|
|
29798
|
+
object3 = await this.scene.addBlock(blockState, {
|
|
29799
|
+
mergeMeshes: true,
|
|
29800
|
+
instanceMeshes: true,
|
|
29801
|
+
maxInstanceCount: 2e3,
|
|
29802
|
+
initialState: stored.properties
|
|
29803
|
+
});
|
|
29804
|
+
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29805
|
+
}
|
|
29806
|
+
this.renderedBlocks.set(index, { get block() {
|
|
29807
|
+
return readBlock();
|
|
29808
|
+
}, object: object3 });
|
|
29809
|
+
} catch (error) {
|
|
29810
|
+
this.data.set(index, void 0);
|
|
29811
|
+
object3?.removeFromScene();
|
|
29812
|
+
if (!failed) failure = error;
|
|
29813
|
+
failed = true;
|
|
29814
|
+
}
|
|
29815
|
+
}
|
|
29816
|
+
if (failed) throw failure;
|
|
29817
|
+
return positions;
|
|
29818
|
+
}
|
|
29399
29819
|
/** Changes visibility at world block coordinates while preserving the block's data. */
|
|
29400
29820
|
async setBlockVisibleAt(pos, visible) {
|
|
29401
29821
|
const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
|
|
@@ -29416,15 +29836,24 @@ var Chunk = class _Chunk {
|
|
|
29416
29836
|
await this.onBlocksChanged?.([pos]);
|
|
29417
29837
|
}
|
|
29418
29838
|
isBlockVisibleAt(pos) {
|
|
29419
|
-
|
|
29839
|
+
return this.isBlockVisibleIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29840
|
+
}
|
|
29841
|
+
/** Reports whether a cell has a visible render object or section entry. */
|
|
29842
|
+
isBlockVisibleIndex(index) {
|
|
29420
29843
|
return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
|
|
29421
29844
|
}
|
|
29422
29845
|
isOccludingAt(pos) {
|
|
29423
|
-
|
|
29846
|
+
return this.isOccludingIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29847
|
+
}
|
|
29848
|
+
/** Reports whether a visible cell hides neighboring cube faces. */
|
|
29849
|
+
isOccludingIndex(index) {
|
|
29424
29850
|
return !this.hiddenBlocks.has(index) && (this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false));
|
|
29425
29851
|
}
|
|
29426
29852
|
async setCullMaskAt(pos, mask) {
|
|
29427
|
-
|
|
29853
|
+
await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
|
|
29854
|
+
}
|
|
29855
|
+
/** Updates hidden faces for a cell's section entry or individual render object. */
|
|
29856
|
+
async setCullMaskIndex(index, mask) {
|
|
29428
29857
|
const entry = this.sectionBlocks.get(index);
|
|
29429
29858
|
if (entry) {
|
|
29430
29859
|
if (entry.cullMask !== mask) {
|
|
@@ -29435,12 +29864,17 @@ var Chunk = class _Chunk {
|
|
|
29435
29864
|
await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
|
|
29436
29865
|
}
|
|
29437
29866
|
}
|
|
29438
|
-
|
|
29867
|
+
/** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
|
|
29868
|
+
async rebuildSectionMesh() {
|
|
29439
29869
|
if (!this.meshDirty) return;
|
|
29440
|
-
|
|
29441
|
-
|
|
29442
|
-
|
|
29443
|
-
) : void 0;
|
|
29870
|
+
this.meshDirty = false;
|
|
29871
|
+
const generation = ++this.meshGeneration;
|
|
29872
|
+
const entries = [...this.sectionBlocks.values()].filter((entry) => !this.hiddenBlocks.has(entry.index));
|
|
29873
|
+
const next = entries.length ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize) : void 0;
|
|
29874
|
+
if (generation !== this.meshGeneration) {
|
|
29875
|
+
next?.dispose();
|
|
29876
|
+
return;
|
|
29877
|
+
}
|
|
29444
29878
|
this.sectionMesh?.dispose();
|
|
29445
29879
|
this.sectionMesh = next;
|
|
29446
29880
|
if (next) {
|
|
@@ -29448,7 +29882,6 @@ var Chunk = class _Chunk {
|
|
|
29448
29882
|
this.scene.add(next);
|
|
29449
29883
|
}
|
|
29450
29884
|
this.scene.dirty = true;
|
|
29451
|
-
this.meshDirty = false;
|
|
29452
29885
|
}
|
|
29453
29886
|
/** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
|
|
29454
29887
|
async clear(onBlocksChanged = this.onBlocksChanged) {
|
|
@@ -29457,6 +29890,7 @@ var Chunk = class _Chunk {
|
|
|
29457
29890
|
));
|
|
29458
29891
|
for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
|
|
29459
29892
|
this.sectionBlocks.clear();
|
|
29893
|
+
this.meshGeneration++;
|
|
29460
29894
|
this.sectionMesh?.dispose();
|
|
29461
29895
|
this.sectionMesh = void 0;
|
|
29462
29896
|
this.meshDirty = false;
|
|
@@ -29493,9 +29927,184 @@ var Chunk = class _Chunk {
|
|
|
29493
29927
|
);
|
|
29494
29928
|
}
|
|
29495
29929
|
};
|
|
29930
|
+
|
|
29931
|
+
// src/world/WorldStreamer.ts
|
|
29932
|
+
var WorldStreamer = class {
|
|
29933
|
+
constructor(world, source, options = {}) {
|
|
29934
|
+
this.world = world;
|
|
29935
|
+
this.source = source;
|
|
29936
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
29937
|
+
this.owned = /* @__PURE__ */ new Map();
|
|
29938
|
+
this.missing = /* @__PURE__ */ new Map();
|
|
29939
|
+
this.failed = /* @__PURE__ */ new Map();
|
|
29940
|
+
this.desired = [];
|
|
29941
|
+
this.disposed = false;
|
|
29942
|
+
this.loadRadius = options.loadRadius ?? 2;
|
|
29943
|
+
this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
|
|
29944
|
+
if (![this.loadRadius, this.unloadRadius].every((value) => Number.isInteger(value) && value >= 0 && value <= 32) || this.unloadRadius < this.loadRadius) {
|
|
29945
|
+
throw new RangeError("Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius");
|
|
29946
|
+
}
|
|
29947
|
+
}
|
|
29948
|
+
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
29949
|
+
get loadedChunks() {
|
|
29950
|
+
return [...this.loaded.values()].map((position) => ({ ...position }));
|
|
29951
|
+
}
|
|
29952
|
+
/** Returns source failures retained within the unload radius. Failed columns do not block other loads. */
|
|
29953
|
+
get failedChunks() {
|
|
29954
|
+
return [...this.failed.values()].map((failure) => ({ ...failure }));
|
|
29955
|
+
}
|
|
29956
|
+
/** Counts desired columns still waiting for a source read or placement, including the active column. */
|
|
29957
|
+
get pendingChunks() {
|
|
29958
|
+
return this.desired.filter((position) => this.needsLoad(position)).length;
|
|
29959
|
+
}
|
|
29960
|
+
/**
|
|
29961
|
+
* Updates the center in absolute chunk coordinates. Repeated calls share the active drain.
|
|
29962
|
+
* Source failures are recorded in failedChunks; other columns continue loading. Missing and failed
|
|
29963
|
+
* columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.
|
|
29964
|
+
* Placement and unload failures reject the drain; call update again to retry those operations.
|
|
29965
|
+
* Reads outside the latest load radius receive an abort signal; placement finishes before unloading.
|
|
29966
|
+
* Sources should represent an unchanged world during streaming.
|
|
29967
|
+
*/
|
|
29968
|
+
async update(x, z) {
|
|
29969
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
29970
|
+
if (![x, z].every((value) => Number.isSafeInteger(value) && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {
|
|
29971
|
+
throw new RangeError("Chunk coordinates must be safe integers within the streaming radius");
|
|
29972
|
+
}
|
|
29973
|
+
if (!this.center || this.center.x !== x || this.center.z !== z) {
|
|
29974
|
+
this.center = { x, z };
|
|
29975
|
+
this.desired = [];
|
|
29976
|
+
for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {
|
|
29977
|
+
for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {
|
|
29978
|
+
this.desired.push({ x: x + dx, z: z + dz });
|
|
29979
|
+
}
|
|
29980
|
+
}
|
|
29981
|
+
this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);
|
|
29982
|
+
if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
|
|
29983
|
+
this.loading.controller.abort();
|
|
29984
|
+
}
|
|
29985
|
+
}
|
|
29986
|
+
return this.running ??= Promise.resolve().then(() => this.drain());
|
|
29987
|
+
}
|
|
29988
|
+
/** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */
|
|
29989
|
+
async updatePosition(position) {
|
|
29990
|
+
if (![position.x, position.z].every(Number.isFinite)) throw new RangeError("Scene coordinates must be finite");
|
|
29991
|
+
return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));
|
|
29992
|
+
}
|
|
29993
|
+
/** Forgets recorded source failures and retries desired columns at the latest center. */
|
|
29994
|
+
async retryFailedChunks() {
|
|
29995
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
29996
|
+
this.failed.clear();
|
|
29997
|
+
if (this.center) await this.update(this.center.x, this.center.z);
|
|
29998
|
+
}
|
|
29999
|
+
/** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
|
|
30000
|
+
dispose() {
|
|
30001
|
+
this.disposed = true;
|
|
30002
|
+
this.desired = [];
|
|
30003
|
+
this.center = void 0;
|
|
30004
|
+
this.loading?.controller.abort();
|
|
30005
|
+
return this.disposal ??= Promise.resolve().then(async () => {
|
|
30006
|
+
await this.running?.catch(() => void 0);
|
|
30007
|
+
let failure;
|
|
30008
|
+
let failed = false;
|
|
30009
|
+
for (const position of this.owned.values()) {
|
|
30010
|
+
try {
|
|
30011
|
+
await this.unload(position);
|
|
30012
|
+
} catch (error) {
|
|
30013
|
+
failed = true;
|
|
30014
|
+
failure ??= error;
|
|
30015
|
+
}
|
|
30016
|
+
}
|
|
30017
|
+
this.missing.clear();
|
|
30018
|
+
this.failed.clear();
|
|
30019
|
+
if (failed) throw failure;
|
|
30020
|
+
}).catch((error) => {
|
|
30021
|
+
this.disposal = void 0;
|
|
30022
|
+
throw error;
|
|
30023
|
+
});
|
|
30024
|
+
}
|
|
30025
|
+
async drain() {
|
|
30026
|
+
try {
|
|
30027
|
+
while (!this.disposed) {
|
|
30028
|
+
for (const [key, position2] of this.missing) {
|
|
30029
|
+
if (!this.within(position2, this.unloadRadius)) this.missing.delete(key);
|
|
30030
|
+
}
|
|
30031
|
+
for (const [key, position2] of this.failed) {
|
|
30032
|
+
if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
|
|
30033
|
+
}
|
|
30034
|
+
const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
|
|
30035
|
+
if (obsolete) {
|
|
30036
|
+
await this.unload(obsolete);
|
|
30037
|
+
continue;
|
|
30038
|
+
}
|
|
30039
|
+
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
30040
|
+
if (!position) return;
|
|
30041
|
+
let chunk;
|
|
30042
|
+
const controller = new AbortController();
|
|
30043
|
+
this.loading = { position, controller };
|
|
30044
|
+
try {
|
|
30045
|
+
chunk = await this.source.getChunk(position.x, position.z, controller.signal);
|
|
30046
|
+
if (chunk) this.validateChunk(chunk, position);
|
|
30047
|
+
} catch (error) {
|
|
30048
|
+
if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {
|
|
30049
|
+
this.failed.set(this.key(position), { ...position, error });
|
|
30050
|
+
}
|
|
30051
|
+
continue;
|
|
30052
|
+
} finally {
|
|
30053
|
+
this.loading = void 0;
|
|
30054
|
+
}
|
|
30055
|
+
if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;
|
|
30056
|
+
if (!chunk) {
|
|
30057
|
+
this.missing.set(this.key(position), position);
|
|
30058
|
+
continue;
|
|
30059
|
+
}
|
|
30060
|
+
this.owned.set(this.key(position), position);
|
|
30061
|
+
try {
|
|
30062
|
+
await this.world.placeChunk(chunk);
|
|
30063
|
+
this.loaded.set(this.key(position), position);
|
|
30064
|
+
} catch (error) {
|
|
30065
|
+
try {
|
|
30066
|
+
await this.unload(position);
|
|
30067
|
+
} catch (cleanupError) {
|
|
30068
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
30069
|
+
try {
|
|
30070
|
+
Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
|
|
30071
|
+
} catch {
|
|
30072
|
+
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
30073
|
+
}
|
|
30074
|
+
throw failure;
|
|
30075
|
+
}
|
|
30076
|
+
throw error;
|
|
30077
|
+
}
|
|
30078
|
+
}
|
|
30079
|
+
} finally {
|
|
30080
|
+
this.running = void 0;
|
|
30081
|
+
}
|
|
30082
|
+
}
|
|
30083
|
+
needsLoad(position) {
|
|
30084
|
+
const key = this.key(position);
|
|
30085
|
+
return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
|
|
30086
|
+
}
|
|
30087
|
+
async unload(position) {
|
|
30088
|
+
await this.world.unloadChunkColumn(position.x, position.z);
|
|
30089
|
+
this.owned.delete(this.key(position));
|
|
30090
|
+
this.loaded.delete(this.key(position));
|
|
30091
|
+
}
|
|
30092
|
+
validateChunk(chunk, position) {
|
|
30093
|
+
if (chunk.x !== position.x || chunk.z !== position.z) {
|
|
30094
|
+
throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);
|
|
30095
|
+
}
|
|
30096
|
+
}
|
|
30097
|
+
within(position, radius) {
|
|
30098
|
+
return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;
|
|
30099
|
+
}
|
|
30100
|
+
key(position) {
|
|
30101
|
+
return `${position.x},${position.z}`;
|
|
30102
|
+
}
|
|
30103
|
+
};
|
|
29496
30104
|
export {
|
|
29497
30105
|
AXES,
|
|
29498
30106
|
AnvilParser,
|
|
30107
|
+
AnvilWorldSource,
|
|
29499
30108
|
ArchiveAssetSource,
|
|
29500
30109
|
AssetKey,
|
|
29501
30110
|
AssetLoadError,
|
|
@@ -29585,6 +30194,7 @@ export {
|
|
|
29585
30194
|
SchematicParser,
|
|
29586
30195
|
SectionMesh,
|
|
29587
30196
|
SectionModels,
|
|
30197
|
+
SectionWorker,
|
|
29588
30198
|
SkinImage,
|
|
29589
30199
|
SkinObject,
|
|
29590
30200
|
SkinPart,
|
|
@@ -29599,6 +30209,7 @@ export {
|
|
|
29599
30209
|
TintColors,
|
|
29600
30210
|
UVMapper,
|
|
29601
30211
|
VideoExporter,
|
|
30212
|
+
WorldStreamer,
|
|
29602
30213
|
WrappedImage,
|
|
29603
30214
|
addBox3WireframeToObject,
|
|
29604
30215
|
addWireframeToMesh,
|
|
@@ -29608,6 +30219,7 @@ export {
|
|
|
29608
30219
|
axisToVec3,
|
|
29609
30220
|
base64decode,
|
|
29610
30221
|
base64encode,
|
|
30222
|
+
buildSectionGeometry,
|
|
29611
30223
|
capeTextureSizes,
|
|
29612
30224
|
captureException,
|
|
29613
30225
|
changeEvent,
|
|
@@ -29659,6 +30271,7 @@ export {
|
|
|
29659
30271
|
sampleEntityAnimation,
|
|
29660
30272
|
sampleEntityKeyframes,
|
|
29661
30273
|
sampleFluid,
|
|
30274
|
+
sectionGeometryTransferables,
|
|
29662
30275
|
serializeBoxGeometryKey,
|
|
29663
30276
|
serializeImageKey,
|
|
29664
30277
|
serializeMaterialKey,
|
|
@@ -29676,6 +30289,7 @@ export {
|
|
|
29676
30289
|
toDegrees,
|
|
29677
30290
|
toRadians,
|
|
29678
30291
|
toTrianglesDrawMode,
|
|
29679
|
-
trimCanvas
|
|
30292
|
+
trimCanvas,
|
|
30293
|
+
yieldToEventLoop
|
|
29680
30294
|
};
|
|
29681
30295
|
//# sourceMappingURL=index.mjs.map
|