minecraft-renderer 0.1.45 → 0.1.47
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 +22 -0
- package/dist/mesher.js +17 -17
- package/dist/mesher.js.map +4 -4
- package/dist/mesherWasm.js +18 -18
- package/dist/minecraft-renderer.js +54 -54
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +389 -389
- package/package.json +1 -1
- package/src/graphicsBackend/appViewer.ts +10 -0
- package/src/graphicsBackend/config.ts +2 -1
- package/src/graphicsBackend/index.ts +2 -0
- package/src/graphicsBackend/rendererOptionsSync.ts +243 -0
- package/src/graphicsBackend/types.ts +9 -1
- package/src/mesher-shared/blockPropertiesForMeshing.ts +79 -0
- package/src/mesher-shared/exportedGeometryTypes.ts +26 -0
- package/src/mesher-shared/models.ts +3 -79
- package/src/performanceMonitor/PerformanceMonitor.ts +77 -0
- package/src/performanceMonitor/constants.ts +24 -0
- package/src/performanceMonitor/formatPerformanceFactorsDebug.ts +16 -0
- package/src/performanceMonitor/index.ts +10 -0
- package/src/performanceMonitor/types.ts +27 -0
- package/src/three/documentRenderer.ts +5 -9
- package/src/three/graphicsBackendBase.ts +1 -2
- package/src/three/menuBackground/defaultOptions.ts +188 -30
- package/src/three/menuBackground/index.ts +10 -2
- package/src/three/worldGeometryExport.ts +3 -24
- package/src/three/worldRendererThree.ts +19 -1
- package/src/wasm-mesher/bridge/render-from-wasm.ts +5 -10
package/package.json
CHANGED
|
@@ -28,6 +28,7 @@ import { PlayerStateReactive } from '../playerState/playerState'
|
|
|
28
28
|
import { ResourcesManager, ResourcesManagerTransferred } from '../resourcesManager'
|
|
29
29
|
import { preloadMesherWorkerScript } from './preloadWorkers'
|
|
30
30
|
import type { MenuBackgroundOptions } from '../three/menuBackground/types'
|
|
31
|
+
import type { RendererStorageOptions } from '../three/menuBackground/defaultOptions'
|
|
31
32
|
|
|
32
33
|
export interface AppViewerOptions {
|
|
33
34
|
config?: Partial<GraphicsBackendConfig>
|
|
@@ -81,6 +82,9 @@ export class AppViewer {
|
|
|
81
82
|
// Timing
|
|
82
83
|
lastCamUpdate = 0
|
|
83
84
|
|
|
85
|
+
/** Bound by `subscribeRendererOptions` / `bindRendererOptions` — source of truth for renderer-owned settings. */
|
|
86
|
+
private getRendererOptions?: () => RendererStorageOptions
|
|
87
|
+
|
|
84
88
|
constructor(options: AppViewerOptions = {}, public resourcesManager: ResourcesManager = new ResourcesManager()) {
|
|
85
89
|
this.config = {
|
|
86
90
|
...defaultGraphicsBackendConfig,
|
|
@@ -117,6 +121,11 @@ export class AppViewer {
|
|
|
117
121
|
return preloadMesherWorkerScript({ script })
|
|
118
122
|
}
|
|
119
123
|
|
|
124
|
+
/** Wire app options storage (valtio proxy) for backend init (WebGL gpuPreference, etc.). */
|
|
125
|
+
bindRendererOptions(getOptions: () => RendererStorageOptions): void {
|
|
126
|
+
this.getRendererOptions = getOptions
|
|
127
|
+
}
|
|
128
|
+
|
|
120
129
|
/**
|
|
121
130
|
* Load a graphics backend.
|
|
122
131
|
*/
|
|
@@ -132,6 +141,7 @@ export class AppViewer {
|
|
|
132
141
|
|
|
133
142
|
const loaderOptions: GraphicsInitOptions = {
|
|
134
143
|
config: this.config,
|
|
144
|
+
getRendererOptions: this.getRendererOptions,
|
|
135
145
|
callbacks: {
|
|
136
146
|
displayCriticalError: (error) => {
|
|
137
147
|
console.error('[AppViewer] Critical error:', error)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { proxy } from 'valtio'
|
|
7
|
+
import { defaultPerformanceInstabilityFactors } from '../performanceMonitor'
|
|
7
8
|
import type {
|
|
8
9
|
GraphicsBackendConfig,
|
|
9
10
|
RendererReactiveState,
|
|
@@ -88,7 +89,6 @@ export type WorldRendererConfig = typeof defaultWorldRendererConfig
|
|
|
88
89
|
*/
|
|
89
90
|
export const defaultGraphicsBackendConfig: GraphicsBackendConfig = {
|
|
90
91
|
fpsLimit: undefined,
|
|
91
|
-
powerPreference: undefined,
|
|
92
92
|
sceneBackground: 'lightblue',
|
|
93
93
|
timeoutRendering: false
|
|
94
94
|
}
|
|
@@ -117,6 +117,7 @@ export const getDefaultRendererState = (): {
|
|
|
117
117
|
heightmaps: new Map<string, Int16Array>(),
|
|
118
118
|
allChunksLoaded: false,
|
|
119
119
|
mesherWork: false,
|
|
120
|
+
instabilityFactors: defaultPerformanceInstabilityFactors(),
|
|
120
121
|
intersectMedia: null
|
|
121
122
|
},
|
|
122
123
|
renderer: '...',
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Maps app options storage → AppViewer runtime (in-world config, graphics config, menu background).
|
|
4
|
+
* Call `subscribeRendererOptions` once after viewer init; keep volume sync in the app.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { subscribe } from 'valtio/vanilla'
|
|
8
|
+
import type { AppViewer } from './appViewer'
|
|
9
|
+
import type { RendererStorageOptions } from '../three/menuBackground/defaultOptions'
|
|
10
|
+
import type { MenuBackgroundOptions } from '../three/menuBackground/types'
|
|
11
|
+
import type { MenuBackgroundRenderer } from '../three/menuBackground/renderer'
|
|
12
|
+
import { menuBackgroundSpeedToMultiplier } from '../three/menuBackground/config'
|
|
13
|
+
import type { FuturisticCameraId, FuturisticSceneId, MinecraftBlockGroupId } from '../three/menuBackground/futuristic'
|
|
14
|
+
import { setSkinsConfig } from '../lib/utils/skins'
|
|
15
|
+
|
|
16
|
+
export type { RendererStorageOptions } from '../three/menuBackground/defaultOptions'
|
|
17
|
+
|
|
18
|
+
export interface ApplyRendererOptionsContext {
|
|
19
|
+
isSafari?: boolean
|
|
20
|
+
isCypress?: boolean
|
|
21
|
+
windowFocused?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RendererOptionsSubscribeHooks {
|
|
25
|
+
isSafari?: boolean
|
|
26
|
+
isCypress?: boolean
|
|
27
|
+
getWindowFocused?: () => boolean
|
|
28
|
+
onRegisterFocusHandlers?: (handlers: { onFocus: () => void, onBlur: () => void }) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RendererWorldViewLike {
|
|
32
|
+
keepChunksDistance: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function menuBackgroundOptionsFromStorage(o: Pick<
|
|
36
|
+
RendererStorageOptions,
|
|
37
|
+
| 'menuBackgroundMode'
|
|
38
|
+
| 'menuBackgroundMinecraftTextures'
|
|
39
|
+
| 'menuBackgroundFuturisticScene'
|
|
40
|
+
| 'menuBackgroundFuturisticCamera'
|
|
41
|
+
| 'menuBackgroundFuturisticBlockGroup'
|
|
42
|
+
| 'menuBackgroundFuturisticCameraSpeed'
|
|
43
|
+
| 'menuBackgroundFuturisticBlockSpeed'
|
|
44
|
+
>): MenuBackgroundOptions {
|
|
45
|
+
return {
|
|
46
|
+
mode: o.menuBackgroundMode as MenuBackgroundOptions['mode'],
|
|
47
|
+
useMinecraftTextures: o.menuBackgroundMinecraftTextures,
|
|
48
|
+
futuristicScene: o.menuBackgroundFuturisticScene as FuturisticSceneId,
|
|
49
|
+
futuristicCamera: o.menuBackgroundFuturisticCamera as FuturisticCameraId,
|
|
50
|
+
futuristicBlockGroup: o.menuBackgroundFuturisticBlockGroup as MinecraftBlockGroupId,
|
|
51
|
+
futuristicCameraSpeed: menuBackgroundSpeedToMultiplier(o.menuBackgroundFuturisticCameraSpeed),
|
|
52
|
+
futuristicBlockSpeed: menuBackgroundSpeedToMultiplier(o.menuBackgroundFuturisticBlockSpeed),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function applyMenuBackgroundLiveOptions(
|
|
57
|
+
menu: MenuBackgroundRenderer,
|
|
58
|
+
o: Pick<
|
|
59
|
+
RendererStorageOptions,
|
|
60
|
+
| 'menuBackgroundFuturisticScene'
|
|
61
|
+
| 'menuBackgroundFuturisticCamera'
|
|
62
|
+
| 'menuBackgroundFuturisticBlockGroup'
|
|
63
|
+
| 'menuBackgroundFuturisticCameraSpeed'
|
|
64
|
+
| 'menuBackgroundFuturisticBlockSpeed'
|
|
65
|
+
>
|
|
66
|
+
): void {
|
|
67
|
+
const futuristic = menu.futuristic
|
|
68
|
+
if (!futuristic) return
|
|
69
|
+
futuristic.setScene?.(o.menuBackgroundFuturisticScene)
|
|
70
|
+
futuristic.setCamera?.(o.menuBackgroundFuturisticCamera)
|
|
71
|
+
void futuristic.setBlockGroup?.(o.menuBackgroundFuturisticBlockGroup)
|
|
72
|
+
futuristic.setCameraSpeed?.(menuBackgroundSpeedToMultiplier(o.menuBackgroundFuturisticCameraSpeed))
|
|
73
|
+
futuristic.setBlockSpeed?.(menuBackgroundSpeedToMultiplier(o.menuBackgroundFuturisticBlockSpeed))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveWasmMesherActive(o: RendererStorageOptions): boolean {
|
|
77
|
+
return o.rendererMesher !== 'legacy-js'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function applyMesherWorkersPreset(
|
|
81
|
+
appViewer: AppViewer,
|
|
82
|
+
o: RendererStorageOptions,
|
|
83
|
+
wasmActive: boolean
|
|
84
|
+
): void {
|
|
85
|
+
const cfg = appViewer.inWorldRenderingConfig
|
|
86
|
+
const override = o.rendererMeshersCountOverride
|
|
87
|
+
const applyMesherWorkers = (workers: number) => {
|
|
88
|
+
cfg.mesherWorkers = override ?? workers
|
|
89
|
+
}
|
|
90
|
+
switch (o.rendererWorldPerformance) {
|
|
91
|
+
case 'low-energy':
|
|
92
|
+
applyMesherWorkers(1)
|
|
93
|
+
cfg.dedicatedChangeWorker = false
|
|
94
|
+
break
|
|
95
|
+
case 'normal':
|
|
96
|
+
applyMesherWorkers(2)
|
|
97
|
+
cfg.dedicatedChangeWorker = !wasmActive
|
|
98
|
+
break
|
|
99
|
+
case 'maximum':
|
|
100
|
+
applyMesherWorkers(Math.max(3, Math.min(navigator.hardwareConcurrency ?? 0, 8)))
|
|
101
|
+
cfg.dedicatedChangeWorker = !wasmActive
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function applyFpsLimit(
|
|
107
|
+
appViewer: AppViewer,
|
|
108
|
+
o: RendererStorageOptions,
|
|
109
|
+
windowFocused: boolean
|
|
110
|
+
): void {
|
|
111
|
+
const backgroundFpsLimit = o.backgroundRendering
|
|
112
|
+
const normalFpsLimit = o.frameLimit
|
|
113
|
+
|
|
114
|
+
if (windowFocused) {
|
|
115
|
+
appViewer.config.fpsLimit = normalFpsLimit || undefined
|
|
116
|
+
} else if (backgroundFpsLimit === '5fps') {
|
|
117
|
+
appViewer.config.fpsLimit = 5
|
|
118
|
+
} else if (backgroundFpsLimit === '20fps') {
|
|
119
|
+
appViewer.config.fpsLimit = 20
|
|
120
|
+
} else {
|
|
121
|
+
appViewer.config.fpsLimit = undefined
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function applyStatsVisible(
|
|
126
|
+
appViewer: AppViewer,
|
|
127
|
+
o: RendererStorageOptions,
|
|
128
|
+
ctx: ApplyRendererOptionsContext
|
|
129
|
+
): void {
|
|
130
|
+
const { renderDebug } = o
|
|
131
|
+
if (renderDebug === 'none' || ctx.isCypress) {
|
|
132
|
+
appViewer.config.statsVisible = 0
|
|
133
|
+
} else if (renderDebug === 'basic') {
|
|
134
|
+
appViewer.config.statsVisible = 1
|
|
135
|
+
} else if (renderDebug === 'advanced') {
|
|
136
|
+
appViewer.config.statsVisible = 2
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ensure no object assigns to the config
|
|
141
|
+
export function applyRendererOptions(
|
|
142
|
+
appViewer: AppViewer,
|
|
143
|
+
o: RendererStorageOptions,
|
|
144
|
+
ctx: ApplyRendererOptionsContext = {}
|
|
145
|
+
): void {
|
|
146
|
+
const cfg = appViewer.inWorldRenderingConfig
|
|
147
|
+
const wasmActive = resolveWasmMesherActive(o)
|
|
148
|
+
|
|
149
|
+
cfg.showChunkBorders = o.showChunkBorders
|
|
150
|
+
cfg.futuristicReveal = o.rendererFuturisticReveal
|
|
151
|
+
applyMesherWorkersPreset(appViewer, o, wasmActive)
|
|
152
|
+
cfg.renderEntities = o.renderEntities
|
|
153
|
+
applyStatsVisible(appViewer, o, ctx)
|
|
154
|
+
applyFpsLimit(appViewer, o, ctx.windowFocused !== false)
|
|
155
|
+
|
|
156
|
+
cfg.vrSupport = o.vrSupport
|
|
157
|
+
cfg.vrPageGameRendering = o.vrPageGameRendering
|
|
158
|
+
cfg.enableDebugOverlay = o.rendererPerfDebugOverlay
|
|
159
|
+
|
|
160
|
+
cfg.clipWorldBelowY = o.clipWorldBelowY
|
|
161
|
+
cfg.extraBlockRenderers = !o.disableBlockEntityTextures
|
|
162
|
+
cfg.fetchPlayerSkins = o.loadPlayerSkins
|
|
163
|
+
cfg.highlightBlockColor = o.highlightBlockColor
|
|
164
|
+
cfg.wasmMesher = wasmActive
|
|
165
|
+
cfg.disableMesherConversionCache = !!ctx.isSafari
|
|
166
|
+
|
|
167
|
+
setSkinsConfig({ apiEnabled: o.loadPlayerSkins })
|
|
168
|
+
|
|
169
|
+
cfg.smoothLighting = o.smoothLighting
|
|
170
|
+
cfg.shadingTheme = o.vanillaLook ? 'vanilla' : 'high-contrast'
|
|
171
|
+
cfg.starfield = o.starfieldRendering
|
|
172
|
+
cfg.defaultSkybox = o.defaultSkybox
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** World-view + hand/camera options (call when WorldView is ready). */
|
|
176
|
+
export function applyRendererWorldViewOptions(
|
|
177
|
+
appViewer: AppViewer,
|
|
178
|
+
worldView: RendererWorldViewLike,
|
|
179
|
+
o: Pick<
|
|
180
|
+
RendererStorageOptions,
|
|
181
|
+
'keepChunksDistance' | 'renderEars' | 'showHand' | 'viewBobbing' | 'dayCycleAndLighting'
|
|
182
|
+
>
|
|
183
|
+
): void {
|
|
184
|
+
worldView.keepChunksDistance = o.keepChunksDistance
|
|
185
|
+
const cfg = appViewer.inWorldRenderingConfig
|
|
186
|
+
cfg.renderEars = o.renderEars
|
|
187
|
+
cfg.showHand = o.showHand
|
|
188
|
+
cfg.viewBobbing = o.viewBobbing
|
|
189
|
+
cfg.dayCycle = o.dayCycleAndLighting
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Subscribe to options changes and sync renderer runtime.
|
|
194
|
+
* Returns unsubscribe. Volume is intentionally excluded — wire it in the app.
|
|
195
|
+
*/
|
|
196
|
+
export function subscribeRendererOptions<T extends RendererStorageOptions>(
|
|
197
|
+
appViewer: AppViewer,
|
|
198
|
+
optionsProxy: T,
|
|
199
|
+
hooks: RendererOptionsSubscribeHooks = {}
|
|
200
|
+
): () => void {
|
|
201
|
+
appViewer.bindRendererOptions(() => optionsProxy as RendererStorageOptions)
|
|
202
|
+
|
|
203
|
+
let windowFocused = hooks.getWindowFocused?.() ?? true
|
|
204
|
+
|
|
205
|
+
const run = () => {
|
|
206
|
+
const snapshot = optionsProxy as RendererStorageOptions
|
|
207
|
+
applyRendererOptions(appViewer, snapshot, {
|
|
208
|
+
isSafari: hooks.isSafari,
|
|
209
|
+
isCypress: hooks.isCypress,
|
|
210
|
+
windowFocused,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
if (appViewer.currentDisplay === 'menu') {
|
|
214
|
+
const menu = appViewer.backend?.getMenuBackground?.()
|
|
215
|
+
if (menu) applyMenuBackgroundLiveOptions(menu, snapshot)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
run()
|
|
220
|
+
|
|
221
|
+
hooks.onRegisterFocusHandlers?.({
|
|
222
|
+
onFocus: () => {
|
|
223
|
+
windowFocused = true
|
|
224
|
+
run()
|
|
225
|
+
},
|
|
226
|
+
onBlur: () => {
|
|
227
|
+
windowFocused = false
|
|
228
|
+
run()
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
return subscribe(optionsProxy, run)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Call when mineflayer bot is created (lighting depends on protocol features). */
|
|
236
|
+
export function applyRendererEnableLighting(
|
|
237
|
+
appViewer: AppViewer,
|
|
238
|
+
newVersionsLighting: boolean,
|
|
239
|
+
blockStateIdSupported: boolean
|
|
240
|
+
): void {
|
|
241
|
+
appViewer.inWorldRenderingConfig.enableLighting =
|
|
242
|
+
!blockStateIdSupported || newVersionsLighting
|
|
243
|
+
}
|
|
@@ -23,11 +23,13 @@ export interface SoundSystem {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
import type { MenuBackgroundOptions } from '../three/menuBackground/types'
|
|
26
|
+
import type { RendererStorageOptions } from '../three/menuBackground/defaultOptions'
|
|
27
|
+
import type { MenuBackgroundRenderer } from '../three/menuBackground/renderer'
|
|
28
|
+
import type { PerformanceInstabilityFactors } from '../performanceMonitor'
|
|
26
29
|
|
|
27
30
|
/** Graphics backend configuration */
|
|
28
31
|
export interface GraphicsBackendConfig {
|
|
29
32
|
fpsLimit?: number
|
|
30
|
-
powerPreference?: 'high-performance' | 'low-power'
|
|
31
33
|
statsVisible?: number
|
|
32
34
|
sceneBackground: string
|
|
33
35
|
timeoutRendering?: boolean
|
|
@@ -76,6 +78,8 @@ export interface RendererReactiveState {
|
|
|
76
78
|
heightmaps: Map<string, Int16Array>
|
|
77
79
|
allChunksLoaded: boolean
|
|
78
80
|
mesherWork: boolean
|
|
81
|
+
/** Low-FPS / render instability factors (see `performanceMonitor`). */
|
|
82
|
+
instabilityFactors: PerformanceInstabilityFactors
|
|
79
83
|
intersectMedia: any | null
|
|
80
84
|
}
|
|
81
85
|
renderer: string
|
|
@@ -93,6 +97,8 @@ export interface RendererReactiveState {
|
|
|
93
97
|
/** Graphics initialization options */
|
|
94
98
|
export interface GraphicsInitOptions<S = any> {
|
|
95
99
|
config: GraphicsBackendConfig
|
|
100
|
+
/** Live app options (e.g. valtio proxy); used for WebGL `gpuPreference` at context creation. */
|
|
101
|
+
getRendererOptions?: () => RendererStorageOptions
|
|
96
102
|
rendererSpecificSettings: S
|
|
97
103
|
callbacks: {
|
|
98
104
|
displayCriticalError: (error: Error) => void
|
|
@@ -124,6 +130,8 @@ export interface GraphicsBackend {
|
|
|
124
130
|
soundSystem?: any
|
|
125
131
|
backendMethods?: any
|
|
126
132
|
getDebugOverlay?(): { entitiesString?: string, left?: Record<string, string>, right?: Record<string, string> }
|
|
133
|
+
/** Active main-menu background, when `currentDisplay === 'menu'`. */
|
|
134
|
+
getMenuBackground?(): MenuBackgroundRenderer | undefined
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
/** Graphics backend loader function type */
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { Vec3 } from 'vec3'
|
|
3
|
+
import type { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
4
|
+
import legacyJson from '../lib/preflatMap.json'
|
|
5
|
+
import type { World, WorldBlock as Block } from './world'
|
|
6
|
+
|
|
7
|
+
const calculatedBlocksEntries = Object.entries(legacyJson.clientCalculatedBlocks)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Block name + properties for model lookup. Only runs neighbor/preflat work when
|
|
11
|
+
* `world.preflat` (legacy); modern block-state worlds use `fromStateId` only.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveBlockPropertiesForMeshing(
|
|
14
|
+
world: World | undefined,
|
|
15
|
+
cursor: Vec3,
|
|
16
|
+
blockProvider: WorldBlockProvider,
|
|
17
|
+
blockStateId: number,
|
|
18
|
+
PrismarineBlockCtor: { fromStateId: (id: number, biome: number) => Block }
|
|
19
|
+
): { name: string, properties: Record<string, unknown> } {
|
|
20
|
+
if (world?.preflat) {
|
|
21
|
+
const block = world.getBlock(cursor, blockProvider, {})
|
|
22
|
+
if (block) {
|
|
23
|
+
let properties: Record<string, unknown> = { ...block.getProperties() }
|
|
24
|
+
const patch = preflatBlockCalculation(block, world, cursor)
|
|
25
|
+
if (patch) properties = { ...properties, ...patch }
|
|
26
|
+
return { name: block.name, properties }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const fromState = PrismarineBlockCtor.fromStateId(blockStateId, 1)
|
|
30
|
+
return { name: fromState.name, properties: fromState.getProperties() }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function preflatBlockCalculation(block: Block, world: World, position: Vec3) {
|
|
34
|
+
const type = calculatedBlocksEntries.find(([name, blocks]) => blocks.includes(block.name))?.[0]
|
|
35
|
+
if (!type) return
|
|
36
|
+
switch (type) {
|
|
37
|
+
case 'directional': {
|
|
38
|
+
const isSolidConnection = !block.name.includes('redstone') && !block.name.includes('tripwire')
|
|
39
|
+
const neighbors = [
|
|
40
|
+
world.getBlock(position.offset(0, 0, 1)),
|
|
41
|
+
world.getBlock(position.offset(0, 0, -1)),
|
|
42
|
+
world.getBlock(position.offset(1, 0, 0)),
|
|
43
|
+
world.getBlock(position.offset(-1, 0, 0))
|
|
44
|
+
]
|
|
45
|
+
const props = {}
|
|
46
|
+
let changed = false
|
|
47
|
+
for (const [i, neighbor] of neighbors.entries()) {
|
|
48
|
+
const isConnectedToSolid = isSolidConnection ? (neighbor && !neighbor.transparent) : false
|
|
49
|
+
if (isConnectedToSolid || neighbor?.name === block.name) {
|
|
50
|
+
props[['south', 'north', 'east', 'west'][i]] = 'true'
|
|
51
|
+
changed = true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return changed ? props : undefined
|
|
55
|
+
}
|
|
56
|
+
case 'block_snowy': {
|
|
57
|
+
const aboveIsSnow = world.getBlock(position.offset(0, 1, 0))?.name === 'snow'
|
|
58
|
+
if (aboveIsSnow) {
|
|
59
|
+
return {
|
|
60
|
+
snowy: `${aboveIsSnow}`
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
case 'door': {
|
|
67
|
+
const { half } = block.getProperties()
|
|
68
|
+
if (half === 'upper') {
|
|
69
|
+
const lower = world.getBlock(position.offset(0, -1, 0))
|
|
70
|
+
if (lower?.name === block.name) {
|
|
71
|
+
return {
|
|
72
|
+
...lower.getProperties(),
|
|
73
|
+
half: 'upper'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/** Shared geometry export shapes (worker bridge + main-thread viewer). */
|
|
3
|
+
|
|
4
|
+
export interface ExportedSection {
|
|
5
|
+
key: string
|
|
6
|
+
position: { x: number, y: number, z: number }
|
|
7
|
+
geometry: {
|
|
8
|
+
positions: number[]
|
|
9
|
+
normals: number[]
|
|
10
|
+
colors: number[]
|
|
11
|
+
uvs: number[]
|
|
12
|
+
indices: number[]
|
|
13
|
+
}
|
|
14
|
+
shaderCubes?: unknown
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ExportedWorldGeometry {
|
|
18
|
+
version: string
|
|
19
|
+
exportedAt: string
|
|
20
|
+
camera: {
|
|
21
|
+
position: { x: number, y: number, z: number }
|
|
22
|
+
rotation: { pitch: number, yaw: number }
|
|
23
|
+
}
|
|
24
|
+
sections: ExportedSection[]
|
|
25
|
+
textureAtlasDataUrl?: string
|
|
26
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { Vec3 } from 'vec3'
|
|
3
3
|
import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
4
4
|
import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
|
|
5
|
-
import legacyJson from '../lib/preflatMap.json'
|
|
6
5
|
import { BlockType } from '../playground/shared'
|
|
7
6
|
import { World, BlockModelPartsResolved, WorldBlock as Block, WorldBlock, worldColumnKey } from './world'
|
|
8
7
|
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
@@ -10,6 +9,9 @@ import { getSideShading, vertexLightFromAo } from './vertexShading'
|
|
|
10
9
|
import { INVISIBLE_BLOCKS } from './worldConstants'
|
|
11
10
|
import { MesherGeometryOutput, HighestBlockInfo } from './shared'
|
|
12
11
|
import { collectBlockEntityMetadata } from './blockEntityMetadata'
|
|
12
|
+
import { preflatBlockCalculation, resolveBlockPropertiesForMeshing } from './blockPropertiesForMeshing'
|
|
13
|
+
|
|
14
|
+
export { preflatBlockCalculation, resolveBlockPropertiesForMeshing } from './blockPropertiesForMeshing'
|
|
13
15
|
|
|
14
16
|
// Log function disabled by default for zero overhead in production hot loops
|
|
15
17
|
const ENABLE_TS_LOGS = false
|
|
@@ -51,84 +53,6 @@ function prepareTints(tints) {
|
|
|
51
53
|
})
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
const calculatedBlocksEntries = Object.entries(legacyJson.clientCalculatedBlocks)
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Block name + properties for model lookup. Only runs neighbor/preflat work when
|
|
58
|
-
* `world.preflat` (legacy); modern block-state worlds use `fromStateId` only.
|
|
59
|
-
*/
|
|
60
|
-
export function resolveBlockPropertiesForMeshing(
|
|
61
|
-
world: World | undefined,
|
|
62
|
-
cursor: Vec3,
|
|
63
|
-
blockProvider: WorldBlockProvider,
|
|
64
|
-
blockStateId: number,
|
|
65
|
-
PrismarineBlockCtor: { fromStateId: (id: number, biome: number) => Block }
|
|
66
|
-
): { name: string, properties: Record<string, unknown> } {
|
|
67
|
-
if (world?.preflat) {
|
|
68
|
-
const block = world.getBlock(cursor, blockProvider, {})
|
|
69
|
-
if (block) {
|
|
70
|
-
let properties: Record<string, unknown> = { ...block.getProperties() }
|
|
71
|
-
const patch = preflatBlockCalculation(block, world, cursor)
|
|
72
|
-
if (patch) properties = { ...properties, ...patch }
|
|
73
|
-
return { name: block.name, properties }
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
const fromState = PrismarineBlockCtor.fromStateId(blockStateId, 1)
|
|
77
|
-
return { name: fromState.name, properties: fromState.getProperties() }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function preflatBlockCalculation(block: Block, world: World, position: Vec3) {
|
|
81
|
-
const type = calculatedBlocksEntries.find(([name, blocks]) => blocks.includes(block.name))?.[0]
|
|
82
|
-
if (!type) return
|
|
83
|
-
switch (type) {
|
|
84
|
-
case 'directional': {
|
|
85
|
-
const isSolidConnection = !block.name.includes('redstone') && !block.name.includes('tripwire')
|
|
86
|
-
const neighbors = [
|
|
87
|
-
world.getBlock(position.offset(0, 0, 1)),
|
|
88
|
-
world.getBlock(position.offset(0, 0, -1)),
|
|
89
|
-
world.getBlock(position.offset(1, 0, 0)),
|
|
90
|
-
world.getBlock(position.offset(-1, 0, 0))
|
|
91
|
-
]
|
|
92
|
-
// set needed props to true: east:'false',north:'false',south:'false',west:'false'
|
|
93
|
-
const props = {}
|
|
94
|
-
let changed = false
|
|
95
|
-
for (const [i, neighbor] of neighbors.entries()) {
|
|
96
|
-
const isConnectedToSolid = isSolidConnection ? (neighbor && !neighbor.transparent) : false
|
|
97
|
-
if (isConnectedToSolid || neighbor?.name === block.name) {
|
|
98
|
-
props[['south', 'north', 'east', 'west'][i]] = 'true'
|
|
99
|
-
changed = true
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return changed ? props : undefined
|
|
103
|
-
}
|
|
104
|
-
// case 'gate_in_wall': {}
|
|
105
|
-
case 'block_snowy': {
|
|
106
|
-
const aboveIsSnow = world.getBlock(position.offset(0, 1, 0))?.name === 'snow'
|
|
107
|
-
if (aboveIsSnow) {
|
|
108
|
-
return {
|
|
109
|
-
snowy: `${aboveIsSnow}`
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
case 'door': {
|
|
116
|
-
// upper half matches lower in
|
|
117
|
-
const { half } = block.getProperties()
|
|
118
|
-
if (half === 'upper') {
|
|
119
|
-
// copy other properties
|
|
120
|
-
const lower = world.getBlock(position.offset(0, -1, 0))
|
|
121
|
-
if (lower?.name === block.name) {
|
|
122
|
-
return {
|
|
123
|
-
...lower.getProperties(),
|
|
124
|
-
half: 'upper'
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
56
|
function tintToGl(tint) {
|
|
133
57
|
const r = (tint >> 16) & 0xff
|
|
134
58
|
const g = (tint >> 8) & 0xff
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import {
|
|
3
|
+
CONSTANT_LONG_RENDER_FRACTION,
|
|
4
|
+
CONSTANT_LONG_RENDER_MIN_SAMPLES,
|
|
5
|
+
FAST_SCENE_WITHOUT_ENTITIES_MS,
|
|
6
|
+
HIGH_TEXTURE_COUNT,
|
|
7
|
+
LONG_RENDER_TIME_MS,
|
|
8
|
+
LOW_FPS_THRESHOLD,
|
|
9
|
+
RENDER_TIME_HISTORY_SIZE,
|
|
10
|
+
SLOW_ENTITIES_RENDER_MS,
|
|
11
|
+
} from './constants'
|
|
12
|
+
import type { FramePerformanceSample, PerformanceInstabilityFactors } from './types'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tracks render/FPS signals and writes instability factors into reactive state
|
|
16
|
+
* (alongside `mesherWork`).
|
|
17
|
+
*/
|
|
18
|
+
export class PerformanceMonitor {
|
|
19
|
+
private readonly renderTimeHistory: number[] = []
|
|
20
|
+
|
|
21
|
+
constructor(private readonly factors: PerformanceInstabilityFactors) {}
|
|
22
|
+
|
|
23
|
+
onFrame(sample: FramePerformanceSample): void {
|
|
24
|
+
this.pushRenderTime(sample.totalMs)
|
|
25
|
+
this.recompute(sample)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private pushRenderTime(ms: number): void {
|
|
29
|
+
this.renderTimeHistory.push(ms)
|
|
30
|
+
if (this.renderTimeHistory.length > RENDER_TIME_HISTORY_SIZE) {
|
|
31
|
+
this.renderTimeHistory.shift()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private recompute(sample: FramePerformanceSample): void {
|
|
36
|
+
const lowFps = sample.fps > 0 && sample.fps <= LOW_FPS_THRESHOLD
|
|
37
|
+
const sceneWithoutEntitiesMs = Math.max(0, sample.totalMs - sample.entitiesMs)
|
|
38
|
+
|
|
39
|
+
const longRenderTime = sample.totalMs >= LONG_RENDER_TIME_MS
|
|
40
|
+
|
|
41
|
+
const historyLen = this.renderTimeHistory.length
|
|
42
|
+
const longFrames = this.renderTimeHistory.filter(t => t >= LONG_RENDER_TIME_MS).length
|
|
43
|
+
const constantLongRenderTime =
|
|
44
|
+
historyLen >= CONSTANT_LONG_RENDER_MIN_SAMPLES &&
|
|
45
|
+
longFrames / historyLen >= CONSTANT_LONG_RENDER_FRACTION
|
|
46
|
+
|
|
47
|
+
const tooManyTextures = sample.loadedTextureCount >= HIGH_TEXTURE_COUNT
|
|
48
|
+
|
|
49
|
+
const tooManyEntities =
|
|
50
|
+
lowFps &&
|
|
51
|
+
sample.entitiesMs >= SLOW_ENTITIES_RENDER_MS &&
|
|
52
|
+
sceneWithoutEntitiesMs <= FAST_SCENE_WITHOUT_ENTITIES_MS
|
|
53
|
+
|
|
54
|
+
const hasKnownCause =
|
|
55
|
+
longRenderTime ||
|
|
56
|
+
constantLongRenderTime ||
|
|
57
|
+
tooManyEntities ||
|
|
58
|
+
tooManyTextures
|
|
59
|
+
|
|
60
|
+
const unknownReason = lowFps && !hasKnownCause
|
|
61
|
+
|
|
62
|
+
this.factors.longRenderTime = longRenderTime
|
|
63
|
+
this.factors.constantLongRenderTime = constantLongRenderTime
|
|
64
|
+
this.factors.tooManyEntities = tooManyEntities
|
|
65
|
+
this.factors.tooManyTextures = tooManyTextures
|
|
66
|
+
this.factors.unknownReason = unknownReason
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
reset(): void {
|
|
70
|
+
this.renderTimeHistory.length = 0
|
|
71
|
+
this.factors.longRenderTime = false
|
|
72
|
+
this.factors.constantLongRenderTime = false
|
|
73
|
+
this.factors.tooManyEntities = false
|
|
74
|
+
this.factors.tooManyTextures = false
|
|
75
|
+
this.factors.unknownReason = false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/** Recent frame exceeded this → `longRenderTime`. */
|
|
3
|
+
export const LONG_RENDER_TIME_MS = 30
|
|
4
|
+
|
|
5
|
+
/** Scene pass without entities faster than this → candidate for entity bottleneck. */
|
|
6
|
+
export const FAST_SCENE_WITHOUT_ENTITIES_MS = 20
|
|
7
|
+
|
|
8
|
+
/** Entity pass slower than this (with low FPS) → `tooManyEntities`. */
|
|
9
|
+
export const SLOW_ENTITIES_RENDER_MS = 8
|
|
10
|
+
|
|
11
|
+
/** FPS at or below this is treated as low performance. */
|
|
12
|
+
export const LOW_FPS_THRESHOLD = 45
|
|
13
|
+
|
|
14
|
+
/** Loaded WebGL textures at or above this → `tooManyTextures` (labels, signs, iOS). */
|
|
15
|
+
export const HIGH_TEXTURE_COUNT = 100
|
|
16
|
+
|
|
17
|
+
/** Ring buffer length for sustained render-time analysis. */
|
|
18
|
+
export const RENDER_TIME_HISTORY_SIZE = 24
|
|
19
|
+
|
|
20
|
+
/** Fraction of recent frames over `LONG_RENDER_TIME_MS` → `constantLongRenderTime`. */
|
|
21
|
+
export const CONSTANT_LONG_RENDER_FRACTION = 0.65
|
|
22
|
+
|
|
23
|
+
/** Minimum frames in history before `constantLongRenderTime` can trigger. */
|
|
24
|
+
export const CONSTANT_LONG_RENDER_MIN_SAMPLES = 8
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import type { PerformanceInstabilityFactors } from './types'
|
|
3
|
+
|
|
4
|
+
const FACTOR_CODES: Array<{ key: keyof PerformanceInstabilityFactors, code: string }> = [
|
|
5
|
+
{ key: 'longRenderTime', code: 'LR' },
|
|
6
|
+
{ key: 'constantLongRenderTime', code: 'CLR' },
|
|
7
|
+
{ key: 'tooManyEntities', code: 'ENT' },
|
|
8
|
+
{ key: 'tooManyTextures', code: 'TEX' },
|
|
9
|
+
{ key: 'unknownReason', code: 'UNK' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
/** Compact debug overlay fragment, e.g. `LR+ENT` or empty string. */
|
|
13
|
+
export function formatPerformanceFactorsDebug(factors: PerformanceInstabilityFactors): string {
|
|
14
|
+
const active = FACTOR_CODES.filter(({ key }) => factors[key]).map(({ code }) => code)
|
|
15
|
+
return active.length > 0 ? active.join('+') : ''
|
|
16
|
+
}
|