minecraft-renderer 0.1.48 → 0.1.50

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 (34) hide show
  1. package/dist/mesher.js +1 -1
  2. package/dist/mesher.js.map +2 -2
  3. package/dist/mesherWasm.js +3740 -183
  4. package/dist/minecraft-renderer.js +332 -60
  5. package/dist/minecraft-renderer.js.meta.json +1 -1
  6. package/dist/threeWorker.js +705 -433
  7. package/package.json +1 -1
  8. package/src/graphicsBackend/config.ts +4 -0
  9. package/src/graphicsBackend/playerState.ts +1 -0
  10. package/src/graphicsBackend/rendererOptionsSync.ts +2 -0
  11. package/src/lib/worldrendererCommon.ts +13 -0
  12. package/src/mesher-shared/exportedGeometryTypes.ts +5 -1
  13. package/src/mesher-shared/shared.ts +8 -0
  14. package/src/three/chunkMeshManager.ts +312 -39
  15. package/src/three/globalBlockBuffer.ts +292 -0
  16. package/src/three/menuBackground/config.ts +1 -1
  17. package/src/three/menuBackground/defaultOptions.ts +52 -19
  18. package/src/three/menuBackground/index.ts +5 -1
  19. package/src/three/modules/sciFiWorldReveal.ts +162 -68
  20. package/src/three/modules/starfield.ts +9 -1
  21. package/src/three/sectionRaycastAabb.ts +167 -0
  22. package/src/three/shaderCubeMesh.ts +93 -0
  23. package/src/three/shaders/cubeBlockShader.ts +354 -0
  24. package/src/three/shaders/textureIndexMapping.ts +122 -0
  25. package/src/three/shaders/tintPalette.ts +198 -0
  26. package/src/three/worldGeometryExport.ts +53 -25
  27. package/src/three/worldRendererThree.ts +56 -23
  28. package/src/wasm-mesher/bridge/render-from-wasm.ts +62 -185
  29. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +399 -0
  30. package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
  31. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +58 -0
  32. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +360 -0
  33. package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +11 -4
  34. package/src/wasm-mesher/worker/mesherWasm.ts +17 -2
