minerender 2.1.0-alpha.143 → 2.1.0-alpha.145

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.
@@ -4024,6 +4024,8 @@ declare class SectionModels {
4024
4024
  pick(position?: Readonly<TripleArray>): SectionMeshTemplate[];
4025
4025
  biomeTint?: boolean;
4026
4026
  } | undefined>;
4027
+ /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
4028
+ preload(blockState: BlockState, properties?: BlockStateProperties): Promise<void>;
4027
4029
  private state;
4028
4030
  /** Returns the shared still/flow atlas used by per-block fluid objects. */
4029
4031
  fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas>;
@@ -4138,6 +4140,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
4138
4140
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
4139
4141
  placeSection(data: ChunkData): Promise<void>;
4140
4142
  private replaceSection;
4143
+ private createBlockObject;
4141
4144
  private positionAt;
4142
4145
  /** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
4143
4146
  refreshLight(indices?: Iterable<number>): Promise<void>;
@@ -4325,7 +4328,7 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
4325
4328
  interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
4326
4329
  /** Render supported saved mobs at their position and yaw, including supported appearance fields. Defaults to false. */
4327
4330
  renderEntities?: boolean;
4328
- /** Merge static opaque cubes into section meshes. Merged blocks have no individual object. */
4331
+ /** Merge block models of any shape and fluid surfaces into per-section meshes; block entities keep individual objects. Merged blocks have no individual object. */
4329
4332
  sectionMeshing?: SectionMeshing;
4330
4333
  /** Shade blocks and fluids with saved sky/block light. Missing or invalid data keeps preview brightness. Defaults to false. Requires `sectionMeshing`. */
4331
4334
  savedLighting?: boolean;
@@ -4335,7 +4338,7 @@ interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
4335
4338
  ambientOcclusion?: boolean;
4336
4339
  /** Average neighboring biome colors within this horizontal radius, from 0 to 7 blocks. Defaults to 0 (off). Requires `sectionMeshing`. */
4337
4340
  biomeBlendRadius?: number;
4338
- /** Maximum width and height of each section atlas page, in pixels. */
4341
+ /** Maximum width and height of each section atlas page, in pixels. Defaults to 2048. */
4339
4342
  maxAtlasSize?: number;
4340
4343
  }
4341
4344
 
@@ -4024,6 +4024,8 @@ declare class SectionModels {
4024
4024
  pick(position?: Readonly<TripleArray>): SectionMeshTemplate[];
4025
4025
  biomeTint?: boolean;
4026
4026
  } | undefined>;
4027
+ /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
4028
+ preload(blockState: BlockState, properties?: BlockStateProperties): Promise<void>;
4027
4029
  private state;
4028
4030
  /** Returns the shared still/flow atlas used by per-block fluid objects. */
4029
4031
  fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas>;
@@ -4138,6 +4140,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
4138
4140
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
4139
4141
  placeSection(data: ChunkData): Promise<void>;
4140
4142
  private replaceSection;
4143
+ private createBlockObject;
4141
4144
  private positionAt;
4142
4145
  /** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
4143
4146
  refreshLight(indices?: Iterable<number>): Promise<void>;
@@ -4325,7 +4328,7 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
4325
4328
  interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
4326
4329
  /** Render supported saved mobs at their position and yaw, including supported appearance fields. Defaults to false. */
4327
4330
  renderEntities?: boolean;
4328
- /** Merge static opaque cubes into section meshes. Merged blocks have no individual object. */
4331
+ /** Merge block models of any shape and fluid surfaces into per-section meshes; block entities keep individual objects. Merged blocks have no individual object. */
4329
4332
  sectionMeshing?: SectionMeshing;
4330
4333
  /** Shade blocks and fluids with saved sky/block light. Missing or invalid data keeps preview brightness. Defaults to false. Requires `sectionMeshing`. */
4331
4334
  savedLighting?: boolean;
@@ -4335,7 +4338,7 @@ interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
4335
4338
  ambientOcclusion?: boolean;
4336
4339
  /** Average neighboring biome colors within this horizontal radius, from 0 to 7 blocks. Defaults to 0 (off). Requires `sectionMeshing`. */
4337
4340
  biomeBlendRadius?: number;
4338
- /** Maximum width and height of each section atlas page, in pixels. */
4341
+ /** Maximum width and height of each section atlas page, in pixels. Defaults to 2048. */
4339
4342
  maxAtlasSize?: number;
4340
4343
  }
4341
4344
 
@@ -18090,6 +18090,7 @@ var AnvilWorldSource = class {
18090
18090
  };
18091
18091
 
18092
18092
  // src/world/Chunk.ts
18093
+ init_BlockObject();
18093
18094
  var import_three47 = require("three");
