minecraft-renderer 0.1.107 → 0.1.109
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 +84 -84
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +517 -517
- 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/cloudCover.ts +22 -0
- package/src/three/cloudGeometry.ts +71 -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/modules/clouds.ts +98 -75
- package/src/three/tests/cloudGeometry.test.ts +87 -0
- package/src/three/tests/clouds.test.ts +65 -14
- 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,87 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { expect, test } from 'vitest'
|
|
3
|
+
import { buildCloudGeometry, CLOUD_CELL_SIZE, CLOUD_THICKNESS, type CloudCover } from '../cloudGeometry'
|
|
4
|
+
|
|
5
|
+
function coverOf(rows: string[]): CloudCover {
|
|
6
|
+
const height = rows.length
|
|
7
|
+
const width = rows[0].length
|
|
8
|
+
const filled = new Uint8Array(width * height)
|
|
9
|
+
rows.forEach((row, z) => [...row].forEach((ch, x) => (filled[z * width + x] = ch === '#' ? 1 : 0)))
|
|
10
|
+
return { width, height, filled }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const quadCount = (data: ReturnType<typeof buildCloudGeometry>) => data.indices.length / 6
|
|
14
|
+
|
|
15
|
+
/** Normal of triangle `tri` from the index buffer. */
|
|
16
|
+
function triangleNormal(data: ReturnType<typeof buildCloudGeometry>, tri: number): [number, number, number] {
|
|
17
|
+
const p = (k: number) => {
|
|
18
|
+
const v = data.indices[tri * 3 + k] * 3
|
|
19
|
+
return [data.positions[v], data.positions[v + 1], data.positions[v + 2]]
|
|
20
|
+
}
|
|
21
|
+
const [a, b, c] = [p(0), p(1), p(2)]
|
|
22
|
+
const u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]]
|
|
23
|
+
const v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]]
|
|
24
|
+
return [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('a lone cell is a closed box of six faces', () => {
|
|
28
|
+
const data = buildCloudGeometry(coverOf(['....', '.#..', '....', '....']), 1, 1, 0)
|
|
29
|
+
expect(quadCount(data)).toBe(6)
|
|
30
|
+
expect(data.positions.length / 3).toBe(24)
|
|
31
|
+
expect(data.shades.length).toBe(24)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('empty sky produces no geometry', () => {
|
|
35
|
+
expect(buildCloudGeometry(coverOf(['....', '....']), 0, 0, 3).indices.length).toBe(0)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('faces between adjacent cells are not emitted', () => {
|
|
39
|
+
// top + bottom for each cell, two side faces each along z, and one end cap each along x.
|
|
40
|
+
const data = buildCloudGeometry(coverOf(['....', '.##.', '....', '....']), 1, 1, 1)
|
|
41
|
+
expect(quadCount(data)).toBe(2 + 2 + 4 + 2)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('every face winds counter-clockwise seen from outside the box', () => {
|
|
45
|
+
const data = buildCloudGeometry(coverOf(['....', '.#..', '....', '....']), 1, 1, 0)
|
|
46
|
+
const normals = new Set<string>()
|
|
47
|
+
for (let tri = 0; tri < data.indices.length / 3; tri++) {
|
|
48
|
+
const n = triangleNormal(data, tri)
|
|
49
|
+
// Only one axis is non-zero for an axis-aligned face.
|
|
50
|
+
const nonZero = n.filter(c => Math.abs(c) > 1e-6)
|
|
51
|
+
expect(nonZero).toHaveLength(1)
|
|
52
|
+
normals.add(n.map(c => Math.sign(c)).join(','))
|
|
53
|
+
}
|
|
54
|
+
expect([...normals].sort()).toEqual(['-1,0,0', '0,-1,0', '0,0,-1', '0,0,1', '0,1,0', '1,0,0'].sort())
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('faces point away from the cell centre', () => {
|
|
58
|
+
const data = buildCloudGeometry(coverOf(['.#', '..']), 1, 0, 0)
|
|
59
|
+
const centre = [CLOUD_CELL_SIZE / 2, CLOUD_THICKNESS / 2, CLOUD_CELL_SIZE / 2]
|
|
60
|
+
for (let tri = 0; tri < data.indices.length / 3; tri++) {
|
|
61
|
+
const n = triangleNormal(data, tri)
|
|
62
|
+
const v = data.indices[tri * 3] * 3
|
|
63
|
+
const toFace = [data.positions[v] - centre[0], data.positions[v + 1] - centre[1], data.positions[v + 2] - centre[2]]
|
|
64
|
+
expect(n[0] * toFace[0] + n[1] * toFace[1] + n[2] * toFace[2]).toBeGreaterThan(0)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('the slab is four blocks thick and cells are 12 blocks wide', () => {
|
|
69
|
+
const data = buildCloudGeometry(coverOf(['#']), 0, 0, 0)
|
|
70
|
+
const xs = [...data.positions].filter((_, i) => i % 3 === 0)
|
|
71
|
+
const ys = [...data.positions].filter((_, i) => i % 3 === 1)
|
|
72
|
+
expect(Math.max(...ys) - Math.min(...ys)).toBe(4)
|
|
73
|
+
expect(Math.max(...xs) - Math.min(...xs)).toBe(12)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('cell lookup wraps like the texture, including negative indices', () => {
|
|
77
|
+
const cover = coverOf(['#...', '....'])
|
|
78
|
+
// Cells (-4, 0) and (4, 2) alias texel (0, 0); a lone filled texel yields exactly one box per alias.
|
|
79
|
+
expect(quadCount(buildCloudGeometry(cover, -4, 0, 0))).toBe(6)
|
|
80
|
+
expect(quadCount(buildCloudGeometry(cover, 4, 2, 0))).toBe(6)
|
|
81
|
+
expect(quadCount(buildCloudGeometry(cover, 1, 0, 0))).toBe(0)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('shading matches vanilla: top brightest, bottom darkest, sides in between', () => {
|
|
85
|
+
const data = buildCloudGeometry(coverOf(['....', '.#..', '....', '....']), 1, 1, 0)
|
|
86
|
+
expect([...new Set(data.shades)].sort()).toEqual([0.7, 0.8, 0.9, 1].map(Math.fround).sort())
|
|
87
|
+
})
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
-
import {
|
|
3
|
+
import { expect, test, vi } from 'vitest'
|
|
4
4
|
import { CloudsModule } from '../modules/clouds'
|
|
5
5
|
import { defaultWorldRendererConfig } from '../../graphicsBackend/config'
|
|
6
|
+
import type { CloudCover } from '../cloudGeometry'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
// No image decoding in node; the module only needs a texture object.
|
|
9
|
-
vi.spyOn(THREE.TextureLoader.prototype, 'load').mockImplementation(() => new THREE.Texture())
|
|
10
|
-
})
|
|
8
|
+
const cover: CloudCover = { width: 2, height: 2, filled: new Uint8Array([1, 0, 0, 1]) }
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
})
|
|
10
|
+
// No image decoding in node.
|
|
11
|
+
vi.mock('../cloudCover', () => ({ loadCloudCover: vi.fn(async () => cover) }))
|
|
15
12
|
|
|
16
13
|
function createModule(config: Partial<typeof defaultWorldRendererConfig> = {}) {
|
|
17
14
|
const scene = new THREE.Scene()
|
|
@@ -26,6 +23,8 @@ function createModule(config: Partial<typeof defaultWorldRendererConfig> = {}) {
|
|
|
26
23
|
return { module, scene, worldRenderer }
|
|
27
24
|
}
|
|
28
25
|
|
|
26
|
+
const cloudMeshes = (scene: THREE.Scene) => scene.getObjectByName('clouds')!.children as THREE.Mesh[]
|
|
27
|
+
|
|
29
28
|
test('clouds are on by default and auto mode follows the clouds option', () => {
|
|
30
29
|
const { module, worldRenderer } = createModule()
|
|
31
30
|
expect(module.autoEnableCheck()).toBe(true)
|
|
@@ -37,18 +36,18 @@ test('clouds stay off in the nether even when enabled', () => {
|
|
|
37
36
|
expect(createModule({ cardinalLight: 'nether' }).module.autoEnableCheck()).toBe(false)
|
|
38
37
|
})
|
|
39
38
|
|
|
40
|
-
test('disable hides the cloud layer and enable shows the same
|
|
39
|
+
test('disable hides the cloud layer and enable shows the same layer again', () => {
|
|
41
40
|
const { module, scene } = createModule()
|
|
42
41
|
module.enable()
|
|
43
|
-
const
|
|
44
|
-
expect(
|
|
42
|
+
const layer = scene.getObjectByName('clouds')!
|
|
43
|
+
expect(layer.visible).toBe(true)
|
|
45
44
|
|
|
46
45
|
module.disable()
|
|
47
|
-
expect(
|
|
46
|
+
expect(layer.visible).toBe(false)
|
|
48
47
|
|
|
49
48
|
module.enable()
|
|
50
|
-
expect(scene.getObjectByName('clouds')).toBe(
|
|
51
|
-
expect(
|
|
49
|
+
expect(scene.getObjectByName('clouds')).toBe(layer)
|
|
50
|
+
expect(layer.visible).toBe(true)
|
|
52
51
|
})
|
|
53
52
|
|
|
54
53
|
test('dispose removes the cloud layer from the scene', () => {
|
|
@@ -57,3 +56,55 @@ test('dispose removes the cloud layer from the scene', () => {
|
|
|
57
56
|
module.dispose()
|
|
58
57
|
expect(scene.getObjectByName('clouds')).toBeUndefined()
|
|
59
58
|
})
|
|
59
|
+
|
|
60
|
+
test('renders a depth pre-pass before the colour pass over the same geometry', () => {
|
|
61
|
+
const { module, scene } = createModule()
|
|
62
|
+
module.enable()
|
|
63
|
+
const [depthPass, colorPass] = cloudMeshes(scene)
|
|
64
|
+
expect(depthPass.material).toMatchObject({ colorWrite: false, depthWrite: true })
|
|
65
|
+
expect(colorPass.material).toMatchObject({ colorWrite: true, depthWrite: false })
|
|
66
|
+
expect(depthPass.renderOrder).toBeLessThan(colorPass.renderOrder)
|
|
67
|
+
expect(depthPass.geometry).toBe(colorPass.geometry)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('builds volumetric cloud geometry once clouds.png has loaded and follows the camera', async () => {
|
|
71
|
+
const { module, scene, worldRenderer } = createModule()
|
|
72
|
+
module.enable()
|
|
73
|
+
const [depthPass, colorPass] = cloudMeshes(scene)
|
|
74
|
+
const sync = () => (depthPass.onBeforeRender as any)()
|
|
75
|
+
|
|
76
|
+
// Nothing to draw until the texture is decoded.
|
|
77
|
+
sync()
|
|
78
|
+
expect(depthPass.geometry.index).toBeNull()
|
|
79
|
+
|
|
80
|
+
await vi.waitFor(() => {
|
|
81
|
+
sync()
|
|
82
|
+
expect(depthPass.geometry.index).not.toBeNull()
|
|
83
|
+
})
|
|
84
|
+
const geometry = depthPass.geometry
|
|
85
|
+
expect(colorPass.geometry).toBe(geometry)
|
|
86
|
+
// Side faces and a 4-block thickness: more than a flat sheet's top/bottom quads.
|
|
87
|
+
const ys = geometry.getAttribute('position').array.filter((_, i) => i % 3 === 1)
|
|
88
|
+
expect(Math.max(...ys) - Math.min(...ys)).toBe(4)
|
|
89
|
+
expect(geometry.getAttribute('shade')).toBeDefined()
|
|
90
|
+
|
|
91
|
+
// Moving within the same cell keeps the geometry and only moves the layer.
|
|
92
|
+
const before = scene.getObjectByName('clouds')!.position.x
|
|
93
|
+
worldRenderer.sceneOrigin.x += 3
|
|
94
|
+
sync()
|
|
95
|
+
expect(depthPass.geometry).toBe(geometry)
|
|
96
|
+
expect(scene.getObjectByName('clouds')!.position.x).toBeCloseTo(before - 3)
|
|
97
|
+
|
|
98
|
+
// Crossing a cell boundary rebuilds around the new cell.
|
|
99
|
+
worldRenderer.sceneOrigin.x += 24
|
|
100
|
+
sync()
|
|
101
|
+
expect(depthPass.geometry).not.toBe(geometry)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('the layer sits at the configured cloud height above the camera', () => {
|
|
105
|
+
const { module, scene, worldRenderer } = createModule({ cloudHeight: 192 })
|
|
106
|
+
worldRenderer.sceneOrigin.y = 100
|
|
107
|
+
module.enable()
|
|
108
|
+
;(cloudMeshes(scene)[0].onBeforeRender as any)()
|
|
109
|
+
expect(scene.getObjectByName('clouds')!.position.y).toBeCloseTo(192.33 - 100)
|
|
110
|
+
})
|
|
@@ -215,3 +215,101 @@ test('GlobalBlockBuffer: uploadEpoch increments when dirty range drains', () =>
|
|
|
215
215
|
buffer.dispose()
|
|
216
216
|
mat.dispose()
|
|
217
217
|
})
|
|
218
|
+
|
|
219
|
+
function faceWords(faceCount: number): Uint32Array {
|
|
220
|
+
return new Uint32Array(faceCount * 4)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function drawableCubeFaces(buffer: GlobalBlockBuffer, key: string): number {
|
|
224
|
+
const start = buffer.getSectionDrawStart(key)
|
|
225
|
+
const count = buffer.getSectionDrawCount(key)
|
|
226
|
+
if (start === undefined || count === undefined) return 0
|
|
227
|
+
const spans = buildVisibleCubeSpans([{ start, count }], buffer.getHighWatermark(), false, undefined, buffer.getPendingDirtyRanges())
|
|
228
|
+
return spans.reduce((sum, span) => sum + span.count, 0)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
test('GlobalBlockBuffer: compaction must not hide pending-replace displayed faces', () => {
|
|
232
|
+
const scene = new THREE.Scene()
|
|
233
|
+
const mat = createCubeBlockMaterial()
|
|
234
|
+
const buffer = new GlobalBlockBuffer(mat, scene)
|
|
235
|
+
|
|
236
|
+
buffer.addSection('H', faceWords(20_000), 20_000)
|
|
237
|
+
buffer.addSection('A', faceWords(8000), 8000)
|
|
238
|
+
buffer.addSection('B', faceWords(1000), 1000)
|
|
239
|
+
drainUploads(buffer)
|
|
240
|
+
const original = buffer.getSectionDrawStart('A')
|
|
241
|
+
|
|
242
|
+
buffer.addSection('A', faceWords(8000), 8000)
|
|
243
|
+
buffer.removeSection('H')
|
|
244
|
+
buffer.compactStep()
|
|
245
|
+
buffer.uploadDirtyRange()
|
|
246
|
+
|
|
247
|
+
expect(drawableCubeFaces(buffer, 'A')).toBe(8000)
|
|
248
|
+
expect(buffer.getSectionDrawStart('A')).toBe(original)
|
|
249
|
+
const move = buffer.getPendingMove()
|
|
250
|
+
expect(move?.key === 'A').toBe(false)
|
|
251
|
+
|
|
252
|
+
buffer.dispose()
|
|
253
|
+
mat.dispose()
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('GlobalBlockBuffer: 27 simultaneous remeshes keep 8000 faces on every frame', () => {
|
|
257
|
+
const scene = new THREE.Scene()
|
|
258
|
+
const mat = createCubeBlockMaterial()
|
|
259
|
+
const buffer = new GlobalBlockBuffer(mat, scene)
|
|
260
|
+
const n = 27
|
|
261
|
+
const faces = 8000
|
|
262
|
+
|
|
263
|
+
for (let i = 0; i < n; i++) buffer.addSection(String(i), faceWords(faces), faces)
|
|
264
|
+
drainUploads(buffer)
|
|
265
|
+
for (let i = 0; i < n; i++) buffer.addSection(String(i), faceWords(faces), faces)
|
|
266
|
+
|
|
267
|
+
for (let frame = 1; frame <= 120; frame++) {
|
|
268
|
+
buffer.compactStep()
|
|
269
|
+
buffer.uploadDirtyRange()
|
|
270
|
+
for (let i = 0; i < n; i++) {
|
|
271
|
+
expect(drawableCubeFaces(buffer, String(i)), `section ${i} on frame ${frame}`).toBe(faces)
|
|
272
|
+
}
|
|
273
|
+
if (!buffer.hasPendingUploads() && !buffer.hasPendingReplace() && !buffer.getPendingMove()) break
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
buffer.dispose()
|
|
277
|
+
mat.dispose()
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test('GlobalBlockBuffer: remesh during move keeps original displayed slot', () => {
|
|
281
|
+
const scene = new THREE.Scene()
|
|
282
|
+
const mat = createCubeBlockMaterial()
|
|
283
|
+
const buffer = new GlobalBlockBuffer(mat, scene)
|
|
284
|
+
|
|
285
|
+
buffer.addSection('H', faceWords(20_000), 20_000)
|
|
286
|
+
buffer.addSection('B', faceWords(1000), 1000)
|
|
287
|
+
buffer.addSection('A', faceWords(8000), 8000)
|
|
288
|
+
drainUploads(buffer)
|
|
289
|
+
buffer.removeSection('H')
|
|
290
|
+
drainUploads(buffer)
|
|
291
|
+
buffer.compactStep()
|
|
292
|
+
expect(buffer.getPendingMove()?.key).toBe('A')
|
|
293
|
+
const displayed = buffer.getSectionDrawStart('A')
|
|
294
|
+
expect(displayed).toBeDefined()
|
|
295
|
+
|
|
296
|
+
buffer.addSection('A', faceWords(8000), 8000)
|
|
297
|
+
expect(buffer.getSectionDrawStart('A')).toBe(displayed)
|
|
298
|
+
expect(buffer.getPendingMove()).toBeNull()
|
|
299
|
+
expect(buffer.hasPendingReplace()).toBe(true)
|
|
300
|
+
expect(drawableCubeFaces(buffer, 'A')).toBe(8000)
|
|
301
|
+
|
|
302
|
+
buffer.compactStep()
|
|
303
|
+
buffer.uploadDirtyRange()
|
|
304
|
+
expect(buffer.getSectionDrawStart('A')).toBe(displayed)
|
|
305
|
+
expect(buffer.getPendingMove()?.key === 'A').toBe(false)
|
|
306
|
+
expect(drawableCubeFaces(buffer, 'A')).toBe(8000)
|
|
307
|
+
|
|
308
|
+
drainUploads(buffer)
|
|
309
|
+
buffer.compactStep()
|
|
310
|
+
finishCurrentMove(buffer)
|
|
311
|
+
expect(drawableCubeFaces(buffer, 'A')).toBe(8000)
|
|
312
|
+
|
|
313
|
+
buffer.dispose()
|
|
314
|
+
mat.dispose()
|
|
315
|
+
})
|
|
@@ -892,3 +892,111 @@ test('GlobalLegacyBuffer: blend reorder metadata survives compaction', () => {
|
|
|
892
892
|
buffer.dispose()
|
|
893
893
|
mat.dispose()
|
|
894
894
|
})
|
|
895
|
+
|
|
896
|
+
function quadGeometry(quadCount: number): ReturnType<typeof makeQuadGeometry> {
|
|
897
|
+
const indices = new Uint32Array(quadCount * 6)
|
|
898
|
+
for (let i = 0; i < quadCount; i++) {
|
|
899
|
+
const v = i * 4
|
|
900
|
+
indices.set([v, v + 1, v + 2, v, v + 2, v + 3], i * 6)
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
positions: new Float32Array(quadCount * 12),
|
|
904
|
+
colors: new Float32Array(quadCount * 12),
|
|
905
|
+
skyLights: new Float32Array(quadCount * 4),
|
|
906
|
+
blockLights: new Float32Array(quadCount * 4),
|
|
907
|
+
uvs: new Float32Array(quadCount * 8),
|
|
908
|
+
indices
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function drawableLegacyIndices(buffer: GlobalLegacyBuffer, key: string): number {
|
|
913
|
+
buffer.updateDrawSpans([{ key, distSq: 0 }], 'opaque')
|
|
914
|
+
return buffer.getVisibleIndexSpans().reduce((sum, span) => sum + span.indexCount, 0)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
test('GlobalLegacyBuffer: compaction must not hide pending-replace displayed indices', () => {
|
|
918
|
+
const scene = new THREE.Scene()
|
|
919
|
+
const mat = createGlobalLegacyBlockMaterial()
|
|
920
|
+
const buffer = new GlobalLegacyBuffer(mat, scene)
|
|
921
|
+
|
|
922
|
+
buffer.addSection('H', quadGeometry(8000), 0, 0, 0)
|
|
923
|
+
buffer.addSection('A', quadGeometry(3000), 0, 0, 0)
|
|
924
|
+
buffer.addSection('B', quadGeometry(500), 0, 0, 0)
|
|
925
|
+
drainUploads(buffer)
|
|
926
|
+
const original = buffer.getSectionDrawStart('A')
|
|
927
|
+
|
|
928
|
+
buffer.addSection('A', quadGeometry(3000), 0, 0, 0)
|
|
929
|
+
buffer.removeSection('H')
|
|
930
|
+
buffer.compactStep()
|
|
931
|
+
buffer.uploadDirtyRange()
|
|
932
|
+
buffer.updateDrawSpans([{ key: 'A', distSq: 0 }], 'opaque')
|
|
933
|
+
|
|
934
|
+
expect(drawableLegacyIndices(buffer, 'A')).toBe(18_000)
|
|
935
|
+
expect(buffer.getSectionDrawStart('A')).toBe(original)
|
|
936
|
+
expect(buffer.getPendingMove()?.key === 'A').toBe(false)
|
|
937
|
+
|
|
938
|
+
buffer.dispose()
|
|
939
|
+
mat.dispose()
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
test('GlobalLegacyBuffer: 27 simultaneous remeshes keep displayed quads on every frame', () => {
|
|
943
|
+
const scene = new THREE.Scene()
|
|
944
|
+
const mat = createGlobalLegacyBlockMaterial()
|
|
945
|
+
const buffer = new GlobalLegacyBuffer(mat, scene)
|
|
946
|
+
const n = 27
|
|
947
|
+
const quads = 1000
|
|
948
|
+
const expectedIndices = quads * 6
|
|
949
|
+
|
|
950
|
+
for (let i = 0; i < n; i++) buffer.addSection(String(i), quadGeometry(quads), 0, 0, 0)
|
|
951
|
+
drainUploads(buffer)
|
|
952
|
+
for (let i = 0; i < n; i++) buffer.addSection(String(i), quadGeometry(quads), 0, 0, 0)
|
|
953
|
+
|
|
954
|
+
for (let frame = 1; frame <= 160; frame++) {
|
|
955
|
+
buffer.compactStep()
|
|
956
|
+
buffer.uploadDirtyRange()
|
|
957
|
+
for (let i = 0; i < n; i++) {
|
|
958
|
+
expect(drawableLegacyIndices(buffer, String(i)), `section ${i} on frame ${frame}`).toBe(expectedIndices)
|
|
959
|
+
}
|
|
960
|
+
if (!buffer.hasPendingUploads() && !buffer.hasPendingReplace() && !buffer.getPendingMove()) break
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
buffer.dispose()
|
|
964
|
+
mat.dispose()
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
test('GlobalLegacyBuffer: remesh during move keeps original displayed slot', () => {
|
|
968
|
+
const scene = new THREE.Scene()
|
|
969
|
+
const mat = createGlobalLegacyBlockMaterial()
|
|
970
|
+
const buffer = new GlobalLegacyBuffer(mat, scene)
|
|
971
|
+
|
|
972
|
+
buffer.addSection('H', quadGeometry(8000), 0, 0, 0)
|
|
973
|
+
buffer.addSection('B', quadGeometry(500), 0, 0, 0)
|
|
974
|
+
buffer.addSection('A', quadGeometry(3000), 0, 0, 0)
|
|
975
|
+
drainUploads(buffer)
|
|
976
|
+
buffer.removeSection('H')
|
|
977
|
+
drainUploads(buffer)
|
|
978
|
+
buffer.compactStep()
|
|
979
|
+
expect(buffer.getPendingMove()?.key).toBe('A')
|
|
980
|
+
const displayed = buffer.getSectionDrawStart('A')
|
|
981
|
+
expect(displayed).toBeDefined()
|
|
982
|
+
|
|
983
|
+
buffer.addSection('A', quadGeometry(3000), 0, 0, 0)
|
|
984
|
+
expect(buffer.getSectionDrawStart('A')).toBe(displayed)
|
|
985
|
+
expect(buffer.getPendingMove()).toBeNull()
|
|
986
|
+
expect(buffer.hasPendingReplace()).toBe(true)
|
|
987
|
+
expect(drawableLegacyIndices(buffer, 'A')).toBe(18_000)
|
|
988
|
+
|
|
989
|
+
buffer.compactStep()
|
|
990
|
+
buffer.uploadDirtyRange()
|
|
991
|
+
expect(buffer.getSectionDrawStart('A')).toBe(displayed)
|
|
992
|
+
expect(buffer.getPendingMove()?.key === 'A').toBe(false)
|
|
993
|
+
expect(drawableLegacyIndices(buffer, 'A')).toBe(18_000)
|
|
994
|
+
|
|
995
|
+
drainUploads(buffer)
|
|
996
|
+
buffer.compactStep()
|
|
997
|
+
finishCurrentMove(buffer)
|
|
998
|
+
expect(drawableLegacyIndices(buffer, 'A')).toBe(18_000)
|
|
999
|
+
|
|
1000
|
+
buffer.dispose()
|
|
1001
|
+
mat.dispose()
|
|
1002
|
+
})
|
|
@@ -40,6 +40,9 @@ import { FireworksManager } from './fireworks'
|
|
|
40
40
|
import { SceneOrigin } from './sceneOrigin'
|
|
41
41
|
import { downloadWorldGeometry } from './worldGeometryExport'
|
|
42
42
|
import { ChunkMeshManager } from './chunkMeshManager'
|
|
43
|
+
import { selectReadySectionFlushes } from '../lighting/versioning/pendingSectionFlush'
|
|
44
|
+
import { isClientLightTraceEnabled, recordClientLightTrace } from '../lighting/debug/clientLightTrace'
|
|
45
|
+
import { EntityLightController } from '../lighting/entity/entityLightController'
|
|
43
46
|
import { raycastVoxelSolid } from './thirdPersonVoxelRaycast'
|
|
44
47
|
import type { RendererModuleManifest, RegisteredModule, RendererModuleController } from './rendererModuleSystem'
|
|
45
48
|
import { BUILTIN_MODULES } from './modules/index'
|
|
@@ -89,6 +92,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
89
92
|
ambientLight = new THREE.AmbientLight(0xcc_cc_cc, WorldRendererThree.LEGACY_TO_PHYSICAL_LIGHT)
|
|
90
93
|
directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5 * WorldRendererThree.LEGACY_TO_PHYSICAL_LIGHT)
|
|
91
94
|
entities = new Entities(this, (globalThis as any).mcData)
|
|
95
|
+
entityLightController?: EntityLightController
|
|
92
96
|
performanceMonitor!: PerformanceMonitor
|
|
93
97
|
cameraGroupVr?: THREE.Object3D
|
|
94
98
|
material = new THREE.MeshBasicMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 })
|
|
@@ -204,6 +208,14 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
204
208
|
|
|
205
209
|
// Initialize chunk mesh manager
|
|
206
210
|
this.chunkMeshManager = new ChunkMeshManager(this, this.scene, this.material, this.worldSizeParams.worldHeight, this.viewDistance)
|
|
211
|
+
this.entityLightController = new EntityLightController({
|
|
212
|
+
getLight: (x, y, z) => this.rendererLightCache.getLight(x, y, z),
|
|
213
|
+
getSkyLevel: () => this.chunkMeshManager.getSkyLevel(),
|
|
214
|
+
getLightmapParams: () => this.chunkMeshManager.getLightmapParams(),
|
|
215
|
+
getColumnRevision: (cx, cz) => this.rendererLightCache.getColumnRevision(cx, cz),
|
|
216
|
+
getGlobalRevision: () => this.rendererLightCache.getGlobalRevision(),
|
|
217
|
+
lightingEnabled: () => this.worldRendererConfig.enableLighting !== false
|
|
218
|
+
})
|
|
207
219
|
this.onRenderDistanceChanged = viewDistance => {
|
|
208
220
|
this.chunkMeshManager.updateViewDistance(viewDistance)
|
|
209
221
|
}
|
|
@@ -563,6 +575,9 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
563
575
|
this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes)
|
|
564
576
|
})
|
|
565
577
|
this.onReactivePlayerStateUpdated('diggingBlock', value => {
|
|
578
|
+
if (value && (value.stage == null || value.stage === 0) && isClientLightTraceEnabled()) {
|
|
579
|
+
recordClientLightTrace({ phase: 'inputClick', sectionKey: `${value.x},${value.y},${value.z}` })
|
|
580
|
+
}
|
|
566
581
|
this.cursorBlock.updateBreakAnimation(value ? { x: value.x, y: value.y, z: value.z } : undefined, value?.stage ?? null, value?.mergedShape)
|
|
567
582
|
})
|
|
568
583
|
this.onReactivePlayerStateUpdated('perspective', value => {
|
|
@@ -594,6 +609,13 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
594
609
|
this.onReactiveConfigUpdated('futuristicReveal', () => {
|
|
595
610
|
this.updateModulesFromConfig()
|
|
596
611
|
})
|
|
612
|
+
this.onReactiveConfigUpdated(
|
|
613
|
+
'enableLighting',
|
|
614
|
+
() => {
|
|
615
|
+
this.rerenderAllChunks()
|
|
616
|
+
},
|
|
617
|
+
false
|
|
618
|
+
)
|
|
597
619
|
this.onReactiveDebugUpdated('disableEntities', () => {
|
|
598
620
|
this.entities.syncSceneAttachment()
|
|
599
621
|
})
|
|
@@ -830,6 +852,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
830
852
|
if (camCollisionBytes > 0) {
|
|
831
853
|
text += `CAM: ${formatCompact(camCollisionBytes)}b `
|
|
832
854
|
}
|
|
855
|
+
const entityLightBytes = this.rendererLightCache.getAllocatedBytes()
|
|
856
|
+
if (entityLightBytes > 0) {
|
|
857
|
+
text += `ELIGHT: ${formatCompact(entityLightBytes)}b `
|
|
858
|
+
}
|
|
833
859
|
const pf = formatPerformanceFactorsDebug(this.reactiveState.world.instabilityFactors)
|
|
834
860
|
if (pf) text += `PF: ${pf} `
|
|
835
861
|
// entities can be seen in F3
|
|
@@ -884,45 +910,37 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
884
910
|
if (this.pendingSectionUpdates.size === 0) return
|
|
885
911
|
|
|
886
912
|
const now = performance.now()
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
`${sx - 16},${sy},${sz}`,
|
|
901
|
-
`${sx + 16},${sy},${sz}`,
|
|
902
|
-
`${sx},${sy - sectionHeight},${sz}`,
|
|
903
|
-
`${sx},${sy + sectionHeight},${sz}`,
|
|
904
|
-
`${sx},${sy},${sz - 16}`,
|
|
905
|
-
`${sx},${sy},${sz + 16}`
|
|
906
|
-
]
|
|
907
|
-
let neighborBusy = false
|
|
908
|
-
for (const neighborKey of neighborKeys) {
|
|
909
|
-
if (this.sectionsWaiting.has(neighborKey) && !this.pendingSectionUpdates.has(neighborKey) && this.sectionObjects[neighborKey]) {
|
|
910
|
-
neighborBusy = true
|
|
911
|
-
break
|
|
913
|
+
// Face-adjacent buffered sections are installed as one group: a lone
|
|
914
|
+
// install would show the neighbour's faces still culled against the old
|
|
915
|
+
// block state, i.e. a see-through hole (sky flash) on dig/place.
|
|
916
|
+
const ready = selectReadySectionFlushes({
|
|
917
|
+
pendingKeys: this.pendingSectionUpdates.keys(),
|
|
918
|
+
startedAt: key => this.pendingSectionBufferStartTimes.get(key),
|
|
919
|
+
now,
|
|
920
|
+
maxBufferMs: WorldRendererThree.MAX_SECTION_UPDATE_BUFFER_MS,
|
|
921
|
+
sectionHeight: this.getSectionHeight(),
|
|
922
|
+
isOutstanding: key => this.sectionsWaiting.has(key) && !this.pendingSectionUpdates.has(key),
|
|
923
|
+
...(this.lightCoordinator.isSessionLive()
|
|
924
|
+
? {
|
|
925
|
+
isTopologyOutstanding: (key: string) => this.lightCoordinator.pendingTopologyBySection.has(key) && !this.pendingSectionUpdates.has(key)
|
|
912
926
|
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
ready.push(key)
|
|
918
|
-
}
|
|
927
|
+
: {}),
|
|
928
|
+
hasSectionObject: key => !!this.sectionObjects[key]
|
|
929
|
+
})
|
|
919
930
|
|
|
920
931
|
if (ready.length === 0) return
|
|
921
932
|
|
|
922
|
-
for (const key of ready) {
|
|
933
|
+
for (const { key, reason } of ready) {
|
|
923
934
|
const update = this.pendingSectionUpdates.get(key)!
|
|
924
935
|
this.pendingSectionUpdates.delete(key)
|
|
925
936
|
this.pendingSectionBufferStartTimes.delete(key)
|
|
937
|
+
if (isClientLightTraceEnabled()) {
|
|
938
|
+
recordClientLightTrace({
|
|
939
|
+
phase: 'flush',
|
|
940
|
+
sectionKey: key,
|
|
941
|
+
flushReason: reason
|
|
942
|
+
})
|
|
943
|
+
}
|
|
926
944
|
|
|
927
945
|
const chunkCoords = update.key.split(',')
|
|
928
946
|
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
@@ -932,6 +950,14 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
932
950
|
continue
|
|
933
951
|
}
|
|
934
952
|
|
|
953
|
+
const decision = this.lightCoordinator.evaluateOwnerGeometry(update)
|
|
954
|
+
if (!decision.accepted || update.geometry?.hadErrors) {
|
|
955
|
+
this.lightCoordinator.noteRejectedOwnerGeometry(update.key, undefined, { expectLaterFinished: false })
|
|
956
|
+
continue
|
|
957
|
+
}
|
|
958
|
+
this.lightCoordinator.acknowledgeAcceptedTopology(update)
|
|
959
|
+
if (decision.keepCovering) this.lightCoordinator.maybeDispatchCoveringRemesh(update.key)
|
|
960
|
+
|
|
935
961
|
if (!this.chunkMeshManager.sectionHasRenderableContent(update.geometry)) {
|
|
936
962
|
this.chunkMeshManager.releaseSection(update.key)
|
|
937
963
|
continue
|
|
@@ -966,6 +992,14 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
966
992
|
return
|
|
967
993
|
}
|
|
968
994
|
|
|
995
|
+
const decision = this.lightCoordinator.evaluateOwnerGeometry(data)
|
|
996
|
+
if (!decision.accepted || data.geometry?.hadErrors) {
|
|
997
|
+
this.lightCoordinator.noteRejectedOwnerGeometry(data.key, undefined, { expectLaterFinished: true })
|
|
998
|
+
return
|
|
999
|
+
}
|
|
1000
|
+
this.lightCoordinator.acknowledgeAcceptedTopology(data)
|
|
1001
|
+
if (decision.keepCovering) this.lightCoordinator.maybeDispatchCoveringRemesh(data.key)
|
|
1002
|
+
|
|
969
1003
|
if (this.sectionObjects[data.key]) {
|
|
970
1004
|
this.pendingSectionUpdates.set(data.key, data)
|
|
971
1005
|
// Per-section deadline: only set if we don't already have one, so
|
|
@@ -973,6 +1007,13 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
973
1007
|
if (!this.pendingSectionBufferStartTimes.has(data.key)) {
|
|
974
1008
|
this.pendingSectionBufferStartTimes.set(data.key, performance.now())
|
|
975
1009
|
}
|
|
1010
|
+
if (isClientLightTraceEnabled()) {
|
|
1011
|
+
recordClientLightTrace({
|
|
1012
|
+
phase: 'pending',
|
|
1013
|
+
sectionKey: data.key,
|
|
1014
|
+
queueDepth: this.pendingSectionUpdates.size
|
|
1015
|
+
})
|
|
1016
|
+
}
|
|
976
1017
|
return
|
|
977
1018
|
}
|
|
978
1019
|
|
|
@@ -1552,8 +1593,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1552
1593
|
}
|
|
1553
1594
|
}
|
|
1554
1595
|
|
|
1555
|
-
removeColumn(x, z) {
|
|
1556
|
-
super.removeColumn(x, z)
|
|
1596
|
+
removeColumn(x, z, opts?: { preserveClientLight?: boolean }) {
|
|
1597
|
+
super.removeColumn(x, z, opts)
|
|
1557
1598
|
|
|
1558
1599
|
this.clearPendingSectionUpdatesForChunk(x, z)
|
|
1559
1600
|
const sectionHeight = this.getSectionHeight()
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// This file takes WASM output and generates full Three.js buffer geometry
|
|
4
4
|
|
|
5
5
|
import * as THREE from 'three'
|
|
6
|
+
import { isClientLightProbeBlock } from '../../lighting/debug/clientLightProbe'
|
|
6
7
|
import worldBlockProviderModule, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
7
8
|
import blocksAtlasesJson from 'mc-assets/dist/blocksAtlases.json'
|
|
8
9
|
import blockStatesModels from 'mc-assets/dist/blockStatesModels.json'
|
|
@@ -692,7 +693,26 @@ export function renderWasmOutputToGeometry(
|
|
|
692
693
|
},
|
|
693
694
|
shaderWordBuffer
|
|
694
695
|
)
|
|
695
|
-
if (emitted)
|
|
696
|
+
if (emitted) {
|
|
697
|
+
if (isClientLightProbeBlock(bx, by, bz)) {
|
|
698
|
+
console.log('[lightProbe:shaderCube]', {
|
|
699
|
+
time: Date.now(),
|
|
700
|
+
position: [bx, by, bz],
|
|
701
|
+
blockStateId,
|
|
702
|
+
name: prismBlock.name,
|
|
703
|
+
useModelLighting: false,
|
|
704
|
+
wasmSky: block.sky_light_data?.map(values => [...values]),
|
|
705
|
+
visibleFaces: block.visible_faces,
|
|
706
|
+
jsNeighborSky: Object.fromEntries(
|
|
707
|
+
Object.entries(elemFaces).map(([face, info]) => [
|
|
708
|
+
face,
|
|
709
|
+
world?.getChannelLightNorm(new Vec3(bx, by, bz).offset(info.dir[0], info.dir[1], info.dir[2])).sky
|
|
710
|
+
])
|
|
711
|
+
)
|
|
712
|
+
})
|
|
713
|
+
}
|
|
714
|
+
continue
|
|
715
|
+
}
|
|
696
716
|
}
|
|
697
717
|
}
|
|
698
718
|
|
|
@@ -842,6 +862,20 @@ export function renderWasmOutputToGeometry(
|
|
|
842
862
|
const tint = getTint(matchingEFace, cachedModel.blockName, cachedModel.blockProps, biome, world)
|
|
843
863
|
|
|
844
864
|
const useModelLighting = (!cachedModel.isCube || globalMatrix != null) && world
|
|
865
|
+
if (isClientLightProbeBlock(bx, by, bz)) {
|
|
866
|
+
console.log('[lightProbe:face]', {
|
|
867
|
+
time: Date.now(),
|
|
868
|
+
position: [bx, by, bz],
|
|
869
|
+
faceName,
|
|
870
|
+
blockStateId,
|
|
871
|
+
name: prismBlock.name,
|
|
872
|
+
isCube: cachedModel.isCube,
|
|
873
|
+
rotated: globalMatrix != null,
|
|
874
|
+
useModelLighting: Boolean(useModelLighting),
|
|
875
|
+
wasmSky: skyValuesRaw ? [...skyValuesRaw] : null,
|
|
876
|
+
jsNeighborSky: world?.getChannelLightNorm(new Vec3(bx, by, bz).offset(...transformedDirI)).sky
|
|
877
|
+
})
|
|
878
|
+
}
|
|
845
879
|
const baseChannels = useModelLighting
|
|
846
880
|
? world.getChannelLightNorm(new Vec3(bx, by, bz).offset(transformedDirI[0], transformedDirI[1], transformedDirI[2]))
|
|
847
881
|
: null
|
|
@@ -921,6 +955,17 @@ export function renderWasmOutputToGeometry(
|
|
|
921
955
|
tgtCol.push(tintAo[0], tintAo[1], tintAo[2])
|
|
922
956
|
tgtSky.push(skyLightNorm)
|
|
923
957
|
tgtBlock.push(blockLightNorm)
|
|
958
|
+
if (isClientLightProbeBlock(bx, by, bz)) {
|
|
959
|
+
console.log('[lightProbe:vertex]', {
|
|
960
|
+
time: Date.now(),
|
|
961
|
+
position: [bx, by, bz],
|
|
962
|
+
faceName,
|
|
963
|
+
cornerIdx,
|
|
964
|
+
useModelLighting: Boolean(useModelLighting),
|
|
965
|
+
skyLightNorm,
|
|
966
|
+
blockLightNorm
|
|
967
|
+
})
|
|
968
|
+
}
|
|
924
969
|
|
|
925
970
|
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
|
|
926
971
|
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
|