@@ -0,0 +1,292 @@
1
+ //@ts-nocheck
2
+ import * as THREE from 'three'
3
+ import { VERTICES_PER_FACE } from './shaders/cubeBlockShader'
4
+ import { packWord2Empty } from '../wasm-mesher/bridge/shaderCubeBridge'
5
+
6
+ const INITIAL_CAPACITY_FACES = 2_000_000
7
+ const EMPTY_W2 = packWord2Empty()
8
+
9
+ export type GlobalBlockBufferShaderData = {
10
+ words: Uint32Array
11
+ count: number
12
+ }
13
+
14
+ /**
15
+ * Single GPU instanced mesh for all shader-cube faces in the world.
16
+ * Camera-relative positioning via u_cameraOrigin; no sceneOrigin tracking.
17
+ */
18
+ export class GlobalBlockBuffer {
19
+ readonly mesh: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>
20
+
21
+ private capacityFaces: number
22
+ private w0: Uint32Array
23
+ private w1: Uint32Array
24
+ private w2: Uint32Array
25
+ private w3: Uint32Array
26
+ private readonly sectionSlots = new Map<string, { start: number, count: number }>()
27
+ private freeList: Array<{ start: number, count: number }> = []
28
+ private highWatermark = 0
29
+ private dirtyMin = Infinity
30
+ private dirtyMax = -1
31
+
32
+ constructor (
33
+ material: THREE.ShaderMaterial,
34
+ scene: THREE.Object3D,
35
+ ) {
36
+ this.capacityFaces = INITIAL_CAPACITY_FACES
37
+ this.w0 = new Uint32Array(this.capacityFaces)
38
+ this.w1 = new Uint32Array(this.capacityFaces)
39
+ this.w2 = new Uint32Array(this.capacityFaces)
40
+ this.w3 = new Uint32Array(this.capacityFaces)
41
+
42
+ const geometry = new THREE.InstancedBufferGeometry()
43
+ const positions = new Float32Array(VERTICES_PER_FACE * 3)
44
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
45
+
46
+ const mkAttr = (arr: Uint32Array) => {
47
+ const attr = new THREE.InstancedBufferAttribute(arr, 1)
48
+ attr.setUsage(THREE.DynamicDrawUsage)
49
+ return attr
50
+ }
51
+ geometry.setAttribute('a_w0', mkAttr(this.w0))
52
+ geometry.setAttribute('a_w1', mkAttr(this.w1))
53
+ geometry.setAttribute('a_w2', mkAttr(this.w2))
54
+ geometry.setAttribute('a_w3', mkAttr(this.w3))
55
+
56
+ geometry.instanceCount = 0
57
+
58
+ this.mesh = new THREE.Mesh(geometry, material)
59
+ this.mesh.name = 'globalShaderCubes'
60
+ this.mesh.frustumCulled = false
61
+ this.mesh.matrixAutoUpdate = false
62
+ this.mesh.matrix.identity()
63
+ this.mesh.position.set(0, 0, 0)
64
+ scene.add(this.mesh)
65
+ }
66
+
67
+ addSection (sectionKey: string, words: Uint32Array, faceCount: number): void {
68
+ if (faceCount <= 0) {
69
+ this.removeSection(sectionKey)
70
+ return
71
+ }
72
+
73
+ if (this.sectionSlots.has(sectionKey)) {
74
+ this.removeSection(sectionKey)
75
+ }
76
+
77
+ if (faceCount > this.capacityFaces) {
78
+ this.growCapacity(faceCount)
79
+ }
80
+
81
+ let slot = this.takeFreeSlot(faceCount)
82
+ if (!slot) {
83
+ if (this.highWatermark + faceCount > this.capacityFaces) {
84
+ this.growCapacity(this.highWatermark + faceCount)
85
+ }
86
+ slot = { start: this.highWatermark, count: faceCount }
87
+ this.highWatermark += faceCount
88
+ }
89
+
90
+ const stride = 4
91
+ for (let i = 0; i < faceCount; i++) {
92
+ const dst = slot.start + i
93
+ const src = i * stride
94
+ this.w0[dst] = words[src]!
95
+ this.w1[dst] = words[src + 1]!
96
+ this.w2[dst] = words[src + 2]!
97
+ this.w3[dst] = words[src + 3]!
98
+ }
99
+
100
+ this.sectionSlots.set(sectionKey, slot)
101
+ this.markDirty(slot.start, slot.start + faceCount - 1)
102
+ this.mesh.geometry.instanceCount = this.highWatermark
103
+ }
104
+
105
+ hasSection (sectionKey: string): boolean {
106
+ return this.sectionSlots.has(sectionKey)
107
+ }
108
+
109
+ /** Copy live GPU words and remove the section (sci-fi reveal hide / restore). */
110
+ takeSectionData (sectionKey: string): GlobalBlockBufferShaderData | undefined {
111
+ const slot = this.sectionSlots.get(sectionKey)
112
+ if (!slot) return undefined
113
+
114
+ const stride = 4
115
+ const words = new Uint32Array(slot.count * stride)
116
+ for (let i = 0; i < slot.count; i++) {
117
+ const dst = slot.start + i
118
+ const src = i * stride
119
+ words[src] = this.w0[dst]!
120
+ words[src + 1] = this.w1[dst]!
121
+ words[src + 2] = this.w2[dst]!
122
+ words[src + 3] = this.w3[dst]!
123
+ }
124
+ this.removeSection(sectionKey)
125
+ return { words, count: slot.count }
126
+ }
127
+
128
+ removeSection (sectionKey: string): void {
129
+ const slot = this.sectionSlots.get(sectionKey)
130
+ if (!slot) return
131
+
132
+ for (let i = slot.start; i < slot.start + slot.count; i++) {
133
+ this.w0[i] = 0
134
+ this.w1[i] = 0
135
+ this.w2[i] = EMPTY_W2
136
+ this.w3[i] = 0
137
+ }
138
+
139
+ this.markDirty(slot.start, slot.start + slot.count - 1)
140
+ this.sectionSlots.delete(sectionKey)
141
+ this.insertFreeSlot(slot)
142
+ this.shrinkHighWatermark()
143
+ this.mesh.geometry.instanceCount = this.highWatermark
144
+ }
145
+
146
+ uploadDirtyRange (): void {
147
+ if (this.dirtyMin > this.dirtyMax) return
148
+
149
+ const offset = this.dirtyMin
150
+ const count = this.dirtyMax - this.dirtyMin + 1
151
+ const geometry = this.mesh.geometry
152
+
153
+ for (const name of ['a_w0', 'a_w1', 'a_w2', 'a_w3'] as const) {
154
+ const attr = geometry.getAttribute(name) as THREE.InstancedBufferAttribute
155
+ attr.updateRange.offset = offset
156
+ attr.updateRange.count = count
157
+ attr.needsUpdate = true
158
+ }
159
+
160
+ this.dirtyMin = Infinity
161
+ this.dirtyMax = -1
162
+ }
163
+
164
+ setCameraOrigin (x: number, y: number, z: number): void {
165
+ // Integer + fractional parts — see cubeBlockShader position math.
166
+ const ix = Math.floor(x)
167
+ const iy = Math.floor(y)
168
+ const iz = Math.floor(z)
169
+ const u = this.mesh.material.uniforms.u_cameraOrigin
170
+ if (u?.value?.set) {
171
+ u.value.set(ix, iy, iz)
172
+ }
173
+ const uf = this.mesh.material.uniforms.u_cameraOriginFrac
174
+ if (uf?.value?.set) {
175
+ uf.value.set(x - ix, y - iy, z - iz)
176
+ }
177
+ }
178
+
179
+ reset (): void {
180
+ this.sectionSlots.clear()
181
+ this.freeList.length = 0
182
+ this.highWatermark = 0
183
+ this.dirtyMin = Infinity
184
+ this.dirtyMax = -1
185
+ this.w0.fill(0)
186
+ this.w1.fill(0)
187
+ this.w2.fill(EMPTY_W2)
188
+ this.w3.fill(0)
189
+ this.mesh.geometry.instanceCount = 0
190
+ }
191
+
192
+ dispose (): void {
193
+ this.mesh.parent?.remove(this.mesh)
194
+ this.mesh.geometry.dispose()
195
+ this.reset()
196
+ }
197
+
198
+ private markDirty (start: number, end: number): void {
199
+ if (start < this.dirtyMin) this.dirtyMin = start
200
+ if (end > this.dirtyMax) this.dirtyMax = end
201
+ }
202
+
203
+ private takeFreeSlot (count: number): { start: number, count: number } | undefined {
204
+ for (let i = 0; i < this.freeList.length; i++) {
205
+ const slot = this.freeList[i]!
206
+ if (slot.count >= count) {
207
+ this.freeList.splice(i, 1)
208
+ if (slot.count === count) return slot
209
+ const used = { start: slot.start, count }
210
+ this.insertFreeSlot({ start: slot.start + count, count: slot.count - count })
211
+ return used
212
+ }
213
+ }
214
+ return undefined
215
+ }
216
+
217
+ private insertFreeSlot (slot: { start: number, count: number }): void {
218
+ this.freeList.push(slot)
219
+ this.freeList.sort((a, b) => a.start - b.start)
220
+ this.mergeFreeList()
221
+ }
222
+
223
+ private mergeFreeList (): void {
224
+ if (this.freeList.length < 2) return
225
+ const merged: Array<{ start: number, count: number }> = []
226
+ let cur = this.freeList[0]!
227
+ for (let i = 1; i < this.freeList.length; i++) {
228
+ const next = this.freeList[i]!
229
+ if (cur.start + cur.count === next.start) {
230
+ cur = { start: cur.start, count: cur.count + next.count }
231
+ } else {
232
+ merged.push(cur)
233
+ cur = next
234
+ }
235
+ }
236
+ merged.push(cur)
237
+ this.freeList = merged
238
+ }
239
+
240
+ private shrinkHighWatermark (): void {
241
+ while (this.highWatermark > 0) {
242
+ const tail = this.highWatermark - 1
243
+ const free = this.freeList.find(s => s.start <= tail && s.start + s.count > tail)
244
+ if (!free || free.start + free.count !== this.highWatermark) break
245
+ this.highWatermark = free.start
246
+ const idx = this.freeList.indexOf(free)
247
+ this.freeList.splice(idx, 1)
248
+ }
249
+ }
250
+
251
+ private growCapacity (minFaces: number): void {
252
+ let newCap = this.capacityFaces
253
+ while (newCap < minFaces) newCap *= 2
254
+
255
+ const nw0 = new Uint32Array(newCap)
256
+ const nw1 = new Uint32Array(newCap)
257
+ const nw2 = new Uint32Array(newCap)
258
+ const nw3 = new Uint32Array(newCap)
259
+ nw0.set(this.w0)
260
+ nw1.set(this.w1)
261
+ nw2.set(this.w2)
262
+ nw3.set(this.w3)
263
+ nw2.fill(EMPTY_W2, this.w0.length)
264
+
265
+ this.w0 = nw0
266
+ this.w1 = nw1
267
+ this.w2 = nw2
268
+ this.w3 = nw3
269
+ this.capacityFaces = newCap
270
+
271
+ const geometry = this.mesh.geometry
272
+ const mkAttr = (arr: Uint32Array, name: string) => {
273
+ const prev = geometry.getAttribute(name)
274
+ if (prev) {
275
+ geometry.deleteAttribute(name)
276
+ if ('dispose' in prev && typeof (prev as { dispose?: () => void }).dispose === 'function') {
277
+ (prev as { dispose: () => void }).dispose()
278
+ }
279
+ }
280
+ const attr = new THREE.InstancedBufferAttribute(arr, 1)
281
+ attr.setUsage(THREE.DynamicDrawUsage)
282
+ geometry.setAttribute(name, attr)
283
+ }
284
+ mkAttr(this.w0, 'a_w0')
285
+ mkAttr(this.w1, 'a_w1')
286
+ mkAttr(this.w2, 'a_w2')
287
+ mkAttr(this.w3, 'a_w3')
288
+
289
+ this.dirtyMin = 0
290
+ this.dirtyMax = this.highWatermark - 1
291
+ }
292
+ }
@@ -5,7 +5,7 @@ import type { FuturisticCameraId, FuturisticSceneId, MinecraftBlockGroupId } fro
5
5
  /** Single source of truth for menu-background defaults (settings + runtime fallbacks). */
