minerender 2.1.0-alpha.87 → 2.1.0-alpha.89
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 +102 -3
- package/dist/browser/index.d.ts +102 -3
- package/dist/browser/index.js +683 -213
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +683 -214
- 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 +700 -221
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +101 -3
- package/dist/node/index.d.ts +101 -3
- package/dist/node/index.js +680 -213
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +675 -213
- 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,12 +18795,14 @@ __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,
|
|
18784
18802
|
SkinTextures: () => SkinTextures,
|
|
18785
18803
|
Skins: () => Skins,
|
|
18786
18804
|
SpecialItems: () => SpecialItems,
|
|
18805
|
+
SpongeSchematicParser: () => SpongeSchematicParser,
|
|
18787
18806
|
StructureParser: () => StructureParser,
|
|
18788
18807
|
TextureAtlas: () => TextureAtlas,
|
|
18789
18808
|
TextureLoader: () => TextureLoader,
|
|
@@ -18802,6 +18821,7 @@ __export(index_browser_exports, {
|
|
|
18802
18821
|
axisToVec3: () => axisToVec3,
|
|
18803
18822
|
base64decode: () => base64decode,
|
|
18804
18823
|
base64encode: () => base64encode,
|
|
18824
|
+
buildSectionGeometry: () => buildSectionGeometry,
|
|
18805
18825
|
capeTextureSizes: () => capeTextureSizes,
|
|
18806
18826
|
captureException: () => captureException,
|
|
18807
18827
|
changeEvent: () => changeEvent,
|
|
@@ -18854,6 +18874,7 @@ __export(index_browser_exports, {
|
|
|
18854
18874
|
sampleEntityAnimation: () => sampleEntityAnimation,
|
|
18855
18875
|
sampleEntityKeyframes: () => sampleEntityKeyframes,
|
|
18856
18876
|
sampleFluid: () => sampleFluid,
|
|
18877
|
+
sectionGeometryTransferables: () => sectionGeometryTransferables,
|
|
18857
18878
|
serializeBoxGeometryKey: () => serializeBoxGeometryKey,
|
|
18858
18879
|
serializeImageKey: () => serializeImageKey,
|
|
18859
18880
|
serializeMaterialKey: () => serializeMaterialKey,
|
|
@@ -18871,7 +18892,8 @@ __export(index_browser_exports, {
|
|
|
18871
18892
|
toDegrees: () => toDegrees,
|
|
18872
18893
|
toRadians: () => toRadians,
|
|
18873
18894
|
toTrianglesDrawMode: () => toTrianglesDrawMode,
|
|
18874
|
-
trimCanvas: () => trimCanvas
|
|
18895
|
+
trimCanvas: () => trimCanvas,
|
|
18896
|
+
yieldToEventLoop: () => yieldToEventLoop
|
|
18875
18897
|
});
|
|
18876
18898
|
module.exports = __toCommonJS(index_browser_exports);
|
|
18877
18899
|
|
|
@@ -18956,6 +18978,7 @@ function probeImageSize(data) {
|
|
|
18956
18978
|
}
|
|
18957
18979
|
|
|
18958
18980
|
// src/env/browser/BrowserEnv.ts
|
|
18981
|
+
var import_meta = {};
|
|
18959
18982
|
var BrowserEnv = class {
|
|
18960
18983
|
constructor() {
|
|
18961
18984
|
this.name = "browser";
|
|
@@ -18983,6 +19006,14 @@ var BrowserEnv = class {
|
|
|
18983
19006
|
version: version2
|
|
18984
19007
|
}));
|
|
18985
19008
|
}
|
|
19009
|
+
createWorker(name) {
|
|
19010
|
+
if (name !== "section" || typeof Worker === "undefined") return void 0;
|
|
19011
|
+
try {
|
|
19012
|
+
return new Worker(new URL("./section.worker.mjs", import_meta.url), { type: "module" });
|
|
19013
|
+
} catch {
|
|
19014
|
+
return void 0;
|
|
19015
|
+
}
|
|
19016
|
+
}
|
|
18986
19017
|
};
|
|
18987
19018
|
function registerBrowserEnv() {
|
|
18988
19019
|
const env = new BrowserEnv();
|
|
@@ -23311,46 +23342,46 @@ function matchesCondition(condition, state) {
|
|
|
23311
23342
|
}
|
|
23312
23343
|
var BlockStateResolver = class {
|
|
23313
23344
|
/** Loads vanilla property defaults, falling back to preview values inferred from the blockstate file. */
|
|
23314
|
-
static async defaults(
|
|
23315
|
-
const defaults =
|
|
23345
|
+
static async defaults(blockState2) {
|
|
23346
|
+
const defaults = blockState2.key ? await BlockStates.getDefaultState(blockState2.key) : void 0;
|
|
23316
23347
|
const state = {};
|
|
23317
23348
|
if (defaults && Object.keys(defaults).length > 0) {
|
|
23318
23349
|
for (const key in defaults) state[key] = defaults[key].default;
|
|
23319
|
-
} else if (
|
|
23320
|
-
const variant = Object.keys(
|
|
23350
|
+
} else if (blockState2.variants) {
|
|
23351
|
+
const variant = Object.keys(blockState2.variants)[0];
|
|
23321
23352
|
if (variant) {
|
|
23322
23353
|
for (const property of variant.split(",")) {
|
|
23323
23354
|
const [key, value] = property.split("=");
|
|
23324
23355
|
state[key] = value;
|
|
23325
23356
|
}
|
|
23326
23357
|
}
|
|
23327
|
-
} else if (
|
|
23328
|
-
const condition =
|
|
23358
|
+
} else if (blockState2.multipart) {
|
|
23359
|
+
const condition = blockState2.multipart.map((part) => part.when).find((when) => when !== void 0 && Object.values(when).every((value) => typeof value === "string"));
|
|
23329
23360
|
for (const [key, value] of Object.entries(condition ?? {})) state[key] = value.split("|")[0];
|
|
23330
23361
|
}
|
|
23331
23362
|
return state;
|
|
23332
23363
|
}
|
|
23333
23364
|
/** Selects matching models. Supply `choose` to control selection within each alternatives array. */
|
|
23334
|
-
static select(
|
|
23335
|
-
return this.matching(
|
|
23365
|
+
static select(blockState2, state, choose = this.choose) {
|
|
23366
|
+
return this.matching(blockState2, state).map(choose);
|
|
23336
23367
|
}
|
|
23337
23368
|
/** Returns matching model groups before choosing weighted alternatives. */
|
|
23338
|
-
static matching(
|
|
23369
|
+
static matching(blockState2, state) {
|
|
23339
23370
|
const out = [];
|
|
23340
|
-
if (
|
|
23341
|
-
if (Object.keys(
|
|
23342
|
-
out.push(
|
|
23371
|
+
if (blockState2.variants) {
|
|
23372
|
+
if (Object.keys(blockState2.variants).length === 1 && "" in blockState2.variants) {
|
|
23373
|
+
out.push(blockState2.variants[""]);
|
|
23343
23374
|
} else {
|
|
23344
|
-
for (const variantKey in
|
|
23375
|
+
for (const variantKey in blockState2.variants) {
|
|
23345
23376
|
const matches = variantKey.split(",").every((property) => {
|
|
23346
23377
|
const [key, value] = property.split("=");
|
|
23347
23378
|
return `${state[key]}` === `${value}`;
|
|
23348
23379
|
});
|
|
23349
|
-
if (matches) out.push(
|
|
23380
|
+
if (matches) out.push(blockState2.variants[variantKey]);
|
|
23350
23381
|
}
|
|
23351
23382
|
}
|
|
23352
|
-
} else if (
|
|
23353
|
-
for (const part of
|
|
23383
|
+
} else if (blockState2.multipart) {
|
|
23384
|
+
for (const part of blockState2.multipart) {
|
|
23354
23385
|
if (part.apply && (!part.when || matchesCondition(part.when, state))) {
|
|
23355
23386
|
out.push(part.apply);
|
|
23356
23387
|
}
|
|
@@ -23478,9 +23509,9 @@ init_ModelCulling();
|
|
|
23478
23509
|
init_FluidGeometry();
|
|
23479
23510
|
init_Entities();
|
|
23480
23511
|
var _BlockObject = class _BlockObject extends SceneObject {
|
|
23481
|
-
constructor(
|
|
23512
|
+
constructor(blockState2, options) {
|
|
23482
23513
|
super(options);
|
|
23483
|
-
this.blockState =
|
|
23514
|
+
this.blockState = blockState2;
|
|
23484
23515
|
this.isBlockObject = true;
|
|
23485
23516
|
this._previousState = {};
|
|
23486
23517
|
this._state = {};
|
|
@@ -25659,6 +25690,157 @@ function compoundList(tags, name) {
|
|
|
25659
25690
|
throw new MineRenderError(`Schematic ${name} must be a compound list`);
|
|
25660
25691
|
}
|
|
25661
25692
|
|
|
25693
|
+
// src/model/multiblock/SpongeSchematicParser.ts
|
|
25694
|
+
init_MineRenderError();
|
|
25695
|
+
var SpongeSchematicParser = class {
|
|
25696
|
+
/**
|
|
25697
|
+
* Parses decoded NBT with a local block palette, omitting air blocks.
|
|
25698
|
+
* Applies Offset to block and entity positions; size remains the source dimensions.
|
|
25699
|
+
* Biomes and metadata are not imported, and DataVersion does not trigger migration.
|
|
25700
|
+
*/
|
|
25701
|
+
static async parse(nbt) {
|
|
25702
|
+
const tags = nbt.value.Schematic === void 0 ? nbt.value : compound(nbt.value.Schematic, "Schematic");
|
|
25703
|
+
const version2 = integer2(tags.Version, "Version");
|
|
25704
|
+
if (version2 !== 2 && version2 !== 3) {
|
|
25705
|
+
throw new MineRenderError(`Unsupported Sponge schematic version ${version2}; expected 2 or 3`);
|
|
25706
|
+
}
|
|
25707
|
+
const dataVersion = integer2(tags.DataVersion, "DataVersion");
|
|
25708
|
+
const size = ["Width", "Height", "Length"].map((name) => {
|
|
25709
|
+
const tag = tags[name];
|
|
25710
|
+
if (tag?.type !== "short" || !Number.isInteger(tag.value) || tag.value < -32768 || tag.value > 65535 || tag.value === 0) {
|
|
25711
|
+
throw new MineRenderError(`Sponge schematic ${name} must be a positive unsigned short`);
|
|
25712
|
+
}
|
|
25713
|
+
return tag.value & 65535;
|
|
25714
|
+
});
|
|
25715
|
+
const offset = tags.Offset === void 0 ? [0, 0, 0] : intPosition(tags.Offset, "Offset");
|
|
25716
|
+
const blocks = [];
|
|
25717
|
+
const container = version2 === 2 ? tags : tags.Blocks === void 0 ? void 0 : compound(tags.Blocks, "Blocks");
|
|
25718
|
+
if (container) {
|
|
25719
|
+
const paletteTag = container.Palette;
|
|
25720
|
+
if (!paletteTag) throw new MineRenderError("Sponge schematic requires a local Palette; global block IDs are not supported");
|
|
25721
|
+
const palette = /* @__PURE__ */ new Map();
|
|
25722
|
+
const paletteMax = version2 === 2 && tags.PaletteMax !== void 0 ? integer2(tags.PaletteMax, "PaletteMax") : void 0;
|
|
25723
|
+
if (paletteMax !== void 0 && paletteMax <= 0) throw new MineRenderError("Sponge schematic PaletteMax must be positive");
|
|
25724
|
+
for (const [state, tag] of Object.entries(compound(paletteTag, "Palette"))) {
|
|
25725
|
+
const id = integer2(tag, "Palette index");
|
|
25726
|
+
if (id < 0 || palette.has(id) || paletteMax !== void 0 && id >= paletteMax) {
|
|
25727
|
+
throw new MineRenderError(`Invalid or duplicate Sponge schematic palette index ${id}`);
|
|
25728
|
+
}
|
|
25729
|
+
palette.set(id, blockState(state));
|
|
25730
|
+
}
|
|
25731
|
+
const [width, height, length] = size;
|
|
25732
|
+
const volume = width * height * length;
|
|
25733
|
+
const data = container[version2 === 2 ? "BlockData" : "Data"];
|
|
25734
|
+
if (data?.type !== "byteArray" || !Array.isArray(data.value) || data.value.length < volume) {
|
|
25735
|
+
throw new MineRenderError(`Sponge schematic block data must contain ${volume} varints`);
|
|
25736
|
+
}
|
|
25737
|
+
const blockEntities = /* @__PURE__ */ new Map();
|
|
25738
|
+
for (const entity of compoundList2(container, "BlockEntities")) {
|
|
25739
|
+
const pos = intPosition(entity.Pos, "BlockEntities Pos");
|
|
25740
|
+
if (pos.some((value2, axis) => value2 < 0 || value2 >= size[axis])) {
|
|
25741
|
+
throw new MineRenderError("Sponge schematic block entity position is outside its dimensions");
|
|
25742
|
+
}
|
|
25743
|
+
const index = pos[0] + pos[2] * width + pos[1] * width * length;
|
|
25744
|
+
if (blockEntities.has(index)) throw new MineRenderError(`Duplicate Sponge schematic block entity at index ${index}`);
|
|
25745
|
+
const value = entityData(entity, version2);
|
|
25746
|
+
for (const [axis, name] of ["x", "y", "z"].entries()) {
|
|
25747
|
+
value[name] = { type: "int", value: pos[axis] + offset[axis] };
|
|
25748
|
+
}
|
|
25749
|
+
blockEntities.set(index, { type: "compound", value });
|
|
25750
|
+
}
|
|
25751
|
+
let cursor = 0;
|
|
25752
|
+
for (let index = 0; index < volume; index++) {
|
|
25753
|
+
let id = 0;
|
|
25754
|
+
for (let shift = 0; ; shift += 7) {
|
|
25755
|
+
const byte = data.value[cursor++];
|
|
25756
|
+
if (!Number.isInteger(byte) || byte < -128 || byte > 255) {
|
|
25757
|
+
throw new MineRenderError(`Invalid or truncated Sponge schematic varint at index ${index}`);
|
|
25758
|
+
}
|
|
25759
|
+
const unsigned = byte & 255;
|
|
25760
|
+
if (shift === 28 && unsigned > 7) {
|
|
25761
|
+
throw new MineRenderError(`Sponge schematic varint exceeds a nonnegative int at index ${index}`);
|
|
25762
|
+
}
|
|
25763
|
+
id += (unsigned & 127) * 2 ** shift;
|
|
25764
|
+
if ((unsigned & 128) === 0) break;
|
|
25765
|
+
}
|
|
25766
|
+
const state = palette.get(id);
|
|
25767
|
+
if (!state) throw new MineRenderError(`Sponge schematic palette index ${id} does not exist`);
|
|
25768
|
+
if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(state.type)) continue;
|
|
25769
|
+
blocks.push({
|
|
25770
|
+
type: state.type,
|
|
25771
|
+
properties: { ...state.properties },
|
|
25772
|
+
position: [
|
|
25773
|
+
index % width + offset[0],
|
|
25774
|
+
Math.floor(index / (width * length)) + offset[1],
|
|
25775
|
+
Math.floor(index / width) % length + offset[2]
|
|
25776
|
+
],
|
|
25777
|
+
nbt: blockEntities.get(index)
|
|
25778
|
+
});
|
|
25779
|
+
}
|
|
25780
|
+
if (cursor !== data.value.length) throw new MineRenderError("Sponge schematic block data has trailing bytes");
|
|
25781
|
+
}
|
|
25782
|
+
const entities = compoundList2(tags, "Entities").map((entity) => {
|
|
25783
|
+
const pos = entity.Pos;
|
|
25784
|
+
if (pos?.type !== "list" || pos.value.type !== "double" || pos.value.value.length !== 3 || !pos.value.value.every(Number.isFinite)) {
|
|
25785
|
+
throw new MineRenderError("Sponge schematic entity Pos must contain three doubles");
|
|
25786
|
+
}
|
|
25787
|
+
const position = pos.value.value.map((value2, axis) => value2 + offset[axis]);
|
|
25788
|
+
const value = entityData(entity, version2);
|
|
25789
|
+
value.Pos = { type: "list", value: { type: "double", value: [...position] } };
|
|
25790
|
+
return { position, nbt: { type: "compound", value } };
|
|
25791
|
+
});
|
|
25792
|
+
return { size, blocks, entities, dataVersion };
|
|
25793
|
+
}
|
|
25794
|
+
};
|
|
25795
|
+
function integer2(tag, name) {
|
|
25796
|
+
if (tag?.type !== "int" || !Number.isInteger(tag.value) || tag.value < -2147483648 || tag.value > 2147483647) {
|
|
25797
|
+
throw new MineRenderError(`Sponge schematic ${name} must be an int`);
|
|
25798
|
+
}
|
|
25799
|
+
return tag.value;
|
|
25800
|
+
}
|
|
25801
|
+
function compound(tag, name) {
|
|
25802
|
+
if (tag?.type !== "compound" || !tag.value || typeof tag.value !== "object" || Array.isArray(tag.value)) {
|
|
25803
|
+
throw new MineRenderError(`Sponge schematic ${name} must be a compound`);
|
|
25804
|
+
}
|
|
25805
|
+
return tag.value;
|
|
25806
|
+
}
|
|
25807
|
+
function intPosition(tag, name) {
|
|
25808
|
+
if (tag?.type !== "intArray" || !Array.isArray(tag.value) || tag.value.length !== 3 || !tag.value.every((value) => Number.isInteger(value) && value >= -2147483648 && value <= 2147483647)) {
|
|
25809
|
+
throw new MineRenderError(`Sponge schematic ${name} must contain three ints`);
|
|
25810
|
+
}
|
|
25811
|
+
return [...tag.value];
|
|
25812
|
+
}
|
|
25813
|
+
function compoundList2(tags, name) {
|
|
25814
|
+
const tag = tags[name];
|
|
25815
|
+
if (tag === void 0) return [];
|
|
25816
|
+
if (tag.type === "list" && Array.isArray(tag.value.value)) {
|
|
25817
|
+
if (tag.value.value.length === 0) return [];
|
|
25818
|
+
if (tag.value.type === "compound") return tag.value.value;
|
|
25819
|
+
}
|
|
25820
|
+
throw new MineRenderError(`Sponge schematic ${name} must be a compound list`);
|
|
25821
|
+
}
|
|
25822
|
+
function blockState(state) {
|
|
25823
|
+
const match = /^((?:[a-z0-9_.-]+:)?[a-z0-9_./-]+)(?:\[([^\[\]]*)\])?$/.exec(state);
|
|
25824
|
+
if (!match) throw new MineRenderError(`Invalid Sponge schematic block state ${state}`);
|
|
25825
|
+
const properties = {};
|
|
25826
|
+
if (match[2]) {
|
|
25827
|
+
for (const entry of match[2].split(",")) {
|
|
25828
|
+
const pair = /^([a-z0-9_]+)=([^\s=,\[\]]+)$/.exec(entry);
|
|
25829
|
+
if (!pair || Object.prototype.hasOwnProperty.call(properties, pair[1])) throw new MineRenderError(`Invalid Sponge schematic block state ${state}`);
|
|
25830
|
+
Object.defineProperty(properties, pair[1], { value: pair[2], enumerable: true, writable: true, configurable: true });
|
|
25831
|
+
}
|
|
25832
|
+
}
|
|
25833
|
+
return { type: match[1].includes(":") ? match[1] : `minecraft:${match[1]}`, properties };
|
|
25834
|
+
}
|
|
25835
|
+
function entityData(entity, version2) {
|
|
25836
|
+
const id = entity.Id;
|
|
25837
|
+
if (id?.type !== "string" || !id.value) throw new MineRenderError("Sponge schematic entity Id must be a nonempty string");
|
|
25838
|
+
const { Id, Pos, ...extra } = entity;
|
|
25839
|
+
const value = version2 === 2 ? extra : entity.Data === void 0 ? {} : { ...compound(entity.Data, "entity Data") };
|
|
25840
|
+
value.id = { type: "string", value: id.value.includes(":") ? id.value : `minecraft:${id.value}` };
|
|
25841
|
+
return value;
|
|
25842
|
+
}
|
|
25843
|
+
|
|
25662
25844
|
// src/model/multiblock/StructureParser.ts
|
|
25663
25845
|
init_MineRenderError();
|
|
25664
25846
|
var StructureParser = class {
|
|
@@ -26883,8 +27065,8 @@ var _MineRenderScene = class _MineRenderScene extends import_three34.Scene {
|
|
|
26883
27065
|
* Set `options.initialState` to choose block properties such as `facing` or `axis`.
|
|
26884
27066
|
* Use the returned object's `setPosition` method to move all of its model parts.
|
|
26885
27067
|
*/
|
|
26886
|
-
async addBlock(
|
|
26887
|
-
return this.addSceneObject(
|
|
27068
|
+
async addBlock(blockState2, options, parent = this) {
|
|
27069
|
+
return this.addSceneObject(blockState2, () => new BlockObject(blockState2, options), options, parent);
|
|
26888
27070
|
}
|
|
26889
27071
|
/**
|
|
26890
27072
|
* Loads a skin and adds an initialized player model.
|
|
@@ -28636,6 +28818,16 @@ init_NBTHelper();
|
|
|
28636
28818
|
|
|
28637
28819
|
// src/world/ChunkData.ts
|
|
28638
28820
|
init_AssetKey();
|
|
28821
|
+
var blockKeys = /* @__PURE__ */ new Map();
|
|
28822
|
+
function blockKey(type) {
|
|
28823
|
+
let key = blockKeys.get(type);
|
|
28824
|
+
if (!key) {
|
|
28825
|
+
key = AssetKey.parse("blockstates", type);
|
|
28826
|
+
if (blockKeys.size >= 4096) blockKeys.clear();
|
|
28827
|
+
blockKeys.set(type, key);
|
|
28828
|
+
}
|
|
28829
|
+
return key;
|
|
28830
|
+
}
|
|
28639
28831
|
var ChunkData = class _ChunkData {
|
|
28640
28832
|
constructor() {
|
|
28641
28833
|
this.indices = new Uint16Array(4096);
|
|
@@ -28663,10 +28855,10 @@ var ChunkData = class _ChunkData {
|
|
|
28663
28855
|
let key;
|
|
28664
28856
|
let nbt;
|
|
28665
28857
|
if (block && !_ChunkData.isAir(block)) {
|
|
28666
|
-
const type =
|
|
28858
|
+
const type = blockKey(block.type).toNamespacedString();
|
|
28667
28859
|
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28668
28860
|
state = Object.freeze(Object.keys(properties).length ? { type, properties: Object.freeze(properties) } : { type });
|
|
28669
|
-
key =
|
|
28861
|
+
key = _ChunkData.paletteKey(block);
|
|
28670
28862
|
if (block.nbt !== void 0) nbt = structuredClone(block.nbt);
|
|
28671
28863
|
}
|
|
28672
28864
|
const previousId = this.indices[index];
|
|
@@ -28703,9 +28895,14 @@ var ChunkData = class _ChunkData {
|
|
|
28703
28895
|
}
|
|
28704
28896
|
static isAir(block) {
|
|
28705
28897
|
if (typeof block === "undefined" || typeof block.type === "undefined") return true;
|
|
28706
|
-
const key =
|
|
28898
|
+
const key = blockKey(block.type);
|
|
28707
28899
|
return key.namespace === "minecraft" && !key.type && ["air", "cave_air", "void_air"].includes(key.path);
|
|
28708
28900
|
}
|
|
28901
|
+
/** Identifies a normalized block type and its sorted properties, excluding per-cell NBT. */
|
|
28902
|
+
static paletteKey(block) {
|
|
28903
|
+
const properties = Object.fromEntries(Object.keys(block.properties ?? {}).sort().map((name) => [name, block.properties[name]]));
|
|
28904
|
+
return JSON.stringify([blockKey(block.type).toNamespacedString(), properties]);
|
|
28905
|
+
}
|
|
28709
28906
|
static copyBlock(state, nbt) {
|
|
28710
28907
|
const block = { type: state.type };
|
|
28711
28908
|
if (state.properties) block.properties = { ...state.properties };
|
|
@@ -28885,14 +29082,17 @@ var AnvilParser = class {
|
|
|
28885
29082
|
const perLong = Math.floor(64 / bits);
|
|
28886
29083
|
const expected = padded ? Math.ceil(4096 / perLong) : Math.ceil(4096 * bits / 64);
|
|
28887
29084
|
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 = (
|
|
29085
|
+
const words = tag.value.map(([high, low]) => [high >>> 0, low >>> 0]);
|
|
29086
|
+
const mask = (1 << bits) - 1;
|
|
28890
29087
|
for (let index = 0; index < 4096; index++) {
|
|
28891
|
-
const
|
|
28892
|
-
const
|
|
28893
|
-
|
|
28894
|
-
|
|
28895
|
-
|
|
29088
|
+
const bitPos = index * bits;
|
|
29089
|
+
const word = padded ? Math.floor(index / perLong) : bitPos >>> 6;
|
|
29090
|
+
const shift = padded ? index % perLong * bits : bitPos & 63;
|
|
29091
|
+
const [high, low] = words[word];
|
|
29092
|
+
let value = shift < 32 ? low >>> shift : high >>> shift - 32;
|
|
29093
|
+
if (shift < 32 && shift + bits > 32) value += high * 2 ** (32 - shift);
|
|
29094
|
+
if (!padded && shift + bits > 64) value += words[word + 1][1] * 2 ** (64 - shift);
|
|
29095
|
+
indices[index] = value & mask;
|
|
28896
29096
|
}
|
|
28897
29097
|
return indices;
|
|
28898
29098
|
}
|
|
@@ -29034,7 +29234,7 @@ init_AssetKey();
|
|
|
29034
29234
|
// src/world/MineRenderWorld.ts
|
|
29035
29235
|
var import_three40 = require("three");
|
|
29036
29236
|
init_Model();
|
|
29037
|
-
|
|
29237
|
+
init_util();
|
|
29038
29238
|
init_AssetKey();
|
|
29039
29239
|
init_BlockStates();
|
|
29040
29240
|
init_CubeFace();
|
|
@@ -29071,18 +29271,18 @@ var SectionModels = class {
|
|
|
29071
29271
|
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("maxAtlasSize must be a positive integer");
|
|
29072
29272
|
}
|
|
29073
29273
|
/** Returns a cube template, or `undefined` when the block requires an individual render object. */
|
|
29074
|
-
async get(
|
|
29075
|
-
if (!
|
|
29076
|
-
let states = this.states.get(
|
|
29077
|
-
if (!states) this.states.set(
|
|
29274
|
+
async get(blockState2, properties = {}) {
|
|
29275
|
+
if (!blockState2.variants || blockState2.multipart) return void 0;
|
|
29276
|
+
let states = this.states.get(blockState2);
|
|
29277
|
+
if (!states) this.states.set(blockState2, states = /* @__PURE__ */ new Map());
|
|
29078
29278
|
const key = JSON.stringify(Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)));
|
|
29079
|
-
const prepared = await cached(states, key, () => this.prepare(
|
|
29279
|
+
const prepared = await cached(states, key, () => this.prepare(blockState2, properties));
|
|
29080
29280
|
if (!prepared) return void 0;
|
|
29081
29281
|
return prepared.templates.get(BlockStateResolver.choose(prepared.variants));
|
|
29082
29282
|
}
|
|
29083
|
-
async prepare(
|
|
29084
|
-
const state = { ...await BlockStateResolver.defaults(
|
|
29085
|
-
const groups = BlockStateResolver.matching(
|
|
29283
|
+
async prepare(blockState2, properties) {
|
|
29284
|
+
const state = { ...await BlockStateResolver.defaults(blockState2), ...properties };
|
|
29285
|
+
const groups = BlockStateResolver.matching(blockState2, state);
|
|
29086
29286
|
if (groups.length !== 1) return void 0;
|
|
29087
29287
|
const variants = groups[0];
|
|
29088
29288
|
const choices = Array.isArray(variants) ? variants : [variants];
|
|
@@ -29094,7 +29294,7 @@ var SectionModels = class {
|
|
|
29094
29294
|
const atlas = await UVMapper.getAtlas(model);
|
|
29095
29295
|
const rotation = BlockStateResolver.rotation(variant);
|
|
29096
29296
|
if (!atlas || atlas.hasAnimation || !ModelCulling.isOpaqueFullCube(atlas) || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize || ModelCulling.toLocalMask(63, rotation) !== 63) return void 0;
|
|
29097
|
-
const tints = await BlockTints.get(
|
|
29297
|
+
const tints = await BlockTints.get(blockState2.key, state, model);
|
|
29098
29298
|
let models = this.models.get(model);
|
|
29099
29299
|
if (!models) this.models.set(model, models = /* @__PURE__ */ new Map());
|
|
29100
29300
|
const key = JSON.stringify([rotation.x, rotation.y, rotation.z, variant.uvlock, tints]);
|
|
@@ -29179,7 +29379,7 @@ var MineRenderWorld = class {
|
|
|
29179
29379
|
* Places a structure's blocks at their stored positions, then refreshes neighboring faces.
|
|
29180
29380
|
* Entity NBT is retained by the structure but is not rendered.
|
|
29181
29381
|
*
|
|
29182
|
-
* @param useBatches -
|
|
29382
|
+
* @param useBatches - Yields between chunks by default. Set to `false` for sequential placement.
|
|
29183
29383
|
* @param executor - Optional queue for batched placement. The caller remains responsible for stopping it.
|
|
29184
29384
|
*/
|
|
29185
29385
|
async placeMultiBlock(multiblock, useBatches = true, executor) {
|
|
@@ -29196,8 +29396,10 @@ var MineRenderWorld = class {
|
|
|
29196
29396
|
};
|
|
29197
29397
|
const place = (block) => this.placeBlock(new import_three40.Vector3(...block.position), block, collect);
|
|
29198
29398
|
const keys = /* @__PURE__ */ new Map();
|
|
29399
|
+
const types = /* @__PURE__ */ new Set();
|
|
29199
29400
|
for (const block of multiblock.blocks) {
|
|
29200
|
-
if (Chunk.isAir(block)) continue;
|
|
29401
|
+
if (Chunk.isAir(block) || types.has(block.type)) continue;
|
|
29402
|
+
types.add(block.type);
|
|
29201
29403
|
const key = AssetKey.parse("blockstates", block.type);
|
|
29202
29404
|
keys.set(key.serialize(), key);
|
|
29203
29405
|
}
|
|
@@ -29206,6 +29408,23 @@ var MineRenderWorld = class {
|
|
|
29206
29408
|
for (const block of multiblock.blocks) await place(block);
|
|
29207
29409
|
return;
|
|
29208
29410
|
}
|
|
29411
|
+
if (!executor) {
|
|
29412
|
+
const groups2 = /* @__PURE__ */ new Map();
|
|
29413
|
+
const position = new import_three40.Vector3();
|
|
29414
|
+
for (const block of multiblock.blocks) {
|
|
29415
|
+
const [x, y, z] = block.position;
|
|
29416
|
+
this.validatePosBounds(position.set(x, y, z));
|
|
29417
|
+
const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
|
|
29418
|
+
const key = `${cx}_${cy}_${cz}`;
|
|
29419
|
+
const chunk = this._chunks.get(key) ?? (Chunk.isAir(block) ? void 0 : this.getOrCreateChunkAt(position));
|
|
29420
|
+
if (!chunk) continue;
|
|
29421
|
+
let group = groups2.get(chunk);
|
|
29422
|
+
if (!group) groups2.set(chunk, group = []);
|
|
29423
|
+
group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
|
|
29424
|
+
}
|
|
29425
|
+
await this.placeChunkGroups(groups2, changes);
|
|
29426
|
+
return;
|
|
29427
|
+
}
|
|
29209
29428
|
const positions = /* @__PURE__ */ new Map();
|
|
29210
29429
|
for (const block of multiblock.blocks) {
|
|
29211
29430
|
const key = block.position.join(",");
|
|
@@ -29214,19 +29433,34 @@ var MineRenderWorld = class {
|
|
|
29214
29433
|
else positions.set(key, [block]);
|
|
29215
29434
|
}
|
|
29216
29435
|
const groups = [...positions.values()];
|
|
29217
|
-
|
|
29218
|
-
|
|
29219
|
-
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
|
|
29223
|
-
|
|
29224
|
-
|
|
29225
|
-
|
|
29226
|
-
|
|
29436
|
+
for (let i = 0; i < groups.length; i += executor.batch) {
|
|
29437
|
+
const results = await Promise.allSettled(groups.slice(i, i + executor.batch).map(
|
|
29438
|
+
(blocks) => executor.submit(async () => {
|
|
29439
|
+
for (const block of blocks) await place(block);
|
|
29440
|
+
})
|
|
29441
|
+
));
|
|
29442
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
29443
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
29444
|
+
}
|
|
29445
|
+
}
|
|
29446
|
+
async placeChunkGroups(groups, changes) {
|
|
29447
|
+
let sliceStart = performance.now();
|
|
29448
|
+
for (const [chunk, blocks] of groups) {
|
|
29449
|
+
let positions;
|
|
29450
|
+
try {
|
|
29451
|
+
positions = await chunk.placeBlocks(blocks);
|
|
29452
|
+
} finally {
|
|
29453
|
+
positions ?? (positions = blocks.map(({ index }) => new import_three40.Vector3(
|
|
29454
|
+
chunk.x * 16 + index % 16,
|
|
29455
|
+
chunk.y * 16 + Math.floor(index / 256),
|
|
29456
|
+
chunk.z * 16 + Math.floor(index / 16) % 16
|
|
29457
|
+
)));
|
|
29458
|
+
for (const pos of positions) changes.set(`${pos.x},${pos.y},${pos.z}`, pos);
|
|
29459
|
+
}
|
|
29460
|
+
if (performance.now() - sliceStart > 8) {
|
|
29461
|
+
await yieldToEventLoop();
|
|
29462
|
+
sliceStart = performance.now();
|
|
29227
29463
|
}
|
|
29228
|
-
} finally {
|
|
29229
|
-
if (!executor) queue.stop();
|
|
29230
29464
|
}
|
|
29231
29465
|
}
|
|
29232
29466
|
/** Replaces one chunk column's blocks. Entity NBT remains available on the parsed chunk. */
|
|
@@ -29234,7 +29468,24 @@ var MineRenderWorld = class {
|
|
|
29234
29468
|
const changes = /* @__PURE__ */ new Map();
|
|
29235
29469
|
try {
|
|
29236
29470
|
await this.clearChunkColumn(chunk.x, chunk.z, changes);
|
|
29471
|
+
const groups = /* @__PURE__ */ new Map();
|
|
29237
29472
|
for (const section of chunk.sections) {
|
|
29473
|
+
if (!executor) {
|
|
29474
|
+
const blocks2 = [];
|
|
29475
|
+
for (let index = 0; index < 4096; index++) {
|
|
29476
|
+
const block = section.data.get(index);
|
|
29477
|
+
if (block) blocks2.push({ index, block });
|
|
29478
|
+
}
|
|
29479
|
+
if (blocks2.length) {
|
|
29480
|
+
const position = new import_three40.Vector3(chunk.x * 16, section.y * 16, chunk.z * 16);
|
|
29481
|
+
this.validatePosBounds(position);
|
|
29482
|
+
const target = this.getOrCreateChunkAt(position);
|
|
29483
|
+
const previous = groups.get(target);
|
|
29484
|
+
if (previous) previous.push(...blocks2);
|
|
29485
|
+
else groups.set(target, blocks2);
|
|
29486
|
+
}
|
|
29487
|
+
continue;
|
|
29488
|
+
}
|
|
29238
29489
|
const blocks = [];
|
|
29239
29490
|
for (let index = 0; index < 4096; index++) {
|
|
29240
29491
|
const block = section.data.get(index);
|
|
@@ -29249,6 +29500,7 @@ var MineRenderWorld = class {
|
|
|
29249
29500
|
}
|
|
29250
29501
|
await this.placeBlocks({ size: [16, 16, 16], blocks }, true, executor, changes);
|
|
29251
29502
|
}
|
|
29503
|
+
if (!executor) await this.placeChunkGroups(groups, changes);
|
|
29252
29504
|
} finally {
|
|
29253
29505
|
await this.updateCulling([...changes.values()]);
|
|
29254
29506
|
}
|
|
@@ -29289,19 +29541,23 @@ var MineRenderWorld = class {
|
|
|
29289
29541
|
this.sectionModels?.clear();
|
|
29290
29542
|
}
|
|
29291
29543
|
updateCulling(positions) {
|
|
29544
|
+
const neighbor = new import_three40.Vector3();
|
|
29292
29545
|
for (const pos of positions) {
|
|
29293
|
-
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
this.pendingCulling.set(neighbor.toArray().join(","), neighbor);
|
|
29297
|
-
}
|
|
29546
|
+
const cx = Math.floor(pos.x / 16), cy = Math.floor(pos.y / 16), cz = Math.floor(pos.z / 16);
|
|
29547
|
+
const chunk = this._chunks.get(`${cx}_${cy}_${cz}`);
|
|
29548
|
+
const lx = pos.x - cx * 16, ly = pos.y - cy * 16, lz = pos.z - cz * 16;
|
|
29298
29549
|
for (let y = -1; y <= 1; y++) {
|
|
29299
29550
|
for (let z = -1; z <= 1; z++) {
|
|
29300
29551
|
for (let x = -1; x <= 1; x++) {
|
|
29301
|
-
const
|
|
29302
|
-
|
|
29303
|
-
|
|
29304
|
-
|
|
29552
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29553
|
+
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)}`);
|
|
29554
|
+
if (!section) continue;
|
|
29555
|
+
const index = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29556
|
+
let pending = this.pendingCulling.get(section);
|
|
29557
|
+
if (pending?.has(index)) continue;
|
|
29558
|
+
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;
|
|
29559
|
+
if (!pending) this.pendingCulling.set(section, pending = /* @__PURE__ */ new Set());
|
|
29560
|
+
pending.add(index);
|
|
29305
29561
|
}
|
|
29306
29562
|
}
|
|
29307
29563
|
}
|
|
@@ -29309,39 +29565,38 @@ var MineRenderWorld = class {
|
|
|
29309
29565
|
return this.culling ?? (this.culling = Promise.resolve().then(async () => {
|
|
29310
29566
|
try {
|
|
29311
29567
|
while (this.pendingCulling.size) {
|
|
29312
|
-
const batch = [...this.pendingCulling
|
|
29568
|
+
const batch = [...this.pendingCulling];
|
|
29313
29569
|
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;
|
|
29570
|
+
for (const [chunk, indices] of batch) {
|
|
29571
|
+
for (const index of indices) {
|
|
29572
|
+
if (!chunk.isBlockVisibleIndex(index)) continue;
|
|
29573
|
+
const lx = index % 16, ly = Math.floor(index / 256), lz = Math.floor(index / 16) % 16;
|
|
29574
|
+
const wx = chunk.x * 16 + lx, wy = chunk.y * 16 + ly, wz = chunk.z * 16 + lz;
|
|
29575
|
+
const block = chunk.getBlockAt(neighbor.set(wx, wy, wz));
|
|
29576
|
+
if (block.object?.fluidKind) {
|
|
29577
|
+
await block.object.updateFluid((x, y, z) => {
|
|
29578
|
+
const nx = lx + x, ny = ly + y, nz = lz + z;
|
|
29579
|
+
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)}`);
|
|
29580
|
+
const neighborIndex = (ny & 15) * 256 + (nz & 15) * 16 + (nx & 15);
|
|
29581
|
+
const object3 = section?.isBlockVisibleIndex(neighborIndex) ? section.getBlockAt(neighbor.set(wx + x, wy + y, wz + z))?.object : void 0;
|
|
29582
|
+
return {
|
|
29583
|
+
fluid: object3?.fluidKind,
|
|
29584
|
+
level: object3?.fluidLevel,
|
|
29585
|
+
solid: section?.isOccludingIndex(neighborIndex) ?? false
|
|
29586
|
+
};
|
|
29587
|
+
});
|
|
29340
29588
|
}
|
|
29589
|
+
let mask = 0;
|
|
29590
|
+
for (let face = 0; face < CUBE_FACE_OFFSETS.length; face++) {
|
|
29591
|
+
const offset = CUBE_FACE_OFFSETS[face];
|
|
29592
|
+
const nx = lx + offset[0], ny = ly + offset[1], nz = lz + offset[2];
|
|
29593
|
+
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)}`);
|
|
29594
|
+
if (section?.isOccludingIndex((ny & 15) * 256 + (nz & 15) * 16 + (nx & 15))) mask |= 1 << face;
|
|
29595
|
+
}
|
|
29596
|
+
await chunk.setCullMaskIndex(index, mask);
|
|
29341
29597
|
}
|
|
29342
|
-
await chunk.setCullMaskAt(pos, mask);
|
|
29343
29598
|
}
|
|
29344
|
-
|
|
29599
|
+
await Promise.all(batch.map(([chunk]) => chunk.rebuildSectionMesh()));
|
|
29345
29600
|
}
|
|
29346
29601
|
} finally {
|
|
29347
29602
|
this.culling = void 0;
|
|
@@ -29403,7 +29658,228 @@ init_Model();
|
|
|
29403
29658
|
// src/world/SectionMesh.ts
|
|
29404
29659
|
var import_three41 = require("three");
|
|
29405
29660
|
init_CanvasCompat();
|
|
29661
|
+
init_CubeFace();
|
|
29406
29662
|
init_Materials();
|
|
29663
|
+
|
|
29664
|
+
// src/world/SectionGeometry.ts
|
|
29665
|
+
init_CubeFace();
|
|
29666
|
+
function buildSectionGeometry(input) {
|
|
29667
|
+
const { count, indices, templates, cullMasks, templateData, atlases, maxAtlasSize } = input;
|
|
29668
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29669
|
+
if (count > indices.length || count > templates.length || count > cullMasks.length) throw new RangeError("Section entry count exceeds its arrays");
|
|
29670
|
+
const visible = [];
|
|
29671
|
+
const visibleAtlases = /* @__PURE__ */ new Set();
|
|
29672
|
+
for (let entry = 0; entry < count; entry++) {
|
|
29673
|
+
const template = templateData[templates[entry]];
|
|
29674
|
+
if (!template.cullFaces.some((direction) => !(cullMasks[entry] & direction))) continue;
|
|
29675
|
+
visible.push(entry);
|
|
29676
|
+
visibleAtlases.add(template.atlas);
|
|
29677
|
+
}
|
|
29678
|
+
const pages = [];
|
|
29679
|
+
const locations = /* @__PURE__ */ new Map();
|
|
29680
|
+
for (const atlas of [...visibleAtlases].sort((a, b) => atlases[b].height - atlases[a].height)) {
|
|
29681
|
+
const { width, height } = atlases[atlas];
|
|
29682
|
+
if (width > maxAtlasSize || height > maxAtlasSize) throw new RangeError("Model atlas exceeds the section atlas size limit");
|
|
29683
|
+
let selected = -1;
|
|
29684
|
+
for (let i = 0; i < pages.length; i++) {
|
|
29685
|
+
const page2 = pages[i];
|
|
29686
|
+
const nextRow = page2.x + width > maxAtlasSize;
|
|
29687
|
+
if ((nextRow ? page2.y + page2.rowHeight : page2.y) + height > maxAtlasSize) continue;
|
|
29688
|
+
if (nextRow) {
|
|
29689
|
+
page2.y += page2.rowHeight;
|
|
29690
|
+
page2.x = 0;
|
|
29691
|
+
page2.rowHeight = 0;
|
|
29692
|
+
}
|
|
29693
|
+
selected = i;
|
|
29694
|
+
break;
|
|
29695
|
+
}
|
|
29696
|
+
if (selected < 0) {
|
|
29697
|
+
selected = pages.length;
|
|
29698
|
+
pages.push({ placements: [], entries: [], x: 0, y: 0, rowHeight: 0, width: 0, height: 0 });
|
|
29699
|
+
}
|
|
29700
|
+
const page = pages[selected];
|
|
29701
|
+
page.placements.push({ atlas, x: page.x, y: page.y });
|
|
29702
|
+
locations.set(atlas, { page: selected, x: page.x, y: page.y });
|
|
29703
|
+
page.x += width;
|
|
29704
|
+
page.rowHeight = Math.max(page.rowHeight, height);
|
|
29705
|
+
page.width = Math.max(page.width, page.x);
|
|
29706
|
+
page.height = Math.max(page.height, page.y + height);
|
|
29707
|
+
}
|
|
29708
|
+
for (const entry of visible) pages[locations.get(templateData[templates[entry]].atlas).page].entries.push(entry);
|
|
29709
|
+
return pages.map((page) => {
|
|
29710
|
+
let faces = 0;
|
|
29711
|
+
for (const entry of page.entries) {
|
|
29712
|
+
const template = templateData[templates[entry]];
|
|
29713
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29714
|
+
if (!(cullMasks[entry] & template.cullFaces[face])) faces++;
|
|
29715
|
+
}
|
|
29716
|
+
}
|
|
29717
|
+
const positions = new Float32Array(faces * 12), normals = new Float32Array(faces * 12);
|
|
29718
|
+
const uvs = new Float32Array(faces * 8), uvBounds = new Float32Array(faces * 16), colors = new Float32Array(faces * 12);
|
|
29719
|
+
const outputIndices = new Uint32Array(faces * 6);
|
|
29720
|
+
let vertex = 0, indexOffset = 0;
|
|
29721
|
+
for (const entry of page.entries) {
|
|
29722
|
+
const template = templateData[templates[entry]];
|
|
29723
|
+
const atlas = atlases[template.atlas];
|
|
29724
|
+
const placement = locations.get(template.atlas);
|
|
29725
|
+
const mapU = (u) => (placement.x + u * atlas.width) / page.width;
|
|
29726
|
+
const mapV = (v) => 1 - (placement.y + (1 - v) * atlas.height) / page.height;
|
|
29727
|
+
const offsetX = indices[entry] % 16 * 16;
|
|
29728
|
+
const offsetY = Math.floor(indices[entry] / 256) * 16;
|
|
29729
|
+
const offsetZ = Math.floor(indices[entry] / 16) % 16 * 16;
|
|
29730
|
+
for (let face = 0; face < CUBE_FACES.length; face++) {
|
|
29731
|
+
if (cullMasks[entry] & template.cullFaces[face]) continue;
|
|
29732
|
+
const start = vertex;
|
|
29733
|
+
for (let corner = 0; corner < 4; corner++, vertex++) {
|
|
29734
|
+
const source = face * 4 + corner;
|
|
29735
|
+
positions[vertex * 3] = template.positions[source * 3] + offsetX;
|
|
29736
|
+
positions[vertex * 3 + 1] = template.positions[source * 3 + 1] + offsetY;
|
|
29737
|
+
positions[vertex * 3 + 2] = template.positions[source * 3 + 2] + offsetZ;
|
|
29738
|
+
normals[vertex * 3] = template.normals[source * 3];
|
|
29739
|
+
normals[vertex * 3 + 1] = template.normals[source * 3 + 1];
|
|
29740
|
+
normals[vertex * 3 + 2] = template.normals[source * 3 + 2];
|
|
29741
|
+
uvs[vertex * 2] = mapU(template.uvs[source * 2]);
|
|
29742
|
+
uvs[vertex * 2 + 1] = mapV(template.uvs[source * 2 + 1]);
|
|
29743
|
+
uvBounds[vertex * 4] = mapU(template.uvBounds[source * 4]);
|
|
29744
|
+
uvBounds[vertex * 4 + 1] = mapV(template.uvBounds[source * 4 + 1]);
|
|
29745
|
+
uvBounds[vertex * 4 + 2] = mapU(template.uvBounds[source * 4 + 2]);
|
|
29746
|
+
uvBounds[vertex * 4 + 3] = mapV(template.uvBounds[source * 4 + 3]);
|
|
29747
|
+
colors[vertex * 3] = template.colors[source * 3];
|
|
29748
|
+
colors[vertex * 3 + 1] = template.colors[source * 3 + 1];
|
|
29749
|
+
colors[vertex * 3 + 2] = template.colors[source * 3 + 2];
|
|
29750
|
+
}
|
|
29751
|
+
for (let corner = 0; corner < 6; corner++) outputIndices[indexOffset++] = start + template.indices[face * 6 + corner] - face * 4;
|
|
29752
|
+
}
|
|
29753
|
+
}
|
|
29754
|
+
return { width: page.width, height: page.height, placements: page.placements, positions, normals, uvs, uvBounds, colors, indices: outputIndices };
|
|
29755
|
+
});
|
|
29756
|
+
}
|
|
29757
|
+
function sectionGeometryTransferables(pages) {
|
|
29758
|
+
return pages.flatMap((page) => [page.positions.buffer, page.normals.buffer, page.uvs.buffer, page.uvBounds.buffer, page.colors.buffer, page.indices.buffer]);
|
|
29759
|
+
}
|
|
29760
|
+
|
|
29761
|
+
// src/world/SectionWorker.ts
|
|
29762
|
+
init_Env();
|
|
29763
|
+
var _SectionWorker = class _SectionWorker {
|
|
29764
|
+
constructor(worker) {
|
|
29765
|
+
this.worker = worker;
|
|
29766
|
+
this.nextId = 0;
|
|
29767
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
29768
|
+
worker.addEventListener("message", (event) => {
|
|
29769
|
+
const reply = event.data;
|
|
29770
|
+
const pending = this.pending.get(reply.id);
|
|
29771
|
+
if (!pending) return;
|
|
29772
|
+
this.pending.delete(reply.id);
|
|
29773
|
+
if (reply.type === "pages") pending.resolve(reply.pages);
|
|
29774
|
+
else pending.reject(new Error(reply.message));
|
|
29775
|
+
});
|
|
29776
|
+
worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
|
|
29777
|
+
worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
|
|
29778
|
+
}
|
|
29779
|
+
/** The shared worker, or undefined when the environment has none or a previous worker failed. */
|
|
29780
|
+
static shared() {
|
|
29781
|
+
if (!this.initialized) {
|
|
29782
|
+
this.initialized = true;
|
|
29783
|
+
const worker = Env.provider.createWorker?.("section");
|
|
29784
|
+
if (worker) this.instance = new _SectionWorker(worker);
|
|
29785
|
+
}
|
|
29786
|
+
return this.instance;
|
|
29787
|
+
}
|
|
29788
|
+
/** Builds geometry without transferring the input buffers away from the caller. */
|
|
29789
|
+
build(input) {
|
|
29790
|
+
if (!this.worker) return Promise.reject(new Error("Section worker terminated"));
|
|
29791
|
+
return new Promise((resolve, reject) => {
|
|
29792
|
+
const id = this.nextId++;
|
|
29793
|
+
this.pending.set(id, { resolve, reject });
|
|
29794
|
+
this.worker.postMessage({ id, type: "build", input });
|
|
29795
|
+
});
|
|
29796
|
+
}
|
|
29797
|
+
/** Rejects pending builds, terminates the worker, and makes shared() return undefined. */
|
|
29798
|
+
terminate() {
|
|
29799
|
+
this.fail("Section worker terminated");
|
|
29800
|
+
}
|
|
29801
|
+
fail(message) {
|
|
29802
|
+
const error = new Error(message);
|
|
29803
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
29804
|
+
this.pending.clear();
|
|
29805
|
+
this.worker?.terminate();
|
|
29806
|
+
this.worker = void 0;
|
|
29807
|
+
_SectionWorker.instance = void 0;
|
|
29808
|
+
}
|
|
29809
|
+
};
|
|
29810
|
+
_SectionWorker.initialized = false;
|
|
29811
|
+
var SectionWorker = _SectionWorker;
|
|
29812
|
+
|
|
29813
|
+
// src/world/SectionMesh.ts
|
|
29814
|
+
var templateCache = /* @__PURE__ */ new WeakMap();
|
|
29815
|
+
function toInput(entries, maxAtlasSize) {
|
|
29816
|
+
if (!Number.isInteger(maxAtlasSize) || maxAtlasSize < 1) throw new RangeError("Section atlas size must be a positive integer");
|
|
29817
|
+
const atlases = [];
|
|
29818
|
+
const atlasIds = /* @__PURE__ */ new Map();
|
|
29819
|
+
const templateIds = /* @__PURE__ */ new Map();
|
|
29820
|
+
const input = {
|
|
29821
|
+
count: entries.length,
|
|
29822
|
+
indices: new Uint16Array(entries.length),
|
|
29823
|
+
templates: new Uint16Array(entries.length),
|
|
29824
|
+
cullMasks: new Uint8Array(entries.length),
|
|
29825
|
+
templateData: [],
|
|
29826
|
+
atlases: [],
|
|
29827
|
+
maxAtlasSize
|
|
29828
|
+
};
|
|
29829
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29830
|
+
const { index, template, cullMask } = entries[i];
|
|
29831
|
+
let id = templateIds.get(template);
|
|
29832
|
+
if (id === void 0) {
|
|
29833
|
+
let atlas = atlasIds.get(template.atlas);
|
|
29834
|
+
if (atlas === void 0) {
|
|
29835
|
+
atlas = atlases.length;
|
|
29836
|
+
atlasIds.set(template.atlas, atlas);
|
|
29837
|
+
atlases.push(template.atlas);
|
|
29838
|
+
input.atlases.push({ width: template.atlas.image.width, height: template.atlas.image.height });
|
|
29839
|
+
}
|
|
29840
|
+
let data = templateCache.get(template);
|
|
29841
|
+
if (!data) {
|
|
29842
|
+
data = {
|
|
29843
|
+
positions: new Float32Array(72),
|
|
29844
|
+
normals: new Float32Array(72),
|
|
29845
|
+
uvs: new Float32Array(48),
|
|
29846
|
+
uvBounds: new Float32Array(96),
|
|
29847
|
+
colors: new Float32Array(72),
|
|
29848
|
+
indices: new Uint16Array(36),
|
|
29849
|
+
cullFaces: new Uint8Array(template.cullFaces),
|
|
29850
|
+
atlas: 0
|
|
29851
|
+
};
|
|
29852
|
+
const position = template.geometry.getAttribute("position");
|
|
29853
|
+
const normal = template.geometry.getAttribute("normal");
|
|
29854
|
+
const uv = template.geometry.getAttribute("uv");
|
|
29855
|
+
const bounds = template.geometry.getAttribute("uvBounds");
|
|
29856
|
+
const color2 = template.geometry.getAttribute("color");
|
|
29857
|
+
for (let vertex = 0; vertex < CUBE_FACES.length * 4; vertex++) {
|
|
29858
|
+
data.positions.set([position.getX(vertex), position.getY(vertex), position.getZ(vertex)], vertex * 3);
|
|
29859
|
+
data.normals.set([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)], vertex * 3);
|
|
29860
|
+
data.uvs.set([uv.getX(vertex), uv.getY(vertex)], vertex * 2);
|
|
29861
|
+
data.uvBounds.set([
|
|
29862
|
+
bounds?.getX(vertex) ?? 0,
|
|
29863
|
+
bounds?.getY(vertex) ?? 0,
|
|
29864
|
+
bounds?.getZ(vertex) ?? 1,
|
|
29865
|
+
bounds?.getW(vertex) ?? 1
|
|
29866
|
+
], vertex * 4);
|
|
29867
|
+
data.colors.set([color2?.getX(vertex) ?? 1, color2?.getY(vertex) ?? 1, color2?.getZ(vertex) ?? 1], vertex * 3);
|
|
29868
|
+
}
|
|
29869
|
+
const indices = template.geometry.getIndex();
|
|
29870
|
+
for (let i2 = 0; i2 < data.indices.length; i2++) data.indices[i2] = indices.getX(i2);
|
|
29871
|
+
templateCache.set(template, data);
|
|
29872
|
+
}
|
|
29873
|
+
id = input.templateData.length;
|
|
29874
|
+
templateIds.set(template, id);
|
|
29875
|
+
input.templateData.push({ ...data, atlas });
|
|
29876
|
+
}
|
|
29877
|
+
input.indices[i] = index;
|
|
29878
|
+
input.templates[i] = id;
|
|
29879
|
+
input.cullMasks[i] = cullMask;
|
|
29880
|
+
}
|
|
29881
|
+
return { input, atlases };
|
|
29882
|
+
}
|
|
29407
29883
|
var SectionMesh = class _SectionMesh extends import_three41.Group {
|
|
29408
29884
|
constructor() {
|
|
29409
29885
|
super(...arguments);
|
|
@@ -29411,86 +29887,31 @@ var SectionMesh = class _SectionMesh extends import_three41.Group {
|
|
|
29411
29887
|
}
|
|
29412
29888
|
/** Builds section-local meshes from visible cube faces. `maxAtlasSize` limits each atlas dimension in pixels. */
|
|
29413
29889
|
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);
|
|
29890
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29891
|
+
return this.fromPages(buildSectionGeometry(input), atlases);
|
|
29892
|
+
}
|
|
29893
|
+
/** Builds section-local meshes in a browser worker when available, with synchronous fallback. */
|
|
29894
|
+
static async buildAsync(entries, maxAtlasSize = 2048) {
|
|
29895
|
+
const { input, atlases } = toInput(entries, maxAtlasSize);
|
|
29896
|
+
const worker = SectionWorker.shared();
|
|
29897
|
+
const pages = worker ? await worker.build(input).catch(() => buildSectionGeometry(input)) : buildSectionGeometry(input);
|
|
29898
|
+
return this.fromPages(pages, atlases);
|
|
29899
|
+
}
|
|
29900
|
+
static fromPages(pages, atlases) {
|
|
29447
29901
|
const section = new _SectionMesh();
|
|
29448
29902
|
for (const page of pages) {
|
|
29449
29903
|
const canvas = createCanvas(page.width, page.height);
|
|
29450
29904
|
const context = canvas.getContext("2d");
|
|
29451
29905
|
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
|
-
}
|
|
29906
|
+
context.drawImage(atlases[atlas].image.canvas, x, y);
|
|
29486
29907
|
}
|
|
29487
29908
|
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);
|
|
29909
|
+
geometry.setAttribute("position", new import_three41.Float32BufferAttribute(page.positions.buffer, 3));
|
|
29910
|
+
geometry.setAttribute("normal", new import_three41.Float32BufferAttribute(page.normals.buffer, 3));
|
|
29911
|
+
geometry.setAttribute("uv", new import_three41.Float32BufferAttribute(page.uvs.buffer, 2));
|
|
29912
|
+
geometry.setAttribute("uvBounds", new import_three41.Float32BufferAttribute(page.uvBounds.buffer, 4));
|
|
29913
|
+
geometry.setAttribute("color", new import_three41.Float32BufferAttribute(page.colors.buffer, 3));
|
|
29914
|
+
geometry.setIndex(new import_three41.Uint32BufferAttribute(page.indices.buffer, 1));
|
|
29494
29915
|
geometry.computeBoundingBox();
|
|
29495
29916
|
geometry.computeBoundingSphere();
|
|
29496
29917
|
const material = Materials.createShadedCanvasMaterial(canvas, false, false, true);
|
|
@@ -29524,7 +29945,9 @@ var Chunk = class _Chunk {
|
|
|
29524
29945
|
this.renderedBlocks = /* @__PURE__ */ new Map();
|
|
29525
29946
|
this.sectionBlocks = /* @__PURE__ */ new Map();
|
|
29526
29947
|
this.hiddenBlocks = /* @__PURE__ */ new Set();
|
|
29948
|
+
this.blockPosition = new import_three42.Vector3();
|
|
29527
29949
|
this.meshDirty = false;
|
|
29950
|
+
this.meshGeneration = 0;
|
|
29528
29951
|
this.scene = scene;
|
|
29529
29952
|
this.x = x;
|
|
29530
29953
|
this.y = y;
|
|
@@ -29537,7 +29960,7 @@ var Chunk = class _Chunk {
|
|
|
29537
29960
|
if (isTripleArray(posOrX)) {
|
|
29538
29961
|
return this.getBlockAt(new import_three42.Vector3(posOrX[0], posOrX[1], posOrX[2]));
|
|
29539
29962
|
}
|
|
29540
|
-
const pos = this.
|
|
29963
|
+
const pos = this.blockPosition.set(posOrX.x - this.x * 16, posOrX.y - this.y * 16, posOrX.z - this.z * 16);
|
|
29541
29964
|
const index = _Chunk.chunkPosToBlockIndex(pos);
|
|
29542
29965
|
return this.renderedBlocks.get(index);
|
|
29543
29966
|
}
|
|
@@ -29561,48 +29984,81 @@ var Chunk = class _Chunk {
|
|
|
29561
29984
|
worldPos = this.chunkPosToWorldPos(pos);
|
|
29562
29985
|
}
|
|
29563
29986
|
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
29987
|
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;
|
|
29988
|
+
await this.placeBlocks([{ index, block }]);
|
|
29989
|
+
return this.renderedBlocks.get(index);
|
|
29602
29990
|
} finally {
|
|
29603
29991
|
await onBlocksChanged?.([worldPos]);
|
|
29604
29992
|
}
|
|
29605
29993
|
}
|
|
29994
|
+
/** Places cells in input order and returns changed world positions without refreshing neighbors. */
|
|
29995
|
+
async placeBlocks(blocks) {
|
|
29996
|
+
const resolutions = /* @__PURE__ */ new Map();
|
|
29997
|
+
for (const { block } of blocks) {
|
|
29998
|
+
if (!block || ChunkData.isAir(block)) continue;
|
|
29999
|
+
const key = ChunkData.paletteKey(block);
|
|
30000
|
+
if (resolutions.has(key)) continue;
|
|
30001
|
+
const stored = { type: block.type, properties: block.properties ? { ...block.properties } : void 0 };
|
|
30002
|
+
const resolved = (async () => {
|
|
30003
|
+
const blockState2 = await BlockStates.get(AssetKey.parse("blockstates", stored.type));
|
|
30004
|
+
const perBlock = !blockState2 || !this.sectionModels || !!getFluidKind(blockState2.key, stored.properties) || !!(blockState2.key && BlockEntities.entry(await BlockEntities.getIndex(blockState2.key.root), blockState2.key.toNamespacedString()));
|
|
30005
|
+
if (!perBlock) await this.sectionModels.get(blockState2, stored.properties);
|
|
30006
|
+
return { blockState: blockState2, perBlock };
|
|
30007
|
+
})();
|
|
30008
|
+
resolutions.set(key, resolved);
|
|
30009
|
+
void resolved.catch(() => void 0);
|
|
30010
|
+
}
|
|
30011
|
+
const positions = [];
|
|
30012
|
+
let failed = false, failure;
|
|
30013
|
+
for (const { index, block } of blocks) {
|
|
30014
|
+
this.hiddenBlocks.delete(index);
|
|
30015
|
+
this.data.set(index, block);
|
|
30016
|
+
const readBlock = this.data.snapshot(index);
|
|
30017
|
+
this.renderedBlocks.get(index)?.object?.removeFromScene();
|
|
30018
|
+
if (this.sectionBlocks.delete(index)) this.meshDirty = true;
|
|
30019
|
+
this.renderedBlocks.delete(index);
|
|
30020
|
+
const worldPos = new import_three42.Vector3(
|
|
30021
|
+
this.x * 16 + index % 16,
|
|
30022
|
+
this.y * 16 + Math.floor(index / 256),
|
|
30023
|
+
this.z * 16 + Math.floor(index / 16) % 16
|
|
30024
|
+
);
|
|
30025
|
+
positions.push(worldPos);
|
|
30026
|
+
let object3;
|
|
30027
|
+
try {
|
|
30028
|
+
if (!readBlock) continue;
|
|
30029
|
+
const stored = readBlock();
|
|
30030
|
+
const key = ChunkData.paletteKey(stored);
|
|
30031
|
+
const { blockState: blockState2, perBlock } = await resolutions.get(key);
|
|
30032
|
+
if (!blockState2) {
|
|
30033
|
+
this.data.set(index, void 0);
|
|
30034
|
+
continue;
|
|
30035
|
+
}
|
|
30036
|
+
const template = perBlock ? void 0 : await this.sectionModels.get(blockState2, stored.properties);
|
|
30037
|
+
if (template) {
|
|
30038
|
+
this.sectionBlocks.set(index, { index, template, cullMask: 0 });
|
|
30039
|
+
this.meshDirty = true;
|
|
30040
|
+
} else {
|
|
30041
|
+
object3 = await this.scene.addBlock(blockState2, {
|
|
30042
|
+
mergeMeshes: true,
|
|
30043
|
+
instanceMeshes: true,
|
|
30044
|
+
maxInstanceCount: 2e3,
|
|
30045
|
+
initialState: stored.properties
|
|
30046
|
+
});
|
|
30047
|
+
object3.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
|
|
30048
|
+
}
|
|
30049
|
+
this.renderedBlocks.set(index, { get block() {
|
|
30050
|
+
return readBlock();
|
|
30051
|
+
}, object: object3 });
|
|
30052
|
+
} catch (error) {
|
|
30053
|
+
this.data.set(index, void 0);
|
|
30054
|
+
object3?.removeFromScene();
|
|
30055
|
+
if (!failed) failure = error;
|
|
30056
|
+
failed = true;
|
|
30057
|
+
}
|
|
30058
|
+
}
|
|
30059
|
+
if (failed) throw failure;
|
|
30060
|
+
return positions;
|
|
30061
|
+
}
|
|
29606
30062
|
/** Changes visibility at world block coordinates while preserving the block's data. */
|
|
29607
30063
|
async setBlockVisibleAt(pos, visible) {
|
|
29608
30064
|
const index = _Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos));
|
|
@@ -29623,15 +30079,24 @@ var Chunk = class _Chunk {
|
|
|
29623
30079
|
await this.onBlocksChanged?.([pos]);
|
|
29624
30080
|
}
|
|
29625
30081
|
isBlockVisibleAt(pos) {
|
|
29626
|
-
|
|
30082
|
+
return this.isBlockVisibleIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
30083
|
+
}
|
|
30084
|
+
/** Reports whether a cell has a visible render object or section entry. */
|
|
30085
|
+
isBlockVisibleIndex(index) {
|
|
29627
30086
|
return this.renderedBlocks.has(index) && !this.hiddenBlocks.has(index);
|
|
29628
30087
|
}
|
|
29629
30088
|
isOccludingAt(pos) {
|
|
29630
|
-
|
|
30089
|
+
return this.isOccludingIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)));
|
|
30090
|
+
}
|
|
30091
|
+
/** Reports whether a visible cell hides neighboring cube faces. */
|
|
30092
|
+
isOccludingIndex(index) {
|
|
29631
30093
|
return !this.hiddenBlocks.has(index) && (this.sectionBlocks.has(index) || (this.renderedBlocks.get(index)?.object?.isOccluding ?? false));
|
|
29632
30094
|
}
|
|
29633
30095
|
async setCullMaskAt(pos, mask) {
|
|
29634
|
-
|
|
30096
|
+
await this.setCullMaskIndex(_Chunk.chunkPosToBlockIndex(this.worldPosToChunkPos(pos)), mask);
|
|
30097
|
+
}
|
|
30098
|
+
/** Updates hidden faces for a cell's section entry or individual render object. */
|
|
30099
|
+
async setCullMaskIndex(index, mask) {
|
|
29635
30100
|
const entry = this.sectionBlocks.get(index);
|
|
29636
30101
|
if (entry) {
|
|
29637
30102
|
if (entry.cullMask !== mask) {
|
|
@@ -29642,12 +30107,17 @@ var Chunk = class _Chunk {
|
|
|
29642
30107
|
await this.renderedBlocks.get(index)?.object?.setCullMask(mask);
|
|
29643
30108
|
}
|
|
29644
30109
|
}
|
|
29645
|
-
|
|
30110
|
+
/** Rebuilds visible section geometry and discards results superseded by another rebuild or clear. */
|
|
30111
|
+
async rebuildSectionMesh() {
|
|
29646
30112
|
if (!this.meshDirty) return;
|
|
29647
|
-
|
|
29648
|
-
|
|
29649
|
-
|
|
29650
|
-
) : void 0;
|
|
30113
|
+
this.meshDirty = false;
|
|
30114
|
+
const generation = ++this.meshGeneration;
|
|
30115
|
+
const entries = [...this.sectionBlocks.values()].filter((entry) => !this.hiddenBlocks.has(entry.index));
|
|
30116
|
+
const next = entries.length ? await SectionMesh.buildAsync(entries, this.sectionModels.maxAtlasSize) : void 0;
|
|
30117
|
+
if (generation !== this.meshGeneration) {
|
|
30118
|
+
next?.dispose();
|
|
30119
|
+
return;
|
|
30120
|
+
}
|
|
29651
30121
|
this.sectionMesh?.dispose();
|
|
29652
30122
|
this.sectionMesh = next;
|
|
29653
30123
|
if (next) {
|
|
@@ -29655,7 +30125,6 @@ var Chunk = class _Chunk {
|
|
|
29655
30125
|
this.scene.add(next);
|
|
29656
30126
|
}
|
|
29657
30127
|
this.scene.dirty = true;
|
|
29658
|
-
this.meshDirty = false;
|
|
29659
30128
|
}
|
|
29660
30129
|
/** Removes stored blocks and their render objects, then notifies the owning world of changed positions. */
|
|
29661
30130
|
async clear(onBlocksChanged = this.onBlocksChanged) {
|
|
@@ -29664,6 +30133,7 @@ var Chunk = class _Chunk {
|
|
|
29664
30133
|
));
|
|
29665
30134
|
for (const info of this.renderedBlocks.values()) info.object?.removeFromScene();
|
|
29666
30135
|
this.sectionBlocks.clear();
|
|
30136
|
+
this.meshGeneration++;
|
|
29667
30137
|
this.sectionMesh?.dispose();
|
|
29668
30138
|
this.sectionMesh = void 0;
|
|
29669
30139
|
this.meshDirty = false;
|