minecraft-renderer 0.1.62 → 0.1.64
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 +1 -1
- package/dist/mesherWasm.js +22 -22
- package/dist/minecraft-renderer.js +54 -54
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +407 -407
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +3 -3
- package/src/graphicsBackend/rendererDefaultOptions.ts +41 -24
- package/src/graphicsBackend/rendererOptionsSync.ts +23 -23
- package/src/graphicsBackend/types.ts +3 -3
- package/src/index.ts +8 -8
- package/src/lib/bindAbortableListener.test.ts +65 -0
- package/src/lib/bindAbortableListener.ts +41 -0
- package/src/lib/workerProxy.ts +238 -118
- package/src/lib/workerSyncOps.test.ts +154 -0
- package/src/lib/worldrendererCommon.removeColumn.test.ts +182 -0
- package/src/lib/worldrendererCommon.ts +86 -54
- package/src/three/documentRenderer.ts +1 -1
- package/src/three/entities.ts +21 -11
- package/src/three/graphicsBackendBase.ts +18 -8
- package/src/three/menuBackground/activeView.ts +1 -1
- package/src/three/menuBackground/config.ts +9 -9
- package/src/three/menuBackground/index.ts +10 -10
- package/src/three/menuBackground/renderer.ts +12 -12
- package/src/three/menuBackground/types.ts +9 -9
- package/src/three/menuBackground/{futuristic.ts → v2.ts} +110 -59
- package/src/three/menuBackground/{futuristicMeta.ts → v2Meta.ts} +6 -6
- package/src/three/modules/rain.ts +1 -1
- package/src/three/worldRendererThree.ts +2 -1
- package/src/wasm-mesher/tests/mesherWasmRequestTracker.test.ts +29 -0
- package/src/wasm-mesher/worker/mesherWasm.ts +7 -0
- package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +10 -0
- package/src/worldView/worldView.spiral.test.ts +38 -0
- package/src/worldView/worldView.ts +41 -8
- package/src/worldView/worldViewWorkerBridge.test.ts +59 -0
- package/src/lib/workerProxy.restore.test.ts +0 -29
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { EventEmitter } from 'events'
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
|
+
import { Vec3 } from 'vec3'
|
|
5
|
+
import { proxy } from 'valtio'
|
|
6
|
+
import { WorldRendererCommon } from './worldrendererCommon'
|
|
7
|
+
import { defaultWorldRendererConfig } from '../graphicsBackend/config'
|
|
8
|
+
import { getInitialPlayerState } from '../playerState/playerState'
|
|
9
|
+
import type { DisplayWorldOptions, GraphicsInitOptions } from '../graphicsBackend/types'
|
|
10
|
+
|
|
11
|
+
vi.mock('./ui/newStats', () => ({
|
|
12
|
+
addNewStat: vi.fn(() => ({ updateText: vi.fn(), setVisibility: vi.fn() })),
|
|
13
|
+
updateStatText: vi.fn(),
|
|
14
|
+
removeAllStats: vi.fn(),
|
|
15
|
+
updatePanesVisibility: vi.fn(),
|
|
16
|
+
MC_RENDERER_DEBUG_OVERLAY_CLASS: 'mc-renderer-debug-overlay',
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
vi.mock('./utils/skins', () => ({
|
|
20
|
+
setSkinsConfig: vi.fn(),
|
|
21
|
+
steveTexture: {},
|
|
22
|
+
stevePngUrl: '',
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
function ensurePromiseWithResolvers() {
|
|
26
|
+
if (!Promise.withResolvers) {
|
|
27
|
+
Promise.withResolvers = function <T>() {
|
|
28
|
+
let resolve!: (value: T | PromiseLike<T>) => void
|
|
29
|
+
let reject!: (reason?: unknown) => void
|
|
30
|
+
const promise = new Promise<T>((res, rej) => {
|
|
31
|
+
resolve = res
|
|
32
|
+
reject = rej
|
|
33
|
+
})
|
|
34
|
+
return { promise, resolve, reject }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class TestWorldRenderer extends WorldRendererCommon {
|
|
40
|
+
outputFormat = 'threeJs' as const
|
|
41
|
+
|
|
42
|
+
changeBackgroundColor() {}
|
|
43
|
+
changeCardinalLight() {}
|
|
44
|
+
handleWorkerMessage() {}
|
|
45
|
+
updateCamera() {}
|
|
46
|
+
render() {}
|
|
47
|
+
updateShowChunksBorder() {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createRenderer() {
|
|
51
|
+
const rendererState = proxy({
|
|
52
|
+
world: {
|
|
53
|
+
chunksLoaded: new Set<string>(),
|
|
54
|
+
heightmaps: new Map<string, Int16Array>(),
|
|
55
|
+
allChunksLoaded: false,
|
|
56
|
+
mesherWork: false,
|
|
57
|
+
instabilityFactors: {},
|
|
58
|
+
intersectMedia: null,
|
|
59
|
+
},
|
|
60
|
+
renderer: '',
|
|
61
|
+
preventEscapeMenu: false,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const displayOptions = {
|
|
65
|
+
version: '1.21.1',
|
|
66
|
+
worldView: new EventEmitter() as DisplayWorldOptions['worldView'],
|
|
67
|
+
inWorldRenderingConfig: { ...defaultWorldRendererConfig },
|
|
68
|
+
playerStateReactive: getInitialPlayerState(),
|
|
69
|
+
rendererState,
|
|
70
|
+
nonReactiveState: {
|
|
71
|
+
fps: 0,
|
|
72
|
+
worstRenderTime: 0,
|
|
73
|
+
avgRenderTime: 0,
|
|
74
|
+
world: {
|
|
75
|
+
chunksLoaded: new Set<string>(),
|
|
76
|
+
chunksTotalNumber: 0,
|
|
77
|
+
chunksFullInfo: '',
|
|
78
|
+
},
|
|
79
|
+
renderer: {
|
|
80
|
+
timeline: { live: [], frozen: [], lastSecond: [] },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
resourcesManager: {} as DisplayWorldOptions['resourcesManager'],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const initOptions: GraphicsInitOptions = {
|
|
87
|
+
config: { sceneBackground: '#000' },
|
|
88
|
+
rendererSpecificSettings: {},
|
|
89
|
+
callbacks: {
|
|
90
|
+
displayCriticalError: vi.fn(),
|
|
91
|
+
setRendererSpecificSettings: vi.fn(),
|
|
92
|
+
fireCustomEvent: vi.fn(),
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const renderer = new TestWorldRenderer(displayOptions.resourcesManager, displayOptions, initOptions)
|
|
97
|
+
renderer.active = true
|
|
98
|
+
renderer.workers = [{ postMessage: vi.fn() }, { postMessage: vi.fn() }]
|
|
99
|
+
renderer.viewDistance = 16
|
|
100
|
+
renderer.viewerChunkPosition = new Vec3(0, 64, 0)
|
|
101
|
+
renderer.worldSizeParams = { minY: 0, worldHeight: 256 }
|
|
102
|
+
renderer.loadedChunks['160,0'] = true
|
|
103
|
+
return renderer
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function sectionKeysForColumn(renderer: TestWorldRenderer, x: number, z: number): string[] {
|
|
107
|
+
const keys: string[] = []
|
|
108
|
+
const sectionHeight = renderer.getSectionHeight()
|
|
109
|
+
for (let y = renderer.worldMinYRender; y < renderer.worldSizeParams.worldHeight; y += sectionHeight) {
|
|
110
|
+
keys.push(`${x},${y},${z}`)
|
|
111
|
+
}
|
|
112
|
+
return keys
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
describe('WorldRendererCommon.removeColumn sectionsWaiting reconciliation', () => {
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
ensurePromiseWithResolvers()
|
|
118
|
+
vi.useFakeTimers()
|
|
119
|
+
vi.stubGlobal('location', { href: 'http://localhost/' })
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
afterEach(() => {
|
|
123
|
+
vi.useRealTimers()
|
|
124
|
+
vi.unstubAllGlobals()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('clears sectionsWaiting when viewDistance gate blocks setSectionDirty(false)', () => {
|
|
128
|
+
const renderer = createRenderer()
|
|
129
|
+
const columnX = 160
|
|
130
|
+
const columnZ = 0
|
|
131
|
+
const sectionPos = new Vec3(columnX, 64, columnZ)
|
|
132
|
+
|
|
133
|
+
renderer.setSectionDirty(sectionPos, true)
|
|
134
|
+
expect(renderer.sectionsWaiting.get(`${columnX},64,${columnZ}`)).toBe(1)
|
|
135
|
+
|
|
136
|
+
renderer.viewDistance = 4
|
|
137
|
+
renderer.removeColumn(columnX, columnZ)
|
|
138
|
+
|
|
139
|
+
for (const key of sectionKeysForColumn(renderer, columnX, columnZ)) {
|
|
140
|
+
expect(renderer.sectionsWaiting.has(key)).toBe(false)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('treats late sectionFinished as a no-op after removeColumn', () => {
|
|
145
|
+
const renderer = createRenderer()
|
|
146
|
+
const sectionKey = '160,64,0'
|
|
147
|
+
const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {})
|
|
148
|
+
|
|
149
|
+
renderer.sectionsWaiting.set(sectionKey, 1)
|
|
150
|
+
renderer.viewDistance = 4
|
|
151
|
+
renderer.removeColumn(160, 0)
|
|
152
|
+
|
|
153
|
+
expect(() => {
|
|
154
|
+
renderer.handleMessage({ type: 'sectionFinished', key: sectionKey, workerIndex: 0 })
|
|
155
|
+
}).not.toThrow()
|
|
156
|
+
|
|
157
|
+
expect(renderer.sectionsWaiting.has(sectionKey)).toBe(false)
|
|
158
|
+
expect(debugSpy).toHaveBeenCalledWith(
|
|
159
|
+
expect.stringContaining('sectionFinished for non-outstanding section'),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
debugSpy.mockRestore()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('clears sectionsWaiting when unload happens before batched dirty flush', () => {
|
|
166
|
+
const renderer = createRenderer()
|
|
167
|
+
renderer.forceCallFromMesherReplayer = false
|
|
168
|
+
const columnX = 160
|
|
169
|
+
const columnZ = 0
|
|
170
|
+
|
|
171
|
+
renderer.setSectionDirty(new Vec3(columnX, 64, columnZ), true)
|
|
172
|
+
expect(renderer.sectionsWaiting.get(`${columnX},64,${columnZ}`)).toBe(1)
|
|
173
|
+
|
|
174
|
+
renderer.viewDistance = 4
|
|
175
|
+
renderer.removeColumn(columnX, columnZ)
|
|
176
|
+
vi.advanceTimersByTime(0)
|
|
177
|
+
|
|
178
|
+
for (const key of sectionKeysForColumn(renderer, columnX, columnZ)) {
|
|
179
|
+
expect(renderer.sectionsWaiting.has(key)).toBe(false)
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
})
|
|
@@ -19,6 +19,7 @@ import { MesherLogReader } from './mesherlogReader'
|
|
|
19
19
|
import { setSkinsConfig } from './utils/skins'
|
|
20
20
|
import { calculateSkyLightSimple } from './skyLight'
|
|
21
21
|
import { WorldViewWorker } from '../worldView'
|
|
22
|
+
import { bindAbortableEmitterListener, bindAbortableListener } from './bindAbortableListener'
|
|
22
23
|
import { generateSpiralMatrix } from './spiral'
|
|
23
24
|
import { PlayerStateReactive } from '../playerState/playerState'
|
|
24
25
|
import { IndexedData } from 'minecraft-data'
|
|
@@ -170,6 +171,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
170
171
|
stopMesherMessagesProcessing = false
|
|
171
172
|
|
|
172
173
|
abortController = new AbortController()
|
|
174
|
+
private valtioUnsubs: Array<() => void> = []
|
|
173
175
|
lastRendered = 0
|
|
174
176
|
renderingActive = true
|
|
175
177
|
geometryReceiveCountPerSec = 0
|
|
@@ -257,10 +259,16 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
257
259
|
})()
|
|
258
260
|
])
|
|
259
261
|
|
|
260
|
-
|
|
262
|
+
const onAssetsTexturesUpdated = async () => {
|
|
261
263
|
if (!this.active) return
|
|
262
264
|
await this.updateAssetsData()
|
|
263
|
-
}
|
|
265
|
+
}
|
|
266
|
+
bindAbortableEmitterListener(
|
|
267
|
+
this.resourcesManager,
|
|
268
|
+
'assetsTexturesUpdated',
|
|
269
|
+
onAssetsTexturesUpdated,
|
|
270
|
+
this.abortController.signal
|
|
271
|
+
)
|
|
264
272
|
|
|
265
273
|
this.watchReactivePlayerState()
|
|
266
274
|
this.watchReactiveConfig()
|
|
@@ -338,18 +346,24 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
watchReactivePlayerState() {
|
|
341
|
-
this.
|
|
342
|
-
this.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
349
|
+
this.valtioUnsubs.push(
|
|
350
|
+
this.onReactivePlayerStateUpdated('backgroundColor', (value) => {
|
|
351
|
+
this.changeBackgroundColor(value)
|
|
352
|
+
})
|
|
353
|
+
)
|
|
354
|
+
this.valtioUnsubs.push(
|
|
355
|
+
this.onReactivePlayerStateUpdated('cardinalLight', (value) => {
|
|
356
|
+
this.changeCardinalLight(value)
|
|
357
|
+
})
|
|
358
|
+
)
|
|
347
359
|
}
|
|
348
360
|
|
|
349
361
|
watchReactiveConfig() {
|
|
350
|
-
this.
|
|
351
|
-
|
|
352
|
-
|
|
362
|
+
this.valtioUnsubs.push(
|
|
363
|
+
this.onReactiveConfigUpdated('fetchPlayerSkins', (value) => {
|
|
364
|
+
setSkinsConfig({ apiEnabled: value })
|
|
365
|
+
})
|
|
366
|
+
)
|
|
353
367
|
}
|
|
354
368
|
|
|
355
369
|
async processMessageQueue(source: string) {
|
|
@@ -410,7 +424,10 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
410
424
|
}
|
|
411
425
|
if (data.type === 'sectionFinished') { // on after load & unload section
|
|
412
426
|
this.logWorkerWork(`<- ${data.workerIndex} sectionFinished ${data.key} ${JSON.stringify({ processTime: data.processTime })}`)
|
|
413
|
-
if (!this.sectionsWaiting.has(data.key))
|
|
427
|
+
if (!this.sectionsWaiting.has(data.key)) {
|
|
428
|
+
console.debug(`sectionFinished for non-outstanding section ${data.key} (viewDistance=${this.viewDistance})`)
|
|
429
|
+
return
|
|
430
|
+
}
|
|
414
431
|
this.sectionsWaiting.set(data.key, this.sectionsWaiting.get(data.key)! - 1)
|
|
415
432
|
if (this.sectionsWaiting.get(data.key) === 0) {
|
|
416
433
|
this.sectionsWaiting.delete(data.key)
|
|
@@ -432,7 +449,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
432
449
|
// CHUNK FINISHED
|
|
433
450
|
this.finishedChunks[chunkKey] = true
|
|
434
451
|
const CHUNK_SIZE = 16
|
|
435
|
-
|
|
452
|
+
const gridKey = `${Math.floor(chunkCoords[0] / CHUNK_SIZE)},${Math.floor(chunkCoords[2] / CHUNK_SIZE)}`
|
|
453
|
+
this.reactiveState.world.chunksLoaded[gridKey] = true
|
|
436
454
|
this.renderUpdateEmitter.emit(`chunkFinished`, `${chunkCoords[0]},${chunkCoords[2]}`)
|
|
437
455
|
this.checkAllFinished()
|
|
438
456
|
// merge highest blocks by sections into highest blocks by chunks
|
|
@@ -513,7 +531,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
513
531
|
}
|
|
514
532
|
|
|
515
533
|
if (data.type === 'heightmap') {
|
|
516
|
-
|
|
534
|
+
const heightmap = new Int16Array(data.heightmap)
|
|
535
|
+
this.reactiveState.world.heightmaps[data.key] = heightmap
|
|
517
536
|
}
|
|
518
537
|
}
|
|
519
538
|
|
|
@@ -684,7 +703,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
684
703
|
|
|
685
704
|
updateChunksStats() {
|
|
686
705
|
const loadedChunks = Object.keys(this.finishedChunks)
|
|
687
|
-
this.displayOptions.nonReactiveState.world.
|
|
706
|
+
this.displayOptions.nonReactiveState.world.chunksLoadedCount = loadedChunks.length
|
|
688
707
|
this.displayOptions.nonReactiveState.world.chunksTotalNumber = this.chunksLength
|
|
689
708
|
this.reactiveState.world.allChunksLoaded = this.allChunksFinished
|
|
690
709
|
|
|
@@ -771,9 +790,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
771
790
|
this.sectionDirtyPendingArgs.delete(key)
|
|
772
791
|
}
|
|
773
792
|
}
|
|
774
|
-
for (
|
|
775
|
-
|
|
793
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
794
|
+
this.toWorkerMessagesQueue[i] ??= []
|
|
795
|
+
this.toWorkerMessagesQueue[i].push({ type: 'unloadChunk', x, z })
|
|
776
796
|
}
|
|
797
|
+
this.dispatchMessages()
|
|
777
798
|
this.logWorkerWork(`-> unloadChunk ${JSON.stringify({ x, z })}`)
|
|
778
799
|
delete this.finishedChunks[`${x},${z}`]
|
|
779
800
|
this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
|
|
@@ -782,12 +803,18 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
782
803
|
this.initialChunkLoadWasStartedIn = undefined
|
|
783
804
|
}
|
|
784
805
|
const sectionHeight = this.getSectionHeight()
|
|
785
|
-
for (let y = this.
|
|
786
|
-
|
|
787
|
-
|
|
806
|
+
for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += sectionHeight) {
|
|
807
|
+
const sectionKey = `${x},${y},${z}`
|
|
808
|
+
const waitingCount = this.sectionsWaiting.get(sectionKey)
|
|
809
|
+
if (waitingCount !== undefined && waitingCount > 0) {
|
|
810
|
+
console.debug(`[removeColumn] clearing non-zero sectionsWaiting for ${sectionKey}: ${waitingCount} (chunk ${x},${z}, viewDistance=${this.viewDistance})`)
|
|
811
|
+
}
|
|
812
|
+
this.sectionsWaiting.delete(sectionKey)
|
|
813
|
+
delete this.finishedSections[sectionKey]
|
|
788
814
|
}
|
|
789
815
|
this.highestBlocksByChunks.delete(`${x},${z}`)
|
|
790
|
-
|
|
816
|
+
const heightmapKey = `${Math.floor(x / 16)},${Math.floor(z / 16)}`
|
|
817
|
+
delete this.reactiveState.world.heightmaps[heightmapKey]
|
|
791
818
|
|
|
792
819
|
this.updateChunksStats()
|
|
793
820
|
|
|
@@ -826,22 +853,23 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
826
853
|
|
|
827
854
|
connect(worldView: WorldViewWorker) {
|
|
828
855
|
const worldEmitter = worldView
|
|
856
|
+
const signal = this.abortController.signal
|
|
829
857
|
|
|
830
|
-
worldEmitter
|
|
858
|
+
bindAbortableListener(worldEmitter, 'entity', (e) => {
|
|
831
859
|
this.updateEntity(e, false)
|
|
832
|
-
})
|
|
833
|
-
worldEmitter
|
|
860
|
+
}, signal)
|
|
861
|
+
bindAbortableListener(worldEmitter, 'entityMoved', (e) => {
|
|
834
862
|
this.updateEntity(e, true)
|
|
835
|
-
})
|
|
836
|
-
worldEmitter
|
|
863
|
+
}, signal)
|
|
864
|
+
bindAbortableListener(worldEmitter, 'playerEntity', (e) => {
|
|
837
865
|
this.updatePlayerEntity?.(e)
|
|
838
|
-
})
|
|
866
|
+
}, signal)
|
|
839
867
|
|
|
840
868
|
let currentLoadChunkBatch = null as {
|
|
841
869
|
timeout
|
|
842
870
|
data
|
|
843
871
|
} | null
|
|
844
|
-
worldEmitter
|
|
872
|
+
bindAbortableListener(worldEmitter, 'loadChunk', ({ x, z, chunk, worldConfig, isLightUpdate }) => {
|
|
845
873
|
this.worldSizeParams = worldConfig
|
|
846
874
|
this.queuedChunks.add(`${x},${z}`)
|
|
847
875
|
const args = [x, z, chunk, isLightUpdate]
|
|
@@ -863,45 +891,44 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
863
891
|
}
|
|
864
892
|
}
|
|
865
893
|
currentLoadChunkBatch.data.push(args)
|
|
866
|
-
})
|
|
894
|
+
}, signal)
|
|
867
895
|
// todo remove and use other architecture instead so data flow is clear
|
|
868
|
-
worldEmitter
|
|
896
|
+
bindAbortableListener(worldEmitter, 'blockEntities', (blockEntities) => {
|
|
869
897
|
this.blockEntities = blockEntities
|
|
870
|
-
})
|
|
898
|
+
}, signal)
|
|
871
899
|
|
|
872
|
-
worldEmitter
|
|
900
|
+
bindAbortableListener(worldEmitter, 'unloadChunk', ({ x, z }) => {
|
|
873
901
|
this.removeColumn(x, z)
|
|
874
|
-
})
|
|
902
|
+
}, signal)
|
|
875
903
|
|
|
876
|
-
worldEmitter
|
|
904
|
+
bindAbortableListener(worldEmitter, 'blockUpdate', ({ pos, stateId }) => {
|
|
877
905
|
this.setBlockStateId(new Vec3(pos.x, pos.y, pos.z), stateId)
|
|
878
|
-
})
|
|
906
|
+
}, signal)
|
|
879
907
|
|
|
880
|
-
worldEmitter
|
|
908
|
+
bindAbortableListener(worldEmitter, 'chunkPosUpdate', ({ pos }) => {
|
|
881
909
|
this.updateViewerPosition(pos)
|
|
882
|
-
})
|
|
910
|
+
}, signal)
|
|
883
911
|
|
|
884
|
-
worldEmitter
|
|
912
|
+
bindAbortableListener(worldEmitter, 'end', () => {
|
|
885
913
|
this.worldStop?.()
|
|
886
|
-
})
|
|
914
|
+
}, signal)
|
|
887
915
|
|
|
888
|
-
|
|
889
|
-
worldEmitter.on('renderDistance', (d) => {
|
|
916
|
+
bindAbortableListener(worldEmitter, 'renderDistance', (d) => {
|
|
890
917
|
this.viewDistance = d
|
|
891
918
|
this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
|
|
892
919
|
this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
|
|
893
920
|
this.onRenderDistanceChanged?.(d)
|
|
894
|
-
})
|
|
921
|
+
}, signal)
|
|
895
922
|
|
|
896
|
-
worldEmitter
|
|
923
|
+
bindAbortableListener(worldEmitter, 'markAsLoaded', ({ x, z }) => {
|
|
897
924
|
this.markAsLoaded(x, z)
|
|
898
|
-
})
|
|
925
|
+
}, signal)
|
|
899
926
|
|
|
900
|
-
worldEmitter
|
|
927
|
+
bindAbortableListener(worldEmitter, 'updateLight', ({ pos }) => {
|
|
901
928
|
this.lightUpdate(pos.x, pos.z)
|
|
902
|
-
})
|
|
929
|
+
}, signal)
|
|
903
930
|
|
|
904
|
-
worldEmitter
|
|
931
|
+
bindAbortableListener(worldEmitter, 'onWorldSwitch', () => {
|
|
905
932
|
for (const fn of this.onWorldSwitched) {
|
|
906
933
|
try {
|
|
907
934
|
fn()
|
|
@@ -912,9 +939,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
912
939
|
}, 0)
|
|
913
940
|
}
|
|
914
941
|
}
|
|
915
|
-
})
|
|
942
|
+
}, signal)
|
|
916
943
|
|
|
917
|
-
worldEmitter
|
|
944
|
+
bindAbortableListener(worldEmitter, 'time', (timeOfDay) => {
|
|
918
945
|
if (!this.worldRendererConfig.dayCycle) return
|
|
919
946
|
this.timeUpdated?.(timeOfDay)
|
|
920
947
|
|
|
@@ -925,15 +952,15 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
925
952
|
// if (this instanceof WorldRendererThree) {
|
|
926
953
|
// (this).rerenderAllChunks?.()
|
|
927
954
|
// }
|
|
928
|
-
})
|
|
955
|
+
}, signal)
|
|
929
956
|
|
|
930
|
-
worldEmitter
|
|
957
|
+
bindAbortableListener(worldEmitter, 'biomeUpdate', ({ biome }) => {
|
|
931
958
|
this.biomeUpdated?.(biome)
|
|
932
|
-
})
|
|
959
|
+
}, signal)
|
|
933
960
|
|
|
934
|
-
worldEmitter
|
|
961
|
+
bindAbortableListener(worldEmitter, 'biomeReset', () => {
|
|
935
962
|
this.biomeReset?.()
|
|
936
|
-
})
|
|
963
|
+
}, signal)
|
|
937
964
|
}
|
|
938
965
|
|
|
939
966
|
setBlockStateIdInner(pos: Vec3, stateId: number | undefined, needAoRecalculation = true) {
|
|
@@ -1217,6 +1244,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
1217
1244
|
}
|
|
1218
1245
|
|
|
1219
1246
|
destroy() {
|
|
1247
|
+
for (const unsub of this.valtioUnsubs) {
|
|
1248
|
+
unsub()
|
|
1249
|
+
}
|
|
1250
|
+
this.valtioUnsubs = []
|
|
1251
|
+
|
|
1220
1252
|
// Cancel all pending heightmap debounce timers
|
|
1221
1253
|
for (const timer of this.heightmapDebounceTimers.values()) {
|
|
1222
1254
|
clearTimeout(timer)
|
package/src/three/entities.ts
CHANGED
|
@@ -1080,6 +1080,7 @@ export class Entities {
|
|
|
1080
1080
|
if (entity.delete) {
|
|
1081
1081
|
if (!e) return
|
|
1082
1082
|
e.userData._posTween?.stop()
|
|
1083
|
+
e.userData._rotTween?.stop()
|
|
1083
1084
|
if (e.additionalCleanup) e.additionalCleanup()
|
|
1084
1085
|
e.traverse(c => {
|
|
1085
1086
|
if (c['additionalCleanup']) c['additionalCleanup']()
|
|
@@ -1392,21 +1393,30 @@ export class Entities {
|
|
|
1392
1393
|
})
|
|
1393
1394
|
.start()
|
|
1394
1395
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1396
|
+
/** World yaw for the whole model: for PlayerObject skins, rotate body to head look dir; head mesh stays yaw-fixed (pitch only). */
|
|
1397
|
+
let targetYaw: number | undefined
|
|
1398
|
+
if (e.playerObject && overrides?.rotation?.head) {
|
|
1399
|
+
const hy = overrides.rotation.head.y
|
|
1400
|
+
const headYawWorld =
|
|
1401
|
+
typeof hy === 'number' && Number.isFinite(hy) ? hy : entity.yaw
|
|
1402
|
+
if (typeof headYawWorld === 'number' && Number.isFinite(headYawWorld)) {
|
|
1403
|
+
targetYaw = headYawWorld
|
|
1404
|
+
}
|
|
1405
|
+
} else if (typeof entity.yaw === 'number' && Number.isFinite(entity.yaw)) {
|
|
1406
|
+
targetYaw = entity.yaw
|
|
1407
|
+
}
|
|
1408
|
+
if (typeof targetYaw === 'number' && Number.isFinite(targetYaw)) {
|
|
1409
|
+
const dy = shortestYawRadians(e.rotation.y, targetYaw)
|
|
1410
|
+
// Stop previous rotation tween to prevent accumulation (mirror _posTween)
|
|
1411
|
+
e.userData._rotTween?.stop()
|
|
1412
|
+
e.userData._rotTween = new TWEEN.Tween(e.rotation)
|
|
1413
|
+
.to({ y: e.rotation.y + dy }, ANIMATION_DURATION)
|
|
1414
|
+
.start()
|
|
1398
1415
|
}
|
|
1399
1416
|
|
|
1400
1417
|
if (e?.playerObject && overrides?.rotation?.head) {
|
|
1401
1418
|
const { playerObject } = e
|
|
1402
|
-
|
|
1403
|
-
const headYawWorld =
|
|
1404
|
-
typeof hy === 'number' && Number.isFinite(hy) ? hy : entity.yaw
|
|
1405
|
-
const headYawOffset =
|
|
1406
|
-
typeof headYawWorld === 'number' && typeof entity.yaw === 'number' && Number.isFinite(headYawWorld) && Number.isFinite(entity.yaw)
|
|
1407
|
-
? shortestYawRadians(entity.yaw, headYawWorld)
|
|
1408
|
-
: 0
|
|
1409
|
-
playerObject.skin.head.rotation.y = headYawOffset
|
|
1419
|
+
playerObject.skin.head.rotation.y = 0
|
|
1410
1420
|
|
|
1411
1421
|
const hp = overrides.rotation.head.x
|
|
1412
1422
|
playerObject.skin.head.rotation.x =
|
|
@@ -191,25 +191,35 @@ export const createGraphicsBackendBase = () => {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
const startWorld = async (displayOptionsArg: DisplayWorldOptions) => {
|
|
194
|
-
const displayOptionsRestorers = [ResourcesManager, WorldViewWorker]
|
|
195
|
-
const displayOptions: DisplayWorldOptions = isWebWorker ? restoreTransferred(displayOptionsArg, displayOptionsRestorers, globalThis as unknown as Worker) : displayOptionsArg
|
|
196
|
-
|
|
197
194
|
if (!documentRenderer) throw new Error('Document renderer not initialized')
|
|
198
195
|
|
|
199
|
-
documentRenderer.nonReactiveState = displayOptions.nonReactiveState
|
|
200
|
-
// Set resourcesManager globally for world rendering
|
|
201
|
-
; (globalThis as any).resourcesManager = displayOptions.resourcesManager
|
|
202
|
-
|
|
203
196
|
if (menuBackgroundRenderer) {
|
|
204
197
|
menuBackgroundRenderer.dispose()
|
|
205
198
|
menuBackgroundRenderer = null
|
|
206
199
|
}
|
|
207
200
|
|
|
201
|
+
if (worldRenderer) {
|
|
202
|
+
worldRenderer.destroy()
|
|
203
|
+
worldRenderer = null
|
|
204
|
+
frameTimingCollector = null
|
|
205
|
+
;(globalThis as any).world = undefined
|
|
206
|
+
;(globalThis as any).frameTimingCollector = undefined
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const displayOptionsRestorers = [ResourcesManager, WorldViewWorker]
|
|
210
|
+
const displayOptions: DisplayWorldOptions = isWebWorker ? restoreTransferred(displayOptionsArg, displayOptionsRestorers, globalThis as unknown as Worker) : displayOptionsArg
|
|
211
|
+
|
|
212
|
+
documentRenderer.nonReactiveState = displayOptions.nonReactiveState
|
|
213
|
+
// Set resourcesManager globally for world rendering
|
|
214
|
+
; (globalThis as any).resourcesManager = displayOptions.resourcesManager
|
|
215
|
+
|
|
208
216
|
worldRenderer = new WorldRendererThree(documentRenderer.renderer, initOptions, displayOptions)
|
|
209
217
|
|
|
210
218
|
await worldRenderer.worldReadyPromise
|
|
211
219
|
|
|
212
|
-
frameTimingCollector =
|
|
220
|
+
frameTimingCollector = displayOptions.inWorldRenderingConfig.enableDebugOverlay
|
|
221
|
+
? new FrameTimingCollector(displayOptions.nonReactiveState)
|
|
222
|
+
: null
|
|
213
223
|
; (globalThis as any).frameTimingCollector = frameTimingCollector
|
|
214
224
|
|
|
215
225
|
const originalRender = documentRenderer.render
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
|
|
4
|
-
/** Contract for a main-menu background implementation (classic cubemap,
|
|
4
|
+
/** Contract for a main-menu background implementation (classic cubemap, v2 scene, etc.). */
|
|
5
5
|
export interface MenuBackgroundView {
|
|
6
6
|
readonly scene: THREE.Scene
|
|
7
7
|
readonly camera: THREE.PerspectiveCamera
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import type { MenuBackgroundMode } from './types'
|
|
3
|
-
import type {
|
|
3
|
+
import type { V2CameraId, V2SceneId, MinecraftBlockGroupId } from './v2'
|
|
4
4
|
|
|
5
5
|
/** Single source of truth for menu-background defaults (settings + runtime fallbacks). */
|
|
6
6
|
export const MENU_BACKGROUND_OPTION_DEFAULTS = {
|
|
7
|
-
mode: '
|
|
7
|
+
mode: 'v2' as MenuBackgroundMode,
|
|
8
8
|
minecraftTextures: true as boolean,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
v2Scene: 'light' as V2SceneId,
|
|
10
|
+
v2Camera: 'dive' as V2CameraId,
|
|
11
|
+
v2BlockGroup: 'stainedGlass' as MinecraftBlockGroupId,
|
|
12
12
|
/** 0–200 (%). 100 = 1× motion. */
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
v2CameraSpeedPercent: 80,
|
|
14
|
+
v2BlockSpeedPercent: 40
|
|
15
15
|
} as const
|
|
16
16
|
|
|
17
17
|
export const menuBackgroundSpeedToMultiplier = (percent: number) => percent / 100
|
|
18
18
|
|
|
19
19
|
/** Default camera / block motion multipliers (1 = 100%). */
|
|
20
20
|
export const MENU_BACKGROUND_MOTION_DEFAULTS = {
|
|
21
|
-
camera: menuBackgroundSpeedToMultiplier(MENU_BACKGROUND_OPTION_DEFAULTS.
|
|
22
|
-
block: menuBackgroundSpeedToMultiplier(MENU_BACKGROUND_OPTION_DEFAULTS.
|
|
21
|
+
camera: menuBackgroundSpeedToMultiplier(MENU_BACKGROUND_OPTION_DEFAULTS.v2CameraSpeedPercent),
|
|
22
|
+
block: menuBackgroundSpeedToMultiplier(MENU_BACKGROUND_OPTION_DEFAULTS.v2BlockSpeedPercent)
|
|
23
23
|
} as const
|
|
@@ -6,21 +6,21 @@ export type { MenuBackgroundMode, MenuBackgroundOptions } from './types'
|
|
|
6
6
|
export { resolveMenuBackgroundMode } from './types'
|
|
7
7
|
export { ClassicMenuBackground } from './classic'
|
|
8
8
|
export type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
V2SceneId,
|
|
10
|
+
V2CameraId,
|
|
11
|
+
V2MenuBackgroundOptions,
|
|
12
12
|
MinecraftBlockGroupId
|
|
13
|
-
} from './
|
|
13
|
+
} from './v2'
|
|
14
14
|
export {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
V2MenuBackground,
|
|
16
|
+
V2_SCENE_IDS,
|
|
17
|
+
V2_CAMERA_IDS,
|
|
18
|
+
V2_SCENE_LABELS,
|
|
19
|
+
V2_CAMERA_LABELS,
|
|
20
20
|
MINECRAFT_BLOCK_GROUPS,
|
|
21
21
|
MINECRAFT_BLOCK_GROUP_IDS,
|
|
22
22
|
MINECRAFT_BLOCK_GROUP_LABELS
|
|
23
|
-
} from './
|
|
23
|
+
} from './v2'
|
|
24
24
|
export { WorldBlocksMenuBackground } from './worldBlocks'
|
|
25
25
|
export { MenuBackgroundRenderer } from './renderer'
|
|
26
26
|
export {
|