6
6
  export const MENU_BACKGROUND_OPTION_DEFAULTS = {
7
7
  mode: 'futuristic' as MenuBackgroundMode,
8
- minecraftTextures: true,
8
+ minecraftTextures: true as boolean,
9
9
  futuristicScene: 'light' as FuturisticSceneId,
10
10
  futuristicCamera: 'dive' as FuturisticCameraId,
11
11
  futuristicBlockGroup: 'stainedGlass' as MinecraftBlockGroupId,
@@ -26,6 +26,26 @@ export type RendererMesherPipeline = 'wasm' | 'legacy-js'
26
26
 
27
27
  export type RendererGpuPreference = 'default' | 'high-performance' | 'low-power'
28
28
 
29
+ export type RendererShaderCubeDebugMode =
30
+ | 'off'
31
+ | 'holes'
32
+ | 'texIndex'
33
+ | 'faces'
34
+ | 'atlasAlpha'
35
+
36
+ const SHADER_CUBE_DEBUG_MODE_TO_VALUE: Record<RendererShaderCubeDebugMode, number> = {
37
+ off: 0,
38
+ holes: 1,
39
+ texIndex: 2,
40
+ faces: 3,
41
+ atlasAlpha: 4,
42
+ }
43
+
44
+ /** Maps stored option → `inWorldRenderingConfig.shaderCubeDebugMode` (0–4). */
45
+ export function rendererShaderCubeDebugModeToValue(mode: RendererShaderCubeDebugMode): number {
46
+ return SHADER_CUBE_DEBUG_MODE_TO_VALUE[mode]
47
+ }
48
+
29
49
  /** Maps stored `gpuPreference` to WebGL `powerPreference` (undefined = browser default). */
30
50
  export function gpuPreferenceToWebGLPowerPreference(
31
51
  preference: RendererGpuPreference
@@ -41,36 +61,37 @@ const MB = MENU_BACKGROUND_OPTION_DEFAULTS
41
61
  export const RENDERER_DEFAULT_OPTIONS = {
42
62
  rendererWorldPerformance: 'normal' as 'low-energy' | 'normal' | 'maximum',
43
63
  rendererMeshersCountOverride: null as number | null,
44
- starfieldRendering: true,
45
- defaultSkybox: true,
64
+ starfieldRendering: true as boolean,
65
+ defaultSkybox: true as boolean,
46
66
  menuBackgroundMode: MB.mode,
47
- menuBackgroundMinecraftTextures: MB.minecraftTextures,
67
+ menuBackgroundMinecraftTextures: MB.minecraftTextures as boolean,
48
68
  menuBackgroundFuturisticScene: MB.futuristicScene,
49
69
  menuBackgroundFuturisticCamera: MB.futuristicCamera,
50
70
  menuBackgroundFuturisticBlockGroup: MB.futuristicBlockGroup,
51
71
  menuBackgroundFuturisticCameraSpeed: MB.futuristicCameraSpeedPercent,
52
72
  menuBackgroundFuturisticBlockSpeed: MB.futuristicBlockSpeedPercent,
53
- rendererFuturisticReveal: false,
54
- rendererPerfDebugOverlay: false,
55
- disableBlockEntityTextures: false,
73
+ rendererFuturisticReveal: false as boolean,
74
+ rendererPerfDebugOverlay: false as boolean,
75
+ disableBlockEntityTextures: false as boolean,
56
76
  rendererMesher: 'wasm' as RendererMesherPipeline,
57
- showChunkBorders: false,
58
- renderEntities: true,
77
+ rendererShaderCubeDebugMode: 'off' as RendererShaderCubeDebugMode,
78
+ showChunkBorders: false as boolean,
79
+ renderEntities: true as boolean,
59
80
  renderDebug: 'basic' as 'none' | 'basic' | 'advanced',
60
81
  frameLimit: false as number | false,
61
82
  backgroundRendering: '20fps' as 'full' | '20fps' | '5fps',
62
- vanillaLook: false,
63
- smoothLighting: true,
64
- newVersionsLighting: false,
65
- vrSupport: true,
66
- vrPageGameRendering: false,
83
+ vanillaLook: false as boolean,
84
+ smoothLighting: true as boolean,
85
+ newVersionsLighting: false as boolean,
86
+ vrSupport: true as boolean,
87
+ vrPageGameRendering: false as boolean,
67
88
  clipWorldBelowY: undefined as number | undefined,
68
89
  highlightBlockColor: 'auto' as 'auto' | 'blue' | 'classic',
69
- loadPlayerSkins: true,
70
- renderEars: true,
71
- showHand: true,
72
- viewBobbing: true,
73
- dayCycleAndLighting: true,
90
+ loadPlayerSkins: true as boolean,
91
+ renderEars: true as boolean,
92
+ showHand: true as boolean,
93
+ viewBobbing: true as boolean,
94
+ dayCycleAndLighting: true as boolean,
74
95
  keepChunksDistance: 1,
75
96
  gpuPreference: 'default' as RendererGpuPreference,
76
97
  fov: 75
@@ -166,6 +187,17 @@ export const RENDERER_OPTIONS_META: Partial<Record<RendererDefaultOptionKey, Ren
166
187
  tooltip: 'WASM is faster. Use JS if WASM is not working. Requires reload.',
167
188
  requiresRestart: true
168
189
  },
190
+ rendererShaderCubeDebugMode: {
191
+ text: 'Shader cube debug',
192
+ tooltip: 'Instanced cube path visualization (requires shader cubes enabled).',
193
+ possibleValues: [
194
+ ['off', 'Off'],
195
+ ['holes', 'Hole test (red)'],
196
+ ['texIndex', 'Tile index colors'],
197
+ ['faces', 'Face id colors'],
198
+ ['atlasAlpha', 'Atlas alpha'],
199
+ ],
200
+ },
169
201
  showChunkBorders: {
170
202
  text: 'Chunk borders'
171
203
  },
@@ -301,7 +333,8 @@ export const RENDERER_RENDER_GUI_SECTIONS: ReadonlyArray<{
301
333
  title: 'Renderer debug',
302
334
  keys: [
303
335
  'rendererFuturisticReveal',
304
- 'rendererPerfDebugOverlay'
336
+ 'rendererPerfDebugOverlay',
337
+ 'rendererShaderCubeDebugMode',
305
338
  ]
306
339
  }
307
340
  ]
@@ -38,7 +38,11 @@ export type {
38
38
  RendererDefaultOptionKey,
39
39
  RendererGpuPreference,
40
40
  RendererMesherPipeline,
41
+ RendererShaderCubeDebugMode,
41
42
  RendererOptionMeta,
42
43
  RendererStorageOptions
43
44
  } from './defaultOptions'
44
- export { gpuPreferenceToWebGLPowerPreference } from './defaultOptions'
45
+ export {
46
+ gpuPreferenceToWebGLPowerPreference,
47
+ rendererShaderCubeDebugModeToValue
48
+ } from './defaultOptions'