minerender 2.1.0-alpha.87 → 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 +92 -3
- package/dist/browser/index.d.ts +92 -3
- package/dist/browser/index.js +502 -184
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +502 -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 +511 -187
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +91 -3
- package/dist/node/index.d.ts +91 -3
- package/dist/node/index.js +498 -184
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +494 -184
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser/index.js
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
|
}
|
|
@@ -18778,6 +18795,7 @@ __export(index_browser_exports, {
|
|
|
18778
18795
|
SchematicParser: () => SchematicParser,
|
|
18779
18796
|
SectionMesh: () => SectionMesh,
|
|
18780
18797
|
SectionModels: () => SectionModels,
|
|
18798
|
+
SectionWorker: () => SectionWorker,
|
|
18781
18799
|
SkinImage: () => SkinImage,
|
|
18782
18800
|
SkinObject: () => SkinObject,
|
|
18783
18801
|
SkinPart: () => SkinPart,
|
|
@@ -18802,6 +18820,7 @@ __export(index_browser_exports, {
|
|
|
18802
18820
|
axisToVec3: () => axisToVec3,
|
|
18803
18821
|
base64decode: () => base64decode,
|
|
18804
18822
|
base64encode: () => base64encode,
|
|
18823
|
+
buildSectionGeometry: () => buildSectionGeometry,
|
|
18805
18824
|
capeTextureSizes: () => capeTextureSizes,
|
|
18806
18825
|
captureException: () => captureException,
|
|
18807
18826
|
changeEvent: () => changeEvent,
|
|
@@ -18854,6 +18873,7 @@ __export(index_browser_exports, {
|
|
|
18854
18873
|
sampleEntityAnimation: () => sampleEntityAnimation,
|
|
18855
18874
|
sampleEntityKeyframes: () => sampleEntityKeyframes,
|
|
18856
18875
|
sampleFluid: () => sampleFluid,
|
|
18876
|
+
sectionGeometryTransferables: () => sectionGeometryTransferables,
|
|
18857
18877
|
serializeBoxGeometryKey: () => serializeBoxGeometryKey,
|
|
18858
18878
|
serializeImageKey: () => serializeImageKey,
|
|
18859
18879
|
serializeMaterialKey: () => serializeMaterialKey,
|
|
@@ -18871,7 +18891,8 @@ __export(index_browser_exports, {
|
|
|
18871
18891
|
toDegrees: () => toDegrees,
|
|
18872
18892
|
toRadians: () => toRadians,
|
|
18873
18893
|
toTrianglesDrawMode: () => toTrianglesDrawMode,
|
|
18874
|
-
trimCanvas: () => trimCanvas
|
|
18894
|
+
trimCanvas: () => trimCanvas,
|
|
18895
|
+
yieldToEventLoop: () => yieldToEventLoop
|
|
18875
18896
|
});
|
|
18876
18897
|
module.exports = __toCommonJS(index_browser_exports);
|
|
18877
18898
|
|
|
@@ -18956,6 +18977,7 @@ function probeImageSize(data) {
|
|
|
18956
18977
|
}
|
|
18957
18978
|
|
|
18958
18979
|
// src/env/browser/BrowserEnv.ts
|
|
18980
|
+
var import_meta = {};
|
|
18959
18981
|
var BrowserEnv = class {
|
|
18960
18982
|
constructor() {
|
|
18961
18983
|
this.name = "browser";
|
|
@@ -18983,6 +19005,14 @@ var BrowserEnv = class {
|
|
|
18983
19005
|
version: version2
|
|
18984
19006
|
}));
|
|
18985
19007
|
}
|
|
19008
|
+
createWorker(name) {
|
|
19009
|
+
if (name !== "section" || typeof Worker === "undefined") return void 0;
|
|
19010
|
+
try {
|
|
19011
|
+
return new Worker(new URL("./section.worker.mjs", import_meta.url), { type: "module" });
|
|
19012
|
+
} catch {
|
|
19013
|
+
return void 0;
|
|
19014
|
+
}
|
|
19015
|
+
}
|
|
18986
19016
|
};
|
|
18987
19017
|
function registerBrowserEnv() {
|
|
18988
19018
|
const env = new BrowserEnv();
|
|
@@ -28636,6 +28666,16 @@ init_NBTHelper();
|
|
|
28636
28666
|
|
|
28637
28667
|
// src/world/ChunkData.ts
|
|
28638
28668
|
init_AssetKey();
|
|
28669
|
+
var blockKeys = /* @__PURE__ */ new Map();
|
|
28670
|
+
function blockKey(type) {
|
|
28671
|
+
let key = blockKeys.get(type);
|
|
28672
|
+
if (!key) {
|
|
28673
|
+
key = AssetKey.parse("blockstates", type);
|
|
28674
|
+
if (blockKeys.size >= 4096) blockKeys.clear();
|
|
28675
|
+
blockKeys.set(type, key);
|
|
28676
|
+
}
|
|
28677
|
+
return key;
|
|
28678
|
+
}
|
|
28639
28679
|
var ChunkData = class _ChunkData {
|
|
28640
28680
|
constructor() {
|
|
28641
28681
|
this.indices = new Uint16Array(4096);
|
|
@@ -28663,10 +28703,10 @@ var ChunkData = class _ChunkData {
|
|
|
28663
28703
|
let key;
|
|
28664
28704
|
let nbt;
|
|
28665
28705
|
if (block && !_ChunkData.isAir(block)) {
|
|
28666
|
-
const type =
|
|
28706
|
+
const type = blockKey(block.type).toNamespacedString();
|
|
28667
28707
|
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28668
28708
|
state = Object.freeze(Object.keys(properties).length ? { type, properties: Object.freeze(properties) } : { type });
|
|
28669
|
-
key =
|
|
28709
|
+
key = _ChunkData.paletteKey(block);
|
|
28670
28710
|
if (block.nbt !== void 0) nbt = structuredClone(block.nbt);
|
|
28671
28711
|
}
|
|
28672
28712
|
const previousId = this.indices[index];
|
|
@@ -28703,9 +28743,14 @@ var ChunkData = class _ChunkData {
|
|
|
28703
28743
|
}
|
|
28704
28744
|
static isAir(block) {
|
|
28705
28745
|
if (typeof block === "undefined" || typeof block.type === "undefined") return true;
|
|
28706
|
-
const key =
|
|
28746
|
+
const key = blockKey(block.type);
|
|
28707
28747
|
return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
|
|
28708
28748
|
}
|
|
28749
|
+
/** Identifies a normalized block type and its sorted properties, excluding per-cell NBT. */
|
|
28750
|
+
static paletteKey(block) {
|
|
28751
|
+
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28752
|
+
return JSON.stringify([blockKey(block.type).toNamespacedString(), properties]);
|
|
28753
|
+
}
|
|
28709
28754
|
static copyBlock(state, nbt) {
|
|
28710
28755
|
const block = { type: state.type };
|
|
28711
28756
|
if (state.properties) block.properties = { ...state.properties };
|
|
@@ -28885,14 +28930,17 @@ var AnvilParser = class {
|
|
|
28885
28930
|
const perLong = Math.floor(64 / bits);
|
|
28886
28931
|
const expected = padded ? Math.ceil(4096 / perLong) : Math.ceil(4096 * bits / 64);
|
|
28887
28932
|
if (tag.value.length !== expected) throw new MineRenderError(`Invalid Anvil block-state array length: expected ${expected}`);
|
|
28888
|
-
const words = tag.value.map(([high, low]) =>
|
|
28889
|
-
const mask = (
|
|
28933
|
+
const words = tag.value.map(([high, low]) => [high >>> 0, low >>> 0]);
|
|
28934
|
+
const mask = (1 << bits) - 1;
|
|
28890
28935
|
for (let index = 0; index < 4096; index++) {
|
|
28891
|
-
const
|
|
28892
|
-
const
|
|
28893
|
-
|
|
28894
|
-
|
|
28895
|
-
|
|
28936
|
+
const bitPos = index * bits;
|
|
28937
|
+
const word = padded ? Math.floor(index / perLong) : bitPos >>> 6;
|
|
28938
|
+
const shift = padded ? index % perLong * bits : bitPos & 63;
|
|
28939
|
+
const [high, low] = words[word];
|
|
28940
|
+
let value = shift < 32 ? low >>> shift : high >>> shift - 32;
|
|
28941
|
+
if (shift < 32 && shift + bits > 32) value += high * 2 ** (32 - shift);
|
|
28942
|
+
if (!padded && shift + bits > 64) value += words[word + 1][1] * 2 ** (64 - shift);
|
|
28943
|
+
indices[index] = value & mask;
|
|
28896
28944
|
}
|
|
28897
28945
|
return indices;
|
|
28898
28946
|
}
|
|
@@ -29034,7 +29082,7 @@ init_AssetKey();
|
|
|
29034
29082
|
// src/world/MineRenderWorld.ts
|
|
29035
29083
|
var import_three40 = require("three");
|
|
29036
29084
|
init_Model();
|
|
29037
|
-
|
|
29085
|
+
init_util();
|
|
29038
29086
|
init_AssetKey();
|
|
29039
29087
|
init_BlockStates();
|
|
29040
29088
|
init_CubeFace();
|
|
@@ -29179,7 +29227,7 @@ var MineRenderWorld = class {
|
|
|
29179
29227
|
* Places a structure's blocks at their stored positions, then refreshes neighboring faces.
|
|
29180
29228
|
* Entity NBT is retained by the structure but is not rendered.
|
|
29181
29229
|
*
|
|
29182
|
-
* @param useBatches -
|
|
29230
|
+
* @param useBatches - Yields between chunks by default. Set to `false` for sequential placement.
|
|
29183
29231
|
* @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
|
|
29184
29232
|
*/
|
|
29185
29233
|
async placeMultiBlock(multiblock, useBatches = true, executor) {
|
|
@@ -29196,8 +29244,10 @@ var MineRenderWorld = class {
|
|
|
29196
29244
|
};
|
|
29197
29245
|
const place = (block) => this.placeBlock(new import_three40.Vector3(...block.position), block, collect);
|
|
29198
29246
|
const keys = /* @__PURE__ */ new Map();
|
|
29247
|
+
const types = /* @__PURE__ */ new Set();
|
|
29199
29248
|
for (const block of multiblock.blocks) {
|
|
29200
|
-
if (Chunk.isAir(block)) continue;
|
|
29249
|
+
if (Chunk.isAir(block) || types.has(block.type)) continue;
|
|
29250
|
+
types.add(block.type);
|
|
29201
29251
|
const key = AssetKey.parse("blockstates", block.type);
|
|
29202
29252
|
keys.set(key.serialize(), key);
|
|
29203
29253
|
}
|
|
@@ -29206,6 +29256,23 @@ var MineRenderWorld = class {
|
|
|
29206
29256
|
for (const block of multiblock.blocks) await place(block);
|
|
29207
29257
|
return;
|
|
29208
29258
|
}
|
|
29259
|
+
if (!executor) {
|
|
29260
|
+
const groups2 = /* @__PURE__ */ new Map();
|
|
29261
|
+
const position = new import_three40.Vector3();
|
|
29262
|
+
for (const block of multiblock.blocks) {
|
|
29263
|
+
const [x, y, z] = block.position;
|
|
29264
|
+
this.validatePosBounds(position.set(x, y, z));
|
|
29265
|
+
const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
|
|
29266
|
+
const key = `${cx}_${cy}_${cz}`;
|
|
29267
|
+
const chunk = this._chunks.get(key) ?? (Chunk.isAir(block) ? void 0 : this.getOrCreateChunkAt(position));
|
|
29268
|
+
if (!chunk) continue;
|
|
29269
|
+
let group = groups2.get(chunk);
|
|
29270
|
+
if (!group) groups2.set(chunk, group = []);
|
|
29271
|
+
group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
|
|
29272
|
+
}
|
|
29273
|
+
await this.placeChunkGroups(groups2, changes);
|
|
29274
|
+
return;
|
|
29275
|
+
}
|
|
29209
29276
|
const positions = /* @__PURE__ */ new Map();
|
|
29210
29277
|
for (const block of multiblock.blocks) {
|
|
29211
29278
|
const key = block.position.join(",");
|
|
@@ -29214,19 +29281,34 @@ var MineRenderWorld = class {
|
|
|
29214
29281
|
else positions.set(key, [block]);
|
|
29215
29282
|
}
|
|
29216
29283
|
const groups = [...positions.values()];
|
|
29217
|
-
|
|
29218
|
-
|
|
29219
|
-
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
|
|
29223
|
-
|
|
29224
|
-
|
|
29225
|
-
|
|
29226
|
-
|
|
29284
|
+
for (let i = 0; i < groups.length; i += executor.batch) {
|
|
29285
|
+
const results = await Promise.allSettled(groups.slice(i, i + executor.batch).map(
|
|
29286
|
+
(blocks) => executor.submit(async () => {
|
|
29287
|
+
for (const block of blocks) await place(block);
|
|
29288
|
+
})
|
|
29289
|
+
));
|
|
29290
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
29291
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
29292
|
+
}
|
|
29293
|
+
}
|
|
29294
|
+
async placeChunkGroups(groups, changes) {
|
|
29295
|
+
let sliceStart = performance.now();
|
|
29296
|
+
for (const [chunk, blocks] of groups) {
|
|
29297
|
+
let positions;
|
|
29298
|
+
try {
|
|
29299
|
+
positions = await chunk.placeBlocks(blocks);
|
|
29300
|
+
} finally {
|
|
29301
|
+
positions ?? (positions = blocks.map(({ index }) => new import_three40.Vector3(
|
|
29302
|
+
chunk.x * 16 + index % 16,
|
|
29303
|
+
chunk.y * 16 + Math.floor(index / 256),
|
|
29304
|
+
chunk.z * 16 + Math.floor(index / 16) % 16
|
|
29305
|
+
)));
|
|
29306
|
+
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
29307
|
+
}
|
|
29308
|
+
if (performance.now() - sliceStart > 8) {
|
|
29309
|
+
await yieldToEventLoop();
|
|
29310
|
+
sliceStart = performance.now();
|
|
29227
29311
|
}
|
|
29228
|
-
} finally {
|
|
29229
|
-
if (!executor) queue.stop();
|
|
29230
29312
|
}
|
|
29231
29313
|
}
|
|
29232
29314
|
/** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
|
|
@@ -29234,7 +29316,24 @@ var MineRenderWorld = class {
|
|
|
29234
29316
|
const changes = /* @__PURE__ */ new Map();
|
|
29235
29317
|
try {
|
|
29236
29318
|
await this.clearChunkColumn(chunk.x, chunk.z, changes);
|
|
29319
|
+
const groups = /* @__PURE__ */ new Map();
|
|
29237
29320
|
for (const section of chunk.sections) {
|
|
29321
|
+
if (!executor) {
|
|
29322
|
+
const blocks2 = [];
|
|
29323
|
+
for (let index = 0; index < 4096; index++) {
|
|
29324
|
+
const block = section.data.get(index);
|
|
29325
|
+
if (block) blocks2.push({ index, block });
|
|
29326
|
+
}
|
|
29327
|
+
if (blocks2.length) {
|
|
29328
|
+
const position = new import_three40.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
|
|
29329
|
+
this.validatePosBounds(position);
|
|
29330
|
+
const target = this.getOrCreateChunkAt(position);
|
|
29331
|
+
const previous = groups.get(target);
|
|
29332
|
+
if (previous) previous.push(...blocks2);
|
|
29333
|
+
else groups.set(target, blocks2);
|
|
29334
|
+
}
|
|
29335
|
+
continue;
|
|
29336
|
+
}
|
|
29238
29337
|
const blocks = [];
|
|
29239
29338
|
for (let index = 0; index < 4096; index++) {
|
|
29240
29339
|
const block = section.data.get(index);
|
|
@@ -29249,6 +29348,7 @@ var MineRenderWorld = class {
|
|
|
29249
29348
|
}
|
|
29250
29349
|
await this.placeBlocks({ size: [16, 16, 16], blocks }, true, executor, changes);
|
|
29251
29350
|
}
|
|
29351
|
+
if (!executor) await this.placeChunkGroups(groups, changes);
|
|
29252
29352
|
} finally {
|
|
29253
29353
|
await this.updateCulling([...changes.values()]);
|
|
29254
29354
|
}
|
|
@@ -29289,19 +29389,23 @@ var MineRenderWorld = class {
|
|
|
29289
29389
|
this.sectionModels?.clear();
|
|
29290
29390
|
}
|
|
29291
29391
|
updateCulling(positions) {
|
|
29392
|
+
const neighbor = new import_three40.Vector3();
|
|
29292
29393
|
for (const pos of positions) {
|
|
29293
|
-
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
|
|
29297
|
-
}
|
|
29394
|
+
const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
|
|
29395
|
+
const chunk = this._chunks.get(`${cx}_${cy}_${cz}`);
|
|
29396
|
+
const lx = pos.x - cx * 16, ly = pos.y - cy * 16, lz = pos.z - cz * 16;
|
|
29298
29397
|
for (let y = -1; y <= 1; y++) {
|
|
29299
29398
|
for (let z = -1; z <= 1; z++) {
|
|
29300
29399
|
for (let x = -1; x <= 1; x++) {
|
|
29301
|
-
const
|
|
29302
|
-
|
|
29303
|
-
|
|
29304
|
-
|
|
29400
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29401
|
+
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)}`);
|
|
29402
|
+
if (!section) continue;
|
|
29403
|
+
const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29404
|
+
let pending = this.pendingCulling.get(section);
|
|
29405
|
+
if (pending?.has(index)) continue;
|
|
29406
|
+
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;
|
|
29407
|
+
if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
|
|
29408
|
+
pending.add(index);
|
|
29305
29409
|
}
|
|
29306
29410
|
}
|
|
29307
29411
|
}
|
|
@@ -29309,39 +29413,38 @@ var MineRenderWorld = class {
|
|
|
29309
29413
|
return this.culling ?? (this.culling = Promise.resolve().then(async () => {
|
|
29310
29414
|
try {
|
|
29311
29415
|
while (this.pendingCulling.size) {
|
|
29312
|
-
const batch = [...this.pendingCulling
|
|
29416
|
+
const batch = [...this.pendingCulling];
|
|
29313
29417
|
this.pendingCulling.clear();
|
|
29314
|
-
const
|
|
29315
|
-
|
|
29316
|
-
|
|
29317
|
-
|
|
29318
|
-
|
|
29319
|
-
|
|
29320
|
-
|
|
29321
|
-
|
|
29322
|
-
|
|
29323
|
-
|
|
29324
|
-
|
|
29325
|
-
|
|
29326
|
-
|
|
29327
|
-
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
};
|
|
29333
|
-
});
|
|
29334
|
-
}
|
|
29335
|
-
let mask = 0;
|
|
29336
|
-
for (const [face, offset] of CUBE_FACE_OFFSETS.entries()) {
|
|
29337
|
-
const neighbor = pos.clone().add(new import_three40.Vector3(...offset));
|
|
29338
|
-
if (this.getChunkAt(neighbor)?.isOccludingAt(neighbor)) {
|
|
29339
|
-
mask |= 1 << face;
|
|
29418
|
+
for (const [chunk, indices] of batch) {
|
|
29419
|
+
for (const index of indices) {
|
|
29420
|
+
if (!chunk.isBlockVisibleIndex(index)) continue;
|
|
29421
|
+
const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
|
|
29422
|
+
const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
|
|
29423
|
+
const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
|
|
29424
|
+
if (block.object?.fluidKind) {
|
|
29425
|
+
await block.object.updateFluid((x, y, z) => {
|
|
29426
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29427
|
+
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)}`);
|
|
29428
|
+
const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29429
|
+
const object3 = section?.isBlockVisibleIndex(neighborIndex) ? section.getBlockAt(neighbor.set(wx + x, wy + y, wz + z))?.object : void 0;
|
|
29430
|
+
return {
|
|
29431
|
+
fluid: object3?.fluidKind,
|
|
29432
|
+
level: object3?.fluidLevel,
|
|
29433
|
+
solid: section?.isOccludingIndex(neighborIndex) ?? false
|
|
29434
|
+
};
|
|
29435
|
+
});
|
|
29340
29436
|
}
|
|
29437
|
+
let mask = 0;
|
|
29438
|
+
for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
|
|
29439
|
+
const offset = CUBE_FACE_OFFSETS[face];
|
|
29440
|
+
const nx = lx + offset[0], ny = ly + offset[1], nz = lz + offset[2];
|
|
29441
|
+
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)}`);
|
|
29442
|
+
if (section?.isOccludingIndex((ny & 15) * 256 + (nz & 15) * 16 + (nx & 15))) mask |= 1 << face;
|
|
29443
|
+
}
|
|
29444
|
+
await chunk.setCullMaskIndex(index, mask);
|
|
29341
29445
|
}
|
|
29342
|
-
await chunk.setCullMaskAt(pos, mask);
|
|
29343
29446
|
}
|
|
29344
|
-
|
|
29447
|
+
await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
|
|
29345
29448
|
}
|
|
29346
29449
|
} finally {
|
|
29347
29450
|
this.culling = void 0;
|
|
@@ -29403,7 +29506,228 @@ init_Model();
|
|
|
29403
29506
|
// src/world/SectionMesh.ts
|
|
29404
29507
|
var import_three41 = require("three");
|
|
29405
29508
|
init_CanvasCompat();
|
|
29509
|
+
init_CubeFace();
|
|
29406
29510
|
init_Materials();
|
|
29511
|
+
|
|
29512
|
+
// src/world/SectionGeometry.ts
|
|
29513
|
+
init_CubeFace();
|
|
29514
|
+
function buildSectionGeometry(input) {
|
|
29515
|
+
const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize } = input;
|
|
29516
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29517
|
+
if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
|
|
29518
|
+
const visible = [];
|
|
29519
|
+
const visibleAtlases = /* @__PURE__ */ new Set();
|
|
29520
|
+
for (let entry = 0; entry < count; entry++) {
|
|
29521
|
+
const template = templateData[templates[entry]];
|
|
29522
|
+
if (!template.cullFaces.some((direction) => !(cullMasks[entry] & direction))) continue;
|
|
29523
|
+
visible.push(entry);
|
|
29524
|
+
visibleAtlases.add(template.atlas);
|
|
29525
|
+
}
|
|
29526
|
+
const pages = [];
|
|
29527
|
+
const locations = /* @__PURE__ */ new Map();
|
|
29528
|
+
for (const atlas of [...visibleAtlases].sort((a, b) => atlases[b].height - atlases[a].height)) {
|
|
29529
|
+
const { width, height } = atlases[atlas];
|
|
29530
|
+
if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
|
|
29531
|
+
let selected = -1;
|
|
29532
|
+
for (let i = 0; i < pages.length; i++) {
|
|
29533
|
+
const page2 = pages[i];
|
|
29534
|
+
const nextRow = page2.x + width > maxAtlasSize;
|
|
29535
|
+
if ((nextRow ? page2.y + page2.rowHeight : page2.y) + height > maxAtlasSize) continue;
|
|
29536
|
+
if (nextRow) {
|
|
29537
|
+
page2.y += page2.rowHeight;
|
|
29538
|
+
page2.x = 0;
|
|
29539
|
+
page2.rowHeight = 0;
|
|
29540
|
+
}
|
|
29541
|
+
selected = i;
|
|
29542
|
+
break;
|
|
29543
|
+
}
|
|
29544
|
+
if (selected < 0) {
|
|
29545
|
+
selected = pages.length;
|
|
29546
|
+
pages.push({ placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 });
|
|
29547
|
+
}
|
|
29548
|
+
const page = pages[selected];
|
|
29549
|
+
page.placements.push({ atlas, x: page.x, y: page.y });
|
|
29550
|
+
locations.set(atlas, { page: selected, x: page.x, y: page.y });
|
|
29551
|
+
page.x += width;
|
|
29552
|
+
page.rowHeight = Math.max(page.rowHeight, height);
|
|
29553
|
+
page.width = Math.max(page.width, page.x);
|
|
29554
|
+
page.height = Math.max(page.height, page.y + height);
|
|
29555
|
+
}
|
|
29556
|
+
for (const entry of visible) pages[locations.get(templateData[templates[entry]].atlas).page].entries.push(entry);
|
|
29557
|
+
return pages.map((page) => {
|
|
29558
|
+
let faces = 0;
|
|
29559
|
+
for (const entry of page.entries) {
|
|
29560
|
+
const template = templateData[templates[entry]];
|
|
29561
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29562
|
+
if (!(cullMasks[entry] & template.cullFaces[face])) faces++;
|
|
29563
|
+
}
|
|
29564
|
+
}
|
|
29565
|
+
const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
|
|
29566
|
+
const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
|
|
29567
|
+
const outputIndices = new Uint32Array(faces * 6);
|
|
29568
|
+
let vertex = 0, indexOffset = 0;
|
|
29569
|
+
for (const entry of page.entries) {
|
|
29570
|
+
const template = templateData[templates[entry]];
|
|
29571
|
+
const atlas = atlases[template.atlas];
|
|
29572
|
+
const placement = locations.get(template.atlas);
|
|
29573
|
+
const mapU = (u) => (placement.x + u * atlas.width) / page.width;
|
|
29574
|
+
const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
|
|
29575
|
+
const offsetX = indices[entry] % 16 * 16;
|
|
29576
|
+
const offsetY = Math.floor(indices[entry] / 256) * 16;
|
|
29577
|
+
const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
|
|
29578
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29579
|
+
if (cullMasks[entry] & template.cullFaces[face]) continue;
|
|
29580
|
+
const start = vertex;
|
|
29581
|
+
for (let corner = 0; corner < 4; corner++, vertex++) {
|
|
29582
|
+
const source = face * 4 + corner;
|
|
29583
|
+
positions[vertex * 3] = template.positions[source * 3] + offsetX;
|
|
29584
|
+
positions[vertex * 3 + 1] = template.positions[source * 3 + 1] + offsetY;
|
|
29585
|
+
positions[vertex * 3 + 2] = template.positions[source * 3 + 2] + offsetZ;
|
|
29586
|
+
normals[vertex * 3] = template.normals[source * 3];
|
|
29587
|
+
normals[vertex * 3 + 1] = template.normals[source * 3 + 1];
|
|
29588
|
+
normals[vertex * 3 + 2] = template.normals[source * 3 + 2];
|
|
29589
|
+
uvs[vertex * 2] = mapU(template.uvs[source * 2]);
|
|
29590
|
+
uvs[vertex * 2 + 1] = mapV(template.uvs[source * 2 + 1]);
|
|
29591
|
+
uvBounds[vertex * 4] = mapU(template.uvBounds[source * 4]);
|
|
29592
|
+
uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
|
|
29593
|
+
uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
|
|
29594
|
+
uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
|
|
29595
|
+
colors[vertex * 3] = template.colors[source * 3];
|
|
29596
|
+
colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
|
|
29597
|
+
colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
|
|
29598
|
+
}
|
|
29599
|
+
for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[face * 6 + corner] - face * 4;
|
|
29600
|
+
}
|
|
29601
|
+
}
|
|
29602
|
+
return { width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
|
|
29603
|
+
});
|
|
29604
|
+
}
|
|
29605
|
+
function sectionGeometryTransferables(pages) {
|
|
29606
|
+
return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
|
|
29607
|
+
}
|
|
29608
|
+
|
|
29609
|
+
// src/world/SectionWorker.ts
|
|
29610
|
+
init_Env();
|
|
29611
|
+
var _SectionWorker = class _SectionWorker {
|
|
29612
|
+
constructor(worker) {
|
|
29613
|
+
this.worker = worker;
|
|
29614
|
+
this.nextId = 0;
|
|
29615
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
29616
|
+
worker.addEventListener("message", (event) => {
|
|
29617
|
+
const reply = event.data;
|
|
29618
|
+
const pending = this.pending.get(reply.id);
|
|
29619
|
+
if (!pending) return;
|
|
29620
|
+
this.pending.delete(reply.id);
|
|
29621
|
+
if (reply.type === "pages") pending.resolve(reply.pages);
|
|
29622
|
+
else pending.reject(new Error(reply.message));
|
|
29623
|
+
});
|
|
29624
|
+
worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
|
|
29625
|
+
worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
|
|
29626
|
+
}
|
|
29627
|
+
/** The shared worker, or undefined when the environment has none or a previous worker failed. */
|
|
29628
|
+
static shared() {
|
|
29629
|
+
if (!this.initialized) {
|
|
29630
|
+
this.initialized = true;
|
|
29631
|
+
const worker = Env.provider.createWorker?.("section");
|
|
29632
|
+
if (worker) this.instance = new _SectionWorker(worker);
|
|
29633
|
+
}
|
|
29634
|
+
return this.instance;
|
|
29635
|
+
}
|
|
29636
|
+
/** Builds geometry without transferring the input buffers away from the caller. */
|
|
29637
|
+
build(input) {
|
|
29638
|
+
if (!this.worker) return Promise.reject(new Error("Section worker terminated"));
|
|
29639
|
+
return new Promise((resolve, reject) => {
|
|
29640
|
+
const id = this.nextId++;
|
|
29641
|
+
this.pending.set(id, { resolve, reject });
|
|
29642
|
+
this.worker.postMessage({ id, type: "build", input });
|
|
29643
|
+
});
|
|
29644
|
+
}
|
|
29645
|
+
/** Rejects pending builds, terminates the worker, and makes shared() return undefined. */
|
|
29646
|
+
terminate() {
|
|
29647
|
+
this.fail("Section worker terminated");
|
|
29648
|
+
}
|
|
29649
|
+
fail(message) {
|
|
29650
|
+
const error = new Error(message);
|
|
29651
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
29652
|
+
this.pending.clear();
|
|
29653
|
+
this.worker?.terminate();
|
|
29654
|
+
this.worker = void 0;
|
|
29655
|
+
_SectionWorker.instance = void 0;
|
|
29656
|
+
}
|
|
29657
|
+
};
|
|
29658
|
+
_SectionWorker.initialized = false;
|
|
29659
|
+
var SectionWorker = _SectionWorker;
|
|
29660
|
+
|
|
29661
|
+
// src/world/SectionMesh.ts
|
|
29662
|
+
var templateCache = /* @__PURE__ */ new WeakMap();
|
|
29663
|
+
function toInput(entries, maxAtlasSize) {
|
|
29664
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29665
|
+
const atlases = [];
|
|
29666
|
+
const atlasIds = /* @__PURE__ */ new Map();
|
|
29667
|
+
const templateIds = /* @__PURE__ */ new Map();
|
|
29668
|
+
const input = {
|
|
29669
|
+
count: entries.length,
|
|
29670
|
+
indices: new Uint16Array(entries.length),
|
|
29671
|
+
templates: new Uint16Array(entries.length),
|
|
29672
|
+
cullMasks: new Uint8Array(entries.length),
|
|
29673
|
+
templateData: [],
|
|
29674
|
+
atlases: [],
|
|
29675
|
+
maxAtlasSize
|
|
29676
|
+
};
|
|
29677
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29678
|
+
const { index, template, cullMask } = entries[i];
|
|
29679
|
+
let id = templateIds.get(template);
|
|
29680
|
+
if (id === void 0) {
|
|
29681
|
+
let atlas = atlasIds.get(template.atlas);
|
|
29682
|
+
if (atlas === void 0) {
|
|
29683
|
+
atlas = atlases.length;
|
|
29684
|
+
atlasIds.set(template.atlas, atlas);
|
|
29685
|
+
atlases.push(template.atlas);
|
|
29686
|
+
input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height });
|
|
29687
|
+
}
|
|
29688
|
+
let data = templateCache.get(template);
|
|
29689
|
+
if (!data) {
|
|
29690
|
+
data = {
|
|
29691
|
+
positions: new Float32Array(72),
|
|
29692
|
+
normals: new Float32Array(72),
|
|
29693
|
+
uvs: new Float32Array(48),
|
|
29694
|
+
uvBounds: new Float32Array(96),
|
|
29695
|
+
colors: new Float32Array(72),
|
|
29696
|
+
indices: new Uint16Array(36),
|
|
29697
|
+
cullFaces: new Uint8Array(template.cullFaces),
|
|
29698
|
+
atlas: 0
|
|
29699
|
+
};
|
|
29700
|
+
const position = template.geometry.getAttribute("position");
|
|
29701
|
+
const normal = template.geometry.getAttribute("normal");
|
|
29702
|
+
const uv = template.geometry.getAttribute("uv");
|
|
29703
|
+
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29704
|
+
const color2 = template.geometry.getAttribute("color");
|
|
29705
|
+
for (let vertex = 0; vertex < CUBE_FACES.length * 4; vertex++) {
|
|
29706
|
+
data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
|
|
29707
|
+
data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
|
|
29708
|
+
data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
|
|
29709
|
+
data.uvBounds.set([
|
|
29710
|
+
bounds?.getX(vertex) ?? 0,
|
|
29711
|
+
bounds?.getY(vertex) ?? 0,
|
|
29712
|
+
bounds?.getZ(vertex) ?? 1,
|
|
29713
|
+
bounds?.getW(vertex) ?? 1
|
|
29714
|
+
], vertex * 4);
|
|
29715
|
+
data.colors.set([color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1], vertex * 3);
|
|
29716
|
+
}
|
|
29717
|
+
const indices = template.geometry.getIndex();
|
|
29718
|
+
for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2);
|
|
29719
|
+
templateCache.set(template, data);
|
|
29720
|
+
}
|
|
29721
|
+
id = input.templateData.length;
|
|
29722
|
+
templateIds.set(template, id);
|
|
29723
|
+
input.templateData.push({ ...data, atlas });
|
|
29724
|
+
}
|
|
29725
|
+
input.indices[i] = index;
|
|
29726
|
+
input.templates[i] = id;
|
|
29727
|
+
input.cullMasks[i] = cullMask;
|
|
29728
|
+
}
|
|
29729
|
+
return { input, atlases };
|
|
29730
|
+
}
|
|
29407
29731
|
var SectionMesh = class _SectionMesh extends import_three41.Group {
|
|
29408
29732
|
constructor() {
|
|
29409
29733
|
super(...arguments);
|
|
@@ -29411,86 +29735,31 @@ var SectionMesh = class _SectionMesh extends import_three41.Group {
|
|
|
29411
29735
|
}
|
|
29412
29736
|
/** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
|
|
29413
29737
|
static build(entries, maxAtlasSize = 2048) {
|
|
29414
|
-
|
|
29415
|
-
|
|
29416
|
-
|
|
29417
|
-
|
|
29418
|
-
|
|
29419
|
-
|
|
29420
|
-
|
|
29421
|
-
|
|
29422
|
-
|
|
29423
|
-
|
|
29424
|
-
|
|
29425
|
-
if ((nextRow ? page.y + page.rowHeight : page.y) + height > maxAtlasSize) continue;
|
|
29426
|
-
if (nextRow) {
|
|
29427
|
-
page.y += page.rowHeight;
|
|
29428
|
-
page.x = 0;
|
|
29429
|
-
page.rowHeight = 0;
|
|
29430
|
-
}
|
|
29431
|
-
selected = page;
|
|
29432
|
-
break;
|
|
29433
|
-
}
|
|
29434
|
-
if (!selected) {
|
|
29435
|
-
selected = { placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 };
|
|
29436
|
-
pages.push(selected);
|
|
29437
|
-
}
|
|
29438
|
-
const placement = { atlas, x: selected.x, y: selected.y };
|
|
29439
|
-
selected.placements.push(placement);
|
|
29440
|
-
selected.x += width;
|
|
29441
|
-
selected.rowHeight = Math.max(selected.rowHeight, height);
|
|
29442
|
-
selected.width = Math.max(selected.width, selected.x);
|
|
29443
|
-
selected.height = Math.max(selected.height, selected.y + height);
|
|
29444
|
-
locations.set(atlas, { page: selected, placement });
|
|
29445
|
-
}
|
|
29446
|
-
for (const entry of visible) locations.get(entry.template.atlas).page.entries.push(entry);
|
|
29738
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29739
|
+
return this.fromPages(buildSectionGeometry(input), atlases);
|
|
29740
|
+
}
|
|
29741
|
+
/** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
|
|
29742
|
+
static async buildAsync(entries, maxAtlasSize = 2048) {
|
|
29743
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29744
|
+
const worker = SectionWorker.shared();
|
|
29745
|
+
const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
|
|
29746
|
+
return this.fromPages(pages, atlases);
|
|
29747
|
+
}
|
|
29748
|
+
static fromPages(pages, atlases) {
|
|
29447
29749
|
const section = new _SectionMesh();
|
|
29448
29750
|
for (const page of pages) {
|
|
29449
29751
|
const canvas = createCanvas(page.width, page.height);
|
|
29450
29752
|
const context = canvas.getContext("2d");
|
|
29451
29753
|
for (const { atlas, x, y } of page.placements) {
|
|
29452
|
-
context.drawImage(atlas.image.canvas, x, y);
|
|
29453
|
-
}
|
|
29454
|
-
const positions = [], normals = [], uvs = [], uvBounds = [], colors = [], indices = [];
|
|
29455
|
-
for (const { index, template, cullMask } of page.entries) {
|
|
29456
|
-
const position = template.geometry.getAttribute("position");
|
|
29457
|
-
const normal = template.geometry.getAttribute("normal");
|
|
29458
|
-
const uv = template.geometry.getAttribute("uv");
|
|
29459
|
-
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29460
|
-
const color2 = template.geometry.getAttribute("color");
|
|
29461
|
-
const sourceIndices = template.geometry.getIndex();
|
|
29462
|
-
const placement = locations.get(template.atlas).placement;
|
|
29463
|
-
const mapU = (u) => (placement.x + u * template.atlas.image.width) / page.width;
|
|
29464
|
-
const mapV = (v) => 1 - (placement.y + (1 - v) * template.atlas.image.height) / page.height;
|
|
29465
|
-
const offsetX = index % 16 * 16;
|
|
29466
|
-
const offsetY = Math.floor(index / 256) * 16;
|
|
29467
|
-
const offsetZ = Math.floor(index / 16) % 16 * 16;
|
|
29468
|
-
for (let face = 0; face < 6; face++) {
|
|
29469
|
-
if (cullMask & template.cullFaces[face]) continue;
|
|
29470
|
-
const start = positions.length / 3;
|
|
29471
|
-
for (let corner = 0; corner < 4; corner++) {
|
|
29472
|
-
const vertex = face * 4 + corner;
|
|
29473
|
-
positions.push(position.getX(vertex) + offsetX, position.getY(vertex) + offsetY, position.getZ(vertex) + offsetZ);
|
|
29474
|
-
normals.push(normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex));
|
|
29475
|
-
uvs.push(mapU(uv.getX(vertex)), mapV(uv.getY(vertex)));
|
|
29476
|
-
uvBounds.push(
|
|
29477
|
-
mapU(bounds?.getX(vertex) ?? 0),
|
|
29478
|
-
mapV(bounds?.getY(vertex) ?? 0),
|
|
29479
|
-
mapU(bounds?.getZ(vertex) ?? 1),
|
|
29480
|
-
mapV(bounds?.getW(vertex) ?? 1)
|
|
29481
|
-
);
|
|
29482
|
-
colors.push(color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1);
|
|
29483
|
-
}
|
|
29484
|
-
for (let corner = 0; corner < 6; corner++) indices.push(start + sourceIndices.getX(face * 6 + corner) - face * 4);
|
|
29485
|
-
}
|
|
29754
|
+
context.drawImage(atlases[atlas].image.canvas, x, y);
|
|
29486
29755
|
}
|
|
29487
29756
|
const geometry = new import_three41.BufferGeometry();
|
|
29488
|
-
geometry.setAttribute("position", new import_three41.Float32BufferAttribute(positions, 3));
|
|
29489
|
-
geometry.setAttribute("normal", new import_three41.Float32BufferAttribute(normals, 3));
|
|
29490
|
-
geometry.setAttribute("uv", new import_three41.Float32BufferAttribute(uvs, 2));
|
|
29491
|
-
geometry.setAttribute("uvBounds", new import_three41.Float32BufferAttribute(uvBounds, 4));
|
|
29492
|
-
geometry.setAttribute("color", new import_three41.Float32BufferAttribute(colors, 3));
|
|
29493
|
-
geometry.setIndex(indices);
|
|
29757
|
+
geometry.setAttribute("position", new import_three41.Float32BufferAttribute(page.positions.buffer, 3));
|
|
29758
|
+
geometry.setAttribute("normal", new import_three41.Float32BufferAttribute(page.normals.buffer, 3));
|
|
29759
|
+
geometry.setAttribute("uv", new import_three41.Float32BufferAttribute(page.uvs.buffer, 2));
|
|
29760
|
+
geometry.setAttribute("uvBounds", new import_three41.Float32BufferAttribute(page.uvBounds.buffer, 4));
|
|
29761
|
+
geometry.setAttribute("color", new import_three41.Float32BufferAttribute(page.colors.buffer, 3));
|
|
29762
|
+
geometry.setIndex(new import_three41.Uint32BufferAttribute(page.indices.buffer, 1));
|
|
29494
29763
|
geometry.computeBoundingBox();
|
|
29495
29764
|
geometry.computeBoundingSphere();
|
|
29496
29765
|
const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
|
|
@@ -29524,7 +29793,9 @@ var Chunk = class _Chunk {
|
|
|
29524
29793
|
this.renderedBlocks = /* @__PURE__ */ new Map();
|
|
29525
29794
|
this.sectionBlocks = /* @__PURE__ */ new Map();
|
|
29526
29795
|
this.hiddenBlocks = /* @__PURE__ */ new Set();
|
|
29796
|
+
this.blockPosition = new import_three42.Vector3();
|
|
29527
29797
|
this.meshDirty = false;
|
|
29798
|
+
this.meshGeneration = 0;
|
|
29528
29799
|
this.scene = scene;
|
|
29529
29800
|
this.x = x;
|
|
29530
29801
|
this.y = y;
|
|
@@ -29537,7 +29808,7 @@ var Chunk = class _Chunk {
|
|
|
29537
29808
|
if (isTripleArray(posOrX)) {
|
|
29538
29809
|
return this.getBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]));
|
|
29539
29810
|
}
|
|
29540
|
-
const pos = this.
|
|
29811
|
+
const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
|
|
29541
29812
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29542
29813
|
return this.renderedBlocks.get(index);
|
|
29543
29814
|
}
|
|
@@ -29561,48 +29832,81 @@ var Chunk = class _Chunk {
|
|
|
29561
29832
|
worldPos = this.chunkPosToWorldPos(pos);
|
|
29562
29833
|
}
|
|
29563
29834
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29564
|
-
this.hiddenBlocks.delete(index);
|
|
29565
|
-
this.data.set(index, block);
|
|
29566
|
-
const readBlock = this.data.snapshot(index);
|
|
29567
|
-
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29568
|
-
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29569
|
-
this.renderedBlocks.delete(index);
|
|
29570
|
-
let object3;
|
|
29571
29835
|
try {
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29575
|
-
if (!blockState) {
|
|
29576
|
-
this.data.set(index, void 0);
|
|
29577
|
-
return void 0;
|
|
29578
|
-
}
|
|
29579
|
-
const perBlock = !this.sectionModels || getFluidKind(blockState.key, stored.properties) || blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString());
|
|
29580
|
-
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29581
|
-
if (template) {
|
|
29582
|
-
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29583
|
-
this.meshDirty = true;
|
|
29584
|
-
} else {
|
|
29585
|
-
object3 = await this.scene.addBlock(blockState, {
|
|
29586
|
-
mergeMeshes: true,
|
|
29587
|
-
instanceMeshes: true,
|
|
29588
|
-
maxInstanceCount: 2e3,
|
|
29589
|
-
initialState: stored.properties
|
|
29590
|
-
});
|
|
29591
|
-
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29592
|
-
}
|
|
29593
|
-
const info = { get block() {
|
|
29594
|
-
return readBlock();
|
|
29595
|
-
}, object: object3 };
|
|
29596
|
-
this.renderedBlocks.set(index, info);
|
|
29597
|
-
return info;
|
|
29598
|
-
} catch (error) {
|
|
29599
|
-
this.data.set(index, void 0);
|
|
29600
|
-
object3?.removeFromScene();
|
|
29601
|
-
throw error;
|
|
29836
|
+
await this.placeBlocks([{ index, block }]);
|
|
29837
|
+
return this.renderedBlocks.get(index);
|
|
29602
29838
|
} finally {
|
|
29603
29839
|
await onBlocksChanged?.([worldPos]);
|
|
29604
29840
|
}
|
|
29605
29841
|
}
|
|
29842
|
+
/** Places cells in input order and returns changed world positions without refreshing neighbors. */
|
|
29843
|
+
async placeBlocks(blocks) {
|
|
29844
|
+
const resolutions = /* @__PURE__ */ new Map();
|
|
29845
|
+
for (const { block } of blocks) {
|
|
29846
|
+
if (!block || ChunkData.isAir(block)) continue;
|
|
29847
|
+
const key = ChunkData.paletteKey(block);
|
|
29848
|
+
if (resolutions.has(key)) continue;
|
|
29849
|
+
const stored = { type: block.type, properties: block.properties ? { ...block.properties } : void 0 };
|
|
29850
|
+
const resolved = (async () => {
|
|
29851
|
+
const blockState = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
29852
|
+
const perBlock = !blockState || !this.sectionModels || !!getFluidKind(blockState.key, stored.properties) || !!(blockState.key && BlockEntities.entry(await BlockEntities.getIndex(blockState.key.root), blockState.key.toNamespacedString()));
|
|
29853
|
+
if (!perBlock) await this.sectionModels.get(blockState, stored.properties);
|
|
29854
|
+
return { blockState, perBlock };
|
|
29855
|
+
})();
|
|
29856
|
+
resolutions.set(key, resolved);
|
|
29857
|
+
void resolved.catch(() => void 0);
|
|
29858
|
+
}
|
|
29859
|
+
const positions = [];
|
|
29860
|
+
let failed = false, failure;
|
|
29861
|
+
for (const { index, block } of blocks) {
|
|
29862
|
+
this.hiddenBlocks.delete(index);
|
|
29863
|
+
this.data.set(index, block);
|
|
29864
|
+
const readBlock = this.data.snapshot(index);
|
|
29865
|
+
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
29866
|
+
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
29867
|
+
this.renderedBlocks.delete(index);
|
|
29868
|
+
const worldPos = new import_three42.Vector3(
|
|
29869
|
+
this.x * 16 + index % 16,
|
|
29870
|
+
this.y * 16 + Math.floor(index / 256),
|
|
29871
|
+
this.z * 16 + Math.floor(index / 16) % 16
|
|
29872
|
+
);
|
|
29873
|
+
positions.push(worldPos);
|
|
29874
|
+
let object3;
|
|
29875
|
+
try {
|
|
29876
|
+
if (!readBlock) continue;
|
|
29877
|
+
const stored = readBlock();
|
|
29878
|
+
const key = ChunkData.paletteKey(stored);
|
|
29879
|
+
const { blockState, perBlock } = await resolutions.get(key);
|
|
29880
|
+
if (!blockState) {
|
|
29881
|
+
this.data.set(index, void 0);
|
|
29882
|
+
continue;
|
|
29883
|
+
}
|
|
29884
|
+
const template = perBlock ? void 0 : await this.sectionModels.get(blockState, stored.properties);
|
|
29885
|
+
if (template) {
|
|
29886
|
+
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
29887
|
+
this.meshDirty = true;
|
|
29888
|
+
} else {
|
|
29889
|
+
object3 = await this.scene.addBlock(blockState, {
|
|
29890
|
+
mergeMeshes: true,
|
|
29891
|
+
instanceMeshes: true,
|
|
29892
|
+
maxInstanceCount: 2e3,
|
|
29893
|
+
initialState: stored.properties
|
|
29894
|
+
});
|
|
29895
|
+
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
29896
|
+
}
|
|
29897
|
+
this.renderedBlocks.set(index, { get block() {
|
|
29898
|
+
return readBlock();
|
|
29899
|
+
}, object: object3 });
|
|
29900
|
+
} catch (error) {
|
|
29901
|
+
this.data.set(index, void 0);
|
|
29902
|
+
object3?.removeFromScene();
|
|
29903
|
+
if (!failed) failure = error;
|
|
29904
|
+
failed = true;
|
|
29905
|
+
}
|
|
29906
|
+
}
|
|
29907
|
+
if (failed) throw failure;
|
|
29908
|
+
return positions;
|
|
29909
|
+
}
|
|
29606
29910
|
/** Changes visibility at world block coordinates while preserving the block's data. */
|
|
29607
29911
|
async setBlockVisibleAt(pos, visible) {
|
|
29608
29912
|
const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
|
|
@@ -29623,15 +29927,24 @@ var Chunk = class _Chunk {
|
|
|
29623
29927
|
await this.onBlocksChanged?.([pos]);
|
|
29624
29928
|
}
|
|
29625
29929
|
isBlockVisibleAt(pos) {
|
|
29626
|
-
|
|
29930
|
+
return this.isBlockVisibleIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29931
|
+
}
|
|
29932
|
+
/** Reports whether a cell has a visible render object or section entry. */
|
|
29933
|
+
isBlockVisibleIndex(index) {
|
|
29627
29934
|
return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
|
|
29628
29935
|
}
|
|
29629
29936
|
isOccludingAt(pos) {
|
|
29630
|
-
|
|
29937
|
+
return this.isOccludingIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
29938
|
+
}
|
|
29939
|
+
/** Reports whether a visible cell hides neighboring cube faces. */
|
|
29940
|
+
isOccludingIndex(index) {
|
|
29631
29941
|
return !this.hiddenBlocks.has(index) && (this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false));
|
|
29632
29942
|
}
|
|
29633
29943
|
async setCullMaskAt(pos, mask) {
|
|
29634
|
-
|
|
29944
|
+
await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
|
|
29945
|
+
}
|
|
29946
|
+
/** Updates hidden faces for a cell's section entry or individual render object. */
|
|
29947
|
+
async setCullMaskIndex(index, mask) {
|
|
29635
29948
|
const entry = this.sectionBlocks.get(index);
|
|
29636
29949
|
if (entry) {
|
|
29637
29950
|
if (entry.cullMask !== mask) {
|
|
@@ -29642,12 +29955,17 @@ var Chunk = class _Chunk {
|
|
|
29642
29955
|
await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
|
|
29643
29956
|
}
|
|
29644
29957
|
}
|
|
29645
|
-
|
|
29958
|
+
/** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
|
|
29959
|
+
async rebuildSectionMesh() {
|
|
29646
29960
|
if (!this.meshDirty) return;
|
|
29647
|
-
|
|
29648
|
-
|
|
29649
|
-
|
|
29650
|
-
) : void 0;
|
|
29961
|
+
this.meshDirty = false;
|
|
29962
|
+
const generation = ++this.meshGeneration;
|
|
29963
|
+
const entries = [...this.sectionBlocks.values()].filter((entry) => !this.hiddenBlocks.has(entry.index));
|
|
29964
|
+
const next = entries.length ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize) : void 0;
|
|
29965
|
+
if (generation !== this.meshGeneration) {
|
|
29966
|
+
next?.dispose();
|
|
29967
|
+
return;
|
|
29968
|
+
}
|
|
29651
29969
|
this.sectionMesh?.dispose();
|
|
29652
29970
|
this.sectionMesh = next;
|
|
29653
29971
|
if (next) {
|
|
@@ -29655,7 +29973,6 @@ var Chunk = class _Chunk {
|
|
|
29655
29973
|
this.scene.add(next);
|
|
29656
29974
|
}
|
|
29657
29975
|
this.scene.dirty = true;
|
|
29658
|
-
this.meshDirty = false;
|
|
29659
29976
|
}
|
|
29660
29977
|
/** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
|
|
29661
29978
|
async clear(onBlocksChanged = this.onBlocksChanged) {
|
|
@@ -29664,6 +29981,7 @@ var Chunk = class _Chunk {
|
|
|
29664
29981
|
));
|
|
29665
29982
|
for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
|
|
29666
29983
|
this.sectionBlocks.clear();
|
|
29984
|
+
this.meshGeneration++;
|
|
29667
29985
|
this.sectionMesh?.dispose();
|
|
29668
29986
|
this.sectionMesh = void 0;
|
|
29669
29987
|
this.meshDirty = false;
|