minecraft-renderer 0.1.47 → 0.1.49
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/mesher.js +1 -1
- package/dist/mesher.js.map +2 -2
- package/dist/mesherWasm.js +3740 -183
- package/dist/minecraft-renderer.js +332 -60
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +705 -433
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +4 -0
- package/src/graphicsBackend/playerState.ts +1 -0
- package/src/graphicsBackend/rendererOptionsSync.ts +1 -0
- package/src/lib/worldrendererCommon.ts +13 -0
- package/src/mesher-shared/exportedGeometryTypes.ts +5 -1
- package/src/mesher-shared/shared.ts +8 -0
- package/src/playerState/playerState.ts +2 -0
- package/src/three/chunkMeshManager.ts +312 -39
- package/src/three/globalBlockBuffer.ts +292 -0
- package/src/three/menuBackground/config.ts +1 -1
- package/src/three/menuBackground/defaultOptions.ts +27 -19
- package/src/three/modules/sciFiWorldReveal.ts +162 -68
- package/src/three/modules/starfield.ts +9 -1
- package/src/three/sectionRaycastAabb.ts +167 -0
- package/src/three/shaderCubeMesh.ts +93 -0
- package/src/three/shaders/cubeBlockShader.ts +354 -0
- package/src/three/shaders/textureIndexMapping.ts +122 -0
- package/src/three/shaders/tintPalette.ts +198 -0
- package/src/three/worldGeometryExport.ts +53 -25
- package/src/three/worldRendererThree.ts +93 -26
- package/src/wasm-mesher/bridge/render-from-wasm.ts +62 -185
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +396 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +58 -0
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +360 -0
- package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +11 -4
- 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,
|
|
@@ -41,38 +41,39 @@ const MB = MENU_BACKGROUND_OPTION_DEFAULTS
|
|
|
41
41
|
export const RENDERER_DEFAULT_OPTIONS = {
|
|
42
42
|
rendererWorldPerformance: 'normal' as 'low-energy' | 'normal' | 'maximum',
|
|
43
43
|
rendererMeshersCountOverride: null as number | null,
|
|
44
|
-
starfieldRendering: true,
|
|
45
|
-
defaultSkybox: true,
|
|
44
|
+
starfieldRendering: true as boolean,
|
|
45
|
+
defaultSkybox: true as boolean,
|
|
46
46
|
menuBackgroundMode: MB.mode,
|
|
47
|
-
menuBackgroundMinecraftTextures: MB.minecraftTextures,
|
|
47
|
+
menuBackgroundMinecraftTextures: MB.minecraftTextures as boolean,
|
|
48
48
|
menuBackgroundFuturisticScene: MB.futuristicScene,
|
|
49
49
|
menuBackgroundFuturisticCamera: MB.futuristicCamera,
|
|
50
50
|
menuBackgroundFuturisticBlockGroup: MB.futuristicBlockGroup,
|
|
51
51
|
menuBackgroundFuturisticCameraSpeed: MB.futuristicCameraSpeedPercent,
|
|
52
52
|
menuBackgroundFuturisticBlockSpeed: MB.futuristicBlockSpeedPercent,
|
|
53
|
-
rendererFuturisticReveal: false,
|
|
54
|
-
rendererPerfDebugOverlay: false,
|
|
55
|
-
disableBlockEntityTextures: false,
|
|
53
|
+
rendererFuturisticReveal: false as boolean,
|
|
54
|
+
rendererPerfDebugOverlay: false as boolean,
|
|
55
|
+
disableBlockEntityTextures: false as boolean,
|
|
56
56
|
rendererMesher: 'wasm' as RendererMesherPipeline,
|
|
57
|
-
showChunkBorders: false,
|
|
58
|
-
renderEntities: true,
|
|
57
|
+
showChunkBorders: false as boolean,
|
|
58
|
+
renderEntities: true as boolean,
|
|
59
59
|
renderDebug: 'basic' as 'none' | 'basic' | 'advanced',
|
|
60
60
|
frameLimit: false as number | false,
|
|
61
61
|
backgroundRendering: '20fps' as 'full' | '20fps' | '5fps',
|
|
62
|
-
vanillaLook: false,
|
|
63
|
-
smoothLighting: true,
|
|
64
|
-
newVersionsLighting: false,
|
|
65
|
-
vrSupport: true,
|
|
66
|
-
vrPageGameRendering: false,
|
|
62
|
+
vanillaLook: false as boolean,
|
|
63
|
+
smoothLighting: true as boolean,
|
|
64
|
+
newVersionsLighting: false as boolean,
|
|
65
|
+
vrSupport: true as boolean,
|
|
66
|
+
vrPageGameRendering: false as boolean,
|
|
67
67
|
clipWorldBelowY: undefined as number | undefined,
|
|
68
68
|
highlightBlockColor: 'auto' as 'auto' | 'blue' | 'classic',
|
|
69
|
-
loadPlayerSkins: true,
|
|
70
|
-
renderEars: true,
|
|
71
|
-
showHand: true,
|
|
72
|
-
viewBobbing: true,
|
|
73
|
-
dayCycleAndLighting: true,
|
|
69
|
+
loadPlayerSkins: true as boolean,
|
|
70
|
+
renderEars: true as boolean,
|
|
71
|
+
showHand: true as boolean,
|
|
72
|
+
viewBobbing: true as boolean,
|
|
73
|
+
dayCycleAndLighting: true as boolean,
|
|
74
74
|
keepChunksDistance: 1,
|
|
75
|
-
gpuPreference: 'default' as RendererGpuPreference
|
|
75
|
+
gpuPreference: 'default' as RendererGpuPreference,
|
|
76
|
+
fov: 75
|
|
76
77
|
} as const
|
|
77
78
|
|
|
78
79
|
/** App options storage shape for renderer-owned keys. */
|
|
@@ -226,6 +227,12 @@ export const RENDERER_OPTIONS_META: Partial<Record<RendererDefaultOptionKey, Ren
|
|
|
226
227
|
max: 5,
|
|
227
228
|
unit: ''
|
|
228
229
|
},
|
|
230
|
+
fov: {
|
|
231
|
+
min: 30,
|
|
232
|
+
max: 110,
|
|
233
|
+
unit: '°',
|
|
234
|
+
text: 'Field of view'
|
|
235
|
+
},
|
|
229
236
|
gpuPreference: {
|
|
230
237
|
text: 'GPU preference',
|
|
231
238
|
tooltip: 'WebGL power preference. Requires reload / backend restart to apply.',
|
|
@@ -260,6 +267,7 @@ export const RENDERER_RENDER_GUI_SECTIONS: ReadonlyArray<{
|
|
|
260
267
|
'renderEars',
|
|
261
268
|
'showHand',
|
|
262
269
|
'viewBobbing',
|
|
270
|
+
'fov',
|
|
263
271
|
'keepChunksDistance',
|
|
264
272
|
'highlightBlockColor',
|
|
265
273
|
'clipWorldBelowY'
|