minecraft-renderer 0.1.71 → 0.1.73
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 +3 -3
- package/dist/mesher.js +81 -81
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +1183 -943
- package/dist/minecraft-renderer.js +250 -79
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +1732 -1001
- package/package.json +3 -3
- package/src/graphicsBackend/rendererDefaultOptions.ts +5 -10
- package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
- package/src/lib/bakeLegacyLight.ts +17 -0
- package/src/lib/blockEntityLightRegistry.test.ts +18 -0
- package/src/lib/blockEntityLightRegistry.ts +75 -0
- package/src/lib/blockEntityLighting.test.ts +30 -0
- package/src/lib/blockEntityLighting.ts +53 -0
- package/src/lib/worldrendererCommon.reconfigure.test.ts +202 -0
- package/src/lib/worldrendererCommon.ts +152 -22
- package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
- package/src/mesher-shared/blockEntityMetadata.ts +19 -3
- package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
- package/src/mesher-shared/models.ts +161 -92
- package/src/mesher-shared/shared.ts +15 -4
- package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
- package/src/mesher-shared/world.ts +12 -0
- package/src/mesher-shared/worldLighting.test.ts +54 -0
- package/src/playground/baseScene.ts +1 -1
- package/src/three/bannerRenderer.ts +10 -3
- package/src/three/chunkMeshManager.ts +663 -69
- package/src/three/cubeDrawSpans.ts +74 -0
- package/src/three/cubeMultiDraw.ts +119 -0
- package/src/three/documentRenderer.ts +0 -2
- package/src/three/entities.ts +5 -6
- package/src/three/entity/EntityMesh.ts +7 -5
- package/src/three/entity/gltfAnimationUtils.ts +5 -3
- package/src/three/globalBlockBuffer.ts +208 -12
- package/src/three/globalLegacyBuffer.ts +701 -0
- package/src/three/graphicsBackendOffThread.ts +16 -1
- package/src/three/itemMesh.ts +5 -2
- package/src/three/legacySectionCull.ts +85 -0
- package/src/three/modules/sciFiWorldReveal.ts +347 -703
- package/src/three/modules/starfield.ts +3 -2
- package/src/three/sectionRaycastAabb.ts +25 -0
- package/src/three/shaders/cubeBlockShader.ts +80 -17
- package/src/three/shaders/legacyBlockShader.ts +292 -0
- package/src/three/skyboxRenderer.ts +1 -1
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
- package/src/three/tests/cubeDrawSpans.test.ts +73 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
- package/src/three/tests/legacySectionCull.test.ts +80 -0
- package/src/three/tests/signTextureCache.test.ts +83 -0
- package/src/three/threeJsMedia.ts +2 -2
- package/src/three/waypointSprite.ts +2 -2
- package/src/three/world/cursorBlock.ts +1 -0
- package/src/three/world/vr.ts +2 -2
- package/src/three/worldGeometryExport.ts +83 -26
- package/src/three/worldRendererThree.ts +94 -25
- package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +67 -5
- package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
- package/src/worldView/worldView.ts +11 -0
|
@@ -33,6 +33,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
33
33
|
worldReadyResolvers = Promise.withResolvers<void>()
|
|
34
34
|
worldReadyPromise = this.worldReadyResolvers.promise
|
|
35
35
|
timeOfTheDay = 0
|
|
36
|
+
lastMesherSkyLight = 15
|
|
36
37
|
worldSizeParams = { minY: 0, worldHeight: 256 }
|
|
37
38
|
reactiveDebugParams = proxy({
|
|
38
39
|
stopRendering: false,
|
|
@@ -127,6 +128,12 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
127
128
|
debugStopGeometryUpdate = false
|
|
128
129
|
|
|
129
130
|
protocolCustomBlocks = new Map<string, CustomBlockModels>()
|
|
131
|
+
private mesherPoolSnapshot = {
|
|
132
|
+
mesherWorkers: -1,
|
|
133
|
+
wasmMesher: false,
|
|
134
|
+
dedicatedChangeWorker: false,
|
|
135
|
+
}
|
|
136
|
+
private mesherReconfigureQueue: Promise<void> = Promise.resolve()
|
|
130
137
|
private heightmapDebounceTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
131
138
|
|
|
132
139
|
// Geometry throttle: first dirty per section is instant, subsequent within window are grouped
|
|
@@ -272,6 +279,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
272
279
|
|
|
273
280
|
this.watchReactivePlayerState()
|
|
274
281
|
this.watchReactiveConfig()
|
|
282
|
+
this.watchMesherPoolConfig()
|
|
275
283
|
this.worldReadyResolvers.resolve()
|
|
276
284
|
}
|
|
277
285
|
|
|
@@ -309,18 +317,127 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
309
317
|
}
|
|
310
318
|
}
|
|
311
319
|
|
|
320
|
+
private getMesherWorkerScript(): 'wasm' | 'legacy' {
|
|
321
|
+
return this.worldRendererConfig.wasmMesher ? 'wasm' : 'legacy'
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private createMesherWorker() {
|
|
325
|
+
const script = this.getMesherWorkerScript()
|
|
326
|
+
return initMesherWorker((data) => {
|
|
327
|
+
if (Array.isArray(data)) {
|
|
328
|
+
this.messageQueue.push(...data)
|
|
329
|
+
} else {
|
|
330
|
+
this.messageQueue.push(data)
|
|
331
|
+
}
|
|
332
|
+
void this.processMessageQueue('worker')
|
|
333
|
+
}, script === 'wasm' ? 'mesherWasm.js' : 'mesher.js')
|
|
334
|
+
}
|
|
335
|
+
|
|
312
336
|
initWorkers(numWorkers = this.worldRendererConfig.mesherWorkers) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
337
|
+
for (let i = 0; i < numWorkers; i++) {
|
|
338
|
+
this.workers.push(this.createMesherWorker())
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private syncMesherPoolSnapshot() {
|
|
343
|
+
this.mesherPoolSnapshot = {
|
|
344
|
+
mesherWorkers: this.worldRendererConfig.mesherWorkers,
|
|
345
|
+
wasmMesher: this.worldRendererConfig.wasmMesher,
|
|
346
|
+
dedicatedChangeWorker: this.worldRendererConfig.dedicatedChangeWorker,
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private watchMesherPoolConfig() {
|
|
351
|
+
this.syncMesherPoolSnapshot()
|
|
352
|
+
|
|
353
|
+
const tryReconfigure = () => {
|
|
354
|
+
const cfg = this.worldRendererConfig
|
|
355
|
+
const snap = this.mesherPoolSnapshot
|
|
356
|
+
if (
|
|
357
|
+
cfg.mesherWorkers === snap.mesherWorkers &&
|
|
358
|
+
cfg.wasmMesher === snap.wasmMesher &&
|
|
359
|
+
cfg.dedicatedChangeWorker === snap.dedicatedChangeWorker
|
|
360
|
+
) {
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
this.syncMesherPoolSnapshot()
|
|
364
|
+
this.enqueueMesherWorkersReconfigure()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const key of ['mesherWorkers', 'wasmMesher', 'dedicatedChangeWorker'] as const) {
|
|
368
|
+
this.valtioUnsubs.push(
|
|
369
|
+
this.onReactiveConfigUpdated(key, tryReconfigure, false)
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private enqueueMesherWorkersReconfigure() {
|
|
375
|
+
this.mesherReconfigureQueue = this.mesherReconfigureQueue
|
|
376
|
+
.then(() => this.reconfigureMesherWorkers())
|
|
377
|
+
.catch((err) => {
|
|
378
|
+
console.error('[Mesher] Failed to reconfigure workers:', err)
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
private clearMesherPendingState() {
|
|
382
|
+
this.sectionsWaiting.clear()
|
|
383
|
+
this.toWorkerMessagesQueue = {}
|
|
384
|
+
this.queueAwaited = false
|
|
385
|
+
this.messageQueue = []
|
|
386
|
+
this.isProcessingQueue = false
|
|
387
|
+
for (const timer of this.sectionDirtyTimers.values()) {
|
|
388
|
+
clearTimeout(timer)
|
|
389
|
+
}
|
|
390
|
+
this.sectionDirtyTimers.clear()
|
|
391
|
+
this.sectionDirtyCount.clear()
|
|
392
|
+
this.sectionDirtyPendingArgs.clear()
|
|
393
|
+
this.reactiveState.world.mesherWork = false
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private terminateAllMesherWorkers() {
|
|
397
|
+
for (const worker of this.workers) {
|
|
398
|
+
worker.terminate()
|
|
399
|
+
}
|
|
400
|
+
this.workers = []
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private async bootstrapMesherWorkers() {
|
|
404
|
+
if (this.workers.length === 0) return
|
|
405
|
+
|
|
406
|
+
this.sendMesherMcData()
|
|
407
|
+
await this.updateAssetsData()
|
|
408
|
+
this.logWorkerWork('# mesher workers bootstrapped')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async reconfigureMesherWorkers() {
|
|
412
|
+
if (!this.active) return
|
|
413
|
+
|
|
414
|
+
this.clearMesherPendingState()
|
|
415
|
+
this.terminateAllMesherWorkers()
|
|
416
|
+
this.initWorkers()
|
|
417
|
+
await this.bootstrapMesherWorkers()
|
|
418
|
+
if (!this.active) return
|
|
419
|
+
|
|
420
|
+
await this.requestLoadedChunksReload()
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private async requestLoadedChunksReload() {
|
|
424
|
+
try {
|
|
425
|
+
const worldView = this.displayOptions.worldView as {
|
|
426
|
+
reloadLoadedChunks?: () => void | Promise<void>
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (typeof worldView.reloadLoadedChunks === 'function') {
|
|
430
|
+
await worldView.reloadLoadedChunks()
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const workerScope = globalThis as typeof globalThis & { WorkerGlobalScope?: typeof WorkerGlobalScope }
|
|
435
|
+
if (typeof workerScope.WorkerGlobalScope !== 'undefined' && globalThis instanceof workerScope.WorkerGlobalScope) {
|
|
436
|
+
// eslint-disable-next-line no-restricted-globals
|
|
437
|
+
self.postMessage({ type: 'reloadLoadedChunks' })
|
|
438
|
+
}
|
|
439
|
+
} catch (err) {
|
|
440
|
+
console.error('[Mesher] Failed to reload chunks after worker reconfigure:', err)
|
|
324
441
|
}
|
|
325
442
|
}
|
|
326
443
|
|
|
@@ -331,13 +448,18 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
331
448
|
return subscribeKey(this.playerStateReactive, key, callback)
|
|
332
449
|
}
|
|
333
450
|
|
|
334
|
-
onReactiveConfigUpdated<T extends keyof typeof this.worldRendererConfig>(
|
|
335
|
-
|
|
451
|
+
onReactiveConfigUpdated<T extends keyof typeof this.worldRendererConfig>(
|
|
452
|
+
key: T,
|
|
453
|
+
callback: (value: typeof this.worldRendererConfig[T]) => void,
|
|
454
|
+
initial = true
|
|
455
|
+
) {
|
|
456
|
+
if (initial) {
|
|
457
|
+
callback(this.worldRendererConfig[key])
|
|
458
|
+
}
|
|
336
459
|
if ((key as any) === '*') {
|
|
337
|
-
subscribe(this.worldRendererConfig, callback as any)
|
|
338
|
-
} else {
|
|
339
|
-
subscribeKey(this.worldRendererConfig, key, callback)
|
|
460
|
+
return subscribe(this.worldRendererConfig, callback as any)
|
|
340
461
|
}
|
|
462
|
+
return subscribeKey(this.worldRendererConfig, key, callback)
|
|
341
463
|
}
|
|
342
464
|
|
|
343
465
|
onReactiveDebugUpdated<T extends keyof typeof this.reactiveDebugParams>(key: T, callback: (value: typeof this.reactiveDebugParams[T]) => void) {
|
|
@@ -574,6 +696,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
574
696
|
|
|
575
697
|
timeUpdated?(newTime: number): void
|
|
576
698
|
|
|
699
|
+
/** Called when day-cycle sky-light bucket changes; Three.js overrides to remesh. */
|
|
700
|
+
protected onDayCycleSkyLightChanged?(_skyLight: number): void
|
|
701
|
+
|
|
577
702
|
biomeUpdated?(biome: any): void
|
|
578
703
|
|
|
579
704
|
biomeReset?(): void
|
|
@@ -637,6 +762,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
637
762
|
|
|
638
763
|
this.initWorkers()
|
|
639
764
|
this.active = true
|
|
765
|
+
this.syncMesherPoolSnapshot()
|
|
640
766
|
|
|
641
767
|
this.sendMesherMcData()
|
|
642
768
|
}
|
|
@@ -942,16 +1068,20 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
942
1068
|
}, signal)
|
|
943
1069
|
|
|
944
1070
|
bindAbortableListener(worldEmitter, 'time', (timeOfDay) => {
|
|
945
|
-
if (!this.worldRendererConfig.dayCycle)
|
|
1071
|
+
if (!this.worldRendererConfig.dayCycle) {
|
|
1072
|
+
return
|
|
1073
|
+
}
|
|
946
1074
|
this.timeUpdated?.(timeOfDay)
|
|
947
1075
|
|
|
948
1076
|
this.timeOfTheDay = timeOfDay
|
|
949
1077
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1078
|
+
const skyLight = (timeOfDay < 0 || timeOfDay > 24_000) ? 15 : calculateSkyLightSimple(timeOfDay)
|
|
1079
|
+
if (this.lastMesherSkyLight === skyLight) return
|
|
1080
|
+
this.lastMesherSkyLight = skyLight
|
|
1081
|
+
if (this.workers.length > 0) {
|
|
1082
|
+
this.sendWorkers({ config: { skyLight } } as WorkerSend)
|
|
1083
|
+
}
|
|
1084
|
+
this.onDayCycleSkyLightChanged?.(skyLight)
|
|
955
1085
|
}, signal)
|
|
956
1086
|
|
|
957
1087
|
bindAbortableListener(worldEmitter, 'biomeUpdate', ({ biome }) => {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { Vec3 } from 'vec3'
|
|
4
|
+
import { collectBlockEntityMetadata } from './blockEntityMetadata'
|
|
5
|
+
|
|
6
|
+
describe('collectBlockEntityMetadata', () => {
|
|
7
|
+
it('stores channel light norms on banner metadata', () => {
|
|
8
|
+
const target = { signs: {}, heads: {}, banners: {} }
|
|
9
|
+
const block = {
|
|
10
|
+
name: 'pink_banner',
|
|
11
|
+
getProperties: () => ({ rotation: 0 }),
|
|
12
|
+
}
|
|
13
|
+
collectBlockEntityMetadata(
|
|
14
|
+
block,
|
|
15
|
+
1, 2, 3,
|
|
16
|
+
target,
|
|
17
|
+
{},
|
|
18
|
+
{
|
|
19
|
+
getChannelLightNorm: (pos: Vec3) => {
|
|
20
|
+
expect(pos).toBeInstanceOf(Vec3)
|
|
21
|
+
return { block: 0.4, sky: 0.8 }
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
expect(target.banners['1,2,3']).toEqual({
|
|
26
|
+
isWall: false,
|
|
27
|
+
blockName: 'pink_banner',
|
|
28
|
+
rotation: 0,
|
|
29
|
+
blockLightNorm: 0.4,
|
|
30
|
+
skyLightNorm: 0.8,
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
+
import { Vec3 } from 'vec3'
|
|
3
|
+
|
|
2
4
|
export interface SignMeta { isWall: boolean; isHanging: boolean; rotation: number }
|
|
3
5
|
export interface HeadMeta { isWall: boolean; rotation: number }
|
|
4
|
-
export interface BannerMeta {
|
|
6
|
+
export interface BannerMeta {
|
|
7
|
+
isWall: boolean
|
|
8
|
+
blockName: string
|
|
9
|
+
rotation: number
|
|
10
|
+
blockLightNorm: number
|
|
11
|
+
skyLightNorm: number
|
|
12
|
+
}
|
|
5
13
|
|
|
6
14
|
export interface BlockEntityMetadataTarget {
|
|
7
15
|
signs: Record<string, SignMeta>
|
|
@@ -15,11 +23,16 @@ export interface BlockEntityMetadataOptions {
|
|
|
15
23
|
|
|
16
24
|
type BlockLike = { name: string; getProperties(): any }
|
|
17
25
|
|
|
26
|
+
type LightSampler = {
|
|
27
|
+
getChannelLightNorm(pos: Vec3): { block: number, sky: number }
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
export function collectBlockEntityMetadata(
|
|
19
31
|
block: BlockLike,
|
|
20
32
|
x: number, y: number, z: number,
|
|
21
33
|
target: BlockEntityMetadataTarget,
|
|
22
|
-
options: BlockEntityMetadataOptions
|
|
34
|
+
options: BlockEntityMetadataOptions,
|
|
35
|
+
world?: LightSampler,
|
|
23
36
|
): void {
|
|
24
37
|
if ((block.name.includes('_sign') || block.name === 'sign') && !options.disableBlockEntityTextures) {
|
|
25
38
|
const key = `${x},${y},${z}`
|
|
@@ -61,10 +74,13 @@ export function collectBlockEntityMetadata(
|
|
|
61
74
|
'east': 3
|
|
62
75
|
}
|
|
63
76
|
const isWall = block.name.endsWith('_wall_banner')
|
|
77
|
+
const light = world?.getChannelLightNorm(new Vec3(x, y, z)) ?? { block: 0, sky: 1 }
|
|
64
78
|
target.banners[key] = {
|
|
65
79
|
isWall,
|
|
66
80
|
blockName: block.name, // Pass block name for base color extraction
|
|
67
|
-
rotation: isWall ? facingRotationMap[props.facing] : (props.rotation === undefined ? 0 : +props.rotation)
|
|
81
|
+
rotation: isWall ? facingRotationMap[props.facing] : (props.rotation === undefined ? 0 : +props.rotation),
|
|
82
|
+
blockLightNorm: light.block,
|
|
83
|
+
skyLightNorm: light.sky,
|
|
68
84
|
}
|
|
69
85
|
}
|
|
70
86
|
}
|
|
@@ -8,6 +8,17 @@ export interface ExportedSection {
|
|
|
8
8
|
positions: number[]
|
|
9
9
|
normals: number[]
|
|
10
10
|
colors: number[]
|
|
11
|
+
skyLights: number[]
|
|
12
|
+
blockLights: number[]
|
|
13
|
+
uvs: number[]
|
|
14
|
+
indices: number[]
|
|
15
|
+
}
|
|
16
|
+
blendGeometry?: {
|
|
17
|
+
positions: number[]
|
|
18
|
+
normals: number[]
|
|
19
|
+
colors: number[]
|
|
20
|
+
skyLights: number[]
|
|
21
|
+
blockLights: number[]
|
|
11
22
|
uvs: number[]
|
|
12
23
|
indices: number[]
|
|
13
24
|
}
|