minecraft-renderer 0.1.106 → 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 +105 -76
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +537 -508
- 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 +8 -0
- package/src/graphicsBackend/rendererDefaultOptions.test.ts +51 -0
- package/src/graphicsBackend/rendererDefaultOptions.ts +18 -1
- package/src/graphicsBackend/rendererOptionsSync.ts +4 -3
- package/src/lib/blockEntityLightRegistry.ts +4 -0
- package/src/lib/skyLight.ts +17 -0
- package/src/lib/worldrendererCommon.lightOwner.test.ts +916 -0
- package/src/lib/worldrendererCommon.removeColumn.test.ts +16 -0
- package/src/lib/worldrendererCommon.ts +340 -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 +97 -8
- package/src/three/entity/EntityMesh.ts +1 -0
- package/src/three/entity/legWalkAnimation.test.ts +154 -0
- package/src/three/entity/legWalkAnimation.ts +193 -0
- 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 +191 -0
- package/src/three/modules/index.ts +4 -0
- package/src/three/modules/sunMoon.ts +151 -0
- package/src/three/skyboxRenderer.ts +15 -8
- package/src/three/tests/clouds.test.ts +59 -0
- package/src/three/tests/globalBlockBuffer.test.ts +98 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +108 -0
- package/src/three/tests/waterFog.test.ts +25 -0
- package/src/three/waterFog.ts +28 -0
- package/src/three/worldRendererThree.ts +84 -39
- 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 +3 -0
- package/src/worldView/worldView.geometryReload.test.ts +68 -0
- package/src/worldView/worldView.spiral.test.ts +1 -1
- package/src/worldView/worldView.ts +49 -1
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { expect, test } from 'vitest'
|
|
3
|
+
import { getWaterFogEnd, getWaterVision, waterFogColorToRgb } from '../waterFog'
|
|
4
|
+
|
|
5
|
+
test('water vision ramps with sqrt over 30 seconds and then stays at 1', () => {
|
|
6
|
+
expect(getWaterVision(0)).toBe(0)
|
|
7
|
+
expect(getWaterVision(7.5)).toBeCloseTo(0.5)
|
|
8
|
+
expect(getWaterVision(30)).toBe(1)
|
|
9
|
+
expect(getWaterVision(300)).toBe(1)
|
|
10
|
+
expect(getWaterVision(-5)).toBe(0)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('fog distance starts at 24 blocks, reaches 96 and breathing skips the ramp', () => {
|
|
14
|
+
expect(getWaterFogEnd(0, false)).toBe(24)
|
|
15
|
+
expect(getWaterFogEnd(7.5, false)).toBeCloseTo(48)
|
|
16
|
+
expect(getWaterFogEnd(30, false)).toBe(96)
|
|
17
|
+
expect(getWaterFogEnd(0, true)).toBe(96)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('default water fog colour is the vanilla dark navy', () => {
|
|
21
|
+
const [r, g, b] = waterFogColorToRgb(0x05_05_33)
|
|
22
|
+
expect(r).toBeCloseTo(5 / 255)
|
|
23
|
+
expect(g).toBeCloseTo(5 / 255)
|
|
24
|
+
expect(b).toBeCloseTo(51 / 255)
|
|
25
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/** Vanilla `Biome.waterFogColor` for most biomes (0x050533). */
|
|
3
|
+
export const DEFAULT_WATER_FOG_COLOR = 0x05_05_33
|
|
4
|
+
|
|
5
|
+
/** Vanilla `FogRenderer`: water fog ends 96 blocks away at full vision. */
|
|
6
|
+
export const WATER_FOG_FULL_DISTANCE = 96
|
|
7
|
+
/** Vision never drops below a quarter of the full distance, so 24 blocks right after diving in. */
|
|
8
|
+
export const WATER_FOG_MIN_VISION = 0.25
|
|
9
|
+
/** `LocalPlayer.waterVisionTime` caps at 600 ticks (30s at 20 TPS). */
|
|
10
|
+
export const WATER_VISION_TICKS = 600
|
|
11
|
+
export const TICKS_PER_SECOND = 20
|
|
12
|
+
|
|
13
|
+
/** Vanilla `LocalPlayer.getWaterVision`: sqrt ramp from 0 to 1 over 30s spent underwater. */
|
|
14
|
+
export function getWaterVision(secondsUnderwater: number): number {
|
|
15
|
+
const ticks = Math.min(Math.max(secondsUnderwater, 0) * TICKS_PER_SECOND, WATER_VISION_TICKS)
|
|
16
|
+
return Math.sqrt(ticks / WATER_VISION_TICKS)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Distance at which water fog is fully opaque. Conduit-like water breathing gives full vision at once. */
|
|
20
|
+
export function getWaterFogEnd(secondsUnderwater: number, waterBreathing: boolean): number {
|
|
21
|
+
const vision = waterBreathing ? 1 : getWaterVision(secondsUnderwater)
|
|
22
|
+
return WATER_FOG_FULL_DISTANCE * Math.max(WATER_FOG_MIN_VISION, vision)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 0xRRGGBB to 0..1 components (same raw-component convention the land fog already uses). */
|
|
26
|
+
export function waterFogColorToRgb(color: number): [number, number, number] {
|
|
27
|
+
return [((color >> 16) & 0xff) / 255, ((color >> 8) & 0xff) / 255, (color & 0xff) / 255]
|
|
28
|
+
}
|
|
@@ -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
|
})
|
|
@@ -732,16 +754,20 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
732
754
|
}
|
|
733
755
|
|
|
734
756
|
timeUpdated(newTime: number): void {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (starfieldModule?.updateTimeOfDay) {
|
|
738
|
-
starfieldModule.updateTimeOfDay(newTime)
|
|
757
|
+
for (const module of Object.values(this.modules)) {
|
|
758
|
+
;(module.controller as { updateTimeOfDay?: (time: number) => void }).updateTimeOfDay?.(newTime)
|
|
739
759
|
}
|
|
740
760
|
|
|
741
761
|
this.skyboxRenderer.updateTime(newTime)
|
|
742
762
|
this.syncSkyLevelFromTime(newTime)
|
|
743
763
|
}
|
|
744
764
|
|
|
765
|
+
moonPhaseUpdated(phase: number): void {
|
|
766
|
+
for (const module of Object.values(this.modules)) {
|
|
767
|
+
;(module.controller as { updateMoonPhase?: (phase: number) => void }).updateMoonPhase?.(phase)
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
745
771
|
private syncSkyLevelFromTime(timeOfDay: number): void {
|
|
746
772
|
const skyLevel = calculateSkyLightSimple(timeOfDay) / 15
|
|
747
773
|
this.chunkMeshManager.setSkyLevel(skyLevel)
|
|
@@ -826,6 +852,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
826
852
|
if (camCollisionBytes > 0) {
|
|
827
853
|
text += `CAM: ${formatCompact(camCollisionBytes)}b `
|
|
828
854
|
}
|
|
855
|
+
const entityLightBytes = this.rendererLightCache.getAllocatedBytes()
|
|
856
|
+
if (entityLightBytes > 0) {
|
|
857
|
+
text += `ELIGHT: ${formatCompact(entityLightBytes)}b `
|
|
858
|
+
}
|
|
829
859
|
const pf = formatPerformanceFactorsDebug(this.reactiveState.world.instabilityFactors)
|
|
830
860
|
if (pf) text += `PF: ${pf} `
|
|
831
861
|
// entities can be seen in F3
|
|
@@ -880,45 +910,37 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
880
910
|
if (this.pendingSectionUpdates.size === 0) return
|
|
881
911
|
|
|
882
912
|
const now = performance.now()
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
`${sx - 16},${sy},${sz}`,
|
|
897
|
-
`${sx + 16},${sy},${sz}`,
|
|
898
|
-
`${sx},${sy - sectionHeight},${sz}`,
|
|
899
|
-
`${sx},${sy + sectionHeight},${sz}`,
|
|
900
|
-
`${sx},${sy},${sz - 16}`,
|
|
901
|
-
`${sx},${sy},${sz + 16}`
|
|
902
|
-
]
|
|
903
|
-
let neighborBusy = false
|
|
904
|
-
for (const neighborKey of neighborKeys) {
|
|
905
|
-
if (this.sectionsWaiting.has(neighborKey) && !this.pendingSectionUpdates.has(neighborKey) && this.sectionObjects[neighborKey]) {
|
|
906
|
-
neighborBusy = true
|
|
907
|
-
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)
|
|
908
926
|
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
ready.push(key)
|
|
914
|
-
}
|
|
927
|
+
: {}),
|
|
928
|
+
hasSectionObject: key => !!this.sectionObjects[key]
|
|
929
|
+
})
|
|
915
930
|
|
|
916
931
|
if (ready.length === 0) return
|
|
917
932
|
|
|
918
|
-
for (const key of ready) {
|
|
933
|
+
for (const { key, reason } of ready) {
|
|
919
934
|
const update = this.pendingSectionUpdates.get(key)!
|
|
920
935
|
this.pendingSectionUpdates.delete(key)
|
|
921
936
|
this.pendingSectionBufferStartTimes.delete(key)
|
|
937
|
+
if (isClientLightTraceEnabled()) {
|
|
938
|
+
recordClientLightTrace({
|
|
939
|
+
phase: 'flush',
|
|
940
|
+
sectionKey: key,
|
|
941
|
+
flushReason: reason
|
|
942
|
+
})
|
|
943
|
+
}
|
|
922
944
|
|
|
923
945
|
const chunkCoords = update.key.split(',')
|
|
924
946
|
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
|
@@ -928,6 +950,14 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
928
950
|
continue
|
|
929
951
|
}
|
|
930
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
|
+
|
|
931
961
|
if (!this.chunkMeshManager.sectionHasRenderableContent(update.geometry)) {
|
|
932
962
|
this.chunkMeshManager.releaseSection(update.key)
|
|
933
963
|
continue
|
|
@@ -962,6 +992,14 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
962
992
|
return
|
|
963
993
|
}
|
|
964
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
|
+
|
|
965
1003
|
if (this.sectionObjects[data.key]) {
|
|
966
1004
|
this.pendingSectionUpdates.set(data.key, data)
|
|
967
1005
|
// Per-section deadline: only set if we don't already have one, so
|
|
@@ -969,6 +1007,13 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
969
1007
|
if (!this.pendingSectionBufferStartTimes.has(data.key)) {
|
|
970
1008
|
this.pendingSectionBufferStartTimes.set(data.key, performance.now())
|
|
971
1009
|
}
|
|
1010
|
+
if (isClientLightTraceEnabled()) {
|
|
1011
|
+
recordClientLightTrace({
|
|
1012
|
+
phase: 'pending',
|
|
1013
|
+
sectionKey: data.key,
|
|
1014
|
+
queueDepth: this.pendingSectionUpdates.size
|
|
1015
|
+
})
|
|
1016
|
+
}
|
|
972
1017
|
return
|
|
973
1018
|
}
|
|
974
1019
|
|
|
@@ -1325,7 +1370,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1325
1370
|
|
|
1326
1371
|
// Update skybox position to follow camera
|
|
1327
1372
|
const cameraPos = this.getCameraPosition()
|
|
1328
|
-
this.skyboxRenderer.update(cameraPos, this.viewDistance)
|
|
1373
|
+
this.skyboxRenderer.update(cameraPos, this.viewDistance, deltaTime)
|
|
1329
1374
|
|
|
1330
1375
|
this.updateSmoothFov()
|
|
1331
1376
|
if (sizeChanged) {
|
|
@@ -1548,8 +1593,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1548
1593
|
}
|
|
1549
1594
|
}
|
|
1550
1595
|
|
|
1551
|
-
removeColumn(x, z) {
|
|
1552
|
-
super.removeColumn(x, z)
|
|
1596
|
+
removeColumn(x, z, opts?: { preserveClientLight?: boolean }) {
|
|
1597
|
+
super.removeColumn(x, z, opts)
|
|
1553
1598
|
|
|
1554
1599
|
this.clearPendingSectionUpdatesForChunk(x, z)
|
|
1555
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
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
/* tslint:disable */
|
|
3
3
|
/* eslint-disable */
|
|
4
4
|
|
|
5
|
+
export class JsLightEngine {
|
|
6
|
+
free(): void;
|
|
7
|
+
[Symbol.dispose](): void;
|
|
8
|
+
pushEvent(event: any): void;
|
|
9
|
+
getSkyLight(x: number, y: number, z: number): number;
|
|
10
|
+
getBlockLight(x: number, y: number, z: number): number;
|
|
11
|
+
setLightTables(emission: Uint8Array, opacity: Uint8Array): void;
|
|
12
|
+
setOcclusionTable(occupancy: Uint8Array): void;
|
|
13
|
+
setSkyLightEnabled(enabled: boolean): void;
|
|
14
|
+
pollCompletedPublication(): any;
|
|
15
|
+
step(budget_ms: number): boolean;
|
|
16
|
+
constructor(world_min_y: number, world_height: number);
|
|
17
|
+
readonly fallbackRelights: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
/**
|
|
6
21
|
* Compute wireframe edge positions from a triangle mesh.
|
|
7
22
|
*
|
|
@@ -210,11 +225,11 @@ export function parseMapChunkV18Plus(raw_packet: Uint8Array, num_sections: numbe
|
|
|
210
225
|
* `raw_packet` includes the leading packet-id varint (we skip it).
|
|
211
226
|
* `num_sections` should match the column the light is for (16 in 1.17).
|
|
212
227
|
*
|
|
213
|
-
* Returns `{ x, z, skyLight
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* (`x + z*16 + y_abs*256`)
|
|
217
|
-
*
|
|
228
|
+
* Returns `{ x, z, trustEdges, skyLight, blockLight, skyLightMask,
|
|
229
|
+
* emptySkyLightMask, blockLightMask, emptyBlockLightMask,
|
|
230
|
+
* skyBelow?, skyAbove?, blockBelow?, blockAbove?, bytesRead }`.
|
|
231
|
+
* World arrays are `num_sections * 4096` (`x + z*16 + y_abs*256`).
|
|
232
|
+
* Omitted sections are left 0 and are not authoritative — use the masks.
|
|
218
233
|
*/
|
|
219
234
|
export function parseUpdateLightV17(raw_packet: Uint8Array, num_sections: number): any;
|
|
220
235
|
|
|
@@ -227,6 +242,7 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
227
242
|
|
|
228
243
|
export interface InitOutput {
|
|
229
244
|
readonly memory: WebAssembly.Memory;
|
|
245
|
+
readonly __wbg_jslightengine_free: (a: number, b: number) => void;
|
|
230
246
|
readonly computeWireframeEdges: (a: number, b: number, c: number, d: number) => [number, number];
|
|
231
247
|
readonly computeWireframeEdgesU16: (a: number, b: number, c: number, d: number) => [number, number];
|
|
232
248
|
readonly generateGeometryFromDump118: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number, i1: number, j1: number, k1: number) => any;
|
|
@@ -236,6 +252,16 @@ export interface InitOutput {
|
|
|
236
252
|
readonly generateGeometryFromParsedV16V17Multi: (a: any, b: number, c: number, d: number, e: number, f: number, g: any, h: number, i: any, j: any, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number, g1: number, h1: number) => any;
|
|
237
253
|
readonly generate_geometry: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number) => any;
|
|
238
254
|
readonly generate_geometry_multi: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number, v: number, w: number, x: number, y: number, z: number, a1: number, b1: number, c1: number, d1: number, e1: number, f1: number) => any;
|
|
255
|
+
readonly jslightengine_fallbackRelights: (a: number) => number;
|
|
256
|
+
readonly jslightengine_getBlockLight: (a: number, b: number, c: number, d: number) => number;
|
|
257
|
+
readonly jslightengine_getSkyLight: (a: number, b: number, c: number, d: number) => number;
|
|
258
|
+
readonly jslightengine_js_new: (a: number, b: number) => number;
|
|
259
|
+
readonly jslightengine_pollCompletedPublication: (a: number) => any;
|
|
260
|
+
readonly jslightengine_pushEvent: (a: number, b: any) => void;
|
|
261
|
+
readonly jslightengine_setLightTables: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
262
|
+
readonly jslightengine_setOcclusionTable: (a: number, b: number, c: number) => void;
|
|
263
|
+
readonly jslightengine_setSkyLightEnabled: (a: number, b: number) => void;
|
|
264
|
+
readonly jslightengine_step: (a: number, b: number) => number;
|
|
239
265
|
readonly parseChunkDump118: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
240
266
|
readonly parseChunkDump118FullColumn: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
241
267
|
readonly parseChunkDump118FullColumnAll: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => any;
|