minecraft-renderer 0.1.72 → 0.1.73

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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/mesher.js +81 -81
  3. package/dist/mesher.js.map +3 -3
  4. package/dist/mesherWasm.js +1183 -943
  5. package/dist/minecraft-renderer.js +249 -78
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +1732 -1001
  8. package/package.json +3 -3
  9. package/src/graphicsBackend/rendererDefaultOptions.ts +2 -7
  10. package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
  11. package/src/lib/bakeLegacyLight.ts +17 -0
  12. package/src/lib/blockEntityLightRegistry.test.ts +18 -0
  13. package/src/lib/blockEntityLightRegistry.ts +75 -0
  14. package/src/lib/blockEntityLighting.test.ts +30 -0
  15. package/src/lib/blockEntityLighting.ts +53 -0
  16. package/src/lib/worldrendererCommon.ts +14 -6
  17. package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
  18. package/src/mesher-shared/blockEntityMetadata.ts +19 -3
  19. package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
  20. package/src/mesher-shared/models.ts +161 -92
  21. package/src/mesher-shared/shared.ts +15 -4
  22. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
  23. package/src/mesher-shared/world.ts +12 -0
  24. package/src/mesher-shared/worldLighting.test.ts +54 -0
  25. package/src/playground/baseScene.ts +1 -1
  26. package/src/three/bannerRenderer.ts +10 -3
  27. package/src/three/chunkMeshManager.ts +663 -69
  28. package/src/three/cubeDrawSpans.ts +74 -0
  29. package/src/three/cubeMultiDraw.ts +119 -0
  30. package/src/three/documentRenderer.ts +0 -2
  31. package/src/three/entities.ts +5 -6
  32. package/src/three/entity/EntityMesh.ts +7 -5
  33. package/src/three/entity/gltfAnimationUtils.ts +5 -3
  34. package/src/three/globalBlockBuffer.ts +208 -12
  35. package/src/three/globalLegacyBuffer.ts +701 -0
  36. package/src/three/itemMesh.ts +5 -2
  37. package/src/three/legacySectionCull.ts +85 -0
  38. package/src/three/modules/sciFiWorldReveal.ts +347 -703
  39. package/src/three/modules/starfield.ts +3 -2
  40. package/src/three/sectionRaycastAabb.ts +25 -0
  41. package/src/three/shaders/cubeBlockShader.ts +80 -17
  42. package/src/three/shaders/legacyBlockShader.ts +292 -0
  43. package/src/three/skyboxRenderer.ts +1 -1
  44. package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
  45. package/src/three/tests/cubeDrawSpans.test.ts +73 -0
  46. package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
  47. package/src/three/tests/legacySectionCull.test.ts +80 -0
  48. package/src/three/tests/signTextureCache.test.ts +83 -0
  49. package/src/three/threeJsMedia.ts +2 -2
  50. package/src/three/waypointSprite.ts +2 -2
  51. package/src/three/world/cursorBlock.ts +1 -0
  52. package/src/three/world/vr.ts +2 -2
  53. package/src/three/worldGeometryExport.ts +83 -26
  54. package/src/three/worldRendererThree.ts +94 -25
  55. package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
  56. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
  57. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
  58. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
  59. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +67 -5
  60. package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
  61. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
  62. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