18094
18095
  init_BlockStates();
18095
18096
  init_AssetKey();
@@ -18100,6 +18101,7 @@ init_Model();
18100
18101
  init_util();
18101
18102
  init_AssetKey();
18102
18103
  init_BlockStates();
18104
+ init_BlockEntities();
18103
18105
  init_CubeFace();
18104
18106
 
18105
18107
  // src/world/SectionModels.ts
@@ -18197,6 +18199,10 @@ var SectionModels = class {
18197
18199
  biomeTint: source ? [...templates.values()].some((template) => template.biomeTint?.some((value) => value === 1)) || void 0 : void 0
18198
18200
  };
18199
18201
  }
18202
+ /** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
18203
+ async preload(blockState3, properties = {}) {
18204
+ await this.state(blockState3, properties);
18205
+ }
18200
18206
  async state(blockState3, properties) {
18201
18207
  let states = this.states.get(blockState3);
18202
18208
  if (!states) this.states.set(blockState3, states = /* @__PURE__ */ new Map());
@@ -18219,22 +18225,24 @@ var SectionModels = class {
18219
18225
  async prepare(blockState3, properties) {
18220
18226
  const state = { ...await BlockStateResolver.defaults(blockState3), ...properties };
18221
18227
  const groups = BlockStateResolver.matching(blockState3, state);
18222
- if (!groups.length) return void 0;
18228
+ if (!groups.length || groups.some((group) => Array.isArray(group) && !group.length)) return void 0;
18229
+ const variants = groups.flatMap((group) => Array.isArray(group) ? group : [group]);
18230
+ if (variants.some((variant) => !variant.model)) return void 0;
18231
+ const settled = await Promise.allSettled(variants.map(async (variant) => {
18232
+ const modelKey = AssetKey.parse("models", variant.model);
18233
+ modelKey.root = blockState3.key?.root;
18234
+ const model = await Models.getMerged(modelKey);
18235
+ if (!model?.elements?.length) return void 0;
18236
+ const atlas = await UVMapper.getAtlas(model);
18237
+ const rotation = BlockStateResolver.rotation(variant);
18238
+ if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
18239
+ return { model, atlas, rotation };
18240
+ }));
18223
18241
  const prepared = /* @__PURE__ */ new Map();
18224
- for (const group of groups) {
18225
- const choices = Array.isArray(group) ? group : [group];
18226
- if (!choices.length) return void 0;
18227
- for (const variant of choices) {
18228
- if (!variant.model) return void 0;
18229
- const modelKey = AssetKey.parse("models", variant.model);
18230
- modelKey.root = blockState3.key?.root;
18231
- const model = await Models.getMerged(modelKey);
18232
- if (!model?.elements?.length) return void 0;
18233
- const atlas = await UVMapper.getAtlas(model);
18234
- const rotation = BlockStateResolver.rotation(variant);
18235
- if (!atlas || atlas.image.width > this.maxAtlasSize || atlas.image.height > this.maxAtlasSize) return void 0;
18236
- prepared.set(variant, { model, atlas, rotation });
18237
- }
18242
+ for (const [i, result] of settled.entries()) {
18243
+ if (result.status === "rejected") throw result.reason;
18244
+ if (!result.value) return void 0;
18245
+ prepared.set(variants[i], result.value);
18238
18246
  }
18239
18247
  return { state, groups, choices: prepared };
18240
18248
  }
@@ -18909,15 +18917,18 @@ var MineRenderWorld = class {
18909
18917
  const key = AssetKey.parse("blockstates", block.type);
18910
18918
  keys.set(key.serialize(), key);
18911
18919
  }
18912
- await BlockStates.getAll(keys.values());
18920
+ const loaded = await BlockStates.getAll(keys.values());
18921
+ const states = new Map([...keys.keys()].map((key, i) => [key, loaded[i]]));
18913
18922
  if (!useBatches) {
18914
18923
  for (const block of multiblock.blocks) await place(block);
18915
18924
  return;
18916
18925
  }
18917
18926
  if (!executor) {
18918
18927
  const groups2 = /* @__PURE__ */ new Map();
18928
+ const distinct = /* @__PURE__ */ new Map();
18919
18929
  const position = new import_three45.Vector3();
18920
18930
  for (const block of multiblock.blocks) {
18931
+ if (this.sectionModels && !Chunk.isAir(block)) distinct.set(ChunkData.paletteKey(block), block);
18921
18932
  const [x, y, z] = block.position;
18922
18933
  this.validatePosBounds(position.set(x, y, z));
18923
18934
  const cx = Math.floor(x / 16), cy = Math.floor(y / 16), cz = Math.floor(z / 16);
@@ -18928,6 +18939,16 @@ var MineRenderWorld = class {
18928
18939
  if (!group) groups2.set(chunk, group = []);
18929
18940
  group.push({ index: (y - cy * 16) * 256 + (z - cz * 16) * 16 + x - cx * 16, block });
18930
18941
  }
18942
+ if (this.sectionModels) {
18943
+ for (const block of distinct.values()) {
18944
+ const state = states.get(AssetKey.parse("blockstates", block.type).serialize());
18945
+ if (!state) continue;
18946
+ void (async () => {
18947
+ if (state.key && BlockEntities.entry(await BlockEntities.getIndex(state.key.root), state.key.toNamespacedString())) return;
18948
+ await this.sectionModels.preload(state, block.properties);
18949
+ })().catch(() => void 0);
18950
+ }
18951
+ }
18931
18952
  await this.placeChunkGroups(groups2, changes);
18932
18953
  return;
18933
18954
  }
@@ -19695,6 +19716,8 @@ function sectionGeometryTransferables(pages) {
19695
19716
 
19696
19717
  // src/world/SectionWorker.ts
19697
19718
  init_Env();
19719
+ init_log();
19720
+ var log19 = logger("SectionWorker");
19698
19721
  var SectionWorker = class _SectionWorker {
19699
19722
  constructor(worker) {
19700
19723
  this.worker = worker;
@@ -19708,8 +19731,12 @@ var SectionWorker = class _SectionWorker {
19708
19731
  if (reply.type === "pages") pending.resolve(reply.pages);
19709
19732
  else pending.reject(new Error(reply.message));
19710
19733
  });
19711
- worker.addEventListener("error", (event) => this.fail(event.message || "Section worker failed"));
19712
- worker.addEventListener("messageerror", () => this.fail("Section worker message could not be deserialized"));
19734
+ const fallBack = (message) => {
19735
+ log19.warn(`${message}, so sections build on the main thread`);
19736
+ this.fail(message);
19737
+ };
19738
+ worker.addEventListener("error", (event) => fallBack(event.message || "Section worker failed"));
19739
+ worker.addEventListener("messageerror", () => fallBack("Section worker message could not be deserialized"));
19713
19740
  }
19714
19741
  static {
19715
19742
  this.initialized = false;
@@ -20255,8 +20282,10 @@ var Chunk = class _Chunk {
20255
20282
  if (rejected?.status === "rejected") throw rejected.reason;
20256
20283
  }
20257
20284
  const positions = [];
20258
- let failed = false, failure;
20259
- for (const { index, key, block } of placements) {
20285
+ const jobs = /* @__PURE__ */ new Map();
20286
+ let failureOrder = Infinity, failure;
20287
+ for (const [order, { index, key, block }] of placements.entries()) {
20288
+ jobs.delete(index);
20260
20289
  this.hiddenBlocks.delete(index);
20261
20290
  this.data.assign(index, this.data.intern(block), block?.nbt);
20262
20291
  const readBlock = this.data.snapshot(index);
@@ -20272,7 +20301,6 @@ var Chunk = class _Chunk {
20272
20301
  this.z * 16 + Math.floor(index / 16) % 16
20273
20302
  );
20274
20303
  positions.push(worldPos);
20275
- let object4;
20276
20304
  try {
20277
20305
  if (!readBlock) continue;
20278
20306
  const resolved = resolutions.get(key);
@@ -20294,7 +20322,7 @@ var Chunk = class _Chunk {
20294
20322
  this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
20295
20323
  this.meshDirty = true;
20296
20324
  } else {
20297
- object4 = await this.scene.addBlock(blockState3, {
20325
+ jobs.set(index, { order, readBlock, blockState: blockState3, position: MineRenderWorld.worldToScenePosition(worldPos), options: {
20298
20326
  mergeMeshes: true,
20299
20327
  instanceMeshes: true,
20300
20328
  maxInstanceCount: 2e3,
@@ -20302,13 +20330,12 @@ var Chunk = class _Chunk {
20302
20330
  variantPosition,
20303
20331
  biome: context?.biome,
20304
20332
  biomePosition: this.biomePosition(index, blockState3, stored)
20305
- });
20306
- object4.setPosition(MineRenderWorld.worldToScenePosition(worldPos));
20333
+ } });
20334
+ continue;
20307
20335
  }
20308
20336
  this.renderedBlocks.set(index, { get block() {
20309
20337
  return readBlock();
20310
- }, object: object4 });
20311
- if (object4) this.objectBlocks.add(index);
20338
+ }, object: void 0 });
20312
20339
  } catch (error) {
20313
20340
  if (preserveDataOnFailure && readBlock) {
20314
20341
  this.renderedBlocks.set(index, { get block() {
@@ -20316,12 +20343,35 @@ var Chunk = class _Chunk {
20316
20343
  }, object: void 0 });
20317
20344
  } else this.data.assign(index, 0);
20318
20345
  this.fluidCells[index] = 0;
20319
- object4?.removeFromScene();
20320
- if (!failed) failure = error;
20321
- failed = true;
20346
+ if (order < failureOrder) {
20347
+ failureOrder = order;
20348
+ failure = error;
20349
+ }
20322
20350
  }
20323
20351
  }
20324
- if (failed) throw failure;
20352
+ const pending = [...jobs];
20353
+ const objects = await Promise.allSettled(pending.map(([, job]) => this.createBlockObject(job.blockState, job.position, job.options)));
20354
+ for (const [i, [index, { order, readBlock }]] of pending.entries()) {
20355
+ const result = objects[i];
20356
+ if (result.status === "fulfilled") {
20357
+ this.renderedBlocks.set(index, { get block() {
20358
+ return readBlock();
20359
+ }, object: result.value });
20360
+ this.objectBlocks.add(index);
20361
+ } else {
20362
+ if (preserveDataOnFailure) {
20363
+ this.renderedBlocks.set(index, { get block() {
20364
+ return readBlock();
20365
+ }, object: void 0 });
20366
+ } else this.data.assign(index, 0);
20367
+ this.fluidCells[index] = 0;
20368
+ if (order < failureOrder) {
20369
+ failureOrder = order;
20370
+ failure = result.reason;
20371
+ }
20372
+ }
20373
+ }
20374
+ if (failureOrder < Infinity) throw failure;
20325
20375
  return positions;
20326
20376
  }
20327
20377
  /** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
@@ -20392,37 +20442,42 @@ var Chunk = class _Chunk {
20392
20442
  failed = true;
20393
20443
  }
20394
20444
  }
20395
- for (const { index, blockState: blockState3, biome } of perBlocks) {
20396
- let object4;
20397
- try {
20398
- object4 = await this.scene.addBlock(blockState3, {
20399
- mergeMeshes: true,
20400
- instanceMeshes: true,
20401
- maxInstanceCount: 2e3,
20402
- initialState: this.data.stateAt(this.data.idAt(index)).properties,
20403
- variantPosition: this.positionAt(index),
20404
- biome,
20405
- biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
20406
- });
20407
- object4.setPosition(new import_three47.Vector3(
20408
- this.x * 256 + index % 16 * 16,
20409
- this.y * 256 + Math.floor(index / 256) * 16,
20410
- this.z * 256 + Math.floor(index / 16) % 16 * 16
20411
- ));
20412
- this.renderedBlocks.get(index).object = object4;
20445
+ const objects = await Promise.allSettled(perBlocks.map(({ index, blockState: blockState3, biome }) => this.createBlockObject(blockState3, new import_three47.Vector3(
20446
+ this.x * 256 + index % 16 * 16,
20447
+ this.y * 256 + Math.floor(index / 256) * 16,
20448
+ this.z * 256 + Math.floor(index / 16) % 16 * 16
20449
+ ), {
20450
+ mergeMeshes: true,
20451
+ instanceMeshes: true,
20452
+ maxInstanceCount: 2e3,
20453
+ initialState: this.data.stateAt(this.data.idAt(index)).properties,
20454
+ variantPosition: this.positionAt(index),
20455
+ biome,
20456
+ biomePosition: this.biomePosition(index, blockState3, this.data.stateAt(this.data.idAt(index)))
20457
+ })));
20458
+ for (const [i, { index }] of perBlocks.entries()) {
20459
+ const result = objects[i];
20460
+ if (result.status === "fulfilled") {
20461
+ this.renderedBlocks.get(index).object = result.value;
20413
20462
  this.objectBlocks.add(index);
20414
- } catch (error) {
20463
+ } else {
20415
20464
  this.data.assign(index, 0);
20416
20465
  this.fluidCells[index] = 0;
20417
20466
  this.renderedBlocks.delete(index);
20418
20467
  this.objectBlocks.delete(index);
20419
- object4?.removeFromScene();
20420
- if (!failed) failure = error;
20468
+ if (!failed) failure = result.reason;
20421
20469
  failed = true;
20422
20470
  }
20423
20471
  }
20424
20472
  if (failed) throw failure;
20425
20473
  }
20474
+ createBlockObject(blockState3, position, options) {
20475
+ return this.scene.addSceneObject(blockState3, () => {
20476
+ const object4 = new BlockObject(blockState3, options);
20477
+ object4.position.copy(position);
20478
+ return object4;
20479
+ }, options);
20480
+ }
20426
20481
  positionAt(index) {
20427
20482
  return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
20428
20483
  }