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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minecraft-renderer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.73",
|
|
4
4
|
"description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"skinview3d": "^3.4.1",
|
|
56
56
|
"stats-gl": "^1.0.5",
|
|
57
57
|
"stats.js": "^0.17.0",
|
|
58
|
-
"three": "0.
|
|
58
|
+
"three": "0.184.0",
|
|
59
59
|
"three-stdlib": "^2.36.1",
|
|
60
60
|
"type-fest": "^5.3.0",
|
|
61
61
|
"typed-emitter": "^2.1.0",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@types/lodash": "^4.17.21",
|
|
71
71
|
"@types/react": "^19.2.7",
|
|
72
72
|
"@types/stats.js": "^0.17.1",
|
|
73
|
-
"@types/three": "0.
|
|
73
|
+
"@types/three": "0.184.0",
|
|
74
74
|
"@zardoy/react-util": "^0.2.7",
|
|
75
75
|
"@zardoy/tsconfig": "^1.5.1",
|
|
76
76
|
"contro-max": "*",
|
|
@@ -63,7 +63,6 @@ export const RENDERER_DEFAULT_OPTIONS = {
|
|
|
63
63
|
rendererPerfDebugOverlay: false as boolean,
|
|
64
64
|
disableBlockEntityTextures: false as boolean,
|
|
65
65
|
rendererMesher: 'wasm' as RendererMesherPipeline,
|
|
66
|
-
rendererShaderCubeBlocks: false as boolean,
|
|
67
66
|
rendererShaderCubeDebugMode: 'off' as RendererShaderCubeDebugMode,
|
|
68
67
|
showChunkBorders: false as boolean,
|
|
69
68
|
renderEntities: true as boolean,
|
|
@@ -110,6 +109,7 @@ export function migrateRendererOptions(saved: Record<string, unknown>): void {
|
|
|
110
109
|
}
|
|
111
110
|
delete saved.wasmExperimentalMesher
|
|
112
111
|
delete saved.rendererWasmMesher
|
|
112
|
+
delete saved.rendererShaderCubeBlocks
|
|
113
113
|
|
|
114
114
|
if (saved.menuBackgroundMode === 'futuristic') {
|
|
115
115
|
saved.menuBackgroundMode = 'v2'
|
|
@@ -166,8 +166,8 @@ export const RENDERER_OPTIONS_META: Partial<Record<RendererDefaultOptionKey, Ren
|
|
|
166
166
|
},
|
|
167
167
|
rendererWorldPerformance: {
|
|
168
168
|
text: 'World performance',
|
|
169
|
-
tooltip: 'Background workers for chunk geometry.
|
|
170
|
-
|
|
169
|
+
tooltip: 'Background workers for chunk geometry. Recreates mesher workers and reloads chunks.',
|
|
170
|
+
requiresChunksReload: true,
|
|
171
171
|
possibleValues: [
|
|
172
172
|
['low-energy', 'Low Energy'],
|
|
173
173
|
['normal', 'Normal'],
|
|
@@ -193,12 +193,7 @@ export const RENDERER_OPTIONS_META: Partial<Record<RendererDefaultOptionKey, Ren
|
|
|
193
193
|
rendererMesher: {
|
|
194
194
|
possibleValues: [['wasm', 'WASM'], ['legacy-js', 'Legacy JS']],
|
|
195
195
|
text: 'Mesher pipeline',
|
|
196
|
-
tooltip: '
|
|
197
|
-
requiresRestart: true
|
|
198
|
-
},
|
|
199
|
-
rendererShaderCubeBlocks: {
|
|
200
|
-
text: '(UNSTABLE) Instanced shader cubes',
|
|
201
|
-
tooltip: 'Render full blocks through the global GPU instanced path. Requires WASM mesher and WebGL2.',
|
|
196
|
+
tooltip: 'Browser technology for processing world geometry before render. WASM is the fastest; if you see a dead tab icon, reloads, or other errors, switch to Legacy JS.',
|
|
202
197
|
requiresChunksReload: true,
|
|
203
198
|
},
|
|
204
199
|
rendererShaderCubeDebugMode: {
|
|
@@ -341,7 +336,7 @@ export const RENDERER_RENDER_GUI_SECTIONS: ReadonlyArray<{
|
|
|
341
336
|
},
|
|
342
337
|
{
|
|
343
338
|
title: 'Mesher',
|
|
344
|
-
keys: ['rendererMesher'
|
|
339
|
+
keys: ['rendererMesher']
|
|
345
340
|
},
|
|
346
341
|
{
|
|
347
342
|
title: 'Renderer debug',
|
|
@@ -163,7 +163,7 @@ export function applyRendererOptions(
|
|
|
163
163
|
cfg.fetchPlayerSkins = o.loadPlayerSkins
|
|
164
164
|
cfg.highlightBlockColor = o.highlightBlockColor
|
|
165
165
|
cfg.wasmMesher = wasmActive
|
|
166
|
-
cfg.shaderCubeBlocks =
|
|
166
|
+
cfg.shaderCubeBlocks = wasmActive
|
|
167
167
|
cfg.disableMesherConversionCache = !!ctx.isSafari
|
|
168
168
|
|
|
169
169
|
setSkinsConfig({ apiEnabled: o.loadPlayerSkins })
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/** Bake tint×AO colors with sky/block channels for static export (no live u_skyLevel uniform). */
|
|
3
|
+
export function bakeLegacyVertexColors (
|
|
4
|
+
colors: ArrayLike<number>,
|
|
5
|
+
skyLights: ArrayLike<number>,
|
|
6
|
+
blockLights: ArrayLike<number>,
|
|
7
|
+
skyLevel: number,
|
|
8
|
+
): number[] {
|
|
9
|
+
const vertCount = colors.length / 3
|
|
10
|
+
const out: number[] = []
|
|
11
|
+
for (let v = 0; v < vertCount; v++) {
|
|
12
|
+
const L = Math.max(blockLights[v] ?? 0, Math.min(skyLights[v] ?? 1, skyLevel))
|
|
13
|
+
const i = v * 3
|
|
14
|
+
out.push(colors[i]! * L, colors[i + 1]! * L, colors[i + 2]! * L)
|
|
15
|
+
}
|
|
16
|
+
return out
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import * as THREE from 'three'
|
|
4
|
+
import { blockEntityBrightness } from './blockEntityLighting'
|
|
5
|
+
import { BlockEntityLightRegistry } from './blockEntityLightRegistry'
|
|
6
|
+
|
|
7
|
+
describe('BlockEntityLightRegistry', () => {
|
|
8
|
+
it('refreshes overlay brightness when sky level changes', () => {
|
|
9
|
+
const registry = new BlockEntityLightRegistry()
|
|
10
|
+
const material = new THREE.MeshBasicMaterial()
|
|
11
|
+
registry.register({ material, blockLightNorm: 0, skyLightNorm: 1 })
|
|
12
|
+
const nightSky = 4 / 15
|
|
13
|
+
registry.setSkyLevel(nightSky)
|
|
14
|
+
expect(material.color.r).toBeCloseTo(blockEntityBrightness(0, 1, nightSky), 5)
|
|
15
|
+
registry.setSkyLevel(1)
|
|
16
|
+
expect(material.color.r).toBe(1)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import {
|
|
4
|
+
blockEntityBrightness,
|
|
5
|
+
DEFAULT_LIGHTMAP_PARAMS,
|
|
6
|
+
type BlockLightmapParams,
|
|
7
|
+
} from './blockEntityLighting'
|
|
8
|
+
|
|
9
|
+
export type BlockEntityOverlayLight = {
|
|
10
|
+
material: THREE.MeshBasicMaterial
|
|
11
|
+
blockLightNorm: number
|
|
12
|
+
skyLightNorm: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class BlockEntityLightRegistry {
|
|
16
|
+
private readonly entries = new Set<BlockEntityOverlayLight>()
|
|
17
|
+
private skyLevel = 1
|
|
18
|
+
private lightmapParams: BlockLightmapParams = { ...DEFAULT_LIGHTMAP_PARAMS }
|
|
19
|
+
|
|
20
|
+
register (entry: BlockEntityOverlayLight): void {
|
|
21
|
+
this.entries.add(entry)
|
|
22
|
+
this.applyBrightness(entry)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
unregister (material: THREE.Material): void {
|
|
26
|
+
for (const entry of this.entries) {
|
|
27
|
+
if (entry.material === material) {
|
|
28
|
+
this.entries.delete(entry)
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setSkyLevel (value: number): void {
|
|
35
|
+
this.skyLevel = value
|
|
36
|
+
this.refreshAll()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setLightmapParams (params: BlockLightmapParams): void {
|
|
40
|
+
this.lightmapParams = { ...this.lightmapParams, ...params }
|
|
41
|
+
this.refreshAll()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getSkyLevel (): number {
|
|
45
|
+
return this.skyLevel
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private refreshAll (): void {
|
|
49
|
+
for (const entry of this.entries) {
|
|
50
|
+
this.applyBrightness(entry)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private applyBrightness (entry: BlockEntityOverlayLight): void {
|
|
55
|
+
const brightness = blockEntityBrightness(
|
|
56
|
+
entry.blockLightNorm,
|
|
57
|
+
entry.skyLightNorm,
|
|
58
|
+
this.skyLevel,
|
|
59
|
+
this.lightmapParams,
|
|
60
|
+
)
|
|
61
|
+
entry.material.color.setScalar(brightness)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function tintBannerMaterial (
|
|
66
|
+
material: THREE.MeshBasicMaterial,
|
|
67
|
+
blockLightNorm: number,
|
|
68
|
+
skyLightNorm: number,
|
|
69
|
+
skyLevel: number,
|
|
70
|
+
lightmapParams: BlockLightmapParams = DEFAULT_LIGHTMAP_PARAMS,
|
|
71
|
+
): number {
|
|
72
|
+
const brightness = blockEntityBrightness(blockLightNorm, skyLightNorm, skyLevel, lightmapParams)
|
|
73
|
+
material.color.setScalar(brightness)
|
|
74
|
+
return brightness
|
|
75
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import {
|
|
4
|
+
applyLightmap,
|
|
5
|
+
blockEntityBrightness,
|
|
6
|
+
combinedBlockLight,
|
|
7
|
+
DEFAULT_LIGHTMAP_PARAMS,
|
|
8
|
+
} from './blockEntityLighting'
|
|
9
|
+
|
|
10
|
+
describe('blockEntityLighting', () => {
|
|
11
|
+
it('applyLightmap(1) === 1 for default params', () => {
|
|
12
|
+
expect(applyLightmap(1, DEFAULT_LIGHTMAP_PARAMS)).toBe(1)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('combinedBlockLight caps sky by skyLevel', () => {
|
|
16
|
+
expect(combinedBlockLight(0, 1, 4 / 15)).toBeCloseTo(4 / 15, 5)
|
|
17
|
+
expect(combinedBlockLight(0.5, 1, 4 / 15)).toBeCloseTo(0.5, 5)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('night outdoor blockEntityBrightness matches cap + lightmap', () => {
|
|
21
|
+
const skyLevel = 4 / 15
|
|
22
|
+
const L = combinedBlockLight(0, 1, skyLevel)
|
|
23
|
+
expect(blockEntityBrightness(0, 1, skyLevel)).toBeCloseTo(applyLightmap(L), 5)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('linear curve matches raw L at minBrightness 0', () => {
|
|
27
|
+
const p = { curve: 0, minBrightness: 0, gamma: 1 }
|
|
28
|
+
expect(applyLightmap(0.5, p)).toBeCloseTo(0.5, 5)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Shared block lighting math — keep in sync with APPLY_LIGHTMAP_GLSL in shaders.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type BlockLightmapParams = {
|
|
7
|
+
curve?: number
|
|
8
|
+
minBrightness?: number
|
|
9
|
+
gamma?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_LIGHTMAP_PARAMS: Required<BlockLightmapParams> = {
|
|
13
|
+
curve: 0,
|
|
14
|
+
minBrightness: 0.12,
|
|
15
|
+
gamma: 1,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** GLSL body for applyLightmap — requires u_lightCurve, u_minBrightness, u_lightGamma uniforms. */
|
|
19
|
+
export const APPLY_LIGHTMAP_GLSL = /* glsl */ `
|
|
20
|
+
float applyLightmap(float L) {
|
|
21
|
+
float curved = L / (4.0 - 3.0 * L);
|
|
22
|
+
float shaped = mix(L, curved, u_lightCurve);
|
|
23
|
+
shaped = mix(u_minBrightness, 1.0, shaped);
|
|
24
|
+
return clamp(pow(shaped, u_lightGamma), 0.0, 1.0);
|
|
25
|
+
}
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
export function applyLightmap (L: number, params: BlockLightmapParams = DEFAULT_LIGHTMAP_PARAMS): number {
|
|
29
|
+
const curve = params.curve ?? DEFAULT_LIGHTMAP_PARAMS.curve
|
|
30
|
+
const minBrightness = params.minBrightness ?? DEFAULT_LIGHTMAP_PARAMS.minBrightness
|
|
31
|
+
const gamma = params.gamma ?? DEFAULT_LIGHTMAP_PARAMS.gamma
|
|
32
|
+
|
|
33
|
+
const curved = L / (4 - 3 * L)
|
|
34
|
+
let shaped = L * (1 - curve) + curved * curve
|
|
35
|
+
shaped = minBrightness + shaped * (1 - minBrightness)
|
|
36
|
+
return Math.min(1, Math.max(0, shaped ** gamma))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Same cap as block shaders: max(block, min(sky, skyLevel)). */
|
|
40
|
+
export function combinedBlockLight (block: number, sky: number, skyLevel: number): number {
|
|
41
|
+
return Math.max(block, Math.min(sky, skyLevel))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 0..1 brightness for MeshBasicMaterial.color.setScalar on block-entity overlays. */
|
|
45
|
+
export function blockEntityBrightness (
|
|
46
|
+
blockNorm: number,
|
|
47
|
+
skyNorm: number,
|
|
48
|
+
skyLevel: number,
|
|
49
|
+
lightmapParams: BlockLightmapParams = DEFAULT_LIGHTMAP_PARAMS,
|
|
50
|
+
): number {
|
|
51
|
+
const L = combinedBlockLight(blockNorm, skyNorm, skyLevel)
|
|
52
|
+
return applyLightmap(L, lightmapParams)
|
|
53
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
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 * as worldRendererModule from './worldrendererCommon'
|
|
7
|
+
import { WorldRendererCommon } from './worldrendererCommon'
|
|
8
|
+
import { defaultWorldRendererConfig } from '../graphicsBackend/config'
|
|
9
|
+
import { getInitialPlayerState } from '../playerState/playerState'
|
|
10
|
+
import type { DisplayWorldOptions, GraphicsInitOptions } from '../graphicsBackend/types'
|
|
11
|
+
|
|
12
|
+
vi.mock('./ui/newStats', () => ({
|
|
13
|
+
addNewStat: vi.fn(() => ({ updateText: vi.fn(), setVisibility: vi.fn() })),
|
|
14
|
+
updateStatText: vi.fn(),
|
|
15
|
+
removeAllStats: vi.fn(),
|
|
16
|
+
updatePanesVisibility: vi.fn(),
|
|
17
|
+
MC_RENDERER_DEBUG_OVERLAY_CLASS: 'mc-renderer-debug-overlay',
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
vi.mock('./utils/skins', () => ({
|
|
21
|
+
setSkinsConfig: vi.fn(),
|
|
22
|
+
steveTexture: {},
|
|
23
|
+
stevePngUrl: '',
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
function ensurePromiseWithResolvers() {
|
|
27
|
+
if (!Promise.withResolvers) {
|
|
28
|
+
Promise.withResolvers = function <T>() {
|
|
29
|
+
let resolve!: (value: T | PromiseLike<T>) => void
|
|
30
|
+
let reject!: (reason?: unknown) => void
|
|
31
|
+
const promise = new Promise<T>((res, rej) => {
|
|
32
|
+
resolve = res
|
|
33
|
+
reject = rej
|
|
34
|
+
})
|
|
35
|
+
return { promise, resolve, reject }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class TestWorldRenderer extends WorldRendererCommon {
|
|
41
|
+
outputFormat = 'threeJs' as const
|
|
42
|
+
|
|
43
|
+
changeBackgroundColor() {}
|
|
44
|
+
changeCardinalLight() {}
|
|
45
|
+
handleWorkerMessage() {}
|
|
46
|
+
updateCamera() {}
|
|
47
|
+
render() {}
|
|
48
|
+
updateShowChunksBorder() {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createRenderer(workerCount = 2, worldView?: DisplayWorldOptions['worldView']) {
|
|
52
|
+
const reloadLoadedChunks = vi.fn(async () => {})
|
|
53
|
+
const rendererState = proxy({
|
|
54
|
+
world: {
|
|
55
|
+
chunksLoaded: {} as Record<string, true>,
|
|
56
|
+
heightmaps: {} as Record<string, Int16Array>,
|
|
57
|
+
allChunksLoaded: false,
|
|
58
|
+
mesherWork: false,
|
|
59
|
+
instabilityFactors: {},
|
|
60
|
+
intersectMedia: null,
|
|
61
|
+
},
|
|
62
|
+
renderer: '',
|
|
63
|
+
preventEscapeMenu: false,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const displayOptions: DisplayWorldOptions = {
|
|
67
|
+
version: '1.21.1',
|
|
68
|
+
worldView: (worldView ?? Object.assign(new EventEmitter(), { reloadLoadedChunks })) as DisplayWorldOptions['worldView'],
|
|
69
|
+
inWorldRenderingConfig: proxy({ ...defaultWorldRendererConfig, mesherWorkers: workerCount }),
|
|
70
|
+
playerStateReactive: getInitialPlayerState(),
|
|
71
|
+
rendererState,
|
|
72
|
+
nonReactiveState: {
|
|
73
|
+
fps: 0,
|
|
74
|
+
worstRenderTime: 0,
|
|
75
|
+
avgRenderTime: 0,
|
|
76
|
+
world: {
|
|
77
|
+
chunksLoadedCount: 0,
|
|
78
|
+
chunksTotalNumber: 0,
|
|
79
|
+
chunksFullInfo: '',
|
|
80
|
+
},
|
|
81
|
+
renderer: {
|
|
82
|
+
timeline: { live: [], frozen: [], lastSecond: [] },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
resourcesManager: {
|
|
86
|
+
currentResources: {
|
|
87
|
+
mcData: { version: {} },
|
|
88
|
+
blocksAtlasJson: {},
|
|
89
|
+
blockstatesModels: {},
|
|
90
|
+
},
|
|
91
|
+
} as DisplayWorldOptions['resourcesManager'],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const initOptions: GraphicsInitOptions = {
|
|
95
|
+
config: { sceneBackground: '#000' },
|
|
96
|
+
rendererSpecificSettings: {},
|
|
97
|
+
callbacks: {
|
|
98
|
+
displayCriticalError: vi.fn(),
|
|
99
|
+
setRendererSpecificSettings: vi.fn(),
|
|
100
|
+
fireCustomEvent: vi.fn(),
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const renderer = new TestWorldRenderer(displayOptions.resourcesManager, displayOptions, initOptions)
|
|
105
|
+
renderer.active = true
|
|
106
|
+
renderer.workers = Array.from({ length: workerCount }, () => ({
|
|
107
|
+
postMessage: vi.fn(),
|
|
108
|
+
terminate: vi.fn(),
|
|
109
|
+
}))
|
|
110
|
+
renderer['syncMesherPoolSnapshot']()
|
|
111
|
+
renderer.viewDistance = 8
|
|
112
|
+
renderer.viewerChunkPosition = new Vec3(0, 64, 0)
|
|
113
|
+
renderer.worldSizeParams = { minY: 0, worldHeight: 256 }
|
|
114
|
+
return { renderer, reloadLoadedChunks }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
describe('WorldRendererCommon.reconfigureMesherWorkers', () => {
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
ensurePromiseWithResolvers()
|
|
120
|
+
vi.stubGlobal('location', { href: 'http://localhost/' })
|
|
121
|
+
vi.stubGlobal('Worker', class MockWorker {
|
|
122
|
+
postMessage = vi.fn()
|
|
123
|
+
terminate = vi.fn()
|
|
124
|
+
addEventListener = vi.fn()
|
|
125
|
+
onmessage: ((event: MessageEvent) => void) | null = null
|
|
126
|
+
})
|
|
127
|
+
vi.spyOn(worldRendererModule, 'meshersSendMcData').mockImplementation(() => {})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
afterEach(() => {
|
|
131
|
+
vi.restoreAllMocks()
|
|
132
|
+
vi.unstubAllGlobals()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('recreates workers with new count and reloads chunks', async () => {
|
|
136
|
+
const { renderer, reloadLoadedChunks } = createRenderer(3)
|
|
137
|
+
const terminated = renderer.workers.map((worker) => worker.terminate)
|
|
138
|
+
renderer.worldRendererConfig.mesherWorkers = 1
|
|
139
|
+
|
|
140
|
+
await renderer.reconfigureMesherWorkers()
|
|
141
|
+
|
|
142
|
+
for (const terminate of terminated) {
|
|
143
|
+
expect(terminate).toHaveBeenCalled()
|
|
144
|
+
}
|
|
145
|
+
expect(renderer.workers).toHaveLength(1)
|
|
146
|
+
expect(reloadLoadedChunks).toHaveBeenCalledTimes(1)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('recreates workers when mesher pipeline changes', async () => {
|
|
150
|
+
const { renderer, reloadLoadedChunks } = createRenderer(2)
|
|
151
|
+
const terminated = renderer.workers.map((worker) => worker.terminate)
|
|
152
|
+
renderer.worldRendererConfig.wasmMesher = false
|
|
153
|
+
|
|
154
|
+
await renderer.reconfigureMesherWorkers()
|
|
155
|
+
|
|
156
|
+
for (const terminate of terminated) {
|
|
157
|
+
expect(terminate).toHaveBeenCalled()
|
|
158
|
+
}
|
|
159
|
+
expect(renderer.workers).toHaveLength(2)
|
|
160
|
+
expect(reloadLoadedChunks).toHaveBeenCalledTimes(1)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('watchMesherPoolConfig registers valtio unsubscribe functions', () => {
|
|
164
|
+
const { renderer } = createRenderer(2)
|
|
165
|
+
|
|
166
|
+
renderer['watchMesherPoolConfig']()
|
|
167
|
+
expect(renderer['valtioUnsubs']).toHaveLength(3)
|
|
168
|
+
for (const unsub of renderer['valtioUnsubs']) {
|
|
169
|
+
expect(typeof unsub).toBe('function')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
expect(() => {
|
|
173
|
+
for (const unsub of renderer['valtioUnsubs']) unsub()
|
|
174
|
+
renderer.destroy()
|
|
175
|
+
}).not.toThrow()
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('config watcher enqueues reconfigure when mesherWorkers changes', async () => {
|
|
179
|
+
const enqueueSpy = vi.spyOn(TestWorldRenderer.prototype as any, 'enqueueMesherWorkersReconfigure')
|
|
180
|
+
const { renderer } = createRenderer(2)
|
|
181
|
+
|
|
182
|
+
renderer['watchMesherPoolConfig']()
|
|
183
|
+
renderer.worldRendererConfig.mesherWorkers = 4
|
|
184
|
+
|
|
185
|
+
await vi.waitFor(() => {
|
|
186
|
+
expect(enqueueSpy).toHaveBeenCalled()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
enqueueSpy.mockRestore()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('skips tail work when destroyed during bootstrap', async () => {
|
|
193
|
+
const { renderer, reloadLoadedChunks } = createRenderer(2)
|
|
194
|
+
vi.spyOn(renderer, 'updateAssetsData').mockImplementation(async () => {
|
|
195
|
+
renderer.active = false
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
await renderer.reconfigureMesherWorkers()
|
|
199
|
+
|
|
200
|
+
expect(reloadLoadedChunks).not.toHaveBeenCalled()
|
|
201
|
+
})
|
|
202
|
+
})
|