@@ -0,0 +1,80 @@
1
+ //@ts-nocheck
2
+ import { test, expect } from 'vitest'
3
+ import * as THREE from 'three'
4
+ import { sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from '../legacySectionCull'
5
+
6
+ test('setupLegacySectionMatrix: translation set once and stable across frames', () => {
7
+ const mesh = new THREE.Mesh(new THREE.BufferGeometry(), new THREE.MeshBasicMaterial())
8
+ mesh.matrixAutoUpdate = false
9
+
10
+ setupLegacySectionMatrix(mesh, 100, 64, -200, { x: 0, y: 0, z: 0 })
11
+
12
+ expect(mesh.matrix.elements[12]).toBe(100)
13
+ expect(mesh.matrix.elements[13]).toBe(64)
14
+ expect(mesh.matrix.elements[14]).toBe(-200)
15
+ expect(mesh.frustumCulled).toBe(false)
16
+
17
+ const before = mesh.matrix.elements.slice()
18
+ // No per-frame matrix write in 2a — matrix must stay unchanged across frames.
19
+ expect(mesh.matrix.elements.slice()).toEqual(before)
20
+ })
21
+
22
+ test('updateLegacySectionCullState: frustum hit sets visible and nearer section sorts later', () => {
23
+ const meshNear = new THREE.Mesh(new THREE.BufferGeometry(), new THREE.MeshBasicMaterial())
24
+ const meshFar = new THREE.Mesh(new THREE.BufferGeometry(), new THREE.MeshBasicMaterial())
25
+
26
+ const frustum = { intersectsBox: () => true } as unknown as THREE.Frustum
27
+
28
+ const box = new THREE.Box3()
29
+ const boxMin = new THREE.Vector3()
30
+ const boxMax = new THREE.Vector3()
31
+
32
+ updateLegacySectionCullState(meshNear, 0, 0, 0, 0, 0, 0, frustum, box, boxMin, boxMax)
33
+ updateLegacySectionCullState(meshFar, 32, 0, 0, 0, 0, 0, frustum, box, boxMin, boxMax)
34
+
35
+ expect(meshNear.visible).toBe(true)
36
+ expect(meshFar.visible).toBe(true)
37
+ expect(meshFar.renderOrder).toBeLessThan(meshNear.renderOrder)
38
+ })
39
+
40
+ test('sectionIntersectsFrustum: returns distSq and visibility', () => {
41
+ const frustum = { intersectsBox: () => true } as unknown as THREE.Frustum
42
+ const box = new THREE.Box3()
43
+ const boxMin = new THREE.Vector3()
44
+ const boxMax = new THREE.Vector3()
45
+
46
+ const result = sectionIntersectsFrustum(10, 0, 0, 0, 0, 0, frustum, box, boxMin, boxMax)
47
+ expect(result.visible).toBe(true)
48
+ expect(result.distSq).toBe(100)
49
+ })
50
+
51
+ test('updateLegacySectionCullState: outside frustum hides mesh', () => {
52
+ const mesh = new THREE.Mesh(new THREE.BufferGeometry(), new THREE.MeshBasicMaterial())
53
+ const frustum = new THREE.Frustum()
54
+ const proj = new THREE.Matrix4()
55
+ const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 100)
56
+ camera.position.set(0, 0, 0)
57
+ camera.lookAt(0, 0, -1)
58
+ camera.updateMatrixWorld()
59
+ proj.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
60
+ frustum.setFromProjectionMatrix(proj)
61
+
62
+ const box = new THREE.Box3()
63
+ const boxMin = new THREE.Vector3()
64
+ const boxMax = new THREE.Vector3()
65
+
66
+ updateLegacySectionCullState(mesh, 500, 0, 0, 0, 0, 0, frustum, box, boxMin, boxMax)
67
+
68
+ expect(mesh.visible).toBe(false)
69
+ })
70
+
71
+ test('setupLegacySectionMatrix: non-zero render origin stores world minus R', () => {
72
+ const mesh = new THREE.Mesh(new THREE.BufferGeometry(), new THREE.MeshBasicMaterial())
73
+ mesh.matrixAutoUpdate = false
74
+
75
+ setupLegacySectionMatrix(mesh, 100, 64, -200, { x: 16, y: 0, z: 16 })
76
+
77
+ expect(mesh.matrix.elements[12]).toBe(84)
78
+ expect(mesh.matrix.elements[13]).toBe(64)
79
+ expect(mesh.matrix.elements[14]).toBe(-216)
80
+ })
@@ -0,0 +1,83 @@
1
+ //@ts-nocheck
2
+ import { test, expect, vi, beforeEach } from 'vitest'
3
+ import * as THREE from 'three'
4
+ import { Vec3 } from 'vec3'
5
+
6
+ vi.mock('../entity/EntityMesh', () => ({
7
+ getMesh: vi.fn(),
8
+ }))
9
+
10
+ const renderSignMock = vi.fn()
11
+ vi.mock('../../sign-renderer', () => ({
12
+ renderSign: (...args: unknown[]) => renderSignMock(...args),
13
+ }))
14
+
15
+ vi.mock('prismarine-chat', () => ({
16
+ default: () => () => ({}),
17
+ }))
18
+
19
+ import { ChunkMeshManager } from '../chunkMeshManager'
20
+ import type { WorldRendererThree } from '../worldRendererThree'
21
+
22
+ function createManager (): ChunkMeshManager {
23
+ const scene = new THREE.Scene()
24
+ const material = new THREE.MeshBasicMaterial()
25
+ const worldRenderer = {
26
+ version: '1.20',
27
+ shaderCubeBlocksEnabled: () => false,
28
+ getModule: () => undefined,
29
+ sceneOrigin: {
30
+ track: () => {},
31
+ removeAndUntrack: () => {},
32
+ removeAndUntrackAll: () => {},
33
+ },
34
+ blockEntities: {},
35
+ worldRendererConfig: {},
36
+ } as unknown as WorldRendererThree
37
+ return new ChunkMeshManager(worldRenderer, scene, material, 256, 1)
38
+ }
39
+
40
+ function stubCanvas () {
41
+ return { width: 64, height: 32 } as HTMLCanvasElement
42
+ }
43
+
44
+ beforeEach(() => {
45
+ renderSignMock.mockReset()
46
+ renderSignMock.mockImplementation(() => stubCanvas())
47
+ })
48
+
49
+ test('getSignTexture: same blockEntity returns cached texture without re-render', () => {
50
+ const manager = createManager()
51
+ const signHeadsRenderer = (manager as unknown as { signHeadsRenderer: { getSignTexture: Function } }).signHeadsRenderer
52
+ const pos = new Vec3(10, 64, 10)
53
+ const blockEntity = { Text1: '{"text":"Hello"}' }
54
+
55
+ const tex1 = signHeadsRenderer.getSignTexture(pos, blockEntity, false)
56
+ const tex2 = signHeadsRenderer.getSignTexture(pos, blockEntity, false)
57
+
58
+ expect(tex1).toBeDefined()
59
+ expect(tex2).toBe(tex1)
60
+ expect(renderSignMock).toHaveBeenCalledTimes(1)
61
+
62
+ manager.dispose()
63
+ })
64
+
65
+ test('getSignTexture: changed blockEntity disposes old texture and renders anew', () => {
66
+ const manager = createManager()
67
+ const signHeadsRenderer = (manager as unknown as { signHeadsRenderer: { getSignTexture: Function } }).signHeadsRenderer
68
+ const pos = new Vec3(10, 64, 10)
69
+ const blockEntity = { Text1: '{"text":"Hello"}' }
70
+
71
+ const tex1 = signHeadsRenderer.getSignTexture(pos, blockEntity, false)!
72
+ const disposeSpy = vi.spyOn(tex1, 'dispose')
73
+
74
+ const changed = { Text1: '{"text":"World"}' }
75
+ const tex2 = signHeadsRenderer.getSignTexture(pos, changed, false)
76
+
77
+ expect(tex2).toBeDefined()
78
+ expect(tex2).not.toBe(tex1)
79
+ expect(disposeSpy).toHaveBeenCalledTimes(1)
80
+ expect(renderSignMock).toHaveBeenCalledTimes(2)
81
+
82
+ manager.dispose()
83
+ })
@@ -81,7 +81,7 @@ export class ThreeJsMedia {
81
81
  }
