minecraft-renderer 0.1.107 → 0.1.108
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/lightOwnerWorker.js +9 -0
- package/dist/lightOwnerWorker.js.map +7 -0
- package/dist/mesher.js +1 -1
- package/dist/mesher.js.map +2 -2
- package/dist/mesherWasm.js +222 -222
- package/dist/minecraft-renderer.js +80 -80
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +513 -513
- package/package.json +1 -1
- package/src/bundler/bundlePrepare.test.ts +25 -0
- package/src/bundler/bundlePrepare.ts +1 -1
- package/src/graphicsBackend/appViewer.ts +15 -0
- package/src/graphicsBackend/config.ts +5 -0
- package/src/graphicsBackend/rendererDefaultOptions.test.ts +51 -0
- package/src/graphicsBackend/rendererDefaultOptions.ts +13 -1
- package/src/graphicsBackend/rendererOptionsSync.ts +3 -3
- package/src/lib/blockEntityLightRegistry.ts +4 -0
- package/src/lib/worldrendererCommon.lightOwner.test.ts +916 -0
- package/src/lib/worldrendererCommon.removeColumn.test.ts +16 -0
- package/src/lib/worldrendererCommon.ts +329 -67
- package/src/lighting/cache/rendererLightCache.test.ts +158 -0
- package/src/lighting/cache/rendererLightCache.ts +248 -0
- package/src/lighting/coordinator/clientLightCoordinator.test.ts +102 -0
- package/src/lighting/coordinator/clientLightCoordinator.ts +574 -0
- package/src/lighting/debug/clientLightProbe.test.ts +24 -0
- package/src/lighting/debug/clientLightProbe.ts +20 -0
- package/src/lighting/debug/clientLightTrace.test.ts +271 -0
- package/src/lighting/debug/clientLightTrace.ts +335 -0
- package/src/lighting/entity/entityLightController.test.ts +216 -0
- package/src/lighting/entity/entityLightController.ts +322 -0
- package/src/lighting/owner/clientLightOwner.test.ts +467 -0
- package/src/lighting/owner/clientLightOwner.ts +523 -0
- package/src/lighting/owner/lightOwnerHost.test.ts +78 -0
- package/src/lighting/owner/lightOwnerHost.ts +202 -0
- package/src/lighting/owner/lightOwnerWorker.ts +154 -0
- package/src/lighting/owner/updateLight1171OwnerPath.test.ts +68 -0
- package/src/lighting/owner/updateLightToOwnerEvents.ts +49 -0
- package/src/lighting/tables/lightTables1171.generated.json +10 -0
- package/src/lighting/tables/lightTables1171.test.ts +168 -0
- package/src/lighting/tables/lightTables1171.ts +59 -0
- package/src/lighting/versioning/clientLightVersions.test.ts +241 -0
- package/src/lighting/versioning/clientLightVersions.ts +203 -0
- package/src/lighting/versioning/pendingSectionFlush.test.ts +214 -0
- package/src/lighting/versioning/pendingSectionFlush.ts +123 -0
- package/src/mesher-shared/lightNibblePack.ts +26 -0
- package/src/mesher-shared/mcDataWorkerShim.cjs +9 -7
- package/src/mesher-shared/modelsGeometryCommon.test.ts +6 -1
- package/src/mesher-shared/shared.ts +48 -1
- package/src/three/chunkMeshManager.ts +26 -0
- package/src/three/entities.ts +77 -11
- package/src/three/entity/EntityMesh.ts +1 -0
- package/src/three/entity/legWalkAnimation.test.ts +11 -2
- package/src/three/entity/legWalkAnimation.ts +1 -9
- package/src/three/globalBlockBuffer.ts +94 -2
- package/src/three/globalLegacyBuffer.ts +94 -2
- package/src/three/graphicsBackendBase.ts +2 -9
- package/src/three/tests/globalBlockBuffer.test.ts +98 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +108 -0
- package/src/three/worldRendererThree.ts +75 -34
- package/src/wasm-mesher/bridge/render-from-wasm.ts +46 -1
- package/src/wasm-mesher/runtime-build/wasm_mesher.d.ts +31 -5
- package/src/wasm-mesher/runtime-build/wasm_mesher.js +180 -5
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm.d.ts +11 -0
- package/src/wasm-mesher/tests/mesherLightDependencies.test.ts +83 -0
- package/src/wasm-mesher/tests/mesherWasmColumnTickSelect.test.ts +39 -0
- package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +30 -1
- package/src/wasm-mesher/tests/mesherWasmTickParseCache.test.ts +73 -0
- package/src/wasm-mesher/tests/mesherWasmTickSchedule.test.ts +51 -0
- package/src/wasm-mesher/tests/mesherWasmTopologyPostCache.test.ts +78 -0
- package/src/wasm-mesher/worker/clientLightDarkPublication.probe.test.ts +32 -0
- package/src/wasm-mesher/worker/mesherLightDependencies.ts +43 -0
- package/src/wasm-mesher/worker/mesherWasm.ownerLightSync.test.ts +198 -0
- package/src/wasm-mesher/worker/mesherWasm.ts +506 -96
- package/src/wasm-mesher/worker/mesherWasmChunkBuffer.ts +1 -0
- package/src/wasm-mesher/worker/mesherWasmColumnTickSelect.ts +47 -0
- package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +16 -5
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +17 -1
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +10 -0
- package/src/wasm-mesher/worker/mesherWasmLightMerge.test.ts +208 -0
- package/src/wasm-mesher/worker/mesherWasmLightMerge.ts +228 -0
- package/src/wasm-mesher/worker/mesherWasmOwnerLight.test.ts +161 -0
- package/src/wasm-mesher/worker/mesherWasmOwnerLight.ts +112 -0
- package/src/wasm-mesher/worker/mesherWasmTickParseCache.ts +45 -0
- package/src/wasm-mesher/worker/mesherWasmTickSchedule.ts +44 -0
- package/src/wasm-mesher/worker/mesherWasmTopologyPostCache.ts +63 -0
- package/src/worldView/types.ts +1 -0
- package/src/worldView/worldView.geometryReload.test.ts +68 -0
- package/src/worldView/worldView.spiral.test.ts +1 -1
- package/src/worldView/worldView.ts +46 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import * as THREE from 'three'
|
|
4
|
+
import { blockEntityBrightness, DEFAULT_LIGHTMAP_PARAMS } from '../../lib/blockEntityLighting'
|
|
5
|
+
import { EntityLightController, entityProbeBlock, resolveEntityLightOverride } from './entityLightController'
|
|
6
|
+
|
|
7
|
+
const nightSky = 4 / 15
|
|
8
|
+
|
|
9
|
+
function makeController(
|
|
10
|
+
getLight: (x: number, y: number, z: number) => { block: number; sky: number } = () => ({ block: 0, sky: 1 }),
|
|
11
|
+
extras: Partial<ConstructorParameters<typeof EntityLightController>[0]> = {}
|
|
12
|
+
) {
|
|
13
|
+
let skyLevel = 1
|
|
14
|
+
let lightingEnabled = true
|
|
15
|
+
let globalRevision = 1
|
|
16
|
+
const columnRevisions = new Map<string, number>([['0,0', 1]])
|
|
17
|
+
const controller = new EntityLightController({
|
|
18
|
+
getLight,
|
|
19
|
+
getSkyLevel: () => skyLevel,
|
|
20
|
+
getLightmapParams: () => DEFAULT_LIGHTMAP_PARAMS,
|
|
21
|
+
getColumnRevision: (cx, cz) => columnRevisions.get(`${cx},${cz}`),
|
|
22
|
+
getGlobalRevision: () => globalRevision,
|
|
23
|
+
lightingEnabled: () => lightingEnabled,
|
|
24
|
+
...extras
|
|
25
|
+
})
|
|
26
|
+
return {
|
|
27
|
+
controller,
|
|
28
|
+
setSkyLevel: (value: number) => {
|
|
29
|
+
skyLevel = value
|
|
30
|
+
controller.setSkyLevel(value)
|
|
31
|
+
},
|
|
32
|
+
setLightingEnabled: (value: boolean) => {
|
|
33
|
+
lightingEnabled = value
|
|
34
|
+
},
|
|
35
|
+
bumpColumn: () => {
|
|
36
|
+
columnRevisions.set('0,0', (columnRevisions.get('0,0') ?? 0) + 1)
|
|
37
|
+
},
|
|
38
|
+
bumpFarColumn: () => {
|
|
39
|
+
columnRevisions.set('16,0', (columnRevisions.get('16,0') ?? 0) + 1)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('entityProbeBlock', () => {
|
|
45
|
+
it('samples the eye block, not the block above the AABB', () => {
|
|
46
|
+
expect(entityProbeBlock({ x: 8.2, y: 64.3, z: 8.7 }, 1.62)).toEqual({ x: 8, y: 65, z: 8 })
|
|
47
|
+
expect(entityProbeBlock({ x: 8.2, y: 64.3, z: 8.7 }, 1.8)).toEqual({ x: 8, y: 66, z: 8 })
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('resolveEntityLightOverride', () => {
|
|
52
|
+
it('fullbrights only reliably identified glow item frames', () => {
|
|
53
|
+
expect(resolveEntityLightOverride('glow_item_frame')).toBe('fullbright')
|
|
54
|
+
expect(resolveEntityLightOverride('item_frame')).toBe('normal')
|
|
55
|
+
expect(resolveEntityLightOverride('blaze')).toBe('normal')
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('EntityLightController', () => {
|
|
60
|
+
it('multiplies immutable base tint and is idempotent', () => {
|
|
61
|
+
const { controller } = makeController(() => ({ block: 0, sky: 1 }))
|
|
62
|
+
const material = new THREE.MeshLambertMaterial({ color: 0xb5_6d_51 })
|
|
63
|
+
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), material)
|
|
64
|
+
const root = new THREE.Group()
|
|
65
|
+
root.add(mesh)
|
|
66
|
+
const base = material.color.clone()
|
|
67
|
+
controller.register(root, { eyeHeight: 1.62 })
|
|
68
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
69
|
+
const brightness = blockEntityBrightness(0, 1, 1)
|
|
70
|
+
expect(material.color.r).toBeCloseTo(base.r * brightness, 5)
|
|
71
|
+
expect(material.color.g).toBeCloseTo(base.g * brightness, 5)
|
|
72
|
+
expect(material.color.b).toBeCloseTo(base.b * brightness, 5)
|
|
73
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
74
|
+
expect(material.color.r).toBeCloseTo(base.r * brightness, 5)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('clones a shared material and leaves the original fullbright', () => {
|
|
78
|
+
const shared = new THREE.MeshBasicMaterial({ color: 0xff_00_00 })
|
|
79
|
+
const { controller } = makeController(() => ({ block: 0, sky: 0 }))
|
|
80
|
+
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), shared)
|
|
81
|
+
const root = new THREE.Group()
|
|
82
|
+
root.add(mesh)
|
|
83
|
+
controller.register(root, { eyeHeight: 0 }, new Set([shared]))
|
|
84
|
+
controller.update(root, { x: 1, y: 64, z: 1 })
|
|
85
|
+
expect(shared.color.getHex()).toBe(0xff_00_00)
|
|
86
|
+
expect(mesh.material).not.toBe(shared)
|
|
87
|
+
const brightness = blockEntityBrightness(0, 0, 1)
|
|
88
|
+
expect((mesh.material as THREE.MeshBasicMaterial).color.r).toBeCloseTo(1 * brightness, 5)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('keeps two roots independent and skips debug helpers', () => {
|
|
92
|
+
let sample = { block: 1, sky: 1 }
|
|
93
|
+
const { controller } = makeController(() => sample)
|
|
94
|
+
const brightMat = new THREE.MeshBasicMaterial({ color: 0xff_ff_ff })
|
|
95
|
+
const darkMat = new THREE.MeshBasicMaterial({ color: 0xff_ff_ff })
|
|
96
|
+
const debugMat = new THREE.MeshBasicMaterial({ color: 0x00_ff_00 })
|
|
97
|
+
const bright = new THREE.Group()
|
|
98
|
+
bright.add(new THREE.Mesh(new THREE.BoxGeometry(), brightMat))
|
|
99
|
+
const dark = new THREE.Group()
|
|
100
|
+
dark.add(new THREE.Mesh(new THREE.BoxGeometry(), darkMat))
|
|
101
|
+
const debug = new THREE.Mesh(new THREE.BoxGeometry(), debugMat)
|
|
102
|
+
debug.name = 'debug'
|
|
103
|
+
dark.add(debug)
|
|
104
|
+
controller.register(bright, { eyeHeight: 0 })
|
|
105
|
+
controller.register(dark, { eyeHeight: 0 })
|
|
106
|
+
controller.update(bright, { x: 1, y: 64, z: 1 })
|
|
107
|
+
sample = { block: 0, sky: 0 }
|
|
108
|
+
controller.update(dark, { x: 20, y: 64, z: 20 })
|
|
109
|
+
expect(brightMat.color.r).toBeCloseTo(1, 5)
|
|
110
|
+
expect(darkMat.color.r).toBeCloseTo(blockEntityBrightness(0, 0, 1), 5)
|
|
111
|
+
expect(debugMat.color.getHex()).toBe(0x00_ff_00)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('refreshes on sky change and stays fullbright when lighting is disabled', () => {
|
|
115
|
+
const { controller, setSkyLevel, setLightingEnabled } = makeController(() => ({ block: 0, sky: 1 }))
|
|
116
|
+
const material = new THREE.MeshBasicMaterial({ color: 0xff_ff_ff })
|
|
117
|
+
const root = new THREE.Group()
|
|
118
|
+
root.add(new THREE.Mesh(new THREE.BoxGeometry(), material))
|
|
119
|
+
controller.register(root, { eyeHeight: 0 })
|
|
120
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
121
|
+
setSkyLevel(nightSky)
|
|
122
|
+
expect(material.color.r).toBeCloseTo(blockEntityBrightness(0, 1, nightSky), 5)
|
|
123
|
+
setLightingEnabled(false)
|
|
124
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
125
|
+
expect(material.color.r).toBeCloseTo(1, 5)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('resamples a stationary root when the column revision changes', () => {
|
|
129
|
+
let sample = { block: 1, sky: 1 }
|
|
130
|
+
const { controller, bumpColumn } = makeController(() => sample)
|
|
131
|
+
const material = new THREE.MeshBasicMaterial({ color: 0xff_ff_ff })
|
|
132
|
+
const root = new THREE.Group()
|
|
133
|
+
root.add(new THREE.Mesh(new THREE.BoxGeometry(), material))
|
|
134
|
+
controller.register(root, { eyeHeight: 0 })
|
|
135
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
136
|
+
expect(material.color.r).toBeCloseTo(1, 5)
|
|
137
|
+
sample = { block: 0, sky: 0 }
|
|
138
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
139
|
+
expect(material.color.r).toBeCloseTo(1, 5)
|
|
140
|
+
bumpColumn()
|
|
141
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
142
|
+
expect(material.color.r).toBeCloseTo(blockEntityBrightness(0, 0, 1), 5)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('does not resample when only a distant column revision changes', () => {
|
|
146
|
+
let sample = { block: 1, sky: 1 }
|
|
147
|
+
const { controller, bumpFarColumn } = makeController(() => sample)
|
|
148
|
+
const material = new THREE.MeshBasicMaterial({ color: 0xff_ff_ff })
|
|
149
|
+
const root = new THREE.Group()
|
|
150
|
+
root.add(new THREE.Mesh(new THREE.BoxGeometry(), material))
|
|
151
|
+
controller.register(root, { eyeHeight: 0 })
|
|
152
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
153
|
+
expect(material.color.r).toBeCloseTo(1, 5)
|
|
154
|
+
sample = { block: 0, sky: 0 }
|
|
155
|
+
bumpFarColumn()
|
|
156
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
157
|
+
expect(material.color.r).toBeCloseTo(1, 5)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('adopts an authored material retint after invalidation', () => {
|
|
161
|
+
const { controller } = makeController(() => ({ block: 0, sky: 0 }))
|
|
162
|
+
const material = new THREE.MeshBasicMaterial({ color: 0xb5_6d_51 })
|
|
163
|
+
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), material)
|
|
164
|
+
const root = new THREE.Group()
|
|
165
|
+
root.add(mesh)
|
|
166
|
+
controller.register(root, { eyeHeight: 0 })
|
|
167
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
168
|
+
const brightness = blockEntityBrightness(0, 0, 1)
|
|
169
|
+
const brown = new THREE.Color(0xb5_6d_51)
|
|
170
|
+
expect(material.color.r).toBeCloseTo(brown.r * brightness, 5)
|
|
171
|
+
material.color.setHex(0x00_ff_00)
|
|
172
|
+
controller.invalidateMaterials(root)
|
|
173
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
174
|
+
expect(material.color.r).toBeCloseTo(0, 5)
|
|
175
|
+
expect(material.color.g).toBeCloseTo(brightness, 5)
|
|
176
|
+
expect(material.color.b).toBeCloseTo(0, 5)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('keeps glow item frames fullbright and recovers after a damage flash', () => {
|
|
180
|
+
const { controller } = makeController(() => ({ block: 0, sky: 0 }))
|
|
181
|
+
const material = new THREE.MeshBasicMaterial({ color: 0xaa_aa_aa })
|
|
182
|
+
const baseR = material.color.r
|
|
183
|
+
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), material)
|
|
184
|
+
const root = new THREE.Group()
|
|
185
|
+
root.add(mesh)
|
|
186
|
+
controller.register(root, { name: 'glow_item_frame', eyeHeight: 0 })
|
|
187
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
188
|
+
expect(material.color.r).toBeCloseTo(baseR, 5)
|
|
189
|
+
|
|
190
|
+
const flashed = material.clone()
|
|
191
|
+
flashed.color.set(0xff_00_00)
|
|
192
|
+
mesh.material = flashed
|
|
193
|
+
controller.markFlash(root, 500)
|
|
194
|
+
controller.invalidateMaterials(root)
|
|
195
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
196
|
+
expect(flashed.color.getHex()).toBe(0xff_00_00)
|
|
197
|
+
controller.endFlash(root)
|
|
198
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
199
|
+
expect(flashed.color.r).toBeCloseTo(baseR, 5)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('unregisters so later updates do not keep mutating materials', () => {
|
|
203
|
+
const { controller } = makeController(() => ({ block: 0, sky: 0 }))
|
|
204
|
+
const material = new THREE.MeshBasicMaterial({ color: 0xff_ff_ff })
|
|
205
|
+
const root = new THREE.Group()
|
|
206
|
+
root.add(new THREE.Mesh(new THREE.BoxGeometry(), material))
|
|
207
|
+
controller.register(root, { eyeHeight: 0 })
|
|
208
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
209
|
+
const dim = material.color.r
|
|
210
|
+
controller.unregister(root)
|
|
211
|
+
material.color.setHex(0xff_ff_ff)
|
|
212
|
+
controller.update(root, { x: 8, y: 64, z: 8 })
|
|
213
|
+
expect(material.color.getHex()).toBe(0xff_ff_ff)
|
|
214
|
+
expect(dim).toBeLessThan(1)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import { blockEntityBrightness, DEFAULT_LIGHTMAP_PARAMS, type BlockLightmapParams } from '../../lib/blockEntityLighting'
|
|
4
|
+
|
|
5
|
+
export type EntityLightOverride = 'normal' | 'fullbright'
|
|
6
|
+
|
|
7
|
+
export type EntityLightWorldPos = { x: number; y: number; z: number }
|
|
8
|
+
|
|
9
|
+
export type EntityLightMeta = {
|
|
10
|
+
name?: string
|
|
11
|
+
height?: number
|
|
12
|
+
eyeHeight?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type EntityLightSampler = {
|
|
16
|
+
getLight: (x: number, y: number, z: number) => { block: number; sky: number }
|
|
17
|
+
getSkyLevel: () => number
|
|
18
|
+
getLightmapParams: () => BlockLightmapParams
|
|
19
|
+
getColumnRevision: (chunkX: number, chunkZ: number) => number | undefined
|
|
20
|
+
getGlobalRevision: () => number
|
|
21
|
+
lightingEnabled: () => boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BASE_COLOR_KEY = 'entityLightBaseColor'
|
|
25
|
+
const LAST_APPLIED_COLOR_KEY = 'entityLightLastAppliedColor'
|
|
26
|
+
const COLOR_EPSILON = 1e-4
|
|
27
|
+
const LIVING_EYE_HEIGHT_RATIO = 0.85
|
|
28
|
+
|
|
29
|
+
type ColorBearingMaterial = THREE.Material & { color: THREE.Color }
|
|
30
|
+
|
|
31
|
+
type ControlledMaterial = {
|
|
32
|
+
material: ColorBearingMaterial
|
|
33
|
+
base: THREE.Color
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type TrackedRoot = {
|
|
37
|
+
meta: EntityLightMeta
|
|
38
|
+
override: EntityLightOverride
|
|
39
|
+
materials: ControlledMaterial[]
|
|
40
|
+
materialsDirty: boolean
|
|
41
|
+
flashing: boolean
|
|
42
|
+
lastProbe?: { x: number; y: number; z: number }
|
|
43
|
+
lastColumnRevision?: number
|
|
44
|
+
lastGlobalRevision?: number
|
|
45
|
+
lastSkyLevel?: number
|
|
46
|
+
lastLightmapParams?: BlockLightmapParams
|
|
47
|
+
lastLightingEnabled?: boolean
|
|
48
|
+
lastBrightness?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const alignColumn = (coord: number) => Math.floor(coord / 16) * 16
|
|
52
|
+
|
|
53
|
+
export function resolveEntityEyeHeight(meta: EntityLightMeta = {}): number {
|
|
54
|
+
if (Number.isFinite(meta.eyeHeight)) return meta.eyeHeight as number
|
|
55
|
+
if (Number.isFinite(meta.height)) return (meta.height as number) * LIVING_EYE_HEIGHT_RATIO
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function entityProbeBlock(worldPos: EntityLightWorldPos, eyeHeight = 0): { x: number; y: number; z: number } {
|
|
60
|
+
return {
|
|
61
|
+
x: Math.floor(worldPos.x),
|
|
62
|
+
y: Math.floor(worldPos.y + eyeHeight),
|
|
63
|
+
z: Math.floor(worldPos.z)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveEntityLightOverride(name?: string): EntityLightOverride {
|
|
68
|
+
return name === 'glow_item_frame' ? 'fullbright' : 'normal'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type Rgb = { r: number; g: number; b: number }
|
|
72
|
+
|
|
73
|
+
const hasColor = (material: THREE.Material): material is ColorBearingMaterial => !!material && (material as ColorBearingMaterial).color instanceof THREE.Color
|
|
74
|
+
|
|
75
|
+
const readRgb = (material: ColorBearingMaterial, key: string): THREE.Color | undefined => {
|
|
76
|
+
const stored = material.userData?.[key] as Rgb | undefined
|
|
77
|
+
if (!stored || !Number.isFinite(stored.r) || !Number.isFinite(stored.g) || !Number.isFinite(stored.b)) return undefined
|
|
78
|
+
return new THREE.Color(stored.r, stored.g, stored.b)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const storeRgb = (material: ColorBearingMaterial, key: string, color: THREE.Color) => {
|
|
82
|
+
material.userData[key] = { r: color.r, g: color.g, b: color.b }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const colorsClose = (a: THREE.Color, b: THREE.Color) =>
|
|
86
|
+
Math.abs(a.r - b.r) < COLOR_EPSILON && Math.abs(a.g - b.g) < COLOR_EPSILON && Math.abs(a.b - b.b) < COLOR_EPSILON
|
|
87
|
+
|
|
88
|
+
const readStoredBase = (material: ColorBearingMaterial): THREE.Color | undefined => readRgb(material, BASE_COLOR_KEY)
|
|
89
|
+
|
|
90
|
+
const storeBase = (material: ColorBearingMaterial, base: THREE.Color) => {
|
|
91
|
+
storeRgb(material, BASE_COLOR_KEY, base)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const readLastApplied = (material: ColorBearingMaterial): THREE.Color | undefined => readRgb(material, LAST_APPLIED_COLOR_KEY)
|
|
95
|
+
|
|
96
|
+
const storeLastApplied = (material: ColorBearingMaterial, color: THREE.Color) => {
|
|
97
|
+
storeRgb(material, LAST_APPLIED_COLOR_KEY, color)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const resolveBaseColor = (material: ColorBearingMaterial, flashing: boolean): THREE.Color => {
|
|
101
|
+
const stored = readStoredBase(material)
|
|
102
|
+
if (!stored) {
|
|
103
|
+
const base = material.color.clone()
|
|
104
|
+
storeBase(material, base)
|
|
105
|
+
return base
|
|
106
|
+
}
|
|
107
|
+
if (flashing) return stored
|
|
108
|
+
const lastApplied = readLastApplied(material)
|
|
109
|
+
if (lastApplied && colorsClose(material.color, lastApplied)) return stored
|
|
110
|
+
const base = material.color.clone()
|
|
111
|
+
storeBase(material, base)
|
|
112
|
+
return base
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const applyBrightnessToMaterial = (entry: ControlledMaterial, brightness: number) => {
|
|
116
|
+
entry.material.color.setRGB(entry.base.r * brightness, entry.base.g * brightness, entry.base.b * brightness)
|
|
117
|
+
storeLastApplied(entry.material, entry.material.color)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const lightmapParamsEqual = (a?: BlockLightmapParams, b?: BlockLightmapParams) =>
|
|
121
|
+
a === b ||
|
|
122
|
+
(!!a &&
|
|
123
|
+
!!b &&
|
|
124
|
+
(a.curve ?? DEFAULT_LIGHTMAP_PARAMS.curve) === (b.curve ?? DEFAULT_LIGHTMAP_PARAMS.curve) &&
|
|
125
|
+
(a.minBrightness ?? DEFAULT_LIGHTMAP_PARAMS.minBrightness) === (b.minBrightness ?? DEFAULT_LIGHTMAP_PARAMS.minBrightness) &&
|
|
126
|
+
(a.gamma ?? DEFAULT_LIGHTMAP_PARAMS.gamma) === (b.gamma ?? DEFAULT_LIGHTMAP_PARAMS.gamma))
|
|
127
|
+
|
|
128
|
+
export class EntityLightController {
|
|
129
|
+
private readonly roots = new Map<THREE.Object3D, TrackedRoot>()
|
|
130
|
+
private skyLevel = 1
|
|
131
|
+
private lightmapParams: BlockLightmapParams = { ...DEFAULT_LIGHTMAP_PARAMS }
|
|
132
|
+
|
|
133
|
+
constructor(private readonly sampler: EntityLightSampler) {
|
|
134
|
+
this.skyLevel = sampler.getSkyLevel()
|
|
135
|
+
this.lightmapParams = { ...DEFAULT_LIGHTMAP_PARAMS, ...sampler.getLightmapParams() }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setSkyLevel(value: number) {
|
|
139
|
+
this.skyLevel = value
|
|
140
|
+
this.refreshAll()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setLightmapParams(params: BlockLightmapParams) {
|
|
144
|
+
this.lightmapParams = { ...this.lightmapParams, ...params }
|
|
145
|
+
this.refreshAll()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
register(root: THREE.Object3D, meta: EntityLightMeta = {}, sharedMaterials?: Iterable<THREE.Material>) {
|
|
149
|
+
const shared = sharedMaterials ? new Set(sharedMaterials) : undefined
|
|
150
|
+
const existing = this.roots.get(root)
|
|
151
|
+
if (existing) {
|
|
152
|
+
existing.meta = { ...existing.meta, ...meta }
|
|
153
|
+
existing.override = resolveEntityLightOverride(existing.meta.name)
|
|
154
|
+
existing.materialsDirty = true
|
|
155
|
+
existing.lastProbe = undefined
|
|
156
|
+
if (shared) this.discoverMaterials(root, existing, shared)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
const tracked: TrackedRoot = {
|
|
160
|
+
meta,
|
|
161
|
+
override: resolveEntityLightOverride(meta.name),
|
|
162
|
+
materials: [],
|
|
163
|
+
materialsDirty: true,
|
|
164
|
+
flashing: false
|
|
165
|
+
}
|
|
166
|
+
this.roots.set(root, tracked)
|
|
167
|
+
if (shared) this.discoverMaterials(root, tracked, shared)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setMeta(root: THREE.Object3D, meta: EntityLightMeta) {
|
|
171
|
+
const tracked = this.roots.get(root)
|
|
172
|
+
if (!tracked) return
|
|
173
|
+
tracked.meta = { ...tracked.meta, ...meta }
|
|
174
|
+
tracked.override = resolveEntityLightOverride(tracked.meta.name)
|
|
175
|
+
tracked.lastProbe = undefined
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
unregister(root: THREE.Object3D) {
|
|
179
|
+
this.roots.delete(root)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
clear() {
|
|
183
|
+
this.roots.clear()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
invalidateMaterials(root: THREE.Object3D) {
|
|
187
|
+
const tracked = this.roots.get(root)
|
|
188
|
+
if (!tracked) return
|
|
189
|
+
tracked.materialsDirty = true
|
|
190
|
+
tracked.lastProbe = undefined
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
invalidateFromDescendant(node: THREE.Object3D) {
|
|
194
|
+
let current: THREE.Object3D | null = node
|
|
195
|
+
while (current) {
|
|
196
|
+
if (this.roots.has(current)) {
|
|
197
|
+
this.invalidateMaterials(current)
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
current = current.parent
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
markFlash(root: THREE.Object3D, _durationMs?: number) {
|
|
205
|
+
const tracked = this.roots.get(root)
|
|
206
|
+
if (!tracked) return
|
|
207
|
+
tracked.flashing = true
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
endFlash(root: THREE.Object3D) {
|
|
211
|
+
const tracked = this.roots.get(root)
|
|
212
|
+
if (!tracked) return
|
|
213
|
+
tracked.flashing = false
|
|
214
|
+
tracked.lastProbe = undefined
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
update(root: THREE.Object3D, worldPos: EntityLightWorldPos, sharedMaterials?: Iterable<THREE.Material>) {
|
|
218
|
+
const tracked = this.roots.get(root)
|
|
219
|
+
if (!tracked) return
|
|
220
|
+
const materialsWereDirty = tracked.materialsDirty
|
|
221
|
+
if (tracked.materialsDirty) {
|
|
222
|
+
this.discoverMaterials(root, tracked, sharedMaterials ? new Set(sharedMaterials) : undefined)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const lightingEnabled = this.sampler.lightingEnabled()
|
|
226
|
+
const probe = entityProbeBlock(worldPos, resolveEntityEyeHeight(tracked.meta))
|
|
227
|
+
const columnRevision = this.sampler.getColumnRevision(alignColumn(probe.x), alignColumn(probe.z))
|
|
228
|
+
const globalRevision = this.sampler.getGlobalRevision()
|
|
229
|
+
const skyLevel = this.skyLevel
|
|
230
|
+
const lightmapParams = this.lightmapParams
|
|
231
|
+
|
|
232
|
+
const unchanged =
|
|
233
|
+
tracked.lastProbe &&
|
|
234
|
+
tracked.lastProbe.x === probe.x &&
|
|
235
|
+
tracked.lastProbe.y === probe.y &&
|
|
236
|
+
tracked.lastProbe.z === probe.z &&
|
|
237
|
+
tracked.lastColumnRevision === columnRevision &&
|
|
238
|
+
tracked.lastGlobalRevision === globalRevision &&
|
|
239
|
+
tracked.lastSkyLevel === skyLevel &&
|
|
240
|
+
tracked.lastLightingEnabled === lightingEnabled &&
|
|
241
|
+
lightmapParamsEqual(tracked.lastLightmapParams, lightmapParams)
|
|
242
|
+
|
|
243
|
+
if (unchanged && !materialsWereDirty) return
|
|
244
|
+
|
|
245
|
+
tracked.lastProbe = probe
|
|
246
|
+
tracked.lastColumnRevision = columnRevision
|
|
247
|
+
tracked.lastGlobalRevision = globalRevision
|
|
248
|
+
tracked.lastSkyLevel = skyLevel
|
|
249
|
+
tracked.lastLightmapParams = lightmapParams
|
|
250
|
+
tracked.lastLightingEnabled = lightingEnabled
|
|
251
|
+
|
|
252
|
+
if (tracked.flashing) return
|
|
253
|
+
|
|
254
|
+
const brightness = this.computeBrightness(tracked, probe, lightingEnabled)
|
|
255
|
+
tracked.lastBrightness = brightness
|
|
256
|
+
for (const entry of tracked.materials) {
|
|
257
|
+
applyBrightnessToMaterial(entry, brightness)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private refreshAll() {
|
|
262
|
+
for (const [root, tracked] of this.roots) {
|
|
263
|
+
if (tracked.flashing || tracked.materialsDirty) {
|
|
264
|
+
tracked.lastProbe = undefined
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
const lightingEnabled = this.sampler.lightingEnabled()
|
|
268
|
+
const probe = tracked.lastProbe ?? { x: 0, y: 0, z: 0 }
|
|
269
|
+
const brightness = tracked.lastProbe ? this.computeBrightness(tracked, probe, lightingEnabled) : undefined
|
|
270
|
+
if (brightness === undefined) {
|
|
271
|
+
tracked.lastProbe = undefined
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
tracked.lastSkyLevel = this.skyLevel
|
|
275
|
+
tracked.lastLightmapParams = this.lightmapParams
|
|
276
|
+
tracked.lastLightingEnabled = lightingEnabled
|
|
277
|
+
tracked.lastBrightness = brightness
|
|
278
|
+
for (const entry of tracked.materials) {
|
|
279
|
+
applyBrightnessToMaterial(entry, brightness)
|
|
280
|
+
}
|
|
281
|
+
void root
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private computeBrightness(tracked: TrackedRoot, probe: { x: number; y: number; z: number }, lightingEnabled: boolean) {
|
|
286
|
+
if (!lightingEnabled || tracked.override === 'fullbright') return 1
|
|
287
|
+
const sample = this.sampler.getLight(probe.x, probe.y, probe.z)
|
|
288
|
+
return blockEntityBrightness(sample.block, sample.sky, this.skyLevel, this.lightmapParams)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private discoverMaterials(root: THREE.Object3D, tracked: TrackedRoot, sharedMaterials?: Set<THREE.Material>) {
|
|
292
|
+
const next: ControlledMaterial[] = []
|
|
293
|
+
const seen = new Set<THREE.Material>()
|
|
294
|
+
root.traverse(child => {
|
|
295
|
+
if (child.name === 'debug') return
|
|
296
|
+
const raw = (child as THREE.Mesh).material
|
|
297
|
+
if (!raw) return
|
|
298
|
+
const list = Array.isArray(raw) ? raw : [raw]
|
|
299
|
+
let replacedArray = false
|
|
300
|
+
const nextList = list.map((material, index) => {
|
|
301
|
+
if (!hasColor(material) || seen.has(material)) return material
|
|
302
|
+
seen.add(material)
|
|
303
|
+
let current = material
|
|
304
|
+
if (sharedMaterials?.has(current)) {
|
|
305
|
+
current = current.clone() as ColorBearingMaterial
|
|
306
|
+
if (Array.isArray(raw)) {
|
|
307
|
+
list[index] = current
|
|
308
|
+
replacedArray = true
|
|
309
|
+
} else {
|
|
310
|
+
;(child as THREE.Mesh).material = current
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const base = resolveBaseColor(current, tracked.flashing)
|
|
314
|
+
next.push({ material: current, base })
|
|
315
|
+
return current
|
|
316
|
+
})
|
|
317
|
+
if (replacedArray) (child as THREE.Mesh).material = nextList
|
|
318
|
+
})
|
|
319
|
+
tracked.materials = next
|
|
320
|
+
tracked.materialsDirty = false
|
|
321
|
+
}
|
|
322
|
+
}
|