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.
- package/README.md +10 -1
- package/dist/browser/esm/env/browser/BrowserEnv.mjs +11 -4
- package/dist/browser/esm/env/browser/BrowserEnv.mjs.map +1 -1
- package/dist/browser/esm/index.browser.d.mts +19 -4
- package/dist/browser/esm/index.browser.mjs.map +1 -1
- package/dist/browser/esm/world/Chunk.mjs +62 -33
- package/dist/browser/esm/world/Chunk.mjs.map +1 -1
- package/dist/browser/esm/world/MineRenderWorld.mjs +16 -1
- package/dist/browser/esm/world/MineRenderWorld.mjs.map +1 -1
- package/dist/browser/esm/world/SectionModels.mjs +21 -15
- package/dist/browser/esm/world/SectionModels.mjs.map +1 -1
- package/dist/browser/esm/world/SectionWorker.mjs +8 -2
- package/dist/browser/esm/world/SectionWorker.mjs.map +1 -1
- package/dist/browser/index.d.ts +19 -4
- package/dist/browser/index.js +217 -155
- package/dist/browser/index.js.map +1 -1
- package/dist/bundle.js +227 -165
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +5 -2
- package/dist/node/index.d.ts +5 -2
- package/dist/node/index.js +106 -51
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +106 -51
- package/dist/node/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/node/index.d.mts
CHANGED
|
@@ -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
|
|
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
|
|
package/dist/node/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
package/dist/node/index.js
CHANGED
|
@@ -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
|
|
18225
|
-
|
|
18226
|
-
if (!
|
|
18227
|
-
|
|
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
|
-
|
|
19712
|
-
|
|
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
|
-
|
|
20259
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20333
|
+
} });
|
|
20334
|
+
continue;
|
|
20307
20335
|
}
|
|
20308
20336
|
this.renderedBlocks.set(index, { get block() {
|
|
20309
20337
|
return readBlock();
|
|
20310
|
-
}, object:
|
|
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
|
-
|
|
20320
|
-
|
|
20321
|
-
|
|
20346
|
+
if (order < failureOrder) {
|
|
20347
|
+
failureOrder = order;
|
|
20348
|
+
failure = error;
|
|
20349
|
+
}
|
|
20322
20350
|
}
|
|
20323
20351
|
}
|
|
20324
|
-
|
|
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
|
-
|
|
20396
|
-
|
|
20397
|
-
|
|
20398
|
-
|
|
20399
|
-
|
|
20400
|
-
|
|
20401
|
-
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
}
|