82
82
  }
83
83
 
84
- private createErrorTexture(width: number, height: number, background = 0xff_ff_ff, error = 'Failed to load'): THREE.CanvasTexture {
84
+ private createErrorTexture(width: number, height: number, background = 0xff_ff_ff, error = 'Failed to load'): THREE.CanvasTexture<OffscreenCanvas> {
85
85
  const canvas = new OffscreenCanvas(100, 100)
86
86
  const MAX_DIMENSION = 100
87
87
 
@@ -111,7 +111,7 @@ export class ThreeJsMedia {
111
111
  return texture
112
112
  }
113
113
 
114
- private createBackgroundTexture(width: number, height: number, color = 0x00_00_00, opacity = 1): THREE.CanvasTexture {
114
+ private createBackgroundTexture(width: number, height: number, color = 0x00_00_00, opacity = 1): THREE.CanvasTexture<OffscreenCanvas> {
115
115
  const canvas = new OffscreenCanvas(1, 1)
116
116
  canvas.width = 1
117
117
  canvas.height = 1
@@ -89,7 +89,7 @@ export function createWaypointSprite (options: {
89
89
  const labelCanvas = createCanvas(getLabelCanvasSize(), getLabelCanvasSize())
90
90
  drawCombinedOntoCanvas(labelCanvas, displayColor, options.label ?? '', '0m', visualScale)
91
91
 
92
- const labelTexture = new THREE.CanvasTexture(labelCanvas)
92
+ const labelTexture: THREE.CanvasTexture<OffscreenCanvas> = new THREE.CanvasTexture(labelCanvas)
93
93
  labelTexture.anisotropy = 1
94
94
  labelTexture.magFilter = THREE.LinearFilter
95
95
  labelTexture.minFilter = THREE.LinearFilter
@@ -112,7 +112,7 @@ export function createWaypointSprite (options: {
112
112
  let arrowSprite: THREE.Sprite | undefined
113
113
  let arrowCanvas: OffscreenCanvas | undefined
114
114
  let arrowCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | undefined
115
- let arrowTexture: THREE.CanvasTexture | undefined
115
+ let arrowTexture: THREE.CanvasTexture<OffscreenCanvas> | undefined
116
116
  let arrowParent: THREE.Object3D | null = null
117
117
  let arrowEnabled = WAYPOINT_CONFIG.ARROW.enabledDefault
118
118
 
@@ -52,6 +52,7 @@ export class CursorBlock {
52
52
  const breakMaterial = new THREE.MeshBasicMaterial({
53
53
  transparent: true,
54
54
  blending: THREE.MultiplyBlending,
55
+ premultipliedAlpha: true,
55
56
  alphaTest: 0.5,
56
57
  })
57
58
  this.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial)
@@ -124,14 +124,14 @@ export async function initVR(worldRenderer: WorldRendererThree, documentRenderer
124
124
  let hand1: any = controllerModelFactory.createControllerModel(controller1)
125
125
  controller1.addEventListener('connected', (event) => {
126
126
  hand1.xrInputSource = event.data
127
- manageXrInputSource(event.data, 'left')
127
+ manageXrInputSource(event.data as { gamepad: Gamepad | undefined, handedness?: string }, 'left')
128
128
  user.add(controller1)
129
129
  })
130
130
  controller1.add(hand1)
131
131
  let hand2: any = controllerModelFactory.createControllerModel(controller2)
132
132
  controller2.addEventListener('connected', (event) => {
133
133
  hand2.xrInputSource = event.data
134
- manageXrInputSource(event.data, 'right')
134
+ manageXrInputSource(event.data as { gamepad: Gamepad | undefined, handedness?: string }, 'right')
135
135
  user.add(controller2)
136
136
  })
137
137
  controller2.add(hand2)
@@ -2,8 +2,10 @@
2
2
  import * as THREE from 'three'
3
3
  import type { WorldRendererThree } from './worldRendererThree'
4
4
  import type { ExportedSection, ExportedWorldGeometry } from '../mesher-shared/exportedGeometryTypes'
5
+ import { calculateSkyLightSimple } from '../lib/skyLight'
6
+ import { bakeLegacyVertexColors } from '../lib/bakeLegacyLight'
5
7
  import { getShaderCubeResources } from '../wasm-mesher/bridge/shaderCubeBridge'
6
- import { createCubeBlockMaterial } from './shaders/cubeBlockShader'
8
+ import { createCubeBlockMaterial, setCubeSkyLevel } from './shaders/cubeBlockShader'
7
9
  import { createShaderCubeMesh } from './shaderCubeMesh'
8
10
 
9
11
  export type { ExportedSection, ExportedWorldGeometry } from '../mesher-shared/exportedGeometryTypes'
@@ -20,35 +22,79 @@ export function exportWorldGeometry(
20
22
  includeTexture = false
21
23
  ): ExportedWorldGeometry {
22
24
  const sections: ExportedSection[] = []
25
+ const skyLevel = calculateSkyLightSimple(worldRenderer.timeOfTheDay) / 15
26
+
27
+ const globalLegacy = worldRenderer.chunkMeshManager.globalLegacyBuffer
23
28
 
24
29
  for (const [key, sectionObject] of Object.entries(worldRenderer.sectionObjects)) {
25
- const mesh = sectionObject.children.find(child => child.name === 'mesh') as THREE.Mesh | undefined
26
- if (!mesh?.geometry) continue
30
+ const positions: number[] = []
31
+ const normals: number[] = []
32
+ const colors: number[] = []
33
+ const skyLights: number[] = []
34
+ const blockLights: number[] = []
35
+ const uvs: number[] = []
36
+ const indices: number[] = []
27
37
 
28
- const { geometry } = mesh
29
- const positionAttr = geometry.getAttribute('position') as THREE.BufferAttribute
30
- const normalAttr = geometry.getAttribute('normal') as THREE.BufferAttribute
31
- const colorAttr = geometry.getAttribute('color') as THREE.BufferAttribute
32
- const uvAttr = geometry.getAttribute('uv') as THREE.BufferAttribute
33
- const indexAttr = geometry.index!
38
+ const globalSlot = globalLegacy?.getSectionGeometryData(key)
39
+ if (globalSlot) {
40
+ positions.push(...globalSlot.positions)
41
+ colors.push(...bakeLegacyVertexColors(globalSlot.colors, globalSlot.skyLights, globalSlot.blockLights, skyLevel))
42
+ skyLights.push(...globalSlot.skyLights)
43
+ blockLights.push(...globalSlot.blockLights)
44
+ uvs.push(...globalSlot.uvs)
45
+ indices.push(...globalSlot.indices)
46
+ }
34
47
 
35
- if (!positionAttr || !indexAttr) continue
48
+ const blendMesh = sectionObject.children.find(child => child.name === 'mesh') as THREE.Mesh | undefined
49
+ if (blendMesh?.geometry) {
50
+ const { geometry } = blendMesh
51
+ const positionAttr = geometry.getAttribute('position') as THREE.BufferAttribute
52
+ const normalAttr = geometry.getAttribute('normal') as THREE.BufferAttribute
53
+ const colorAttr = geometry.getAttribute('color') as THREE.BufferAttribute
54
+ const skyAttr = geometry.getAttribute('a_skyLight') as THREE.BufferAttribute | undefined
55
+ const blockAttr = geometry.getAttribute('a_blockLight') as THREE.BufferAttribute | undefined
56
+ const uvAttr = geometry.getAttribute('uv') as THREE.BufferAttribute
57
+ const indexAttr = geometry.index
58
+ if (positionAttr && indexAttr && colorAttr) {
59
+ const vertOffset = positions.length / 3
60
+ const vertCount = positionAttr.count
61
+ const rawColors = Array.from(colorAttr.array as Float32Array)
62
+ const rawSky = skyAttr
63
+ ? Array.from(skyAttr.array as Float32Array)
64
+ : new Array(vertCount).fill(1)
65
+ const rawBlock = blockAttr
66
+ ? Array.from(blockAttr.array as Float32Array)
67
+ : new Array(vertCount).fill(0)
68
+ positions.push(...Array.from(positionAttr.array))
69
+ if (normalAttr) normals.push(...Array.from(normalAttr.array))
70
+ colors.push(...bakeLegacyVertexColors(rawColors, rawSky, rawBlock, skyLevel))
71
+ skyLights.push(...rawSky)
72
+ blockLights.push(...rawBlock)
73
+ if (uvAttr) uvs.push(...Array.from(uvAttr.array))
74
+ for (const idx of Array.from(indexAttr.array)) {
75
+ indices.push(idx + vertOffset)
76
+ }
77
+ }
78
+ }
79
+
80
+ if (positions.length === 0 || indices.length === 0) continue
36
81
 
37
- const wp = worldRenderer.sceneOrigin.getWorldPosition(mesh)
38
82
  sections.push({
39
83
  key,
40
84
  position: {
41
- x: wp?.x ?? worldRenderer.sceneOrigin.toWorldX(mesh.position.x),
42
- y: wp?.y ?? worldRenderer.sceneOrigin.toWorldY(mesh.position.y),
43
- z: wp?.z ?? worldRenderer.sceneOrigin.toWorldZ(mesh.position.z)
85
+ x: sectionObject.worldX ?? 0,
86
+ y: sectionObject.worldY ?? 0,
87
+ z: sectionObject.worldZ ?? 0,
44
88
  },
45
89
  geometry: {
46
- positions: [...positionAttr.array],
47
- normals: normalAttr ? [...normalAttr.array] : [],
48
- colors: colorAttr ? [...colorAttr.array] : [],
49
- uvs: uvAttr ? [...uvAttr.array] : [],
50
- indices: [...indexAttr.array]
51
- }
90
+ positions,
91
+ normals,
92
+ colors,
93
+ skyLights,
94
+ blockLights,
95
+ uvs,
96
+ indices,
97
+ },
52
98
  })
53
99
  }
54
100
 
@@ -65,7 +111,7 @@ export function exportWorldGeometry(
65
111
  // Optionally include texture atlas as data URL
66
112
  if (includeTexture && worldRenderer.material.map) {
67
113
  const canvas = document.createElement('canvas')
68
- const texture = worldRenderer.material.map
114
+ const texture = worldRenderer.material.map as THREE.Texture<HTMLImageElement | ImageBitmap>
69
115
  const { image } = texture
70
116
  if (image) {
71
117
  canvas.width = image.width
@@ -117,12 +163,13 @@ export async function loadWorldGeometryFromUrl(url: string): Promise<ExportedWor
117
163
  * Recreate THREE.js meshes from exported geometry
118
164
  * Returns an array of mesh groups that can be added to a scene
119
165
  */
120
- function shaderMaterialForExport(legacyMaterial: THREE.Material): THREE.ShaderMaterial | null {
166
+ function shaderMaterialForExport(legacyMaterial: THREE.Material, skyLevel: number): THREE.ShaderMaterial | null {
121
167
  const atlas = (legacyMaterial as THREE.MeshBasicMaterial).map
122
168
  ?? (legacyMaterial as THREE.MeshLambertMaterial).map
123
169
  if (!atlas) return null
124
170
  const shaderMat = createCubeBlockMaterial()
125
171
  shaderMat.uniforms.u_atlas.value = atlas
172
+ setCubeSkyLevel(shaderMat, skyLevel)
126
173
  const resources = getShaderCubeResources()
127
174
  if (!resources) return null
128
175
  const { tintPalette } = resources
@@ -135,9 +182,10 @@ export function createMeshesFromExport(
135
182
  exportData: ExportedWorldGeometry,
136
183
  material: THREE.Material,
137
184
  shaderMaterial?: THREE.ShaderMaterial | null,
185
+ skyLevel = 1,
138
186
  ): THREE.Group[] {
139
187
  const groups: THREE.Group[] = []
140
- const resolvedShaderMat = shaderMaterial ?? shaderMaterialForExport(material)
188
+ const resolvedShaderMat = shaderMaterial ?? shaderMaterialForExport(material, skyLevel)
141
189
 
142
190
  for (const section of exportData.sections) {
143
191
  const group = new THREE.Group()
@@ -151,7 +199,15 @@ export function createMeshesFromExport(
151
199
  geometry.setAttribute('normal', new THREE.Float32BufferAttribute(section.geometry.normals, 3))
152
200
  }
153
201
  if (section.geometry.colors.length) {
154
- geometry.setAttribute('color', new THREE.Float32BufferAttribute(section.geometry.colors, 3))
202
+ const baked = section.geometry.skyLights?.length
203
+ ? bakeLegacyVertexColors(
204
+ section.geometry.colors,
205
+ section.geometry.skyLights,
206
+ section.geometry.blockLights,
207
+ skyLevel,
208
+ )
209
+ : section.geometry.colors
210
+ geometry.setAttribute('color', new THREE.Float32BufferAttribute(baked, 3))
155
211
  }
156
212
  if (section.geometry.uvs.length) {
157
213
  geometry.setAttribute('uv', new THREE.Float32BufferAttribute(section.geometry.uvs, 2))
@@ -249,10 +305,11 @@ export async function applyWorldGeometryExport(
249
305
  material = rendererMaterial
250
306
  }
251
307
 
308
+ const skyLevel = calculateSkyLightSimple(worldRenderer.timeOfTheDay) / 15
252
309
  const shaderMat = exportData.sections.some(s => (s.shaderCubes?.count ?? 0) > 0)
253
- ? shaderMaterialForExport(material)
310
+ ? shaderMaterialForExport(material, skyLevel)
254
311
  : null
255
- const groups = createMeshesFromExport(exportData, material, shaderMat)
312
+ const groups = createMeshesFromExport(exportData, material, shaderMat, skyLevel)
256
313
  const container = new THREE.Group()
257
314
  container.name = GEOMETRY_EXPORT_GROUP_NAME
258
315
  if (hasEmbeddedTexture) {
@@ -9,6 +9,7 @@ import { renderSign } from '../sign-renderer'
9
9
  import { DisplayWorldOptions, GraphicsInitOptions } from '../graphicsBackend/types'
10
10
  import { chunkPos, sectionPos } from '../lib/simpleUtils'
11
11
  import { WorldRendererCommon } from '../lib/worldrendererCommon'
12
+ import { calculateSkyLightSimple } from '../lib/skyLight'
12
13
  import { addNewStat, MC_RENDERER_DEBUG_OVERLAY_CLASS } from '../lib/ui/newStats'
13
14
  import { MesherGeometryOutput } from '../mesher-shared/shared'
14
15
  import { ItemSpecificContextProperties } from '../playerState/types'
@@ -43,6 +44,9 @@ type SectionKey = string
43
44
  export class WorldRendererThree extends WorldRendererCommon {
44
45
  outputFormat = 'threeJs' as const
45
46
 
47
+ /** r184 removed useLegacyLights; physical intensities ≈ legacy / π. */
48
+ private static readonly LEGACY_TO_PHYSICAL_LIGHT = Math.PI
49
+
46
50
  protected override isShaderCubeBlocksEnabled(): boolean {
47
51
  return this.worldRendererConfig.shaderCubeBlocks === true
48
52
  && !!this.renderer?.capabilities?.isWebGL2
@@ -60,8 +64,8 @@ export class WorldRendererThree extends WorldRendererCommon {
60
64
  get realScene() {
61
65
  return this.scene
62
66
  }
63
- ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
64
- directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
67
+ ambientLight = new THREE.AmbientLight(0xcc_cc_cc, WorldRendererThree.LEGACY_TO_PHYSICAL_LIGHT)
68
+ directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5 * WorldRendererThree.LEGACY_TO_PHYSICAL_LIGHT)
65
69
  entities = new Entities(this, (globalThis as any).mcData)
66
70
  performanceMonitor!: PerformanceMonitor
67
71
  cameraGroupVr?: THREE.Object3D
@@ -70,6 +74,7 @@ export class WorldRendererThree extends WorldRendererCommon {
70
74
  cursorBlock: CursorBlock
71
75
  onRender: Array<(deltaTime: number) => void> = []
72
76
  private lastRenderTime = 0
77
+ private lastSciFiTickMs = 0
73
78
  private animatedFov = 0
74
79
  private lastFovAnimTime = 0
75
80
  private static readonly FOV_TRANSITION_MS = 200
@@ -176,6 +181,15 @@ export class WorldRendererThree extends WorldRendererCommon {
176
181
  this.chunkMeshManager.updateViewDistance(viewDistance)
177
182
  }
178
183
 
184
+ // Final lightmap values TBD — compare against https://v99.mcraft.fun/
185
+ if (typeof window !== 'undefined') {
186
+ (window as any).setBlockLightmap = (params: { curve?: number, minBrightness?: number, gamma?: number }) => {
187
+ this.chunkMeshManager.setBlockLightmapParams(params)
188
+ }
189
+ }
190
+
191
+ this.syncSkyLevelFromTime(this.timeOfTheDay)
192
+
179
193
  this.cursorBlock = new CursorBlock(this)
180
194
  this.holdingBlock = createHoldingBlock(this)
181
195
  this.holdingBlockLeft = createHoldingBlock(this, true)
@@ -431,6 +445,7 @@ export class WorldRendererThree extends WorldRendererCommon {
431
445
 
432
446
  worldSwitchActions() {
433
447
  this.onWorldSwitched.push(() => {
448
+ this.getModule<{ onWorldSwitched?: () => void }>('futuristicReveal')?.onWorldSwitched?.()
434
449
  // clear custom blocks
435
450
  this.protocolCustomBlocks.clear()
436
451
  // Reset section animations
@@ -441,6 +456,7 @@ export class WorldRendererThree extends WorldRendererCommon {
441
456
  this.cinimaticScript.stopScript()
442
457
  // Clear fireworks
443
458
  this.fireworks.clear()
459
+ this.syncSkyLevelFromTime(this.timeOfTheDay)
444
460
  })
445
461
  }
446
462
 
@@ -500,11 +516,11 @@ export class WorldRendererThree extends WorldRendererCommon {
500
516
  })
501
517
  this.onReactivePlayerStateUpdated('ambientLight', (value) => {
502
518
  if (!value) return
503
- this.ambientLight.intensity = value
519
+ this.ambientLight.intensity = value * WorldRendererThree.LEGACY_TO_PHYSICAL_LIGHT
504
520
  })
505
521
  this.onReactivePlayerStateUpdated('directionalLight', (value) => {
506
522
  if (!value) return
507
- this.directionalLight.intensity = value
523
+ this.directionalLight.intensity = value * WorldRendererThree.LEGACY_TO_PHYSICAL_LIGHT
508
524
  })
509
525
  this.onReactivePlayerStateUpdated('lookingAtBlock', (value) => {
510
526
  this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes)
@@ -530,6 +546,7 @@ export class WorldRendererThree extends WorldRendererCommon {
530
546
  })
531
547
  this.onReactiveConfigUpdated('shaderCubeDebugMode', () => {
532
548
  this.chunkMeshManager.syncCubeShaderUniforms()
549
+ this.chunkMeshManager.syncLegacyShaderUniforms()
533
550
  })
534
551
  this.onReactiveConfigUpdated('futuristicReveal', () => {
535
552
  this.updateModulesFromConfig()
@@ -631,6 +648,7 @@ export class WorldRendererThree extends WorldRendererCommon {
631
648
  texture.flipY = false
632
649
  this.material.map = texture
633
650
  this.chunkMeshManager.syncCubeShaderUniforms()
651
+ this.chunkMeshManager.syncLegacyShaderUniforms()
634
652
 
635
653
  const itemsTexture = loadThreeJsTextureFromBitmap(resources.itemsAtlasImage!)
636
654
  itemsTexture.needsUpdate = true
@@ -675,6 +693,16 @@ export class WorldRendererThree extends WorldRendererCommon {
675
693
  }
676
694
 
677
695
  this.skyboxRenderer.updateTime(newTime)
696
+ this.syncSkyLevelFromTime(newTime)
697
+ }
698
+
699
+ private syncSkyLevelFromTime (timeOfDay: number): void {
700
+ const skyLevel = calculateSkyLightSimple(timeOfDay) / 15
701
+ this.chunkMeshManager.setSkyLevel(skyLevel)
702
+ }
703
+
704
+ protected onDayCycleSkyLightChanged(_skyLight: number): void {
705
+ // Sky cap is driven by u_skyLevel uniform; no remesh on day/night.
678
706
  }
679
707
 
680
708
  biomeUpdated(biome: Biome): void {
@@ -787,6 +815,7 @@ export class WorldRendererThree extends WorldRendererCommon {
787
815
  // "Batch Chunks Display" (`_renderByChunks`) option. No-op when the
788
816
  // option is off — `waitingChunksToDisplay` is empty in that case.
789
817
  this.chunkMeshManager.finishChunkDisplay(chunkKey)
818
+ this.getModule<{ onChunkFinished?: () => void }>('futuristicReveal')?.onChunkFinished?.()
790
819
  }
791
820
 
792
821
  private applyPendingSectionUpdates() {
@@ -847,8 +876,12 @@ export class WorldRendererThree extends WorldRendererCommon {
847
876
  continue
848
877
  }
849
878
 
850
- this.chunkMeshManager.updateSection(update.key, update.geometry)
879
+ const sectionObject = this.chunkMeshManager.updateSection(update.key, update.geometry)
851
880
  this.updatePosDataChunk(update.key)
881
+ if (sectionObject) {
882
+ this.getModule<{ onSectionMeshed?: (key: string, geometry: MesherGeometryOutput, section: typeof sectionObject) => void }>('futuristicReveal')
883
+ ?.onSectionMeshed?.(update.key, update.geometry, sectionObject)
884
+ }
852
885
  }
853
886
  }
854
887
 
@@ -885,8 +918,12 @@ export class WorldRendererThree extends WorldRendererCommon {
885
918
  this.chunkMeshManager.releaseSection(data.key)
886
919
  return
887
920
  }
888
- this.chunkMeshManager.updateSection(data.key, data.geometry)
921
+ const sectionObject = this.chunkMeshManager.updateSection(data.key, data.geometry)
889
922
  this.updatePosDataChunk(data.key)
923
+ if (sectionObject) {
924
+ this.getModule<{ onSectionMeshed?: (key: string, geometry: MesherGeometryOutput, section: typeof sectionObject) => void }>('futuristicReveal')
925
+ ?.onSectionMeshed?.(data.key, data.geometry, sectionObject)
926
+ }
890
927
  }
891
928
  }
892
929
 
@@ -975,17 +1012,9 @@ export class WorldRendererThree extends WorldRendererCommon {
975
1012
  this.debugRaycast(pos, direction, distance)
976
1013
  }
977
1014
 
978
- // Convert world position to scene-relative coordinates for raycasting
979
- const scenePos = this._tpScenePos.set(
980
- this.sceneOrigin.toSceneX(pos.x),
981
- this.sceneOrigin.toSceneY(pos.y),
982
- this.sceneOrigin.toSceneZ(pos.z)
983
- )
984
-
985
- // Perform raycast to avoid camera going through blocks
986
1015
  const raycaster = this._tpRaycaster
987
- raycaster.set(scenePos, direction)
988
- raycaster.far = distance // Limit raycast distance
1016
+ raycaster.set(pos, direction)
1017
+ raycaster.far = distance
989
1018
 
990
1019
  const maxCenterDistance = 80
991
1020
  const maxCenterDistSq = maxCenterDistance * maxCenterDistance
@@ -993,8 +1022,8 @@ export class WorldRendererThree extends WorldRendererCommon {
993
1022
  const oy = pos.y
994
1023
  const oz = pos.z
995
1024
 
996
- // Legacy / deferred-shader meshes (scene-relative raycast)
997
- const meshes: THREE.Object3D[] = []
1025
+ // Legacy section meshes: world-space raycast (static mesh.matrix translation).
1026
+ const legacyMeshes: THREE.Object3D[] = []
998
1027
  for (const obj of Object.values(this.sectionObjects)) {
999
1028
  if (obj.name !== 'chunk' || !obj.visible) continue
1000
1029
  if (obj.worldX === undefined) continue
@@ -1002,11 +1031,11 @@ export class WorldRendererThree extends WorldRendererCommon {
1002
1031
  const dcy = obj.worldY! - oy
1003
1032
  const dcz = obj.worldZ! - oz
1004
1033
  if (dcx * dcx + dcy * dcy + dcz * dcz > maxCenterDistSq) continue
1005
- const mesh = obj.children.find(child => child.name === 'mesh' || child.name === 'shaderMesh')
1006
- if (mesh) meshes.push(mesh)
1034
+ const mesh = obj.children.find(child => child.name === 'mesh')
1035
+ if (mesh) legacyMeshes.push(mesh)
1007
1036
  }
1008
1037
 
1009
- const intersects = raycaster.intersectObjects(meshes, false)
1038
+ const intersects = raycaster.intersectObjects(legacyMeshes, false)
1010
1039
 
1011
1040
  let finalDistance = distance
1012
1041
  if (intersects.length > 0) {
@@ -1024,6 +1053,15 @@ export class WorldRendererThree extends WorldRendererCommon {
1024
1053
  finalDistance = Math.max(0.5, boxHit - 0.2)
1025
1054
  }
1026
1055
 
1056
+ const legacyGlobalHit = this.chunkMeshManager.raycastGlobalLegacySections(
1057
+ raycaster,
1058
+ pos,
1059
+ maxCenterDistance,
1060
+ )
1061
+ if (legacyGlobalHit !== undefined) {
1062
+ finalDistance = Math.max(0.5, legacyGlobalHit - 0.2)
1063
+ }
1064
+
1027
1065
  const finalPos = new Vec3(
1028
1066
  pos.x + direction.x * finalDistance,
1029
1067
  pos.y + direction.y * finalDistance,
@@ -1280,12 +1318,45 @@ export class WorldRendererThree extends WorldRendererCommon {
1280
1318
 
1281
1319
  // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
1282
1320
  const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
1321
+ // Flush buffered section geometry before reveal tick (calls onSectionMeshed synchronously in handleWorkerMessage).
1283
1322
  this.applyPendingSectionUpdates()
1323
+ const sciFiNow = performance.now()
1324
+ const sciFiDeltaMs = sciFiNow - this.lastSciFiTickMs
1325
+ this.lastSciFiTickMs = sciFiNow
1326
+ this.getModule<{ tick?: (deltaMs: number, now?: number) => void }>('futuristicReveal')?.tick?.(sciFiDeltaMs, sciFiNow)
1327
+ const camX = this.cameraWorldPos.x
1328
+ const camY = this.cameraWorldPos.y
1329
+ const camZ = this.cameraWorldPos.z
1330
+ this.chunkMeshManager.maybeRebase({ x: camX, y: camY, z: camZ })
1331
+ const renderOrigin = this.chunkMeshManager.getRenderOrigin()
1284
1332
  const globalBuffer = this.chunkMeshManager.globalBlockBuffer
1285
1333
  if (globalBuffer) {
1286
- globalBuffer.setCameraOrigin(this.cameraWorldPos.x, this.cameraWorldPos.y, this.cameraWorldPos.z)
1334
+ globalBuffer.setCameraOrigin(renderOrigin, camX, camY, camZ)
1335
+ globalBuffer.setDebugOverlay(this.displayOptions.inWorldRenderingConfig.enableDebugOverlay)
1287
1336
  globalBuffer.compactStep()
1288
- globalBuffer.uploadDirtyRange()
1337
+ if (globalBuffer.hasPendingUploads()) {
1338
+ globalBuffer.uploadDirtyRange()
1339
+ }
1340
+ globalBuffer.suppressThreeDraw()
1341
+ }
1342
+ const globalLegacyBuffer = this.chunkMeshManager.globalLegacyBuffer
1343
+ if (globalLegacyBuffer?.hasPendingUploads()) {
1344
+ globalLegacyBuffer.uploadDirtyRange()
1345
+ }
1346
+ const globalLegacyBlendBuffer = this.chunkMeshManager.globalLegacyBlendBuffer
1347
+ if (globalLegacyBlendBuffer?.hasPendingUploads()) {
1348
+ globalLegacyBlendBuffer.uploadDirtyRange()
1349
+ }
1350
+ this.chunkMeshManager.setLegacyCameraOrigin(camX, camY, camZ)
1351
+ this.chunkMeshManager.updateCullDirtyFromCamera(cam, camX, camY, camZ)
1352
+ if (this.chunkMeshManager.cullDirty) {
1353
+ this.chunkMeshManager.updateSectionCullAndSort(
1354
+ cam,
1355
+ this.cameraWorldPos.x,
1356
+ this.cameraWorldPos.y,
1357
+ this.cameraWorldPos.z,
1358
+ )
1359
+ this.chunkMeshManager.clearCullDirty()
1289
1360
  }
1290
1361
  this.renderer.render(this.scene, cam)
1291
1362
 
@@ -1524,8 +1595,6 @@ export class WorldRendererThree extends WorldRendererCommon {
1524
1595
  }
1525
1596
 
1526
1597
  setSectionDirty(...args: Parameters<WorldRendererCommon['setSectionDirty']>) {
1527
- const [pos] = args
1528
- this.cleanChunkTextures(pos.x, pos.z) // todo don't do this!
1529
1598
  super.setSectionDirty(...args)
1530
1599
  }
1531
1600