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/browser/index.mjs
CHANGED
|
@@ -191,6 +191,23 @@ async function sleep(timeout) {
|
|
|
191
191
|
setTimeout(() => resolve(), timeout);
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
|
+
function yieldToEventLoop() {
|
|
195
|
+
const scheduler = globalThis.scheduler;
|
|
196
|
+
if (scheduler?.yield) return scheduler.yield();
|
|
197
|
+
return new Promise((resolve) => {
|
|
198
|
+
if (typeof MessageChannel !== "undefined") {
|
|
199
|
+
const channel = new MessageChannel();
|
|
200
|
+
channel.port1.onmessage = () => {
|
|
201
|
+
channel.port1.close();
|
|
202
|
+
channel.port2.close();
|
|
203
|
+
resolve();
|
|
204
|
+
};
|
|
205
|
+
channel.port2.postMessage(void 0);
|
|
206
|
+
} else {
|
|
207
|
+
setTimeout(resolve, 0);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
194
211
|
function clampRotationDegrees(deg) {
|
|
195
212
|
return deg % 360;
|
|
196
213
|
}
|
|
@@ -18798,6 +18815,14 @@ var BrowserEnv = class {
|
|
|
18798
18815
|
version: version2
|
|
18799
18816
|
}));
|
|
18800
18817
|
}
|
|
18818
|
+
createWorker(name) {
|
|
18819
|
+
if (name !== "section" || typeof Worker === "undefined") return void 0;
|
|
18820
|
+
try {
|
|
18821
|
+
return new Worker(new URL("./section.worker.mjs", import.meta.url), { type: "module" });
|
|
18822
|
+
} catch {
|
|
18823
|
+
return void 0;
|
|
18824
|
+
}
|
|
18825
|
+
}
|
|
18801
18826
|
};
|
|
18802
18827
|
function registerBrowserEnv() {
|
|
18803
18828
|
const env = new BrowserEnv();
|
|
@@ -28500,6 +28525,16 @@ init_NBTHelper();
|
|
|
28500
28525
|
|
|
28501
28526
|
// src/world/ChunkData.ts
|
|
28502
28527
|
init_AssetKey();
|
|
28528
|
+
var blockKeys = /* @__PURE__ */ new Map();
|
|
28529
|
+
function blockKey(type) {
|
|
28530
|
+
let key = blockKeys.get(type);
|
|
28531
|
+
if (!key) {
|
|
28532
|
+
key = AssetKey.parse("blockstates", type);
|
|
28533
|
+
if (blockKeys.size >= 4096) blockKeys.clear();
|
|
28534
|
+
blockKeys.set(type, key);
|
|
28535
|
+
}
|
|
28536
|
+
return key;
|
|
28537
|
+
}
|
|
28503
28538
|
var ChunkData = class _ChunkData {
|
|
28504
28539
|
constructor() {
|
|
28505
28540
|
this.indices = new Uint16Array(4096);
|
|
@@ -28527,10 +28562,10 @@ var ChunkData = class _ChunkData {
|
|
|
28527
28562
|
let key;
|
|
28528
28563
|
let nbt;
|
|
28529
28564
|
if (block && !_ChunkData.isAir(block)) {
|
|
28530
|
-
const type =
|
|
28565
|
+
const type = blockKey(block.type).toNamespacedString();
|
|
28531
28566
|
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28532
28567
|
state = Object.freeze(Object.keys(properties).length ? { type, properties: Object.freeze(properties) } : { type });
|
|
28533
|
-
key =
|
|
28568
|
+
key = _ChunkData.paletteKey(block);
|
|
28534
28569
|
if (block.nbt !== void 0) nbt = structuredClone(block.nbt);
|
|
28535
28570
|
}
|
|
28536
28571
|
const previousId = this.indices[index];
|
|
@@ -28567,9 +28602,14 @@ var ChunkData = class _ChunkData {
|
|
|
28567
28602
|
}
|
|
28568
28603
|
static isAir(block) {
|
|
28569
28604
|
if (typeof block === "undefined" || typeof block.type === "undefined") return true;
|
|
28570
|
-
const key =
|
|
28605
|
+
const key = blockKey(block.type);
|
|
28571
28606
|
return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
|
|
28572
28607
|
}
|
|
28608
|
+
/** Identifies a normalized block type and its sorted properties, excluding per-cell NBT. */
|
|
28609
|
+
static paletteKey(block) {
|
|
28610
|
+
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28611
|
+
return JSON.stringify([blockKey(block.type).toNamespacedString(), properties]);
|
|
28612
|
+
}
|
|
28573
28613
|
static copyBlock(state, nbt) {
|
|
28574
28614
|
const block = { type: state.type };
|
|
28575
28615
|
if (state.properties) block.properties = { ...state.properties };
|
|
@@ -28749,19 +28789,150 @@ var AnvilParser = class {
|
|
|
28749
28789
|
const perLong = Math.floor(64 / bits);
|
|
28750
28790
|
const expected = padded ? Math.ceil(4096 / perLong) : Math.ceil(4096 * bits / 64);
|
|
28751
28791
|
if (tag.value.length !== expected) throw new MineRenderError(`Invalid Anvil block-state array length: expected ${expected}`);
|
|
28752
|
-
const words = tag.value.map(([high, low]) =>
|
|
28753
|
-
const mask = (
|
|
28792
|
+
const words = tag.value.map(([high, low]) => [high >>> 0, low >>> 0]);
|
|
28793
|
+
const mask = (1 << bits) - 1;
|
|
28754
28794
|
for (let index = 0; index < 4096; index++) {
|
|
28755
|
-
const
|
|
28756
|
-
const
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28795
|
+
const bitPos = index * bits;
|
|
28796
|
+
const word = padded ? Math.floor(index / perLong) : bitPos >>> 6;
|
|
28797
|
+
const shift = padded ? index % perLong * bits : bitPos & 63;
|
|
28798
|
+
const [high, low] = words[word];
|
|
28799
|
+
let value = shift < 32 ? low >>> shift : high >>> shift - 32;
|
|
28800
|
+
if (shift < 32 && shift + bits > 32) value += high * 2 ** (32 - shift);
|
|
28801
|
+
if (!padded && shift + bits > 64) value += words[word + 1][1] * 2 ** (64 - shift);
|
|
28802
|
+
indices[index] = value & mask;
|
|
28760
28803
|
}
|
|
28761
28804
|
return indices;
|
|
28762
28805
|
}
|
|
28763
28806
|
};
|
|
28764
28807
|
|
|
28808
|
+
// src/world/AnvilWorldSource.ts
|
|
28809
|
+
init_MineRenderError();
|
|
28810
|
+
async function abortable2(promise, signal) {
|
|
28811
|
+
if (!signal) return promise;
|
|
28812
|
+
let abort;
|
|
28813
|
+
const cancelled = new Promise((_, reject) => {
|
|
28814
|
+
abort = () => reject(signal.reason);
|
|
28815
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
28816
|
+
if (signal.aborted) abort();
|
|
28817
|
+
});
|
|
28818
|
+
try {
|
|
28819
|
+
return await Promise.race([promise, cancelled]);
|
|
28820
|
+
} finally {
|
|
28821
|
+
signal.removeEventListener("abort", abort);
|
|
28822
|
+
}
|
|
28823
|
+
}
|
|
28824
|
+
var AnvilWorldSource = class {
|
|
28825
|
+
constructor(readRegion, options = {}) {
|
|
28826
|
+
this.readRegion = readRegion;
|
|
28827
|
+
this.regions = /* @__PURE__ */ new Map();
|
|
28828
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
28829
|
+
this.cachedBytes = 0;
|
|
28830
|
+
this.maxCachedRegions = options.maxCachedRegions ?? 4;
|
|
28831
|
+
this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
|
|
28832
|
+
for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
|
|
28833
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
|
|
28834
|
+
}
|
|
28835
|
+
}
|
|
28836
|
+
/** Decodes one column at absolute chunk coordinates. Aborting a caller leaves other shared readers active. */
|
|
28837
|
+
async getChunk(x, z, signal) {
|
|
28838
|
+
if (!Number.isSafeInteger(x) || !Number.isSafeInteger(z)) {
|
|
28839
|
+
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
28840
|
+
}
|
|
28841
|
+
const regionX = Math.floor(x / 32), regionZ = Math.floor(z / 32);
|
|
28842
|
+
const data = await this.getRegion(regionX, regionZ, signal);
|
|
28843
|
+
signal?.throwIfAborted();
|
|
28844
|
+
if (!data) return void 0;
|
|
28845
|
+
const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
|
|
28846
|
+
signal?.throwIfAborted();
|
|
28847
|
+
if (chunk && (chunk.x !== x || chunk.z !== z)) {
|
|
28848
|
+
throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
|
|
28849
|
+
}
|
|
28850
|
+
return chunk;
|
|
28851
|
+
}
|
|
28852
|
+
/** Releases cached regions. Reads already in progress finish without repopulating this cache. */
|
|
28853
|
+
clearCache() {
|
|
28854
|
+
this.regions.clear();
|
|
28855
|
+
this.pending.clear();
|
|
28856
|
+
this.cachedBytes = 0;
|
|
28857
|
+
}
|
|
28858
|
+
getRegion(x, z, signal) {
|
|
28859
|
+
signal?.throwIfAborted();
|
|
28860
|
+
const key = `${x},${z}`;
|
|
28861
|
+
const cached2 = this.regions.get(key);
|
|
28862
|
+
if (cached2) {
|
|
28863
|
+
this.regions.delete(key);
|
|
28864
|
+
this.regions.set(key, cached2);
|
|
28865
|
+
return Promise.resolve(cached2.data);
|
|
28866
|
+
}
|
|
28867
|
+
let loading = this.pending.get(key);
|
|
28868
|
+
if (!loading) {
|
|
28869
|
+
const controller = new AbortController();
|
|
28870
|
+
loading = {
|
|
28871
|
+
controller,
|
|
28872
|
+
waiters: 0,
|
|
28873
|
+
settled: false,
|
|
28874
|
+
promise: Promise.resolve().then(() => {
|
|
28875
|
+
controller.signal.throwIfAborted();
|
|
28876
|
+
return this.readRegion(x, z, controller.signal);
|
|
28877
|
+
}).then((data) => {
|
|
28878
|
+
controller.signal.throwIfAborted();
|
|
28879
|
+
let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);
|
|
28880
|
+
if (bytes && bytes.byteLength !== bytes.buffer.byteLength) bytes = new Uint8Array(bytes);
|
|
28881
|
+
if (bytes) AnvilParser.getChunkList(bytes);
|
|
28882
|
+
loading.settled = true;
|
|
28883
|
+
if (this.pending.get(key) === loading) this.cacheRegion(key, bytes);
|
|
28884
|
+
return bytes;
|
|
28885
|
+
}).finally(() => {
|
|
28886
|
+
loading.settled = true;
|
|
28887
|
+
if (this.pending.get(key) === loading) this.pending.delete(key);
|
|
28888
|
+
})
|
|
28889
|
+
};
|
|
28890
|
+
this.pending.set(key, loading);
|
|
28891
|
+
}
|
|
28892
|
+
const read = loading;
|
|
28893
|
+
read.waiters++;
|
|
28894
|
+
return new Promise((resolve, reject) => {
|
|
28895
|
+
let finished = false;
|
|
28896
|
+
const release = () => {
|
|
28897
|
+
if (finished) return false;
|
|
28898
|
+
finished = true;
|
|
28899
|
+
signal?.removeEventListener("abort", abort);
|
|
28900
|
+
read.waiters--;
|
|
28901
|
+
return true;
|
|
28902
|
+
};
|
|
28903
|
+
const abort = () => {
|
|
28904
|
+
if (!release()) return;
|
|
28905
|
+
if (!read.waiters && !read.settled) {
|
|
28906
|
+
if (this.pending.get(key) === read) this.pending.delete(key);
|
|
28907
|
+
read.controller.abort(signal.reason);
|
|
28908
|
+
}
|
|
28909
|
+
reject(signal.reason);
|
|
28910
|
+
};
|
|
28911
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
28912
|
+
read.promise.then(
|
|
28913
|
+
(data) => {
|
|
28914
|
+
if (release()) resolve(data);
|
|
28915
|
+
},
|
|
28916
|
+
(error) => {
|
|
28917
|
+
if (release()) reject(error);
|
|
28918
|
+
}
|
|
28919
|
+
);
|
|
28920
|
+
if (signal?.aborted) abort();
|
|
28921
|
+
});
|
|
28922
|
+
}
|
|
28923
|
+
cacheRegion(key, data) {
|
|
28924
|
+
const size = data?.byteLength ?? 0;
|
|
28925
|
+
if (!this.maxCachedRegions || size > this.maxCachedBytes) return;
|
|
28926
|
+
this.regions.set(key, { data, size });
|
|
28927
|
+
this.cachedBytes += size;
|
|
28928
|
+
while (this.regions.size > this.maxCachedRegions || this.cachedBytes > this.maxCachedBytes) {
|
|
28929
|
+
const oldest = this.regions.keys().next().value;
|
|
28930
|
+
this.cachedBytes -= this.regions.get(oldest).size;
|
|
28931
|
+
this.regions.delete(oldest);
|
|
28932
|
+
}
|
|
28933
|
+
}
|
|
28934
|
+
};
|
|
28935
|
+
|
|
28765
28936
|
// src/world/Chunk.ts
|
|
28766
28937
|
import { Vector3 as Vector317 } from "three";
|
|
28767
28938
|
init_BlockStates();
|
|
@@ -28769,8 +28940,8 @@ init_AssetKey();
|
|
|
28769
28940
|
|
|
28770
28941
|
// src/world/MineRenderWorld.ts
|
|
28771
28942
|
init_Model();
|
|
28943
|
+
init_util();
|
|
28772
28944
|
import { Vector3 as Vector316 } from "three";
|
|
28773
|
-
init_BatchedExecutor();
|
|
28774
28945
|
init_AssetKey();
|
|
28775
28946
|
init_BlockStates();
|
|
28776
28947
|
init_CubeFace();
|
|
@@ -28915,7 +29086,7 @@ var MineRenderWorld = class {
|
|
|
28915
29086
|
* Places a structure's blocks at their stored positions, then refreshes neighboring faces.
|
|
28916
29087
|
* Entity NBT is retained by the structure but is not rendered.
|
|
28917
29088
|
*
|
|
28918
|
-
* @param useBatches -
|
|
29089
|
+
* @param useBatches - Yields between chunks by default. Set to `false` for sequential placement.
|
|
28919
29090
|
* @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
|
|
28920
29091
|
*/
|
|
28921
29092
|
async placeMultiBlock(multiblock, useBatches = true, executor) {
|
|
@@ -28932,8 +29103,10 @@ var MineRenderWorld = class {
|
|
|
28932
29103
|
};
|
|
28933
29104
|
const place = (block) => this.placeBlock(new Vector316(...block.position), block, collect);
|
|
28934
29105
|
const keys = /* @__PURE__ */ new Map();
|
|
29106
|
+
const types = /* @__PURE__ */ new Set();
|
|
28935
29107
|
for (const block of multiblock.blocks) {
|
|
28936
|
-
if (Chunk.isAir(block)) continue;
|
|
29108
|
+
if (Chunk.isAir(block) || types.has(block.type)) continue;
|
|
29109
|
+
types.add(block.type);
|
|
28937
29110
|
const key = AssetKey.parse("blockstates", block.type);
|
|
28938
29111
|
keys.set(key.serialize(), key);
|
|
28939
29112
|
}
|
|
@@ -28942,6 +29115,23 @@ var MineRenderWorld = class {
|
|
|
28942
29115
|
for (const block of multiblock.blocks) await place(block);
|
|
28943
29116
|
return;
|
|
28944
29117
|
}
|
|
29118
|
+
if (!executor) {
|
|
29119
|
+
const groups2 = /* @__PURE__ */ new Map();
|
|
29120
|
+
const position = new Vector316();
|
|
29121
|
+
for (const block of multiblock.blocks) {
|
|
29122
|
+
const [x, y, z] = block.position;
|
|
29123
|
+
this.validatePosBounds(position.set(x, y, z));
|
|
29124
|
+
const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
|
|
29125
|
+
const key = `${cx}_${cy}_${cz}`;
|
|
29126
|
+
const chunk = this._chunks.get(key) ?? (Chunk.isAir(block) ? void 0 : this.getOrCreateChunkAt(position));
|
|
29127
|
+
if (!chunk) continue;
|
|
29128
|
+
let group = groups2.get(chunk);
|
|
29129
|
+
if (!group) groups2.set(chunk, group = []);
|
|
29130
|
+
group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
|
|
29131
|
+
}
|
|
29132
|
+
await this.placeChunkGroups(groups2, changes);
|
|
29133
|
+
return;
|
|
29134
|
+
}
|
|
28945
29135
|
const positions = /* @__PURE__ */ new Map();
|
|
28946
29136
|
for (const block of multiblock.blocks) {
|
|
28947
29137
|
const key = block.position.join(",");
|
|
@@ -28950,19 +29140,34 @@ var MineRenderWorld = class {
|
|
|
28950
29140
|
else positions.set(key, [block]);
|
|
28951
29141
|
}
|
|
28952
29142
|
const groups = [...positions.values()];
|
|
28953
|
-
|
|
28954
|
-
|
|
28955
|
-
|
|
28956
|
-
|
|
28957
|
-
|
|
28958
|
-
|
|
28959
|
-
|
|
28960
|
-
|
|
28961
|
-
|
|
28962
|
-
|
|
29143
|
+
for (let i = 0; i < groups.length; i += executor.batch) {
|
|
29144
|
+
const results = await Promise.allSettled(groups.slice(i, i + executor.batch).map(
|
|
29145
|
+
(blocks) => executor.submit(async () => {
|
|
29146
|
+
for (const block of blocks) await place(block);
|
|
29147
|
+
})
|
|
29148
|
+
));
|
|
29149
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
29150
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
29151
|
+
}
|
|
29152
|
+
}
|
|
29153
|
+
async placeChunkGroups(groups, changes) {
|
|
29154
|
+
let sliceStart = performance.now();
|
|
29155
|
+
for (const [chunk, blocks] of groups) {
|
|
29156
|
+
let positions;
|
|
29157
|
+
try {
|
|
29158
|
+
positions = await chunk.placeBlocks(blocks);
|
|
29159
|
+
} finally {
|
|
29160
|
+
positions ?? (positions = blocks.map(({ index }) => new Vector316(
|
|
29161
|
+
chunk.x * 16 + index % 16,
|
|
29162
|
+
chunk.y * 16 + Math.floor(index / 256),
|
|
29163
|
+
chunk.z * 16 + Math.floor(index / 16) % 16
|
|
29164
|
+
)));
|
|
29165
|
+
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
29166
|
+
}
|
|
29167
|
+
if (performance.now() - sliceStart > 8) {
|
|
29168
|
+
await yieldToEventLoop();
|
|
29169
|
+
sliceStart = performance.now();
|
|
28963
29170
|
}
|
|
28964
|
-
} finally {
|
|
28965
|
-
if (!executor) queue.stop();
|
|
28966
29171
|
}
|
|
28967
29172
|
}
|
|
28968
29173
|
/** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
|
|
@@ -28970,7 +29175,24 @@ var MineRenderWorld = class {
|
|
|
28970
29175
|
const changes = /* @__PURE__ */ new Map();
|
|
28971
29176
|
try {
|
|
28972
29177
|
await this.clearChunkColumn(chunk.x, chunk.z, changes);
|
|
29178
|
+
const groups = /* @__PURE__ */ new Map();
|
|
28973
29179
|
for (const section of chunk.sections) {
|
|
29180
|
+
if (!executor) {
|
|
29181
|
+
const blocks2 = [];
|
|
29182
|
+
for (let index = 0; index < 4096; index++) {
|
|
29183
|
+
const block = section.data.get(index);
|
|
29184
|
+
if (block) blocks2.push({ index, block });
|
|
29185
|
+
}
|
|
29186
|
+
if (blocks2.length) {
|
|
29187
|
+
const position = new Vector316(chunk.x * 16, section.y * 16, chunk.z * 16);
|
|
29188
|
+
this.validatePosBounds(position);
|
|
29189
|
+
const target = this.getOrCreateChunkAt(position);
|
|
29190
|
+
const previous = groups.get(target);
|
|
29191
|
+
if (previous) previous.push(...blocks2);
|
|
29192
|
+
else groups.set(target, blocks2);
|
|
29193
|
+
}
|
|
29194
|
+
continue;
|
|
29195
|
+
}
|
|
28974
29196
|
const blocks = [];
|
|
28975
29197
|
for (let index = 0; index < 4096; index++) {
|
|
28976
29198
|
const block = section.data.get(index);
|
|
@@ -28985,6 +29207,7 @@ var MineRenderWorld = class {
|
|
|
28985
29207
|
}
|
|
28986
29208
|
await this.placeBlocks({ size: [16, 16, 16], blocks }, true, executor, changes);
|
|
28987
29209
|
}
|
|
29210
|
+
if (!executor) await this.placeChunkGroups(groups, changes);
|
|
28988
29211
|
} finally {
|
|
28989
29212
|
await this.updateCulling([...changes.values()]);
|
|
28990
29213
|
}
|
|
@@ -29025,19 +29248,23 @@ var MineRenderWorld = class {
|
|
|
29025
29248
|
this.sectionModels?.clear();
|
|
29026
29249
|
}
|
|
29027
29250
|
updateCulling(positions) {
|
|
29251
|
+
const neighbor = new Vector316();
|
|
29028
29252
|
for (const pos of positions) {
|
|
29029
|
-
|
|
29030
|
-
|
|
29031
|
-
|
|
29032
|
-
this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
|
|
29033
|
-
}
|
|
29253
|
+
const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
|
|
29254
|
+
const chunk = this._chunks.get(`${cx}_${cy}_${cz}`);
|
|
29255
|
+
const lx = pos.x - cx * 16, ly = pos.y - cy * 16, lz = pos.z - cz * 16;
|
|
29034
29256
|
for (let y = -1; y <= 1; y++) {
|
|
29035
29257
|
for (let z = -1; z <= 1; z++) {
|
|
29036
29258
|
for (let x = -1; x <= 1; x++) {
|
|
29037
|
-
const
|
|
29038
|
-
|
|
29039
|
-
|
|
29040
|
-
|
|
29259
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29260
|
+
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)}`);
|
|
29261
|
+
if (!section) continue;
|
|
29262
|
+
const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29263
|
+
let pending = this.pendingCulling.get(section);
|
|
29264
|
+
if (pending?.has(index)) continue;
|
|
29265
|
+
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;
|
|
29266
|
+
if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
|
|
29267
|
+
pending.add(index);
|
|
29041
29268
|
}
|
|
29042
29269
|
}
|
|
29043
29270
|
}
|
|
@@ -29045,39 +29272,38 @@ var MineRenderWorld = class {
|
|
|
29045
29272
|
return this.culling ?? (this.culling = Promise.resolve().then(async () => {
|
|
29046
29273
|
try {
|
|
29047
29274
|
while (this.pendingCulling.size) {
|
|
29048
|
-
const batch = [...this.pendingCulling
|
|
29275
|
+
const batch = [...this.pendingCulling];
|
|
29049
29276
|
this.pendingCulling.clear();
|
|
29050
|
-
const
|
|
29051
|
-
|
|
29052
|
-
|
|
29053
|
-
|
|
29054
|
-
|
|
29055
|
-
|
|
29056
|
-
|
|
29057
|
-
|
|
29058
|
-
|
|
29059
|
-
|
|
29060
|
-
|
|
29061
|
-
|
|
29062
|
-
|
|
29063
|
-
|
|
29064
|
-
|
|
29065
|
-
|
|
29066
|
-
|
|
29067
|
-
|
|
29068
|
-
};
|
|
29069
|
-
});
|
|
29070
|
-
}
|
|
29071
|
-
let mask = 0;
|
|
29072
|
-
for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
|
|
29073
|
-
const neighbor = pos.clone().add(new Vector316(...offset));
|
|
29074
|
-
if (this.getChunkAt(neighbor)?.isOccludingAt(neighbor)) {
|
|
29075
|
-
mask |= 1 << face;
|
|
29277
|
+
for (const [chunk, indices] of batch) {
|
|
29278
|
+
for (const index of indices) {
|
|
29279
|
+
if (!chunk.isBlockVisibleIndex(index)) continue;
|
|
29280
|
+
const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
|
|
29281
|
+
const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
|
|
29282
|
+
const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
|
|
29283
|
+
if (block.object?.fluidKind) {
|
|
29284
|
+
await block.object.updateFluid((x, y, z) => {
|
|
29285
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29286
|
+
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)}`);
|
|
29287
|
+
const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29288
|
+
const object3 = section?.isBlockVisibleIndex(neighborIndex) ? section.getBlockAt(neighbor.set(wx + x, wy + y, wz + z))?.object : void 0;
|
|
29289
|
+
return {
|
|
29290
|
+
fluid: object3?.fluidKind,
|
|
29291
|
+
level: object3?.fluidLevel,
|
|
29292
|
+
solid: section?.isOccludingIndex(neighborIndex) ?? false
|
|
29293
|
+
};
|
|
29294
|
+
});
|
|
29076
29295
|
}
|
|
29296
|
+
let mask = 0;
|
|
29297
|
+
for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
|
|
29298
|
+
const offset = CUBE_FACE_OFFSETS[face];
|
|
29299
|
+
const nx = lx + offset[0], ny = ly + offset[1], nz = lz + offset[2];
|
|
29300
|
+
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)}`);
|
|
29301
|
+
if (section?.isOccludingIndex((ny & 15) * 256 + (nz & 15) * 16 + (nx & 15))) mask |= 1 << face;
|
|
29302
|
+
}
|
|
29303
|
+
await chunk.setCullMaskIndex(index, mask);
|
|
29077
29304
|
}
|
|
29078
|
-
await chunk.setCullMaskAt(pos, mask);
|
|
29079
29305
|
}
|
|
29080
|
-
|
|
29306
|
+
await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
|
|
29081
29307
|
}
|
|
29082
29308
|
} finally {
|
|
29083
29309
|
this.culling = void 0;
|
|
@@ -29138,8 +29364,229 @@ init_Model();
|
|
|
29138
29364
|
|
|
29139
29365
|
// src/world/SectionMesh.ts
|
|
29140
29366
|
init_CanvasCompat();
|
|
29367
|
+
init_CubeFace();
|
|
29141
29368
|
init_Materials();
|
|
29142
|
-
import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group3, Mesh as Mesh9 } from "three";
|
|
29369
|
+
import { BufferGeometry as BufferGeometry8, Float32BufferAttribute as Float32BufferAttribute8, Group as Group3, Mesh as Mesh9, Uint32BufferAttribute } from "three";
|
|
29370
|
+
|
|
29371
|
+
// src/world/SectionGeometry.ts
|
|
29372
|
+
init_CubeFace();
|
|
29373
|
+
function buildSectionGeometry(input) {
|
|
29374
|
+
const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize } = input;
|
|
29375
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29376
|
+
if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
|
|
29377
|
+
const visible = [];
|
|
29378
|
+
const visibleAtlases = /* @__PURE__ */ new Set();
|
|
29379
|
+
for (let entry = 0; entry < count; entry++) {
|
|
29380
|
+
const template = templateData[templates[entry]];
|
|
29381
|
+
if (!template.cullFaces.some((direction) => !(cullMasks[entry] & direction))) continue;
|
|
29382
|
+
visible.push(entry);
|
|
29383
|
+
visibleAtlases.add(template.atlas);
|
|
29384
|
+
}
|
|
29385
|
+
const pages = [];
|
|
29386
|
+
const locations = /* @__PURE__ */ new Map();
|
|
29387
|
+
for (const atlas of [...visibleAtlases].sort((a, b) => atlases[b].height - atlases[a].height)) {
|
|
29388
|
+
const { width, height } = atlases[atlas];
|
|
29389
|
+
if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
|
|
29390
|
+
let selected = -1;
|
|
29391
|
+
for (let i = 0; i < pages.length; i++) {
|
|
29392
|
+
const page2 = pages[i];
|
|
29393
|
+
const nextRow = page2.x + width > maxAtlasSize;
|
|
29394
|
+
if ((nextRow ? page2.y + page2.rowHeight : page2.y) + height > maxAtlasSize) continue;
|
|
29395
|
+
if (nextRow) {
|
|
29396
|
+
page2.y += page2.rowHeight;
|
|
29397
|
+
page2.x = 0;
|
|
29398
|
+
page2.rowHeight = 0;
|
|
29399
|
+
}
|
|
29400
|
+
selected = i;
|
|
29401
|
+
break;
|
|
29402
|
+
}
|
|
29403
|
+
if (selected < 0) {
|
|
29404
|
+
selected = pages.length;
|
|
29405
|
+
pages.push({ placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 });
|
|
29406
|
+
}
|
|
29407
|
+
const page = pages[selected];
|
|
29408
|
+
page.placements.push({ atlas, x: page.x, y: page.y });
|
|
29409
|
+
locations.set(atlas, { page: selected, x: page.x, y: page.y });
|
|
29410
|
+
page.x += width;
|
|
29411
|
+
page.rowHeight = Math.max(page.rowHeight, height);
|
|
29412
|
+
page.width = Math.max(page.width, page.x);
|
|
29413
|
+
page.height = Math.max(page.height, page.y + height);
|
|
29414
|
+
}
|
|
29415
|
+
for (const entry of visible) pages[locations.get(templateData[templates[entry]].atlas).page].entries.push(entry);
|
|
29416
|
+
return pages.map((page) => {
|
|
29417
|
+
let faces = 0;
|
|
29418
|
+
for (const entry of page.entries) {
|
|
29419
|
+
const template = templateData[templates[entry]];
|
|
29420
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29421
|
+
if (!(cullMasks[entry] & template.cullFaces[face])) faces++;
|
|
29422
|
+
}
|
|
29423
|
+
}
|
|
29424
|
+
const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
|
|
29425
|
+
const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
|
|
29426
|
+
const outputIndices = new Uint32Array(faces * 6);
|
|
29427
|
+
let vertex = 0, indexOffset = 0;
|
|
29428
|
+
for (const entry of page.entries) {
|
|
29429
|
+
const template = templateData[templates[entry]];
|
|
29430
|
+
const atlas = atlases[template.atlas];
|
|
29431
|
+
const placement = locations.get(template.atlas);
|
|
29432
|
+
const mapU = (u) => (placement.x + u * atlas.width) / page.width;
|
|
29433
|
+
const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
|
|
29434
|
+
const offsetX = indices[entry] % 16 * 16;
|
|
29435
|
+
const offsetY = Math.floor(indices[entry] / 256) * 16;
|
|
29436
|
+
const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
|
|
29437
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29438
|
+
if (cullMasks[entry] & template.cullFaces[face]) continue;
|
|
29439
|
+
const start = vertex;
|
|
29440
|
+
for (let corner = 0; corner < 4; corner++, vertex++) {
|
|
29441
|
+
const source = face * 4 + corner;
|
|
29442
|
+
positions[vertex * 3] = template.positions[source * 3] + offsetX;
|
|
29443
|
+
positions[vertex * 3 + 1] = template.positions[source * 3 + 1] + offsetY;
|
|
29444
|
+
positions[vertex * 3 + 2] = template.positions[source * 3 + 2] + offsetZ;
|
|
29445
|
+
normals[vertex * 3] = template.normals[source * 3];
|
|
29446
|
+
normals[vertex * 3 + 1] = template.normals[source * 3 + 1];
|
|
29447
|
+
normals[vertex * 3 + 2] = template.normals[source * 3 + 2];
|
|
29448
|
+
uvs[vertex * 2] = mapU(template.uvs[source * 2]);
|
|
29449
|
+
uvs[vertex * 2 + 1] = mapV(template.uvs[source * 2 + 1]);
|
|
29450
|
+
uvBounds[vertex * 4] = mapU(template.uvBounds[source * 4]);
|
|
29451
|
+
uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
|
|
29452
|
+
uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
|
|
29453
|
+
uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
|
|
29454
|
+
colors[vertex * 3] = template.colors[source * 3];
|
|
29455
|
+
colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
|
|
29456
|
+
colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
|
|
29457
|
+
}
|
|
29458
|
+
for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[face * 6 + corner] - face * 4;
|
|
29459
|
+
}
|
|
29460
|
+
}
|
|
29461
|
+
return { width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
|
|
29462
|
+
});
|
|
29463
|
+
}
|
|
29464
|
+
function sectionGeometryTransferables(pages) {
|
|
29465
|
+
return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
|
|
29466
|
+
}
|
|
29467
|
+
|
|
29468
|
+
// src/world/SectionWorker.ts
|
|
29469
|
+
init_Env();
|
|
29470
|
+
var _SectionWorker = class _SectionWorker {
|
|
29471
|
+
constructor(worker) {
|
|
29472
|
+
this.worker = worker;
|
|
29473
|
+
this.nextId = 0;
|
|
29474
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
29475
|
+
worker.addEventListener("message", (event) => {
|
|
29476
|
+
const reply = event.data;
|
|
29477
|
+
const pending = this.pending.get(reply.id);
|
|
29478
|
+
if (!pending) return;
|
|
29479
|
+
this.pending.delete(reply.id);
|
|
29480
|
+
if (reply.type === "pages") pending.resolve(reply.pages);
|
|
29481
|
+
else pending.reject(new Error(reply.message));
|
|
29482
|
+
});
|
|
29483
|
+
worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
|
|
29484
|
+
worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
|
|
29485
|
+
}
|
|
29486
|
+
/** The shared worker, or undefined when the environment has none or a previous worker failed. */
|
|
29487
|
+
static shared() {
|
|
29488
|
+
if (!this.initialized) {
|
|
29489
|
+
this.initialized = true;
|
|
29490
|
+
const worker = Env.provider.createWorker?.("section");
|
|
29491
|
+
if (worker) this.instance = new _SectionWorker(worker);
|
|
29492
|
+
}
|
|
29493
|
+
return this.instance;
|
|
29494
|
+
}
|
|
29495
|
+
/** Builds geometry without transferring the input buffers away from the caller. */
|
|
29496
|
+
build(input) {
|
|
29497
|
+
if (!this.worker) return Promise.reject(new Error("Section worker terminated"));
|
|
29498
|
+
return new Promise((resolve, reject) => {
|
|
29499
|
+
const id = this.nextId++;
|
|
29500
|
+
this.pending.set(id, { resolve, reject });
|
|
29501
|
+
this.worker.postMessage({ id, type: "build", input });
|
|
29502
|
+
});
|
|
29503
|
+
}
|
|
29504
|
+
/** Rejects pending builds, terminates the worker, and makes shared() return undefined. */
|
|
29505
|
+
terminate() {
|
|
29506
|
+
this.fail("Section worker terminated");
|
|
29507
|
+
}
|
|
29508
|
+
fail(message) {
|
|
29509
|
+
const error = new Error(message);
|
|
29510
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
29511
|
+
this.pending.clear();
|
|
29512
|
+
this.worker?.terminate();
|
|
29513
|
+
this.worker = void 0;
|
|
29514
|
+
_SectionWorker.instance = void 0;
|
|
29515
|
+
}
|
|
29516
|
+
};
|
|
29517
|
+
_SectionWorker.initialized = false;
|
|
29518
|
+
var SectionWorker = _SectionWorker;
|
|
29519
|
+
|
|
29520
|
+
// src/world/SectionMesh.ts
|
|
29521
|
+
var templateCache = /* @__PURE__ */ new WeakMap();
|
|
29522
|
+
function toInput(entries, maxAtlasSize) {
|
|
29523
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29524
|
+
const atlases = [];
|
|
29525
|
+
const atlasIds = /* @__PURE__ */ new Map();
|
|
29526
|
+
const templateIds = /* @__PURE__ */ new Map();
|
|
29527
|
+
const input = {
|
|
29528
|
+
count: entries.length,
|
|
29529
|
+
indices: new Uint16Array(entries.length),
|
|
29530
|
+
templates: new Uint16Array(entries.length),
|
|
29531
|
+
cullMasks: new Uint8Array(entries.length),
|
|
29532
|
+
templateData: [],
|
|
29533
|
+
atlases: [],
|
|
29534
|
+
maxAtlasSize
|
|
29535
|
+
};
|
|
29536
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29537
|
+
const { index, template, cullMask } = entries[i];
|
|
29538
|
+
let id = templateIds.get(template);
|
|
29539
|
+
if (id === void 0) {
|
|
29540
|
+
let atlas = atlasIds.get(template.atlas);
|
|
29541
|
+
if (atlas === void 0) {
|
|
29542
|
+
atlas = atlases.length;
|
|
29543
|
+
atlasIds.set(template.atlas, atlas);
|
|
29544
|
+
atlases.push(template.atlas);
|
|
29545
|
+
input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height });
|
|
29546
|
+
}
|
|
29547
|
+
let data = templateCache.get(template);
|
|
29548
|
+
if (!data) {
|
|
29549
|
+
data = {
|
|
29550
|
+
positions: new Float32Array(72),
|
|
29551
|
+
normals: new Float32Array(72),
|
|
29552
|
+
uvs: new Float32Array(48),
|
|
29553
|
+
uvBounds: new Float32Array(96),
|
|
29554
|
+
colors: new Float32Array(72),
|
|
29555
|
+
indices: new Uint16Array(36),
|
|
29556
|
+
cullFaces: new Uint8Array(template.cullFaces),
|
|
29557
|
+
atlas: 0
|
|
29558
|
+
};
|
|
29559
|
+
const position = template.geometry.getAttribute("position");
|
|
29560
|
+
const normal = template.geometry.getAttribute("normal");
|
|
29561
|
+
const uv = template.geometry.getAttribute("uv");
|
|
29562
|
+
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29563
|
+
const color2 = template.geometry.getAttribute("color");
|
|
29564
|
+
for (let vertex = 0; vertex < CUBE_FACES.length * 4; vertex++) {
|
|
29565
|
+
data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
|
|
29566
|
+
data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
|
|
29567
|
+
data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
|
|
29568
|
+
data.uvBounds.set([
|
|
29569
|
+
bounds?.getX(vertex) ?? 0,
|
|
29570
|
+
bounds?.getY(vertex) ?? 0,
|
|
29571
|
+
bounds?.getZ(vertex) ?? 1,
|
|
29572
|
+
bounds?.getW(vertex) ?? 1
|
|
29573
|
+
], vertex * 4);
|
|
29574
|
+
data.colors.set([color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1], vertex * 3);
|
|
29575
|
+
}
|
|
29576
|
+
const indices = template.geometry.getIndex();
|
|
29577
|
+
for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2);
|
|
29578
|
+
templateCache.set(template, data);
|
|
29579
|
+
}
|
|
29580
|
+
id = input.templateData.length;
|
|
29581
|
+
templateIds.set(template, id);
|
|
29582
|
+
input.templateData.push({ ...data, atlas });
|
|
29583
|
+
}
|
|
29584
|
+
input.indices[i] = index;
|
|
29585
|
+
input.templates[i] = id;
|
|
29586
|
+
input.cullMasks[i] = cullMask;
|
|
29587
|
+
}
|
|
29588
|
+
return { input, atlases };
|
|
29589
|
+
}
|
|
29143
29590
|
var SectionMesh = class _SectionMesh extends Group3 {
|
|
29144
29591
|
constructor() {
|
|
29145
29592
|
super(...arguments);
|
|
@@ -29147,86 +29594,31 @@ var SectionMesh = class _SectionMesh extends Group3 {
|
|
|
29147
29594
|
}
|
|
29148
29595
|
/** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
|
|
29149
29596
|
static build(entries, maxAtlasSize = 2048) {
|
|
29150
|
-
|
|
29151
|
-
|
|
29152
|
-
|
|
29153
|
-
|
|
29154
|
-
|
|
29155
|
-
|
|
29156
|
-
|
|
29157
|
-
|
|
29158
|
-
|
|
29159
|
-
|
|
29160
|
-
|
|
29161
|
-
if ((nextRow ? page.y + page.rowHeight : page.y) + height > maxAtlasSize) continue;
|
|
29162
|
-
if (nextRow) {
|
|
29163
|
-
page.y += page.rowHeight;
|
|
29164
|
-
page.x = 0;
|
|
29165
|
-
page.rowHeight = 0;
|
|
29166
|
-
}
|
|
29167
|
-
selected = page;
|
|
29168
|
-
break;
|
|
29169
|
-
}
|
|
29170
|
-
if (!selected) {
|
|
29171
|
-
selected = { placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 };
|
|
29172
|
-
pages.push(selected);
|
|
29173
|
-
}
|
|
29174
|
-
const placement = { atlas, x: selected.x, y: selected.y };
|
|
29175
|
-
selected.placements.push(placement);
|
|
29176
|
-
selected.x += width;
|
|
29177
|
-
selected.rowHeight = Math.max(selected.rowHeight, height);
|
|
29178
|
-
selected.width = Math.max(selected.width, selected.x);
|
|
29179
|
-
selected.height = Math.max(selected.height, selected.y + height);
|
|
29180
|
-
locations.set(atlas, { page: selected, placement });
|
|
29181
|
-
}
|
|
29182
|
-
for (const entry of visible) locations.get(entry.template.atlas).page.entries.push(entry);
|
|
29597
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29598
|
+
return this.fromPages(buildSectionGeometry(input), atlases);
|
|
29599
|
+
}
|
|
29600
|
+
/** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
|
|
29601
|
+
static async buildAsync(entries, maxAtlasSize = 2048) {
|
|
29602
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29603
|
+
const worker = SectionWorker.shared();
|
|
29604
|
+
const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
|
|
29605
|
+
return this.fromPages(pages, atlases);
|
|
29606
|
+
}
|
|
29607
|
+
static fromPages(pages, atlases) {
|
|
29183
29608
|
const section = new _SectionMesh();
|
|
29184
29609
|
for (const page of pages) {
|
|
29185
29610
|
const canvas = createCanvas(page.width, page.height);
|
|
29186
29611
|
const context = canvas.getContext("2d");
|
|
29187
29612
|
for (const { atlas, x, y } of page.placements) {
|
|
29188
|
-
context.drawImage(atlas.image.canvas, x, y);
|
|
29189
|
-
}
|
|
29190
|
-
const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
|
|
29191
|
-
for (const { index, template, cullMask } of page.entries) {
|
|
29192
|
-
const position = template.geometry.getAttribute("position");
|
|
29193
|
-
const normal = template.geometry.getAttribute("normal");
|
|
29194
|
-
const uv = template.geometry.getAttribute("uv");
|
|
29195
|
-
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29196
|
-
const color2 = template.geometry.getAttribute("color");
|
|
29197
|
-
const sourceIndices = template.geometry.getIndex();
|
|
29198
|
-
const placement = locations.get(template.atlas).placement;
|
|
29199
|
-
const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
|
|
29200
|
-
const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
|
|
29201
|
-
const offsetX = index % 16 * 16;
|
|
29202
|
-
const offsetY = Math.floor(index / 256) * 16;
|
|
29203
|
-
const offsetZ = Math.floor(index / 16) % 16 * 16;
|
|
29204
|
-
for (let face = 0; face < 6; face++) {
|
|
29205
|
-
if (cullMask & template.cullFaces[face]) continue;
|
|
29206
|
-
const start = positions.length / 3;
|
|
29207
|
-
for (let corner = 0; corner < 4; corner++) {
|
|
29208
|
-
const vertex = face * 4 + corner;
|
|
29209
|
-
positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
|
|
29210
|
-
normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
|
|
29211
|
-
uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
|
|
29212
|
-
uvBounds.push(
|
|
29213
|
-
mapU(bounds?.getX(vertex) ?? 0),
|
|
29214
|
-
mapV(bounds?.getY(vertex) ?? 0),
|
|
29215
|
-
mapU(bounds?.getZ(vertex) ?? 1),
|
|
29216
|
-
mapV(bounds?.getW(vertex) ?? 1)
|
|
29217
|
-
);
|
|
29218
|
-
colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
|
|
29219
|
-
}
|
|
29220
|
-
for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
|
|
29221
|
-
}
|
|
29613
|
+
context.drawImage(atlases[atlas].image.canvas, x, y);
|
|
29222
29614
|
}
|
|
29223
29615
|
const geometry = new BufferGeometry8();
|
|
29224
|
-
geometry.setAttribute("position", new Float32BufferAttribute8(positions, 3));
|
|
29225
|
-
geometry.setAttribute("normal", new Float32BufferAttribute8(normals, 3));
|
|
29226
|
-
geometry.setAttribute("uv", new Float32BufferAttribute8(uvs, 2));
|
|
29227
|
-
geometry.setAttribute("uvBounds", new Float32BufferAttribute8(uvBounds, 4));
|
|
29228
|
-
geometry.setAttribute("color", new Float32BufferAttribute8(colors, 3));
|
|
29229
|
-
geometry.setIndex(indices);
|
|
29616
|
+
geometry.setAttribute("position", new Float32BufferAttribute8(page.positions.buffer, 3));
|
|
29617
|
+
geometry.setAttribute("normal", new Float32BufferAttribute8(page.normals.buffer, 3));
|
|
29618
|
+
geometry.setAttribute("uv", new Float32BufferAttribute8(page.uvs.buffer, 2));
|
|
29619
|
+
geometry.setAttribute("uvBounds", new Float32BufferAttribute8(page.uvBounds.buffer, 4));
|
|
29620
|
+
geometry.setAttribute("color", new Float32BufferAttribute8(page.colors.buffer, 3));
|
|
29621
|
+
geometry.setIndex(new Uint32BufferAttribute(page.indices.buffer, 1));
|
|
29230
29622
|
geometry.computeBoundingBox();
|
|
29231
29623
|
geometry.computeBoundingSphere();
|
|
29232
29624
|
const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
|
|
@@ -29260,7 +29652,9 @@ var Chunk = class _Chunk {
|
|
|
29260
29652
|
this.renderedBlocks = /* @__PURE__ */ new Map();
|
|
29261
29653
|
this.sectionBlocks = /* @__PURE__ */ new Map();
|
|
29262
29654
|
this.hiddenBlocks = /* @__PURE__ */ new Set();
|
|
29655
|
+
this.blockPosition = new Vector317();
|
|
29263
29656
|
this.meshDirty = false;
|
|
29657
|
+
this.meshGeneration = 0;
|
|
29264
29658
|
this.scene = scene;
|
|
29265
29659
|
this.x = x;
|
|
29266
29660
|
this.y = y;
|
|
@@ -29273,7 +29667,7 @@ var Chunk = class _Chunk {
|
|
|
29273
29667
|
if (isTripleArray(posOrX)) {
|
|
29274
29668
|
return this.getBlockAt(new Vector317(posOrX[0], posOrX[1], posOrX[2]));
|
|
29275
29669
|
}
|
|
29276
|
-
const pos = this.
|
|
29670
|
+
const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
|
|
29277
29671
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29278
29672
|
return this.renderedBlocks.get(index);
|
|
29279
29673
|
}
|
|
@@ -29297,48 +29691,81 @@ var Chunk = class _Chunk {
|
|
|
29297
29691
|
worldPos = this.chunkPosToWorldPos(pos);
|
|
29298
29692
|
}
|
|
29299
29693
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29300
|
-
this.hiddenBlocks.delete(index);
|
|
29301
|
-
this.data.set(index, block);
|
|
29302
|
-
const readBlock = this.data.snapshot(index);
|
|
29303
|
-
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29304
|
-
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29305
|
-
this.renderedBlocks.delete(index);
|
|
29306
|
-
let object3;
|
|
29307
29694
|
try {
|
|
29308
|
-
|
|
29309
|
-
|
|
29310
|
-
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29311
|
-
if (!blockState) {
|
|
29312
|
-
this.data.set(index, void 0);
|
|
29313
|
-
return void 0;
|
|
29314
|
-
}
|
|
29315
|
-
const perBlock = !this.sectionModels || getFluidKind(blockState.key, stored.properties) || blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString());
|
|
29316
|
-
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29317
|
-
if (template) {
|
|
29318
|
-
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29319
|
-
this.meshDirty = true;
|
|
29320
|
-
} else {
|
|
29321
|
-
object3 = await this.scene.addBlock(blockState, {
|
|
29322
|
-
mergeMeshes: true,
|
|
29323
|
-
instanceMeshes: true,
|
|
29324
|
-
maxInstanceCount: 2e3,
|
|
29325
|
-
initialState: stored.properties
|
|
29326
|
-
});
|
|
29327
|
-
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29328
|
-
}
|
|
29329
|
-
const info = { get block() {
|
|
29330
|
-
return readBlock();
|
|
29331
|
-
}, object: object3 };
|
|
29332
|
-
this.renderedBlocks.set(index, info);
|
|
29333
|
-
return info;
|
|
29334
|
-
} catch (error) {
|
|
29335
|
-
this.data.set(index, void 0);
|
|
29336
|
-
object3?.removeFromScene();
|
|
29337
|
-
throw error;
|
|
29695
|
+
await this.placeBlocks([{ index, block }]);
|
|
29696
|
+
return this.renderedBlocks.get(index);
|
|
29338
29697
|
} finally {
|
|
29339
29698
|
await onBlocksChanged?.([worldPos]);
|
|
29340
29699
|
}
|
|
29341
29700
|
}
|
|
29701
|
+
/** Places cells in input order and returns changed world positions without refreshing neighbors. */
|
|
29702
|
+
async placeBlocks(blocks) {
|
|
29703
|
+
const resolutions = /* @__PURE__ */ new Map();
|
|
29704
|
+
for (const { block } of blocks) {
|
|
29705
|
+
if (!block || ChunkData.isAir(block)) continue;
|
|
29706
|
+
const key = ChunkData.paletteKey(block);
|
|
29707
|
+
if (resolutions.has(key)) continue;
|
|
29708
|
+
const stored = { type: block.type, properties: block.properties ? { ...block.properties } : void 0 };
|
|
29709
|
+
const resolved = (async () => {
|
|
29710
|
+
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29711
|
+
const perBlock = !blockState || !this.sectionModels || !!getFluidKind(blockState.key, stored.properties) || !!(blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString()));
|
|
29712
|
+
if (!perBlock) await this.sectionModels.get(blockState, stored.properties);
|
|
29713
|
+
return { blockState, perBlock };
|
|
29714
|
+
})();
|
|
29715
|
+
resolutions.set(key, resolved);
|
|
29716
|
+
void resolved.catch(() => void 0);
|
|
29717
|
+
}
|
|
29718
|
+
const positions = [];
|
|
29719
|
+
let failed = false, failure;
|
|
29720
|
+
for (const { index, block } of blocks) {
|
|
29721
|
+
this.hiddenBlocks.delete(index);
|
|
29722
|
+
this.data.set(index, block);
|
|
29723
|
+
const readBlock = this.data.snapshot(index);
|
|
29724
|
+
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29725
|
+
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29726
|
+
this.renderedBlocks.delete(index);
|
|
29727
|
+
const worldPos = new Vector317(
|
|
29728
|
+
this.x * 16 + index % 16,
|
|
29729
|
+
this.y * 16 + Math.floor(index / 256),
|
|
29730
|
+
this.z * 16 + Math.floor(index / 16) % 16
|
|
29731
|
+
);
|
|
29732
|
+
positions.push(worldPos);
|
|
29733
|
+
let object3;
|
|
29734
|
+
try {
|
|
29735
|
+
if (!readBlock) continue;
|
|
29736
|
+
const stored = readBlock();
|
|
29737
|
+
const key = ChunkData.paletteKey(stored);
|
|
29738
|
+
const { blockState, perBlock } = await resolutions.get(key);
|
|
29739
|
+
if (!blockState) {
|
|
29740
|
+
this.data.set(index, void 0);
|
|
29741
|
+
continue;
|
|
29742
|
+
}
|
|
29743
|
+
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29744
|
+
if (template) {
|
|
29745
|
+
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29746
|
+
this.meshDirty = true;
|
|
29747
|
+
} else {
|
|
29748
|
+
object3 = await this.scene.addBlock(blockState, {
|
|
29749
|
+
mergeMeshes: true,
|
|
29750
|
+
instanceMeshes: true,
|
|
29751
|
+
maxInstanceCount: 2e3,
|
|
29752
|
+
initialState: stored.properties
|
|
29753
|
+
});
|
|
29754
|
+
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29755
|
+
}
|
|
29756
|
+
this.renderedBlocks.set(index, { get block() {
|
|
29757
|
+
return readBlock();
|
|
29758
|
+
}, object: object3 });
|
|
29759
|
+
} catch (error) {
|
|
29760
|
+
this.data.set(index, void 0);
|
|
29761
|
+
object3?.removeFromScene();
|
|
29762
|
+
if (!failed) failure = error;
|
|
29763
|
+
failed = true;
|
|
29764
|
+
}
|
|
29765
|
+
}
|
|
29766
|
+
if (failed) throw failure;
|
|
29767
|
+
return positions;
|
|
29768
|
+
}
|
|
29342
29769
|
/** Changes visibility at world block coordinates while preserving the block's data. */
|
|
29343
29770
|
async setBlockVisibleAt(pos, visible) {
|
|
29344
29771
|
const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
|
|
@@ -29359,15 +29786,24 @@ var Chunk = class _Chunk {
|
|
|
29359
29786
|
await this.onBlocksChanged?.([pos]);
|
|
29360
29787
|
}
|
|
29361
29788
|
isBlockVisibleAt(pos) {
|
|
29362
|
-
|
|
29789
|
+
return this.isBlockVisibleIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29790
|
+
}
|
|
29791
|
+
/** Reports whether a cell has a visible render object or section entry. */
|
|
29792
|
+
isBlockVisibleIndex(index) {
|
|
29363
29793
|
return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
|
|
29364
29794
|
}
|
|
29365
29795
|
isOccludingAt(pos) {
|
|
29366
|
-
|
|
29796
|
+
return this.isOccludingIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29797
|
+
}
|
|
29798
|
+
/** Reports whether a visible cell hides neighboring cube faces. */
|
|
29799
|
+
isOccludingIndex(index) {
|
|
29367
29800
|
return !this.hiddenBlocks.has(index) && (this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false));
|
|
29368
29801
|
}
|
|
29369
29802
|
async setCullMaskAt(pos, mask) {
|
|
29370
|
-
|
|
29803
|
+
await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
|
|
29804
|
+
}
|
|
29805
|
+
/** Updates hidden faces for a cell's section entry or individual render object. */
|
|
29806
|
+
async setCullMaskIndex(index, mask) {
|
|
29371
29807
|
const entry = this.sectionBlocks.get(index);
|
|
29372
29808
|
if (entry) {
|
|
29373
29809
|
if (entry.cullMask !== mask) {
|
|
@@ -29378,12 +29814,17 @@ var Chunk = class _Chunk {
|
|
|
29378
29814
|
await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
|
|
29379
29815
|
}
|
|
29380
29816
|
}
|
|
29381
|
-
|
|
29817
|
+
/** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
|
|
29818
|
+
async rebuildSectionMesh() {
|
|
29382
29819
|
if (!this.meshDirty) return;
|
|
29383
|
-
|
|
29384
|
-
|
|
29385
|
-
|
|
29386
|
-
) : void 0;
|
|
29820
|
+
this.meshDirty = false;
|
|
29821
|
+
const generation = ++this.meshGeneration;
|
|
29822
|
+
const entries = [...this.sectionBlocks.values()].filter((entry) => !this.hiddenBlocks.has(entry.index));
|
|
29823
|
+
const next = entries.length ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize) : void 0;
|
|
29824
|
+
if (generation !== this.meshGeneration) {
|
|
29825
|
+
next?.dispose();
|
|
29826
|
+
return;
|
|
29827
|
+
}
|
|
29387
29828
|
this.sectionMesh?.dispose();
|
|
29388
29829
|
this.sectionMesh = next;
|
|
29389
29830
|
if (next) {
|
|
@@ -29391,7 +29832,6 @@ var Chunk = class _Chunk {
|
|
|
29391
29832
|
this.scene.add(next);
|
|
29392
29833
|
}
|
|
29393
29834
|
this.scene.dirty = true;
|
|
29394
|
-
this.meshDirty = false;
|
|
29395
29835
|
}
|
|
29396
29836
|
/** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
|
|
29397
29837
|
async clear(onBlocksChanged = this.onBlocksChanged) {
|
|
@@ -29400,6 +29840,7 @@ var Chunk = class _Chunk {
|
|
|
29400
29840
|
));
|
|
29401
29841
|
for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
|
|
29402
29842
|
this.sectionBlocks.clear();
|
|
29843
|
+
this.meshGeneration++;
|
|
29403
29844
|
this.sectionMesh?.dispose();
|
|
29404
29845
|
this.sectionMesh = void 0;
|
|
29405
29846
|
this.meshDirty = false;
|
|
@@ -29436,9 +29877,184 @@ var Chunk = class _Chunk {
|
|
|
29436
29877
|
);
|
|
29437
29878
|
}
|
|
29438
29879
|
};
|
|
29880
|
+
|
|
29881
|
+
// src/world/WorldStreamer.ts
|
|
29882
|
+
var WorldStreamer = class {
|
|
29883
|
+
constructor(world, source, options = {}) {
|
|
29884
|
+
this.world = world;
|
|
29885
|
+
this.source = source;
|
|
29886
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
29887
|
+
this.owned = /* @__PURE__ */ new Map();
|
|
29888
|
+
this.missing = /* @__PURE__ */ new Map();
|
|
29889
|
+
this.failed = /* @__PURE__ */ new Map();
|
|
29890
|
+
this.desired = [];
|
|
29891
|
+
this.disposed = false;
|
|
29892
|
+
this.loadRadius = options.loadRadius ?? 2;
|
|
29893
|
+
this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
|
|
29894
|
+
if (![this.loadRadius, this.unloadRadius].every((value) => Number.isInteger(value) && value >= 0 && value <= 32) || this.unloadRadius < this.loadRadius) {
|
|
29895
|
+
throw new RangeError("Chunk radii must be integers from 0 to 32, with unloadRadius at least loadRadius");
|
|
29896
|
+
}
|
|
29897
|
+
}
|
|
29898
|
+
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
29899
|
+
get loadedChunks() {
|
|
29900
|
+
return [...this.loaded.values()].map((position) => ({ ...position }));
|
|
29901
|
+
}
|
|
29902
|
+
/** Returns source failures retained within the unload radius. Failed columns do not block other loads. */
|
|
29903
|
+
get failedChunks() {
|
|
29904
|
+
return [...this.failed.values()].map((failure) => ({ ...failure }));
|
|
29905
|
+
}
|
|
29906
|
+
/** Counts desired columns still waiting for a source read or placement, including the active column. */
|
|
29907
|
+
get pendingChunks() {
|
|
29908
|
+
return this.desired.filter((position) => this.needsLoad(position)).length;
|
|
29909
|
+
}
|
|
29910
|
+
/**
|
|
29911
|
+
* Updates the center in absolute chunk coordinates. Repeated calls share the active drain.
|
|
29912
|
+
* Source failures are recorded in failedChunks; other columns continue loading. Missing and failed
|
|
29913
|
+
* columns are remembered within the retention radius. Call retryFailedChunks to retry source failures.
|
|
29914
|
+
* Placement and unload failures reject the drain; call update again to retry those operations.
|
|
29915
|
+
* Reads outside the latest load radius receive an abort signal; placement finishes before unloading.
|
|
29916
|
+
* Sources should represent an unchanged world during streaming.
|
|
29917
|
+
*/
|
|
29918
|
+
async update(x, z) {
|
|
29919
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
29920
|
+
if (![x, z].every((value) => Number.isSafeInteger(value) && Number.isSafeInteger(value - this.unloadRadius) && Number.isSafeInteger(value + this.unloadRadius))) {
|
|
29921
|
+
throw new RangeError("Chunk coordinates must be safe integers within the streaming radius");
|
|
29922
|
+
}
|
|
29923
|
+
if (!this.center || this.center.x !== x || this.center.z !== z) {
|
|
29924
|
+
this.center = { x, z };
|
|
29925
|
+
this.desired = [];
|
|
29926
|
+
for (let dz = -this.loadRadius; dz <= this.loadRadius; dz++) {
|
|
29927
|
+
for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {
|
|
29928
|
+
this.desired.push({ x: x + dx, z: z + dz });
|
|
29929
|
+
}
|
|
29930
|
+
}
|
|
29931
|
+
this.desired.sort((a, b) => (a.x - x) ** 2 + (a.z - z) ** 2 - (b.x - x) ** 2 - (b.z - z) ** 2);
|
|
29932
|
+
if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
|
|
29933
|
+
this.loading.controller.abort();
|
|
29934
|
+
}
|
|
29935
|
+
}
|
|
29936
|
+
return this.running ?? (this.running = Promise.resolve().then(() => this.drain()));
|
|
29937
|
+
}
|
|
29938
|
+
/** Updates from a camera or view-center position in scene units. One chunk spans 256 scene units. */
|
|
29939
|
+
async updatePosition(position) {
|
|
29940
|
+
if (![position.x, position.z].every(Number.isFinite)) throw new RangeError("Scene coordinates must be finite");
|
|
29941
|
+
return this.update(Math.floor(position.x / 256), Math.floor(position.z / 256));
|
|
29942
|
+
}
|
|
29943
|
+
/** Forgets recorded source failures and retries desired columns at the latest center. */
|
|
29944
|
+
async retryFailedChunks() {
|
|
29945
|
+
if (this.disposed) throw new Error("WorldStreamer is disposed");
|
|
29946
|
+
this.failed.clear();
|
|
29947
|
+
if (this.center) await this.update(this.center.x, this.center.z);
|
|
29948
|
+
}
|
|
29949
|
+
/** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
|
|
29950
|
+
dispose() {
|
|
29951
|
+
this.disposed = true;
|
|
29952
|
+
this.desired = [];
|
|
29953
|
+
this.center = void 0;
|
|
29954
|
+
this.loading?.controller.abort();
|
|
29955
|
+
return this.disposal ?? (this.disposal = Promise.resolve().then(async () => {
|
|
29956
|
+
await this.running?.catch(() => void 0);
|
|
29957
|
+
let failure;
|
|
29958
|
+
let failed = false;
|
|
29959
|
+
for (const position of this.owned.values()) {
|
|
29960
|
+
try {
|
|
29961
|
+
await this.unload(position);
|
|
29962
|
+
} catch (error) {
|
|
29963
|
+
failed = true;
|
|
29964
|
+
failure ?? (failure = error);
|
|
29965
|
+
}
|
|
29966
|
+
}
|
|
29967
|
+
this.missing.clear();
|
|
29968
|
+
this.failed.clear();
|
|
29969
|
+
if (failed) throw failure;
|
|
29970
|
+
}).catch((error) => {
|
|
29971
|
+
this.disposal = void 0;
|
|
29972
|
+
throw error;
|
|
29973
|
+
}));
|
|
29974
|
+
}
|
|
29975
|
+
async drain() {
|
|
29976
|
+
try {
|
|
29977
|
+
while (!this.disposed) {
|
|
29978
|
+
for (const [key, position2] of this.missing) {
|
|
29979
|
+
if (!this.within(position2, this.unloadRadius)) this.missing.delete(key);
|
|
29980
|
+
}
|
|
29981
|
+
for (const [key, position2] of this.failed) {
|
|
29982
|
+
if (!this.within(position2, this.unloadRadius)) this.failed.delete(key);
|
|
29983
|
+
}
|
|
29984
|
+
const obsolete = [...this.owned.values()].find((position2) => !this.loaded.has(this.key(position2)) || !this.within(position2, this.unloadRadius));
|
|
29985
|
+
if (obsolete) {
|
|
29986
|
+
await this.unload(obsolete);
|
|
29987
|
+
continue;
|
|
29988
|
+
}
|
|
29989
|
+
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
29990
|
+
if (!position) return;
|
|
29991
|
+
let chunk;
|
|
29992
|
+
const controller = new AbortController();
|
|
29993
|
+
this.loading = { position, controller };
|
|
29994
|
+
try {
|
|
29995
|
+
chunk = await this.source.getChunk(position.x, position.z, controller.signal);
|
|
29996
|
+
if (chunk) this.validateChunk(chunk, position);
|
|
29997
|
+
} catch (error) {
|
|
29998
|
+
if (!controller.signal.aborted && !this.disposed && this.within(position, this.loadRadius)) {
|
|
29999
|
+
this.failed.set(this.key(position), { ...position, error });
|
|
30000
|
+
}
|
|
30001
|
+
continue;
|
|
30002
|
+
} finally {
|
|
30003
|
+
this.loading = void 0;
|
|
30004
|
+
}
|
|
30005
|
+
if (controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) continue;
|
|
30006
|
+
if (!chunk) {
|
|
30007
|
+
this.missing.set(this.key(position), position);
|
|
30008
|
+
continue;
|
|
30009
|
+
}
|
|
30010
|
+
this.owned.set(this.key(position), position);
|
|
30011
|
+
try {
|
|
30012
|
+
await this.world.placeChunk(chunk);
|
|
30013
|
+
this.loaded.set(this.key(position), position);
|
|
30014
|
+
} catch (error) {
|
|
30015
|
+
try {
|
|
30016
|
+
await this.unload(position);
|
|
30017
|
+
} catch (cleanupError) {
|
|
30018
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
30019
|
+
try {
|
|
30020
|
+
Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
|
|
30021
|
+
} catch {
|
|
30022
|
+
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
30023
|
+
}
|
|
30024
|
+
throw failure;
|
|
30025
|
+
}
|
|
30026
|
+
throw error;
|
|
30027
|
+
}
|
|
30028
|
+
}
|
|
30029
|
+
} finally {
|
|
30030
|
+
this.running = void 0;
|
|
30031
|
+
}
|
|
30032
|
+
}
|
|
30033
|
+
needsLoad(position) {
|
|
30034
|
+
const key = this.key(position);
|
|
30035
|
+
return !this.loaded.has(key) && !this.missing.has(key) && !this.failed.has(key);
|
|
30036
|
+
}
|
|
30037
|
+
async unload(position) {
|
|
30038
|
+
await this.world.unloadChunkColumn(position.x, position.z);
|
|
30039
|
+
this.owned.delete(this.key(position));
|
|
30040
|
+
this.loaded.delete(this.key(position));
|
|
30041
|
+
}
|
|
30042
|
+
validateChunk(chunk, position) {
|
|
30043
|
+
if (chunk.x !== position.x || chunk.z !== position.z) {
|
|
30044
|
+
throw new Error(`World source returned chunk ${chunk.x},${chunk.z} for ${position.x},${position.z}`);
|
|
30045
|
+
}
|
|
30046
|
+
}
|
|
30047
|
+
within(position, radius) {
|
|
30048
|
+
return !!this.center && Math.abs(position.x - this.center.x) <= radius && Math.abs(position.z - this.center.z) <= radius;
|
|
30049
|
+
}
|
|
30050
|
+
key(position) {
|
|
30051
|
+
return `${position.x},${position.z}`;
|
|
30052
|
+
}
|
|
30053
|
+
};
|
|
29439
30054
|
export {
|
|
29440
30055
|
AXES,
|
|
29441
30056
|
AnvilParser,
|
|
30057
|
+
AnvilWorldSource,
|
|
29442
30058
|
ArchiveAssetSource,
|
|
29443
30059
|
AssetKey,
|
|
29444
30060
|
AssetLoadError,
|
|
@@ -29528,6 +30144,7 @@ export {
|
|
|
29528
30144
|
SchematicParser,
|
|
29529
30145
|
SectionMesh,
|
|
29530
30146
|
SectionModels,
|
|
30147
|
+
SectionWorker,
|
|
29531
30148
|
SkinImage,
|
|
29532
30149
|
SkinObject,
|
|
29533
30150
|
SkinPart,
|
|
@@ -29542,6 +30159,7 @@ export {
|
|
|
29542
30159
|
TintColors,
|
|
29543
30160
|
UVMapper,
|
|
29544
30161
|
VideoExporter,
|
|
30162
|
+
WorldStreamer,
|
|
29545
30163
|
WrappedImage,
|
|
29546
30164
|
addBox3WireframeToObject,
|
|
29547
30165
|
addWireframeToMesh,
|
|
@@ -29551,6 +30169,7 @@ export {
|
|
|
29551
30169
|
axisToVec3,
|
|
29552
30170
|
base64decode,
|
|
29553
30171
|
base64encode,
|
|
30172
|
+
buildSectionGeometry,
|
|
29554
30173
|
capeTextureSizes,
|
|
29555
30174
|
captureException,
|
|
29556
30175
|
changeEvent,
|
|
@@ -29603,6 +30222,7 @@ export {
|
|
|
29603
30222
|
sampleEntityAnimation,
|
|
29604
30223
|
sampleEntityKeyframes,
|
|
29605
30224
|
sampleFluid,
|
|
30225
|
+
sectionGeometryTransferables,
|
|
29606
30226
|
serializeBoxGeometryKey,
|
|
29607
30227
|
serializeImageKey,
|
|
29608
30228
|
serializeMaterialKey,
|
|
@@ -29620,6 +30240,7 @@ export {
|
|
|
29620
30240
|
toDegrees,
|
|
29621
30241
|
toRadians,
|
|
29622
30242
|
toTrianglesDrawMode,
|
|
29623
|
-
trimCanvas
|
|
30243
|
+
trimCanvas,
|
|
30244
|
+
yieldToEventLoop
|
|
29624
30245
|
};
|
|
29625
30246
|
//# sourceMappingURL=index.mjs.map
|