minerender 2.1.0-alpha.142 → 2.1.0-alpha.144
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/index.browser.d.mts +21 -4
- package/dist/browser/esm/util/util.mjs +0 -2
- package/dist/browser/esm/util/util.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 +98 -13
- 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/WorldStreamer.mjs +109 -15
- package/dist/browser/esm/world/WorldStreamer.mjs.map +1 -1
- package/dist/browser/index.d.ts +21 -4
- package/dist/browser/index.js +289 -78
- package/dist/browser/index.js.map +1 -1
- package/dist/bundle.js +289 -78
- package/dist/bundle.js.map +1 -1
- package/dist/node/index.d.mts +21 -4
- package/dist/node/index.d.ts +21 -4
- package/dist/node/index.js +289 -78
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +289 -78
- package/dist/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MineRender V2
|
|
2
2
|
|
|
3
|
-
A TypeScript library for interactive Minecraft skins, models, blocks, entities, and worlds.
|
|
3
|
+
A TypeScript library for interactive Minecraft skins, models, blocks, entities, GUIs, and worlds.
|
|
4
4
|
V2 is in beta. See the [roadmap](https://github.com/InventivetalentDev/MineRender/blob/main/ROADMAP.md) for feature parity and remaining work.
|
|
5
5
|
|
|
6
6
|
Live examples and usage docs: [beta.minerender.org](https://beta.minerender.org/). API reference: [beta-docs.minerender.org](https://beta-docs.minerender.org/).
|
|
@@ -29,6 +29,11 @@ an injected WebGL 2 context or the optional native `gl` backend. See
|
|
|
29
29
|
|
|
30
30
|
Requires Node.js 22.12+ or a browser with WebGL 2, Fetch, `AbortSignal.any()`, and `AbortSignal.timeout()`.
|
|
31
31
|
|
|
32
|
+
The library prints only warnings and errors. To see debug output, call
|
|
33
|
+
`Logging.configure({ level: "debug" })`, or set the `minerender:log` localStorage key
|
|
34
|
+
in a browser or the `MINERENDER_LOG` environment variable in Node. See
|
|
35
|
+
[Logging](https://beta-docs.minerender.org/concepts#logging).
|
|
36
|
+
|
|
32
37
|
## Resource-pack ZIPs
|
|
33
38
|
|
|
34
39
|
Register a browser `File` or `Blob` before loading models or textures:
|
|
@@ -123,3 +128,7 @@ Supported object types are `skin`, `block`, `item`, `model`, `entity`, and `gui`
|
|
|
123
128
|
Positions use model units (16 per block); JSON rotations use radians.
|
|
124
129
|
|
|
125
130
|
Callers apply the saved camera settings and select the matching asset version.
|
|
131
|
+
|
|
132
|
+
`encodeSceneDocument` and `decodeSceneDocument` convert a document to and from the compact
|
|
133
|
+
link format used by the editor. To show a scene on another page without bundling the library,
|
|
134
|
+
use the [iframe embed page](https://beta-docs.minerender.org/embeds).
|
|
@@ -783,7 +783,10 @@ declare function toRadians(degrees: number): number;
|
|
|
783
783
|
declare function toDegrees(radians: number): number;
|
|
784
784
|
/** Resolves after a delay in milliseconds. */
|
|
785
785
|
declare function sleep(timeout: number): Promise<void>;
|
|
786
|
-
/**
|
|
786
|
+
/**
|
|
787
|
+
* Yields to the next macrotask without timer clamping when the platform supports it.
|
|
788
|
+
* Not `scheduler.yield()`: Chrome runs its continuations ahead of rendering, so chained yields delay frames.
|
|
789
|
+
*/
|
|
787
790
|
declare function yieldToEventLoop(): Promise<void>;
|
|
788
791
|
/** Reduces an angle modulo 360, retaining a negative sign for negative inputs. */
|
|
789
792
|
declare function clampRotationDegrees(deg: number): number;
|
|
@@ -4019,6 +4022,8 @@ declare class SectionModels {
|
|
|
4019
4022
|
pick(position?: Readonly<TripleArray>): SectionMeshTemplate[];
|
|
4020
4023
|
biomeTint?: boolean;
|
|
4021
4024
|
} | undefined>;
|
|
4025
|
+
/** Starts loading the state's models and atlases so a later `prepareState` with the same properties finds them loaded. */
|
|
4026
|
+
preload(blockState: BlockState, properties?: BlockStateProperties): Promise<void>;
|
|
4022
4027
|
private state;
|
|
4023
4028
|
/** Returns the shared still/flow atlas used by per-block fluid objects. */
|
|
4024
4029
|
fluidAtlas(kind: FluidKind, root?: string): Promise<TextureAtlas>;
|
|
@@ -4133,6 +4138,7 @@ declare class Chunk<SectionMeshing extends boolean = false> {
|
|
|
4133
4138
|
/** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
|
|
4134
4139
|
placeSection(data: ChunkData): Promise<void>;
|
|
4135
4140
|
private replaceSection;
|
|
4141
|
+
private createBlockObject;
|
|
4136
4142
|
private positionAt;
|
|
4137
4143
|
/** Refreshes saved lighting after adjacent columns load or unload, including columns with only light data. */
|
|
4138
4144
|
refreshLight(indices?: Iterable<number>): Promise<void>;
|
|
@@ -4282,6 +4288,12 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
|
|
|
4282
4288
|
private placeChunkGroups;
|
|
4283
4289
|
/** Replaces a chunk column's blocks, biomes, and saved light and, with `renderEntities`, its supported saved mobs. */
|
|
4284
4290
|
placeChunk(chunk: AnvilChunk, executor?: BatchedExecutor): Promise<void>;
|
|
4291
|
+
/**
|
|
4292
|
+
* Replaces parsed columns in input order, then refreshes neighboring geometry once.
|
|
4293
|
+
* Duplicate coordinates retain their order. A failed column stops placement; queued refreshes finish before rejection.
|
|
4294
|
+
*/
|
|
4295
|
+
placeChunks(chunks: readonly AnvilChunk[]): Promise<void>;
|
|
4296
|
+
private placeColumn;
|
|
4285
4297
|
/**
|
|
4286
4298
|
* Removes blocks and owned entities at chunk-column coordinates, then refreshes neighboring faces and fluids.
|
|
4287
4299
|
* Coordinates match AnvilChunk.x/z, not block positions. Missing columns are ignored.
|
|
@@ -4314,7 +4326,7 @@ declare class MineRenderWorld<SectionMeshing extends boolean = false> {
|
|
|
4314
4326
|
interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
|
|
4315
4327
|
/** Render supported saved mobs at their position and yaw, including supported appearance fields. Defaults to false. */
|
|
4316
4328
|
renderEntities?: boolean;
|
|
4317
|
-
/** Merge
|
|
4329
|
+
/** Merge block models of any shape and fluid surfaces into per-section meshes; block entities keep individual objects. Merged blocks have no individual object. */
|
|
4318
4330
|
sectionMeshing?: SectionMeshing;
|
|
4319
4331
|
/** Shade blocks and fluids with saved sky/block light. Missing or invalid data keeps preview brightness. Defaults to false. Requires `sectionMeshing`. */
|
|
4320
4332
|
savedLighting?: boolean;
|
|
@@ -4324,7 +4336,7 @@ interface MineRenderWorldOptions<SectionMeshing extends boolean = boolean> {
|
|
|
4324
4336
|
ambientOcclusion?: boolean;
|
|
4325
4337
|
/** Average neighboring biome colors within this horizontal radius, from 0 to 7 blocks. Defaults to 0 (off). Requires `sectionMeshing`. */
|
|
4326
4338
|
biomeBlendRadius?: number;
|
|
4327
|
-
/** Maximum width and height of each section atlas page, in pixels. */
|
|
4339
|
+
/** Maximum width and height of each section atlas page, in pixels. Defaults to 2048. */
|
|
4328
4340
|
maxAtlasSize?: number;
|
|
4329
4341
|
}
|
|
4330
4342
|
|
|
@@ -4369,10 +4381,11 @@ declare class WorldStreamer {
|
|
|
4369
4381
|
private desired;
|
|
4370
4382
|
private center?;
|
|
4371
4383
|
private loading?;
|
|
4384
|
+
private readonly reads;
|
|
4372
4385
|
private running?;
|
|
4373
4386
|
private disposal?;
|
|
4374
4387
|
private disposed;
|
|
4375
|
-
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns">, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4388
|
+
constructor(world: Pick<MineRenderWorld<boolean>, "placeChunk" | "unloadChunkColumns"> & Partial<Pick<MineRenderWorld<boolean>, "placeChunks">>, source: WorldChunkSource, options?: WorldStreamerOptions);
|
|
4376
4389
|
/** Returns absolute coordinates of successfully placed columns, including retained columns. */
|
|
4377
4390
|
get loadedChunks(): readonly {
|
|
4378
4391
|
x: number;
|
|
@@ -4405,6 +4418,10 @@ declare class WorldStreamer {
|
|
|
4405
4418
|
/** Aborts source reads, waits for active work, then unloads owned columns. The world and source remain caller-owned. */
|
|
4406
4419
|
dispose(): Promise<void>;
|
|
4407
4420
|
private drain;
|
|
4421
|
+
private fillReadSlots;
|
|
4422
|
+
/** Places only the ready prefix, so later source reads cannot delay the nearest ready column. */
|
|
4423
|
+
private placeReadyChunks;
|
|
4424
|
+
private placeOwnedColumns;
|
|
4408
4425
|
private needsLoad;
|
|
4409
4426
|
private unloadMany;
|
|
4410
4427
|
private validateChunk;
|
|
@@ -28,8 +28,6 @@ async function sleep(timeout) {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
function yieldToEventLoop() {
|
|
31
|
-
const scheduler = globalThis.scheduler;
|
|
32
|
-
if (scheduler?.yield) return scheduler.yield();
|
|
33
31
|
return new Promise((resolve) => {
|
|
34
32
|
if (typeof MessageChannel !== "undefined") {
|
|
35
33
|
const channel = new MessageChannel();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/util/util.ts"],"sourcesContent":["// Node ESM needs the explicit .js extension for the CommonJS hash module.\nimport MD5 from \"crypto-js/md5.js\";\nimport { Vector3 } from \"three\";\n\nexport const changeEvent = { type: 'change' as const };\n\nexport type Maybe<T> = T | undefined;\n\n// MD5 runs in both environments without pulling Node's crypto into the browser graph.\nexport function md5(str: string): string {\n return MD5(str).toString();\n}\n\nexport function base64encode(str: string): string {\n if (typeof btoa !== \"undefined\") {\n return btoa(str);\n }\n return Buffer.from(str).toString(\"base64\");\n}\n\nexport function base64decode(str: string): string {\n if (typeof atob !== \"undefined\") {\n return atob(str);\n }\n return Buffer.from(str, \"base64\").toString(\"ascii\");\n}\n\nexport function toRadians(degrees: number): number {\n return degrees * Math.PI / 180;\n}\n\nexport function toDegrees(radians: number): number {\n return radians * 180 / Math.PI;\n}\n\n/** Resolves after a delay in milliseconds. */\nexport async function sleep(timeout: number): Promise<void> {\n return new Promise(resolve => {\n setTimeout(() => resolve(), timeout);\n });\n}\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/util/util.ts"],"sourcesContent":["// Node ESM needs the explicit .js extension for the CommonJS hash module.\nimport MD5 from \"crypto-js/md5.js\";\nimport { Vector3 } from \"three\";\n\nexport const changeEvent = { type: 'change' as const };\n\nexport type Maybe<T> = T | undefined;\n\n// MD5 runs in both environments without pulling Node's crypto into the browser graph.\nexport function md5(str: string): string {\n return MD5(str).toString();\n}\n\nexport function base64encode(str: string): string {\n if (typeof btoa !== \"undefined\") {\n return btoa(str);\n }\n return Buffer.from(str).toString(\"base64\");\n}\n\nexport function base64decode(str: string): string {\n if (typeof atob !== \"undefined\") {\n return atob(str);\n }\n return Buffer.from(str, \"base64\").toString(\"ascii\");\n}\n\nexport function toRadians(degrees: number): number {\n return degrees * Math.PI / 180;\n}\n\nexport function toDegrees(radians: number): number {\n return radians * 180 / Math.PI;\n}\n\n/** Resolves after a delay in milliseconds. */\nexport async function sleep(timeout: number): Promise<void> {\n return new Promise(resolve => {\n setTimeout(() => resolve(), timeout);\n });\n}\n\n/**\n * Yields to the next macrotask without timer clamping when the platform supports it.\n * Not `scheduler.yield()`: Chrome runs its continuations ahead of rendering, so chained yields delay frames.\n */\nexport function yieldToEventLoop(): Promise<void> {\n return new Promise(resolve => {\n if (typeof MessageChannel !== \"undefined\") {\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n channel.port1.close();\n channel.port2.close();\n resolve();\n };\n channel.port2.postMessage(undefined);\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n\n/** Reduces an angle modulo 360, retaining a negative sign for negative inputs. */\nexport function clampRotationDegrees(deg: number): number {\n return deg % 360;\n}\n\nexport function isVector3(obj: any): obj is Vector3 {\n return (<Vector3>obj).isVector3;\n}\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: DeepPartial<T[P]>;\n};\n"],"mappings":";AACA,OAAO,SAAS;AAGT,IAAM,cAAc,EAAE,MAAM,SAAkB;AAK9C,SAAS,IAAI,KAAqB;AACrC,SAAO,IAAI,GAAG,EAAE,SAAS;AAC7B;AAEO,SAAS,aAAa,KAAqB;AAC9C,MAAI,OAAO,SAAS,aAAa;AAC7B,WAAO,KAAK,GAAG;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,GAAG,EAAE,SAAS,QAAQ;AAC7C;AAEO,SAAS,aAAa,KAAqB;AAC9C,MAAI,OAAO,SAAS,aAAa;AAC7B,WAAO,KAAK,GAAG;AAAA,EACnB;AACA,SAAO,OAAO,KAAK,KAAK,QAAQ,EAAE,SAAS,OAAO;AACtD;AAEO,SAAS,UAAU,SAAyB;AAC/C,SAAO,UAAU,KAAK,KAAK;AAC/B;AAEO,SAAS,UAAU,SAAyB;AAC/C,SAAO,UAAU,MAAM,KAAK;AAChC;AAGA,eAAsB,MAAM,SAAgC;AACxD,SAAO,IAAI,QAAQ,aAAW;AAC1B,eAAW,MAAM,QAAQ,GAAG,OAAO;AAAA,EACvC,CAAC;AACL;AAMO,SAAS,mBAAkC;AAC9C,SAAO,IAAI,QAAQ,aAAW;AAC1B,QAAI,OAAO,mBAAmB,aAAa;AACvC,YAAM,UAAU,IAAI,eAAe;AACnC,cAAQ,MAAM,YAAY,MAAM;AAC5B,gBAAQ,MAAM,MAAM;AACpB,gBAAQ,MAAM,MAAM;AACpB,gBAAQ;AAAA,MACZ;AACA,cAAQ,MAAM,YAAY,MAAS;AAAA,IACvC,OAAO;AACH,iBAAW,SAAS,CAAC;AAAA,IACzB;AAAA,EACJ,CAAC;AACL;AAGO,SAAS,qBAAqB,KAAqB;AACtD,SAAO,MAAM;AACjB;AAEO,SAAS,UAAU,KAA0B;AAChD,SAAiB,IAAK;AAC1B;","names":[]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/world/Chunk.ts
|
|
2
|
+
import { BlockObject } from "../model/block/scene/BlockObject.mjs";
|
|
2
3
|
import { Color, Vector3 } from "three";
|
|
3
4
|
import { ChunkData } from "./ChunkData.mjs";
|
|
4
5
|
import { BlockStates } from "../assets/BlockStates.mjs";
|
|
@@ -269,8 +270,10 @@ var Chunk = class _Chunk {
|
|
|
269
270
|
if (rejected?.status === "rejected") throw rejected.reason;
|
|
270
271
|
}
|
|
271
272
|
const positions = [];
|
|
272
|
-
|
|
273
|
-
|
|
273
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
274
|
+
let failureOrder = Infinity, failure;
|
|
275
|
+
for (const [order, { index, key, block }] of placements.entries()) {
|
|
276
|
+
jobs.delete(index);
|
|
274
277
|
this.hiddenBlocks.delete(index);
|
|
275
278
|
this.data.assign(index, this.data.intern(block), block?.nbt);
|
|
276
279
|
const readBlock = this.data.snapshot(index);
|
|
@@ -286,7 +289,6 @@ var Chunk = class _Chunk {
|
|
|
286
289
|
this.z * 16 + Math.floor(index / 16) % 16
|
|
287
290
|
);
|
|
288
291
|
positions.push(worldPos);
|
|
289
|
-
let object;
|
|
290
292
|
try {
|
|
291
293
|
if (!readBlock) continue;
|
|
292
294
|
const resolved = resolutions.get(key);
|
|
@@ -308,7 +310,7 @@ var Chunk = class _Chunk {
|
|
|
308
310
|
this.sectionBlocks.set(index, templates.map((template) => ({ index, template, cullMask: 0, biomeTint: context?.tint })));
|
|
309
311
|
this.meshDirty = true;
|
|
310
312
|
} else {
|
|
311
|
-
|
|
313
|
+
jobs.set(index, { order, readBlock, blockState, position: MineRenderWorld.worldToScenePosition(worldPos), options: {
|
|
312
314
|
mergeMeshes: true,
|
|
313
315
|
instanceMeshes: true,
|
|
314
316
|
maxInstanceCount: 2e3,
|
|
@@ -316,13 +318,12 @@ var Chunk = class _Chunk {
|
|
|
316
318
|
variantPosition,
|
|
317
319
|
biome: context?.biome,
|
|
318
320
|
biomePosition: this.biomePosition(index, blockState, stored)
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
+
} });
|
|
322
|
+
continue;
|
|
321
323
|
}
|
|
322
324
|
this.renderedBlocks.set(index, { get block() {
|
|
323
325
|
return readBlock();
|
|
324
|
-
}, object });
|
|
325
|
-
if (object) this.objectBlocks.add(index);
|
|
326
|
+
}, object: void 0 });
|
|
326
327
|
} catch (error) {
|
|
327
328
|
if (preserveDataOnFailure && readBlock) {
|
|
328
329
|
this.renderedBlocks.set(index, { get block() {
|
|
@@ -330,12 +331,35 @@ var Chunk = class _Chunk {
|
|
|
330
331
|
}, object: void 0 });
|
|
331
332
|
} else this.data.assign(index, 0);
|
|
332
333
|
this.fluidCells[index] = 0;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
if (order < failureOrder) {
|
|
335
|
+
failureOrder = order;
|
|
336
|
+
failure = error;
|
|
337
|
+
}
|
|
336
338
|
}
|
|
337
339
|
}
|
|
338
|
-
|
|
340
|
+
const pending = [...jobs];
|
|
341
|
+
const objects = await Promise.allSettled(pending.map(([, job]) => this.createBlockObject(job.blockState, job.position, job.options)));
|
|
342
|
+
for (const [i, [index, { order, readBlock }]] of pending.entries()) {
|
|
343
|
+
const result = objects[i];
|
|
344
|
+
if (result.status === "fulfilled") {
|
|
345
|
+
this.renderedBlocks.set(index, { get block() {
|
|
346
|
+
return readBlock();
|
|
347
|
+
}, object: result.value });
|
|
348
|
+
this.objectBlocks.add(index);
|
|
349
|
+
} else {
|
|
350
|
+
if (preserveDataOnFailure) {
|
|
351
|
+
this.renderedBlocks.set(index, { get block() {
|
|
352
|
+
return readBlock();
|
|
353
|
+
}, object: void 0 });
|
|
354
|
+
} else this.data.assign(index, 0);
|
|
355
|
+
this.fluidCells[index] = 0;
|
|
356
|
+
if (order < failureOrder) {
|
|
357
|
+
failureOrder = order;
|
|
358
|
+
failure = result.reason;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (failureOrder < Infinity) throw failure;
|
|
339
363
|
return positions;
|
|
340
364
|
}
|
|
341
365
|
/** Replaces every cell from parsed section data. The caller refreshes neighbor culling. */
|
|
@@ -406,37 +430,42 @@ var Chunk = class _Chunk {
|
|
|
406
430
|
failed = true;
|
|
407
431
|
}
|
|
408
432
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
)
|
|
426
|
-
this.renderedBlocks.get(index).object = object;
|
|
433
|
+
const objects = await Promise.allSettled(perBlocks.map(({ index, blockState, biome }) => this.createBlockObject(blockState, new Vector3(
|
|
434
|
+
this.x * 256 + index % 16 * 16,
|
|
435
|
+
this.y * 256 + Math.floor(index / 256) * 16,
|
|
436
|
+
this.z * 256 + Math.floor(index / 16) % 16 * 16
|
|
437
|
+
), {
|
|
438
|
+
mergeMeshes: true,
|
|
439
|
+
instanceMeshes: true,
|
|
440
|
+
maxInstanceCount: 2e3,
|
|
441
|
+
initialState: this.data.stateAt(this.data.idAt(index)).properties,
|
|
442
|
+
variantPosition: this.positionAt(index),
|
|
443
|
+
biome,
|
|
444
|
+
biomePosition: this.biomePosition(index, blockState, this.data.stateAt(this.data.idAt(index)))
|
|
445
|
+
})));
|
|
446
|
+
for (const [i, { index }] of perBlocks.entries()) {
|
|
447
|
+
const result = objects[i];
|
|
448
|
+
if (result.status === "fulfilled") {
|
|
449
|
+
this.renderedBlocks.get(index).object = result.value;
|
|
427
450
|
this.objectBlocks.add(index);
|
|
428
|
-
}
|
|
451
|
+
} else {
|
|
429
452
|
this.data.assign(index, 0);
|
|
430
453
|
this.fluidCells[index] = 0;
|
|
431
454
|
this.renderedBlocks.delete(index);
|
|
432
455
|
this.objectBlocks.delete(index);
|
|
433
|
-
|
|
434
|
-
if (!failed) failure = error;
|
|
456
|
+
if (!failed) failure = result.reason;
|
|
435
457
|
failed = true;
|
|
436
458
|
}
|
|
437
459
|
}
|
|
438
460
|
if (failed) throw failure;
|
|
439
461
|
}
|
|
462
|
+
createBlockObject(blockState, position, options) {
|
|
463
|
+
return this.scene.addSceneObject(blockState, () => {
|
|
464
|
+
const object = new BlockObject(blockState, options);
|
|
465
|
+
object.position.copy(position);
|
|
466
|
+
return object;
|
|
467
|
+
}, options);
|
|
468
|
+
}
|
|
440
469
|
positionAt(index) {
|
|
441
470
|
return [this.x * 16 + index % 16, this.y * 16 + Math.floor(index / 256), this.z * 16 + Math.floor(index / 16) % 16];
|
|
442
471
|
}
|