minerender 2.1.0-alpha.141 → 2.1.0-alpha.143
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/esm/index.browser.d.mts +16 -2
- package/dist/browser/esm/util/util.mjs +0 -2
- package/dist/browser/esm/util/util.mjs.map +1 -1
- package/dist/browser/esm/world/MineRenderWorld.mjs +82 -12
- package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
- package/dist/browser/esm/world/SectionMesh.mjs +65 -9
- package/dist/browser/esm/world/SectionMesh.mjs.map +1 -1
- package/dist/browser/esm/world/WorldStreamer.mjs +109 -15
- package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
- package/dist/browser/index.d.ts +16 -2
- package/dist/browser/index.js +255 -37
- package/dist/browser/index.js.map +1 -1
- package/dist/bundle.js +257 -38
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +16 -2
- package/dist/node/index.d.ts +16 -2
- package/dist/node/index.js +255 -37
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +271 -53
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node/index.d.mts
CHANGED
|
@@ -785,7 +785,10 @@ declare function toRadians(degrees: number): number;
|
|
|
785
785
|
declare function toDegrees(radians: number): number;
|
|
786
786
|
/** Resolves after a delay in milliseconds. */
|
|
787
787
|
declare function sleep(timeout: number): Promise<void>;
|
|
788
|
-
/**
|
|
788
|
+
/**
|
|
789
|
+
* Yields to the next macrotask without timer clamping when the platform supports it.
|
|
790
|
+
* Not `scheduler.yield()`: Chrome runs its continuations ahead of rendering, so chained yields delay frames.
|
|
791
|
+
*/
|
|
789
792
|
declare function yieldToEventLoop(): Promise<void>;
|
|
790
793
|
/** Reduces an angle modulo 360, retaining a negative sign for negative inputs. */
|
|
791
794
|
declare function clampRotationDegrees(deg: number): number;
|
|
@@ -4284,6 +4287,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
|
|
|
4284
4287
|
private placeChunkGroups;
|
|
4285
4288
|
/** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
|
|
4286
4289
|
placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
|
|
4290
|
+
/**
|
|
4291
|
+
* Replaces parsed columns in input order, then refreshes neighboring geometry once.
|
|
4292
|
+
* Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
|
|
4293
|
+
*/
|
|
4294
|
+
placeChunks(chunks: readonly AnvilChunk[]): Promise<void>;
|
|
4295
|
+
private placeColumn;
|
|
4287
4296
|
/**
|
|
4288
4297
|
* Removes blocks and owned entities at chunk-column coordinates, then refreshes neighboring faces and fluids.
|
|
4289
4298
|
* Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
|
|
@@ -4371,10 +4380,11 @@ declare class WorldStreamer {
|
|
|
4371
4380
|
private desired;
|
|
4372
4381
|
private center?;
|
|
4373
4382
|
private loading?;
|
|
4383
|
+
private readonly reads;
|
|
4374
4384
|
private running?;
|
|
4375
4385
|
private disposal?;
|
|
4376
4386
|
private disposed;
|
|
4377
|
-
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns">, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4387
|
+
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns"> & Partial<Pick<MineRenderWorld<boolean>, "placeChunks">>, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4378
4388
|
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
4379
4389
|
get loadedChunks(): readonly {
|
|
4380
4390
|
x: number;
|
|
@@ -4407,6 +4417,10 @@ declare class WorldStreamer {
|
|
|
4407
4417
|
/** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
|
|
4408
4418
|
dispose(): Promise<void>;
|
|
4409
4419
|
private drain;
|
|
4420
|
+
private fillReadSlots;
|
|
4421
|
+
/** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
|
|
4422
|
+
private placeReadyChunks;
|
|
4423
|
+
private placeOwnedColumns;
|
|
4410
4424
|
private needsLoad;
|
|
4411
4425
|
private unloadMany;
|
|
4412
4426
|
private validateChunk;
|
package/dist/node/index.d.ts
CHANGED
|
@@ -785,7 +785,10 @@ declare function toRadians(degrees: number): number;
|
|
|
785
785
|
declare function toDegrees(radians: number): number;
|
|
786
786
|
/** Resolves after a delay in milliseconds. */
|
|
787
787
|
declare function sleep(timeout: number): Promise<void>;
|
|
788
|
-
/**
|
|
788
|
+
/**
|
|
789
|
+
* Yields to the next macrotask without timer clamping when the platform supports it.
|
|
790
|
+
* Not `scheduler.yield()`: Chrome runs its continuations ahead of rendering, so chained yields delay frames.
|
|
791
|
+
*/
|
|
789
792
|
declare function yieldToEventLoop(): Promise<void>;
|
|
790
793
|
/** Reduces an angle modulo 360, retaining a negative sign for negative inputs. */
|
|
791
794
|
declare function clampRotationDegrees(deg: number): number;
|
|
@@ -4284,6 +4287,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
|
|
|
4284
4287
|
private placeChunkGroups;
|
|
4285
4288
|
/** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
|
|
4286
4289
|
placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
|
|
4290
|
+
/**
|
|
4291
|
+
* Replaces parsed columns in input order, then refreshes neighboring geometry once.
|
|
4292
|
+
* Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
|
|
4293
|
+
*/
|
|
4294
|
+
placeChunks(chunks: readonly AnvilChunk[]): Promise<void>;
|
|
4295
|
+
private placeColumn;
|
|
4287
4296
|
/**
|
|
4288
4297
|
* Removes blocks and owned entities at chunk-column coordinates, then refreshes neighboring faces and fluids.
|
|
4289
4298
|
* Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
|
|
@@ -4371,10 +4380,11 @@ declare class WorldStreamer {
|
|
|
4371
4380
|
private desired;
|
|
4372
4381
|
private center?;
|
|
4373
4382
|
private loading?;
|
|
4383
|
+
private readonly reads;
|
|
4374
4384
|
private running?;
|
|
4375
4385
|
private disposal?;
|
|
4376
4386
|
private disposed;
|
|
4377
|
-
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns">, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4387
|
+
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns"> & Partial<Pick<MineRenderWorld<boolean>, "placeChunks">>, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4378
4388
|
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
4379
4389
|
get loadedChunks(): readonly {
|
|
4380
4390
|
x: number;
|
|
@@ -4407,6 +4417,10 @@ declare class WorldStreamer {
|
|
|
4407
4417
|
/** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
|
|
4408
4418
|
dispose(): Promise<void>;
|
|
4409
4419
|
private drain;
|
|
4420
|
+
private fillReadSlots;
|
|
4421
|
+
/** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
|
|
4422
|
+
private placeReadyChunks;
|
|
4423
|
+
private placeOwnedColumns;
|
|
4410
4424
|
private needsLoad;
|
|
4411
4425
|
private unloadMany;
|
|
4412
4426
|
private validateChunk;
|
package/dist/node/index.js
CHANGED
|
@@ -184,8 +184,6 @@ async function sleep(timeout) {
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
function yieldToEventLoop() {
|
|
187
|
-
const scheduler = globalThis.scheduler;
|
|
188
|
-
if (scheduler?.yield) return scheduler.yield();
|
|
189
187
|
return new Promise((resolve) => {
|
|
190
188
|
if (typeof MessageChannel !== "undefined") {
|
|
191
189
|
const channel = new MessageChannel();
|
|
@@ -18696,6 +18694,7 @@ function skipsBlockFace(data, rule, block, neighbor, face) {
|
|
|
18696
18694
|
// src/world/MineRenderWorld.ts
|
|
18697
18695
|
init_log();
|
|
18698
18696
|
var log18 = logger("MineRenderWorld");
|
|
18697
|
+
var YIELD_INTERVAL_MS = 8;
|
|
18699
18698
|
var MineRenderWorld = class {
|
|
18700
18699
|
constructor(scene, options = {}) {
|
|
18701
18700
|
this._chunks = /* @__PURE__ */ new Map();
|
|
@@ -18809,7 +18808,7 @@ var MineRenderWorld = class {
|
|
|
18809
18808
|
failures.set(chunk, error);
|
|
18810
18809
|
if (this._chunks.get(`${chunk.x}_${chunk.y}_${chunk.z}`) === chunk) this.pendingBiomes.set(chunk, void 0);
|
|
18811
18810
|
}
|
|
18812
|
-
if (performance.now() - sliceStart >
|
|
18811
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18813
18812
|
await yieldToEventLoop();
|
|
18814
18813
|
sliceStart = performance.now();
|
|
18815
18814
|
}
|
|
@@ -18964,7 +18963,7 @@ var MineRenderWorld = class {
|
|
|
18964
18963
|
));
|
|
18965
18964
|
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
18966
18965
|
}
|
|
18967
|
-
if (performance.now() - sliceStart >
|
|
18966
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
18968
18967
|
await yieldToEventLoop();
|
|
18969
18968
|
sliceStart = performance.now();
|
|
18970
18969
|
}
|
|
@@ -18972,6 +18971,62 @@ var MineRenderWorld = class {
|
|
|
18972
18971
|
}
|
|
18973
18972
|
/** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
|
|
18974
18973
|
async placeChunk(chunk, executor) {
|
|
18974
|
+
return this.placeColumn(chunk, executor);
|
|
18975
|
+
}
|
|
18976
|
+
/**
|
|
18977
|
+
* Replaces parsed columns in input order, then refreshes neighboring geometry once.
|
|
18978
|
+
* Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
|
|
18979
|
+
*/
|
|
18980
|
+
async placeChunks(chunks) {
|
|
18981
|
+
const columns = [];
|
|
18982
|
+
for (const chunk of chunks) {
|
|
18983
|
+
const { x, z } = chunk ?? {};
|
|
18984
|
+
if (![x, z].every(Number.isSafeInteger)) {
|
|
18985
|
+
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
18986
|
+
}
|
|
18987
|
+
const sections = Array.from(chunk.sections, (section) => {
|
|
18988
|
+
const { y } = section ?? {};
|
|
18989
|
+
if (!Number.isSafeInteger(y)) throw new RangeError("Chunk section coordinates must be safe integers");
|
|
18990
|
+
return { ...section, y };
|
|
18991
|
+
});
|
|
18992
|
+
columns.push({ ...chunk, x, z, sections });
|
|
18993
|
+
}
|
|
18994
|
+
if (!columns.length) return;
|
|
18995
|
+
await this.culling;
|
|
18996
|
+
let failed = false, failure, sliceStart = performance.now();
|
|
18997
|
+
try {
|
|
18998
|
+
for (const column of columns) {
|
|
18999
|
+
await this.placeColumn(column, void 0, true);
|
|
19000
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19001
|
+
await yieldToEventLoop();
|
|
19002
|
+
sliceStart = performance.now();
|
|
19003
|
+
}
|
|
19004
|
+
}
|
|
19005
|
+
} catch (error) {
|
|
19006
|
+
failed = true;
|
|
19007
|
+
failure = error;
|
|
19008
|
+
} finally {
|
|
19009
|
+
try {
|
|
19010
|
+
const failures = await this.refreshBiomes(true);
|
|
19011
|
+
if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
19012
|
+
} catch (error) {
|
|
19013
|
+
if (!failed) {
|
|
19014
|
+
failed = true;
|
|
19015
|
+
failure = error;
|
|
19016
|
+
}
|
|
19017
|
+
}
|
|
19018
|
+
try {
|
|
19019
|
+
await this.updateCulling([]);
|
|
19020
|
+
} catch (error) {
|
|
19021
|
+
if (!failed) {
|
|
19022
|
+
failed = true;
|
|
19023
|
+
failure = error;
|
|
19024
|
+
}
|
|
19025
|
+
}
|
|
19026
|
+
}
|
|
19027
|
+
if (failed) throw failure;
|
|
19028
|
+
}
|
|
19029
|
+
async placeColumn(chunk, executor, deferCulling = false) {
|
|
18975
19030
|
if (![chunk.x, chunk.z].every(Number.isSafeInteger)) {
|
|
18976
19031
|
throw new RangeError("Chunk column coordinates must be safe integers");
|
|
18977
19032
|
}
|
|
@@ -19033,7 +19088,7 @@ var MineRenderWorld = class {
|
|
|
19033
19088
|
} finally {
|
|
19034
19089
|
this.markSectionChanged(target);
|
|
19035
19090
|
}
|
|
19036
|
-
if (performance.now() - sliceStart >
|
|
19091
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19037
19092
|
await yieldToEventLoop();
|
|
19038
19093
|
sliceStart = performance.now();
|
|
19039
19094
|
}
|
|
@@ -19056,9 +19111,12 @@ var MineRenderWorld = class {
|
|
|
19056
19111
|
await placeEntities?.();
|
|
19057
19112
|
} finally {
|
|
19058
19113
|
this.markBiomesChanged(chunk.x, chunk.z, void 0, false);
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19114
|
+
if (deferCulling) await this.updateCulling([...changes.values()], true);
|
|
19115
|
+
else {
|
|
19116
|
+
const failures = await this.refreshBiomes();
|
|
19117
|
+
if (failures.size) log18.warn("Failed to refresh biome colors", failures.values().next().value);
|
|
19118
|
+
await this.updateCulling([...changes.values()]);
|
|
19119
|
+
}
|
|
19062
19120
|
}
|
|
19063
19121
|
}
|
|
19064
19122
|
/**
|
|
@@ -19088,7 +19146,7 @@ var MineRenderWorld = class {
|
|
|
19088
19146
|
for (const { x, z } of columns.values()) {
|
|
19089
19147
|
this.entities?.clearColumn(x, z);
|
|
19090
19148
|
await this.clearChunkColumn(x, z);
|
|
19091
|
-
if (performance.now() - sliceStart >
|
|
19149
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19092
19150
|
await yieldToEventLoop();
|
|
19093
19151
|
sliceStart = performance.now();
|
|
19094
19152
|
}
|
|
@@ -19227,15 +19285,25 @@ var MineRenderWorld = class {
|
|
|
19227
19285
|
for (const [index, mask] of masks) await chunk.setCullMaskIndex(index, mask);
|
|
19228
19286
|
await chunk.refreshLight(indices === "all" ? void 0 : indices);
|
|
19229
19287
|
chunk.refreshAmbientOcclusion(this._ambientOcclusion);
|
|
19230
|
-
if (
|
|
19288
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19231
19289
|
await yieldToEventLoop();
|
|
19232
19290
|
sliceStart = performance.now();
|
|
19233
19291
|
}
|
|
19234
19292
|
}
|
|
19235
|
-
const
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19293
|
+
const pending = [];
|
|
19294
|
+
for (const chunk of [...batch.keys()]) {
|
|
19295
|
+
const build = (async () => {
|
|
19296
|
+
await chunk.rebuildSectionMesh();
|
|
19297
|
+
batch.delete(chunk);
|
|
19298
|
+
})();
|
|
19299
|
+
void build.catch(() => void 0);
|
|
19300
|
+
pending.push(build);
|
|
19301
|
+
if (performance.now() - sliceStart > YIELD_INTERVAL_MS) {
|
|
19302
|
+
await yieldToEventLoop();
|
|
19303
|
+
sliceStart = performance.now();
|
|
19304
|
+
}
|
|
19305
|
+
}
|
|
19306
|
+
const builds = await Promise.allSettled(pending);
|
|
19239
19307
|
const failure = builds.find((result) => result.status === "rejected");
|
|
19240
19308
|
if (failure?.status === "rejected") throw failure.reason;
|
|
19241
19309
|
} catch (error) {
|
|
@@ -19679,6 +19747,9 @@ var SectionWorker = class _SectionWorker {
|
|
|
19679
19747
|
};
|
|
19680
19748
|
|
|
19681
19749
|
// src/world/SectionMesh.ts
|
|
19750
|
+
var BLOCK_SIZE = 16;
|
|
19751
|
+
var QUAD_VERTICES = 4;
|
|
19752
|
+
var QUAD_INDICES = 6;
|
|
19682
19753
|
var templateCache = /* @__PURE__ */ new WeakMap();
|
|
19683
19754
|
var WATER_TINT = new import_three46.Color(4159204).toArray();
|
|
19684
19755
|
function toInput(entries, maxAtlasSize, fluids, lighting) {
|
|
@@ -19715,8 +19786,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
|
|
|
19715
19786
|
if (!data) {
|
|
19716
19787
|
const indices = template.geometry.getIndex();
|
|
19717
19788
|
const position = template.geometry.getAttribute("position");
|
|
19718
|
-
const quads = indices.count /
|
|
19719
|
-
if (indices.count %
|
|
19789
|
+
const quads = indices.count / QUAD_INDICES;
|
|
19790
|
+
if (indices.count % QUAD_INDICES !== 0 || position.count !== quads * QUAD_VERTICES) {
|
|
19720
19791
|
throw new RangeError("Section template geometry must hold four vertices and six indices per quad");
|
|
19721
19792
|
}
|
|
19722
19793
|
data = {
|
|
@@ -19726,8 +19797,8 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
|
|
|
19726
19797
|
uvs: new Float32Array(quads * 8),
|
|
19727
19798
|
uvBounds: new Float32Array(quads * 16),
|
|
19728
19799
|
colors: new Float32Array(quads * 12),
|
|
19729
|
-
shade: new Float32Array(quads *
|
|
19730
|
-
indices: new Uint16Array(quads *
|
|
19800
|
+
shade: new Float32Array(quads * QUAD_VERTICES),
|
|
19801
|
+
indices: new Uint16Array(quads * QUAD_INDICES),
|
|
19731
19802
|
cullFaces: new Uint8Array(template.cullFaces),
|
|
19732
19803
|
biomeTint: template.biomeTint ? new Uint8Array(template.biomeTint) : void 0,
|
|
19733
19804
|
atlas: 0,
|
|
@@ -19739,7 +19810,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
|
|
|
19739
19810
|
const bounds = template.geometry.getAttribute("uvBounds");
|
|
19740
19811
|
const color2 = template.geometry.getAttribute("color");
|
|
19741
19812
|
const shade = template.geometry.getAttribute("shade");
|
|
19742
|
-
for (let vertex = 0; vertex < quads *
|
|
19813
|
+
for (let vertex = 0; vertex < quads * QUAD_VERTICES; vertex++) {
|
|
19743
19814
|
data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
|
|
19744
19815
|
data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
|
|
19745
19816
|
data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
|
|
@@ -19752,7 +19823,7 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
|
|
|
19752
19823
|
data.colors.set([color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1], vertex * 3);
|
|
19753
19824
|
data.shade[vertex] = shade?.getX(vertex) ?? 1;
|
|
19754
19825
|
}
|
|
19755
|
-
for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2) -
|
|
19826
|
+
for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2) - QUAD_VERTICES * Math.floor(i2 / QUAD_INDICES);
|
|
19756
19827
|
templateCache.set(template, data);
|
|
19757
19828
|
}
|
|
19758
19829
|
id = input.templateData.length;
|
|
@@ -19785,6 +19856,54 @@ function toInput(entries, maxAtlasSize, fluids, lighting) {
|
|
|
19785
19856
|
}
|
|
19786
19857
|
return { input, atlases: atlases2 };
|
|
19787
19858
|
}
|
|
19859
|
+
function sortTransparentQuads(mesh) {
|
|
19860
|
+
const index = mesh.geometry.getIndex();
|
|
19861
|
+
if (index.count <= QUAD_INDICES) return;
|
|
19862
|
+
index.setUsage(import_three46.DynamicDrawUsage);
|
|
19863
|
+
const count = index.count / QUAD_INDICES;
|
|
19864
|
+
let source, centers, depths, order;
|
|
19865
|
+
const view = new import_three46.Matrix4(), localCamera = new import_three46.Vector3(), key = new import_three46.Vector3(), previous = new import_three46.Vector3();
|
|
19866
|
+
let previousOrthographic;
|
|
19867
|
+
mesh.onBeforeRender = (_renderer, _scene, camera) => {
|
|
19868
|
+
localCamera.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(view.copy(mesh.matrixWorld).invert());
|
|
19869
|
+
const orthographic = !!camera.isOrthographicCamera;
|
|
19870
|
+
if (orthographic) {
|
|
19871
|
+
view.multiplyMatrices(camera.matrixWorldInverse, mesh.matrixWorld);
|
|
19872
|
+
key.set(view.elements[2], view.elements[6], view.elements[10]);
|
|
19873
|
+
} else {
|
|
19874
|
+
key.copy(localCamera).divideScalar(BLOCK_SIZE).floor();
|
|
19875
|
+
}
|
|
19876
|
+
if (orthographic === previousOrthographic && key.equals(previous)) return;
|
|
19877
|
+
previous.copy(key);
|
|
19878
|
+
previousOrthographic = orthographic;
|
|
19879
|
+
if (!centers) {
|
|
19880
|
+
source = new Uint32Array(index.array);
|
|
19881
|
+
centers = new Float32Array(count * 3);
|
|
19882
|
+
depths = new Float32Array(count);
|
|
19883
|
+
order = Uint32Array.from({ length: count }, (_, quad) => quad);
|
|
19884
|
+
const positions = mesh.geometry.getAttribute("position");
|
|
19885
|
+
for (let quad = 0; quad < count; quad++) {
|
|
19886
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
19887
|
+
let center = 0;
|
|
19888
|
+
for (let corner = 0; corner < QUAD_VERTICES; corner++) center += positions.getComponent(quad * QUAD_VERTICES + corner, axis);
|
|
19889
|
+
centers[quad * 3 + axis] = center / QUAD_VERTICES;
|
|
19890
|
+
}
|
|
19891
|
+
}
|
|
19892
|
+
}
|
|
19893
|
+
for (let quad = 0; quad < count; quad++) {
|
|
19894
|
+
const x = centers[quad * 3], y = centers[quad * 3 + 1], z = centers[quad * 3 + 2];
|
|
19895
|
+
depths[quad] = orthographic ? key.x * x + key.y * y + key.z * z : -((x - localCamera.x) ** 2 + (y - localCamera.y) ** 2 + (z - localCamera.z) ** 2);
|
|
19896
|
+
}
|
|
19897
|
+
order.sort((a, b) => depths[a] - depths[b] || a - b);
|
|
19898
|
+
if (!order.some((quad, offset) => index.array[offset * QUAD_INDICES] !== source[quad * QUAD_INDICES])) return;
|
|
19899
|
+
for (let offset = 0; offset < count; offset++) {
|
|
19900
|
+
for (let indexOffset = 0; indexOffset < QUAD_INDICES; indexOffset++) {
|
|
19901
|
+
index.array[offset * QUAD_INDICES + indexOffset] = source[order[offset] * QUAD_INDICES + indexOffset];
|
|
19902
|
+
}
|
|
19903
|
+
}
|
|
19904
|
+
index.needsUpdate = true;
|
|
19905
|
+
};
|
|
19906
|
+
}
|
|
19788
19907
|
var SectionMesh = class _SectionMesh extends import_three46.Group {
|
|
19789
19908
|
constructor() {
|
|
19790
19909
|
super(...arguments);
|
|
@@ -19794,7 +19913,11 @@ var SectionMesh = class _SectionMesh extends import_three46.Group {
|
|
|
19794
19913
|
/** Returns the owning section cell for a triangle hit on this mesh's merged geometry. */
|
|
19795
19914
|
getBlockIndex(hit) {
|
|
19796
19915
|
if (hit.faceIndex === void 0 || hit.faceIndex === null) return void 0;
|
|
19797
|
-
|
|
19916
|
+
const mesh = hit.object;
|
|
19917
|
+
const owners = this.blockIndices.get(mesh);
|
|
19918
|
+
if (!owners) return void 0;
|
|
19919
|
+
const vertex = mesh.geometry.getIndex().getX(hit.faceIndex * 3);
|
|
19920
|
+
return owners[Math.floor(vertex / QUAD_VERTICES)];
|
|
19798
19921
|
}
|
|
19799
19922
|
/** Builds section-local meshes from visible quads. `maxAtlasSize` limits each atlas dimension in pixels. */
|
|
19800
19923
|
static build(entries, maxAtlasSize = 2048, fluids, lighting) {
|
|
@@ -19830,13 +19953,14 @@ var SectionMesh = class _SectionMesh extends import_three46.Group {
|
|
|
19830
19953
|
const material = Materials.createShadedCanvasMaterial(canvas, transparent, false, true);
|
|
19831
19954
|
material.side = import_three46.FrontSide;
|
|
19832
19955
|
if (page.layer === 2) {
|
|
19956
|
+
material.depthWrite = !material.transparent;
|
|
19833
19957
|
material.side = import_three46.DoubleSide;
|
|
19834
19958
|
material.forceSinglePass = true;
|
|
19835
|
-
material.depthWrite = !material.transparent;
|
|
19836
19959
|
}
|
|
19837
19960
|
material.vertexColors = true;
|
|
19838
19961
|
const texture = material.uniforms?.map?.value ?? material.map;
|
|
19839
19962
|
const mesh = new import_three46.Mesh(geometry, material);
|
|
19963
|
+
if (transparent) sortTransparentQuads(mesh);
|
|
19840
19964
|
section.add(mesh);
|
|
19841
19965
|
section.blockIndices.set(mesh, page.blockIndices);
|
|
19842
19966
|
const unsubscribe = [];
|
|
@@ -20639,6 +20763,7 @@ var Chunk = class _Chunk {
|
|
|
20639
20763
|
};
|
|
20640
20764
|
|
|
20641
20765
|
// src/world/WorldStreamer.ts
|
|
20766
|
+
var MAX_PREFETCHED_COLUMNS = 4;
|
|
20642
20767
|
var WorldStreamer = class {
|
|
20643
20768
|
constructor(world, source, options = {}) {
|
|
20644
20769
|
this.world = world;
|
|
@@ -20648,6 +20773,7 @@ var WorldStreamer = class {
|
|
|
20648
20773
|
this.missing = /* @__PURE__ */ new Map();
|
|
20649
20774
|
this.failed = /* @__PURE__ */ new Map();
|
|
20650
20775
|
this.desired = [];
|
|
20776
|
+
this.reads = /* @__PURE__ */ new Map();
|
|
20651
20777
|
this.disposed = false;
|
|
20652
20778
|
this.loadRadius = options.loadRadius ?? 2;
|
|
20653
20779
|
this.unloadRadius = options.unloadRadius ?? Math.min(32, this.loadRadius + 1);
|
|
@@ -20692,6 +20818,12 @@ var WorldStreamer = class {
|
|
|
20692
20818
|
if (this.loading && !this.within(this.loading.position, this.loadRadius)) {
|
|
20693
20819
|
this.loading.controller.abort();
|
|
20694
20820
|
}
|
|
20821
|
+
for (const [key, read] of this.reads) {
|
|
20822
|
+
if (!this.within(read.position, this.loadRadius)) {
|
|
20823
|
+
read.controller.abort();
|
|
20824
|
+
if (read.ready) this.reads.delete(key);
|
|
20825
|
+
}
|
|
20826
|
+
}
|
|
20695
20827
|
}
|
|
20696
20828
|
return this.running ??= Promise.resolve().then(() => this.drain());
|
|
20697
20829
|
}
|
|
@@ -20712,8 +20844,11 @@ var WorldStreamer = class {
|
|
|
20712
20844
|
this.desired = [];
|
|
20713
20845
|
this.center = void 0;
|
|
20714
20846
|
this.loading?.controller.abort();
|
|
20847
|
+
for (const read of this.reads.values()) read.controller.abort();
|
|
20715
20848
|
return this.disposal ??= Promise.resolve().then(async () => {
|
|
20716
20849
|
await this.running?.catch(() => void 0);
|
|
20850
|
+
await Promise.all([...this.reads.values()].map((read) => read.promise));
|
|
20851
|
+
this.reads.clear();
|
|
20717
20852
|
try {
|
|
20718
20853
|
await this.unloadMany([...this.owned.values()]);
|
|
20719
20854
|
} finally {
|
|
@@ -20739,6 +20874,10 @@ var WorldStreamer = class {
|
|
|
20739
20874
|
await this.unloadMany(obsolete);
|
|
20740
20875
|
continue;
|
|
20741
20876
|
}
|
|
20877
|
+
if (this.world.placeChunks) {
|
|
20878
|
+
if (await this.placeReadyChunks()) continue;
|
|
20879
|
+
return;
|
|
20880
|
+
}
|
|
20742
20881
|
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
20743
20882
|
if (!position) return;
|
|
20744
20883
|
let chunk;
|
|
@@ -20761,26 +20900,105 @@ var WorldStreamer = class {
|
|
|
20761
20900
|
continue;
|
|
20762
20901
|
}
|
|
20763
20902
|
this.owned.set(this.key(position), position);
|
|
20903
|
+
await this.placeOwnedColumns([position], () => this.world.placeChunk(chunk));
|
|
20904
|
+
}
|
|
20905
|
+
} finally {
|
|
20906
|
+
for (const [key, read] of this.reads) {
|
|
20907
|
+
read.controller.abort();
|
|
20908
|
+
if (read.ready) this.reads.delete(key);
|
|
20909
|
+
}
|
|
20910
|
+
this.running = void 0;
|
|
20911
|
+
}
|
|
20912
|
+
}
|
|
20913
|
+
fillReadSlots() {
|
|
20914
|
+
while (!this.disposed && this.reads.size < MAX_PREFETCHED_COLUMNS) {
|
|
20915
|
+
const position = this.desired.find((position2) => this.needsLoad(position2) && !this.reads.has(this.key(position2)));
|
|
20916
|
+
if (!position) return;
|
|
20917
|
+
const key = this.key(position);
|
|
20918
|
+
const read = {
|
|
20919
|
+
position,
|
|
20920
|
+
controller: new AbortController(),
|
|
20921
|
+
promise: Promise.resolve(),
|
|
20922
|
+
ready: false,
|
|
20923
|
+
failed: false
|
|
20924
|
+
};
|
|
20925
|
+
this.reads.set(key, read);
|
|
20926
|
+
read.promise = (async () => {
|
|
20764
20927
|
try {
|
|
20765
|
-
await this.
|
|
20766
|
-
this.
|
|
20928
|
+
read.chunk = await this.source.getChunk(position.x, position.z, read.controller.signal);
|
|
20929
|
+
if (read.chunk) this.validateChunk(read.chunk, position);
|
|
20767
20930
|
} catch (error) {
|
|
20768
|
-
|
|
20769
|
-
|
|
20770
|
-
|
|
20771
|
-
|
|
20772
|
-
|
|
20773
|
-
|
|
20774
|
-
} catch {
|
|
20775
|
-
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
20776
|
-
}
|
|
20777
|
-
throw failure;
|
|
20931
|
+
read.failed = true;
|
|
20932
|
+
read.error = error;
|
|
20933
|
+
} finally {
|
|
20934
|
+
read.ready = true;
|
|
20935
|
+
if (read.controller.signal.aborted || this.disposed || !this.within(position, this.loadRadius)) {
|
|
20936
|
+
this.reads.delete(key);
|
|
20778
20937
|
}
|
|
20779
|
-
|
|
20938
|
+
}
|
|
20939
|
+
})();
|
|
20940
|
+
}
|
|
20941
|
+
}
|
|
20942
|
+
/** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
|
|
20943
|
+
async placeReadyChunks() {
|
|
20944
|
+
this.fillReadSlots();
|
|
20945
|
+
const position = this.desired.find((position2) => this.needsLoad(position2));
|
|
20946
|
+
if (!position) {
|
|
20947
|
+
if (!this.reads.size) return false;
|
|
20948
|
+
await Promise.all([...this.reads.values()].map((read) => read.promise));
|
|
20949
|
+
return true;
|
|
20950
|
+
}
|
|
20951
|
+
const head = this.reads.get(this.key(position));
|
|
20952
|
+
if (!head) {
|
|
20953
|
+
for (let index = this.desired.length - 1; index >= 0; index--) {
|
|
20954
|
+
const key = this.key(this.desired[index]);
|
|
20955
|
+
if (this.reads.get(key)?.ready) {
|
|
20956
|
+
this.reads.delete(key);
|
|
20957
|
+
return true;
|
|
20780
20958
|
}
|
|
20781
20959
|
}
|
|
20782
|
-
|
|
20783
|
-
|
|
20960
|
+
await Promise.race([...this.reads.values()].map((read) => read.promise));
|
|
20961
|
+
return true;
|
|
20962
|
+
}
|
|
20963
|
+
if (!head.ready) {
|
|
20964
|
+
await head.promise;
|
|
20965
|
+
return true;
|
|
20966
|
+
}
|
|
20967
|
+
const chunks = [], positions = [];
|
|
20968
|
+
for (const desired of this.desired) {
|
|
20969
|
+
if (!this.needsLoad(desired)) continue;
|
|
20970
|
+
const key = this.key(desired), read = this.reads.get(key);
|
|
20971
|
+
if (!read?.ready) break;
|
|
20972
|
+
this.reads.delete(key);
|
|
20973
|
+
if (read.failed) this.failed.set(key, { ...desired, error: read.error });
|
|
20974
|
+
else if (!read.chunk) this.missing.set(key, desired);
|
|
20975
|
+
else {
|
|
20976
|
+
chunks.push(read.chunk);
|
|
20977
|
+
positions.push(desired);
|
|
20978
|
+
}
|
|
20979
|
+
}
|
|
20980
|
+
if (!chunks.length) return true;
|
|
20981
|
+
for (const position2 of positions) this.owned.set(this.key(position2), position2);
|
|
20982
|
+
await this.placeOwnedColumns(positions, () => this.world.placeChunks(chunks));
|
|
20983
|
+
return true;
|
|
20984
|
+
}
|
|
20985
|
+
async placeOwnedColumns(positions, place) {
|
|
20986
|
+
try {
|
|
20987
|
+
await place();
|
|
20988
|
+
for (const position of positions) this.loaded.set(this.key(position), position);
|
|
20989
|
+
} catch (error) {
|
|
20990
|
+
try {
|
|
20991
|
+
await this.unloadMany(positions);
|
|
20992
|
+
} catch (cleanupError) {
|
|
20993
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
20994
|
+
try {
|
|
20995
|
+
Object.defineProperty(failure, "cause", { value: cleanupError, configurable: true });
|
|
20996
|
+
} catch {
|
|
20997
|
+
throw Object.assign(new Error(failure.message), { cause: cleanupError });
|
|
20998
|
+
}
|
|
20999
|
+
throw failure;
|
|
21000
|
+
}
|
|
21001
|
+
throw error;
|
|
20784
21002
|
}
|
|
20785
21003
|
}
|
|
20786
21004
|
needsLoad(position) {
|