minecraft-renderer 0.1.44 → 0.1.46
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/dist/mesher.js +35 -35
- package/dist/mesher.js.map +4 -4
- package/dist/mesherWasm.js +61 -61
- package/dist/minecraft-renderer.js +52 -52
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +385 -385
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +2 -0
- package/src/graphicsBackend/index.ts +1 -0
- package/src/graphicsBackend/types.ts +3 -0
- package/src/mesher-shared/models.ts +28 -11
- package/src/mesher-shared/vertexShading.ts +35 -0
- 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/modules/rain.ts +21 -3
- package/src/three/worldRendererThree.ts +19 -1
- package/src/wasm-mesher/bridge/render-from-wasm.ts +57 -12
package/package.json
CHANGED
|
@@ -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,
|
|
@@ -117,6 +118,7 @@ export const getDefaultRendererState = (): {
|
|
|
117
118
|
heightmaps: new Map<string, Int16Array>(),
|
|
118
119
|
allChunksLoaded: false,
|
|
119
120
|
mesherWork: false,
|
|
121
|
+
instabilityFactors: defaultPerformanceInstabilityFactors(),
|
|
120
122
|
intersectMedia: null
|
|
121
123
|
},
|
|
122
124
|
renderer: '...',
|
|
@@ -23,6 +23,7 @@ export interface SoundSystem {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
import type { MenuBackgroundOptions } from '../three/menuBackground/types'
|
|
26
|
+
import type { PerformanceInstabilityFactors } from '../performanceMonitor'
|
|
26
27
|
|
|
27
28
|
/** Graphics backend configuration */
|
|
28
29
|
export interface GraphicsBackendConfig {
|
|
@@ -76,6 +77,8 @@ export interface RendererReactiveState {
|
|
|
76
77
|
heightmaps: Map<string, Int16Array>
|
|
77
78
|
allChunksLoaded: boolean
|
|
78
79
|
mesherWork: boolean
|
|
80
|
+
/** Low-FPS / render instability factors (see `performanceMonitor`). */
|
|
81
|
+
instabilityFactors: PerformanceInstabilityFactors
|
|
79
82
|
intersectMedia: any | null
|
|
80
83
|
}
|
|
81
84
|
renderer: string
|
|
@@ -6,6 +6,7 @@ import legacyJson from '../lib/preflatMap.json'
|
|
|
6
6
|
import { BlockType } from '../playground/shared'
|
|
7
7
|
import { World, BlockModelPartsResolved, WorldBlock as Block, WorldBlock, worldColumnKey } from './world'
|
|
8
8
|
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
9
|
+
import { getSideShading, vertexLightFromAo } from './vertexShading'
|
|
9
10
|
import { INVISIBLE_BLOCKS } from './worldConstants'
|
|
10
11
|
import { MesherGeometryOutput, HighestBlockInfo } from './shared'
|
|
11
12
|
import { collectBlockEntityMetadata } from './blockEntityMetadata'
|
|
@@ -51,6 +52,31 @@ function prepareTints(tints) {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
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
|
+
|
|
54
80
|
export function preflatBlockCalculation(block: Block, world: World, position: Vec3) {
|
|
55
81
|
const type = calculatedBlocksEntries.find(([name, blocks]) => blocks.includes(block.name))?.[0]
|
|
56
82
|
if (!type) return
|
|
@@ -398,13 +424,7 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
398
424
|
// 10%
|
|
399
425
|
const { smoothLighting, shadingTheme, cardinalLight } = world.config
|
|
400
426
|
const faceLight = world.getLight(neighborPos, undefined, undefined, block.name)
|
|
401
|
-
const sideShading = (shadingTheme
|
|
402
|
-
? (0.8 + 0.5 * Math.max(0, 0.66 * dir[0] + 0.66 * dir[1] + 0.33 * dir[2])) // old directional light behavior
|
|
403
|
-
: (
|
|
404
|
-
cardinalLight === 'nether'
|
|
405
|
-
? (0.5 + Math.abs(0.1 * dir[0] + 0.4 * dir[1] + 0.3 * dir[2]))
|
|
406
|
-
: (0.75 + 0.25 * dir[1] + 0.05 * (Math.abs(dir[2]) - 3 * Math.abs(dir[0])))
|
|
407
|
-
)
|
|
427
|
+
const sideShading = getSideShading(dir, shadingTheme, cardinalLight)
|
|
408
428
|
const baseLight = sideShading * faceLight / 15
|
|
409
429
|
for (const pos of corners) {
|
|
410
430
|
let vertex = [
|
|
@@ -478,10 +498,7 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
478
498
|
// TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block)
|
|
479
499
|
|
|
480
500
|
const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
|
|
481
|
-
|
|
482
|
-
const ao_scale = (shadingTheme === 'high-contrast') ? 0.25 : 0.2
|
|
483
|
-
// todo light should go upper on lower blocks
|
|
484
|
-
light = sideShading * (ao * ao_scale + ao_bias) * (cornerLightResult / 15)
|
|
501
|
+
light = vertexLightFromAo(ao, cornerLightResult, sideShading, shadingTheme)
|
|
485
502
|
aos.push(ao)
|
|
486
503
|
|
|
487
504
|
// Log AO and light for this corner (corner index is aos.length - 1)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import type { MesherConfig } from './shared'
|
|
3
|
+
|
|
4
|
+
export type FaceDirection = readonly [number, number, number]
|
|
5
|
+
|
|
6
|
+
/** Directional face darkening (matches legacy `renderElement` in models.ts). */
|
|
7
|
+
export function getSideShading(
|
|
8
|
+
dir: FaceDirection,
|
|
9
|
+
shadingTheme: MesherConfig['shadingTheme'],
|
|
10
|
+
cardinalLight: MesherConfig['cardinalLight']
|
|
11
|
+
): number {
|
|
12
|
+
if (shadingTheme === 'high-contrast') {
|
|
13
|
+
return 0.8 + 0.5 * Math.max(0, 0.66 * dir[0] + 0.66 * dir[1] + 0.33 * dir[2])
|
|
14
|
+
}
|
|
15
|
+
if (cardinalLight === 'nether') {
|
|
16
|
+
return 0.5 + Math.abs(0.1 * dir[0] + 0.4 * dir[1] + 0.3 * dir[2])
|
|
17
|
+
}
|
|
18
|
+
return 0.75 + 0.25 * dir[1] + 0.05 * (Math.abs(dir[2]) - 3 * Math.abs(dir[0]))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Per-vertex brightness from AO (0–3) and corner light (0–15). */
|
|
22
|
+
export function vertexLightFromAo(
|
|
23
|
+
ao: number,
|
|
24
|
+
cornerLight15: number,
|
|
25
|
+
sideShading: number,
|
|
26
|
+
shadingTheme: MesherConfig['shadingTheme']
|
|
27
|
+
): number {
|
|
28
|
+
const lightNorm = cornerLight15 / 15
|
|
29
|
+
if (shadingTheme === 'high-contrast') {
|
|
30
|
+
return sideShading * ((ao + 1) / 4) * lightNorm
|
|
31
|
+
}
|
|
32
|
+
const aoBias = 0.4
|
|
33
|
+
const aoScale = 0.2
|
|
34
|
+
return sideShading * (ao * aoScale + aoBias) * lightNorm
|
|
35
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
export type { FramePerformanceSample, PerformanceInstabilityFactors } from './types'
|
|
3
|
+
export { defaultPerformanceInstabilityFactors } from './types'
|
|
4
|
+
export {
|
|
5
|
+
LONG_RENDER_TIME_MS,
|
|
6
|
+
LOW_FPS_THRESHOLD,
|
|
7
|
+
HIGH_TEXTURE_COUNT,
|
|
8
|
+
} from './constants'
|
|
9
|
+
export { PerformanceMonitor } from './PerformanceMonitor'
|
|
10
|
+
export { formatPerformanceFactorsDebug } from './formatPerformanceFactorsDebug'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/** Low-FPS / instability factors written to reactive renderer state. */
|
|
3
|
+
export interface PerformanceInstabilityFactors {
|
|
4
|
+
longRenderTime: boolean
|
|
5
|
+
constantLongRenderTime: boolean
|
|
6
|
+
tooManyEntities: boolean
|
|
7
|
+
tooManyTextures: boolean
|
|
8
|
+
unknownReason: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const defaultPerformanceInstabilityFactors = (): PerformanceInstabilityFactors => ({
|
|
12
|
+
longRenderTime: false,
|
|
13
|
+
constantLongRenderTime: false,
|
|
14
|
+
tooManyEntities: false,
|
|
15
|
+
tooManyTextures: false,
|
|
16
|
+
unknownReason: false,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export interface FramePerformanceSample {
|
|
20
|
+
/** Full `WorldRendererThree.render()` duration in ms. */
|
|
21
|
+
totalMs: number
|
|
22
|
+
/** Time spent in `entities.render()` this frame (0 if skipped). */
|
|
23
|
+
entitiesMs: number
|
|
24
|
+
loadedTextureCount: number
|
|
25
|
+
/** FPS from the last completed 1s window (0 before first sample). */
|
|
26
|
+
fps: number
|
|
27
|
+
}
|
|
@@ -58,7 +58,9 @@ export class RainModule implements RendererModuleController {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
render?: (deltaTime: number) => void = (deltaTime) => {
|
|
61
|
-
if (!this.enabled || !this.instancedMesh) return
|
|
61
|
+
if (!this.enabled || !this.instancedMesh || !this.material) return
|
|
62
|
+
|
|
63
|
+
this.syncMaterialToSceneFog()
|
|
62
64
|
|
|
63
65
|
const cameraPos = this.worldRenderer.getCameraPosition()
|
|
64
66
|
this.instancedMesh.position.set(0, 0, 0)
|
|
@@ -135,16 +137,32 @@ export class RainModule implements RendererModuleController {
|
|
|
135
137
|
this.particles = []
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
/** Match scene fog so rain fades with distance instead of a flat blue sheet. */
|
|
141
|
+
private syncMaterialToSceneFog(): void {
|
|
142
|
+
if (!this.material) return
|
|
143
|
+
const fog = this.worldRenderer.scene.fog
|
|
144
|
+
if (fog instanceof THREE.Fog || fog instanceof THREE.FogExp2) {
|
|
145
|
+
this.material.color.copy(fog.color)
|
|
146
|
+
} else {
|
|
147
|
+
this.material.color.set(0xcc_dd_ee)
|
|
148
|
+
}
|
|
149
|
+
this.material.fog = true
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
private createRain(): void {
|
|
139
153
|
this.geometry = new THREE.BoxGeometry(0.03, 0.3, 0.03)
|
|
140
154
|
this.material = new THREE.MeshBasicMaterial({
|
|
141
|
-
color:
|
|
155
|
+
color: 0xcc_dd_ee,
|
|
142
156
|
transparent: true,
|
|
143
|
-
opacity: 0.
|
|
157
|
+
opacity: 0.35,
|
|
158
|
+
depthWrite: false,
|
|
159
|
+
fog: true,
|
|
144
160
|
})
|
|
145
161
|
|
|
146
162
|
this.instancedMesh = new THREE.InstancedMesh(this.geometry, this.material, PARTICLE_COUNT)
|
|
147
163
|
this.instancedMesh.name = 'rain-particles'
|
|
164
|
+
this.instancedMesh.frustumCulled = false
|
|
165
|
+
this.syncMaterialToSceneFog()
|
|
148
166
|
|
|
149
167
|
const dummy = new THREE.Matrix4()
|
|
150
168
|
const position = new THREE.Vector3()
|
|
@@ -36,6 +36,7 @@ import { downloadWorldGeometry } from './worldGeometryExport'
|
|
|
36
36
|
import { ChunkMeshManager } from './chunkMeshManager'
|
|
37
37
|
import type { RendererModuleManifest, RegisteredModule, RendererModuleController } from './rendererModuleSystem'
|
|
38
38
|
import { BUILTIN_MODULES } from './modules/index'
|
|
39
|
+
import { formatPerformanceFactorsDebug, PerformanceMonitor } from '../performanceMonitor'
|
|
39
40
|
|
|
40
41
|
type SectionKey = string
|
|
41
42
|
|
|
@@ -57,6 +58,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
57
58
|
ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
|
|
58
59
|
directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
|
|
59
60
|
entities = new Entities(this, (globalThis as any).mcData)
|
|
61
|
+
performanceMonitor!: PerformanceMonitor
|
|
60
62
|
cameraGroupVr?: THREE.Object3D
|
|
61
63
|
material = new THREE.MeshBasicMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 })
|
|
62
64
|
itemsTexture!: THREE.Texture
|
|
@@ -154,6 +156,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
154
156
|
if (!displayOptions.resourcesManager) throw new Error('resourcesManager is required in displayOptions')
|
|
155
157
|
super(displayOptions.resourcesManager, displayOptions, initOptions)
|
|
156
158
|
|
|
159
|
+
this.performanceMonitor = new PerformanceMonitor(this.reactiveState.world.instabilityFactors)
|
|
160
|
+
|
|
157
161
|
this.renderer = renderer
|
|
158
162
|
displayOptions.rendererState.renderer = WorldRendererThree.getRendererInfo(renderer) ?? '...'
|
|
159
163
|
|
|
@@ -713,7 +717,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
713
717
|
text += `B: ${formatCompact(this.blocksRendered)} `
|
|
714
718
|
text += `MEM: ${this.chunkMeshManager.getEstimatedMemoryUsage().total} `
|
|
715
719
|
const poolStats = this.chunkMeshManager.getStats()
|
|
716
|
-
text += `POOL: ${poolStats.activeCount}/${poolStats.poolSize}`
|
|
720
|
+
text += `POOL: ${poolStats.activeCount}/${poolStats.poolSize} `
|
|
721
|
+
const pf = formatPerformanceFactorsDebug(this.reactiveState.world.instabilityFactors)
|
|
722
|
+
if (pf) text += `PF: ${pf} `
|
|
723
|
+
// entities can be seen in F3
|
|
717
724
|
pane.updateText(text)
|
|
718
725
|
this.backendInfoReport = text
|
|
719
726
|
}
|
|
@@ -1201,8 +1208,11 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1201
1208
|
this.camera.updateProjectionMatrix()
|
|
1202
1209
|
}
|
|
1203
1210
|
|
|
1211
|
+
let entitiesRenderMs = 0
|
|
1204
1212
|
if (!this.reactiveDebugParams.disableEntities) {
|
|
1213
|
+
const entitiesStart = performance.now()
|
|
1205
1214
|
this.entities.render()
|
|
1215
|
+
entitiesRenderMs = performance.now() - entitiesStart
|
|
1206
1216
|
}
|
|
1207
1217
|
|
|
1208
1218
|
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
@@ -1243,6 +1253,13 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1243
1253
|
this.renderTimeAvgCount++
|
|
1244
1254
|
this.renderTimeAvg = ((this.renderTimeAvg * (this.renderTimeAvgCount - 1)) + totalTime) / this.renderTimeAvgCount
|
|
1245
1255
|
this.renderTimeMax = Math.max(this.renderTimeMax, totalTime)
|
|
1256
|
+
|
|
1257
|
+
this.performanceMonitor.onFrame({
|
|
1258
|
+
totalMs: totalTime,
|
|
1259
|
+
entitiesMs: entitiesRenderMs,
|
|
1260
|
+
loadedTextureCount: this.renderer.info.memory.textures,
|
|
1261
|
+
fps: this.lastFps,
|
|
1262
|
+
})
|
|
1246
1263
|
}
|
|
1247
1264
|
|
|
1248
1265
|
renderHead(position: Vec3, rotation: number, isWall: boolean, blockEntity) {
|
|
@@ -1450,6 +1467,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1450
1467
|
}
|
|
1451
1468
|
|
|
1452
1469
|
destroy(): void {
|
|
1470
|
+
this.performanceMonitor?.reset()
|
|
1453
1471
|
this.pendingSectionUpdates.clear()
|
|
1454
1472
|
this.pendingSectionBufferStartTimes.clear()
|
|
1455
1473
|
this.chunkMeshManager.dispose()
|
|
@@ -13,6 +13,8 @@ import { elemFaces, buildRotationMatrix, matmul3, matmulmat3, vecadd3, vecsub3 }
|
|
|
13
13
|
import type { ExportedWorldGeometry, ExportedSection } from '../../three/worldGeometryExport'
|
|
14
14
|
import type { MesherGeometryOutput } from '../../mesher-shared/shared'
|
|
15
15
|
import type { World } from '../../mesher-shared/world'
|
|
16
|
+
import { resolveBlockPropertiesForMeshing } from '../../mesher-shared/models'
|
|
17
|
+
import { getSideShading, vertexLightFromAo } from '../../mesher-shared/vertexShading'
|
|
16
18
|
|
|
17
19
|
// Handle both default and named export
|
|
18
20
|
const worldBlockProvider = (worldBlockProviderModule as any).default || worldBlockProviderModule
|
|
@@ -134,6 +136,18 @@ export function extractColumnHeightmap(
|
|
|
134
136
|
return out
|
|
135
137
|
}
|
|
136
138
|
|
|
139
|
+
function computeMesherVertexLight(
|
|
140
|
+
world: World | undefined,
|
|
141
|
+
ao: number,
|
|
142
|
+
cornerLight15: number,
|
|
143
|
+
faceDir: [number, number, number]
|
|
144
|
+
): number {
|
|
145
|
+
const shadingTheme = world?.config.shadingTheme ?? 'high-contrast'
|
|
146
|
+
const cardinalLight = world?.config.cardinalLight ?? 'default'
|
|
147
|
+
const sideShading = getSideShading(faceDir, shadingTheme, cardinalLight)
|
|
148
|
+
return vertexLightFromAo(ao, cornerLight15, sideShading, shadingTheme)
|
|
149
|
+
}
|
|
150
|
+
|
|
137
151
|
/**
|
|
138
152
|
* Get or create cached block model with precomputed matrices
|
|
139
153
|
*/
|
|
@@ -141,10 +155,32 @@ function getCachedBlockModel(
|
|
|
141
155
|
blockStateId: number,
|
|
142
156
|
version: string,
|
|
143
157
|
blockProvider: WorldBlockProvider,
|
|
144
|
-
PrismarineBlock: any
|
|
158
|
+
PrismarineBlock: any,
|
|
159
|
+
world?: World,
|
|
160
|
+
blockPos?: { x: number, y: number, z: number }
|
|
145
161
|
): CachedBlockModel | null {
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
const usePreflat = !!(world?.preflat && blockPos)
|
|
163
|
+
let blockName: string
|
|
164
|
+
let blockProps: Record<string, unknown>
|
|
165
|
+
if (usePreflat) {
|
|
166
|
+
const resolved = resolveBlockPropertiesForMeshing(
|
|
167
|
+
world,
|
|
168
|
+
new Vec3(blockPos!.x, blockPos!.y, blockPos!.z),
|
|
169
|
+
blockProvider,
|
|
170
|
+
blockStateId,
|
|
171
|
+
PrismarineBlock
|
|
172
|
+
)
|
|
173
|
+
blockName = resolved.name
|
|
174
|
+
blockProps = resolved.properties
|
|
175
|
+
} else {
|
|
176
|
+
const blockObj = PrismarineBlock.fromStateId(blockStateId, 1)
|
|
177
|
+
blockName = blockObj.name
|
|
178
|
+
blockProps = blockObj.getProperties()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const cacheKey = usePreflat
|
|
182
|
+
? `${version}:${blockStateId}:${blockName}:${JSON.stringify(blockProps)}`
|
|
183
|
+
: `${version}:${blockStateId}`
|
|
148
184
|
if (!(globalThis as any).__wasmBlockModelCache) {
|
|
149
185
|
(globalThis as any).__wasmBlockModelCache = new Map()
|
|
150
186
|
}
|
|
@@ -156,11 +192,13 @@ function getCachedBlockModel(
|
|
|
156
192
|
|
|
157
193
|
try {
|
|
158
194
|
const blockObj = PrismarineBlock.fromStateId(blockStateId, 1)
|
|
159
|
-
|
|
160
|
-
|
|
195
|
+
if (!usePreflat) {
|
|
196
|
+
blockName = blockObj.name
|
|
197
|
+
blockProps = blockObj.getProperties()
|
|
198
|
+
}
|
|
161
199
|
|
|
162
200
|
const models = blockProvider.getAllResolvedModels0_1(
|
|
163
|
-
{ name: blockName, properties: blockProps },
|
|
201
|
+
{ name: blockName, properties: blockProps as Record<string, string | number | boolean> },
|
|
164
202
|
false
|
|
165
203
|
)
|
|
166
204
|
|
|
@@ -505,7 +543,14 @@ export function renderWasmOutputToGeometry(
|
|
|
505
543
|
}
|
|
506
544
|
}
|
|
507
545
|
|
|
508
|
-
const cachedModel = getCachedBlockModel(
|
|
546
|
+
const cachedModel = getCachedBlockModel(
|
|
547
|
+
blockStateId,
|
|
548
|
+
version,
|
|
549
|
+
blockProvider,
|
|
550
|
+
PrismarineBlock,
|
|
551
|
+
world,
|
|
552
|
+
{ x: bx, y: by, z: bz }
|
|
553
|
+
)
|
|
509
554
|
if (!cachedModel) continue
|
|
510
555
|
|
|
511
556
|
if (false) {
|
|
@@ -654,10 +699,9 @@ export function renderWasmOutputToGeometry(
|
|
|
654
699
|
// But WASM light calculation seems to return 0.0, so we need to handle that
|
|
655
700
|
// In the test case, TypeScript gets baseLight = 1.0 (full brightness)
|
|
656
701
|
// So we should use 1.0 as the base light value when WASM returns 0
|
|
657
|
-
const
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
const light = (ao + 1) / 4 * (cornerLightResult / 15)
|
|
702
|
+
const cornerLight15 = (lightValues[cornerIdx] ?? 1) * 15
|
|
703
|
+
const faceDir = transformedDir as [number, number, number]
|
|
704
|
+
const light = computeMesherVertexLight(world, ao, cornerLight15, faceDir)
|
|
661
705
|
|
|
662
706
|
colors.push(tint[0] * light, tint[1] * light, tint[2] * light)
|
|
663
707
|
|
|
@@ -907,7 +951,8 @@ export function renderWasmOutputToGeometry(
|
|
|
907
951
|
}
|
|
908
952
|
|
|
909
953
|
if (doAO) {
|
|
910
|
-
|
|
954
|
+
const faceDir = transformedDirI as [number, number, number]
|
|
955
|
+
light = computeMesherVertexLight(world, ao, cornerLightResult, faceDir)
|
|
911
956
|
}
|
|
912
957
|
|
|
913
958
|
colors.push(tint[0] * light!, tint[1] * light!, tint[2] * light!)
|