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,701 @@
1
+ //@ts-nocheck
2
+ import * as THREE from 'three'
3
+ import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
4
+
5
+ const VERTS_PER_QUAD = 4
6
+ const INDICES_PER_QUAD = 6
7
+ const FLOATS_PER_VERT = 3
8
+ const FLOATS_PER_UV_VERT = 2
9
+ const FLOATS_PER_LIGHT_VERT = 1
10
+
11
+ const DEFAULT_INITIAL_CAPACITY_QUADS = 128_000
12
+ const DEFAULT_GROWTH_INCREMENT_QUADS = 128_000
13
+ const MAX_UPLOAD_QUADS_PER_FRAME = 5_000
14
+
15
+ export const FULL_DRAW_VISIBLE_FRACTION = 0.75
16
+ export const SPAN_GAP_TOLERANCE_QUADS = 256
17
+ export const MAX_OPAQUE_SPANS = 64
18
+
19
+ export type GlobalLegacyBufferOptions = {
20
+ name?: string
21
+ initialCapacityQuads?: number
22
+ growthIncrementQuads?: number
23
+ }
24
+
25
+ export type VisibleSectionSpan = { key: string, distSq: number }
26
+
27
+ export type LegacySectionGeometry = {
28
+ positions: Float32Array
29
+ colors: Float32Array
30
+ skyLights: Float32Array
31
+ blockLights: Float32Array
32
+ uvs: Float32Array
33
+ indices: Uint32Array | Uint16Array
34
+ }
35
+
36
+ export type LegacySectionGeometryData = LegacySectionGeometry & {
37
+ sx: number
38
+ sy: number
39
+ sz: number
40
+ }
41
+
42
+ /**
43
+ * Single GPU mesh for legacy quads (opaque+cutout or transparent blend).
44
+ * Camera-relative via per-vertex a_origin (relative to render origin) + u_originDelta uniforms.
45
+ */
46
+ export class GlobalLegacyBuffer {
47
+ readonly mesh: THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial | THREE.ShaderMaterial[]>
48
+ readonly material: THREE.ShaderMaterial
49
+
50
+ private readonly growthIncrementQuads: number
51
+ private capacityQuads: number
52
+ private positions: Float32Array
53
+ private colors: Float32Array
54
+ private skyLights: Float32Array
55
+ private blockLights: Float32Array
56
+ private uvs: Float32Array
57
+ private aOrigin: Float32Array
58
+ private indices: Uint32Array
59
+ private readonly sectionSlots = new Map<string, { start: number, count: number }>()
60
+ private freeList: Array<{ start: number, count: number }> = []
61
+ private highWatermark = 0
62
+ private pendingRanges: Array<{ start: number, end: number }> = []
63
+ private readonly _spanScratch: Array<{ start: number, count: number }> = []
64
+ private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
65
+
66
+ constructor (
67
+ material: THREE.ShaderMaterial,
68
+ scene: THREE.Object3D,
69
+ opts?: GlobalLegacyBufferOptions,
70
+ ) {
71
+ this.material = material
72
+ this.growthIncrementQuads = opts?.growthIncrementQuads ?? DEFAULT_GROWTH_INCREMENT_QUADS
73
+ this.capacityQuads = opts?.initialCapacityQuads ?? DEFAULT_INITIAL_CAPACITY_QUADS
74
+ const maxVerts = this.capacityQuads * VERTS_PER_QUAD
75
+ this.positions = new Float32Array(maxVerts * FLOATS_PER_VERT)
76
+ this.colors = new Float32Array(maxVerts * FLOATS_PER_VERT)
77
+ this.skyLights = new Float32Array(maxVerts * FLOATS_PER_LIGHT_VERT)
78
+ this.blockLights = new Float32Array(maxVerts * FLOATS_PER_LIGHT_VERT)
79
+ this.uvs = new Float32Array(maxVerts * FLOATS_PER_UV_VERT)
80
+ this.aOrigin = new Float32Array(maxVerts * FLOATS_PER_VERT)
81
+ this.indices = new Uint32Array(this.capacityQuads * INDICES_PER_QUAD)
82
+
83
+ const geometry = new THREE.BufferGeometry()
84
+ const mkAttr = (arr: Float32Array, itemSize: number, name: string) => {
85
+ const attr = new THREE.BufferAttribute(arr, itemSize)
86
+ attr.setUsage(THREE.DynamicDrawUsage)
87
+ geometry.setAttribute(name, attr)
88
+ return attr
89
+ }
90
+ mkAttr(this.positions, FLOATS_PER_VERT, 'position')
91
+ mkAttr(this.colors, FLOATS_PER_VERT, 'color')
92
+ mkAttr(this.skyLights, FLOATS_PER_LIGHT_VERT, 'a_skyLight')
93
+ mkAttr(this.blockLights, FLOATS_PER_LIGHT_VERT, 'a_blockLight')
94
+ mkAttr(this.uvs, FLOATS_PER_UV_VERT, 'uv')
95
+ mkAttr(this.aOrigin, FLOATS_PER_VERT, 'a_origin')
96
+
97
+ const indexAttr = new THREE.BufferAttribute(this.indices, 1)
98
+ indexAttr.setUsage(THREE.DynamicDrawUsage)
99
+ geometry.setIndex(indexAttr)
100
+
101
+ geometry.setDrawRange(0, 0)
102
+ geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(), Infinity)
103
+
104
+ this.mesh = new THREE.Mesh(geometry, [material])
105
+ this.mesh.name = opts?.name ?? 'globalLegacyOpaque'
106
+ this.mesh.frustumCulled = false
107
+ this.mesh.matrixAutoUpdate = false
108
+ this.mesh.matrix.identity()
109
+ this.mesh.position.set(0, 0, 0)
110
+ scene.add(this.mesh)
111
+ this.syncDefaultDrawGroups()
112
+ }
113
+
114
+ private syncDefaultDrawGroups (): void {
115
+ const geometry = this.mesh.geometry
116
+ geometry.clearGroups()
117
+ const indexCount = this.highWatermark * INDICES_PER_QUAD
118
+ if (indexCount > 0) {
119
+ geometry.addGroup(0, indexCount, 0)
120
+ }
121
+ geometry.setDrawRange(0, indexCount)
122
+ }
123
+
124
+ addSection (
125
+ sectionKey: string,
126
+ geo: LegacySectionGeometry,
127
+ sx: number,
128
+ sy: number,
129
+ sz: number,
130
+ ): boolean {
131
+ const vertCount = geo.positions.length / FLOATS_PER_VERT
132
+ const quadCount = vertCount / VERTS_PER_QUAD
133
+ if (vertCount === 0 || quadCount * VERTS_PER_QUAD !== vertCount) {
134
+ this.removeSection(sectionKey)
135
+ return false
136
+ }
137
+ if (geo.indices.length % INDICES_PER_QUAD !== 0 || geo.indices.length / INDICES_PER_QUAD !== quadCount) {
138
+ return false
139
+ }
140
+
141
+ if (this.sectionSlots.has(sectionKey)) {
142
+ this.removeSection(sectionKey)
143
+ }
144
+
145
+ if (quadCount > this.capacityQuads) {
146
+ this.growCapacity(quadCount)
147
+ }
148
+
149
+ let slot = this.takeFreeSlot(quadCount)
150
+ if (!slot) {
151
+ if (this.highWatermark + quadCount > this.capacityQuads) {
152
+ this.growCapacity(this.highWatermark + quadCount)
153
+ }
154
+ slot = { start: this.highWatermark, count: quadCount }
155
+ this.highWatermark += quadCount
156
+ }
157
+
158
+ const dstVertBase = slot.start * VERTS_PER_QUAD
159
+ const dstFloatBase = dstVertBase * FLOATS_PER_VERT
160
+ const dstUvBase = dstVertBase * FLOATS_PER_UV_VERT
161
+ const dstLightBase = dstVertBase * FLOATS_PER_LIGHT_VERT
162
+ this.positions.set(geo.positions, dstFloatBase)
163
+ this.colors.set(geo.colors, dstFloatBase)
164
+ this.skyLights.set(geo.skyLights, dstLightBase)
165
+ this.blockLights.set(geo.blockLights, dstLightBase)
166
+ this.uvs.set(geo.uvs, dstUvBase)
167
+
168
+ const originOff = dstFloatBase
169
+ const rx = this.renderOrigin.x
170
+ const ry = this.renderOrigin.y
171
+ const rz = this.renderOrigin.z
172
+ for (let v = 0; v < vertCount; v++) {
173
+ const o = originOff + v * FLOATS_PER_VERT
174
+ this.aOrigin[o] = sx - rx
175
+ this.aOrigin[o + 1] = sy - ry
176
+ this.aOrigin[o + 2] = sz - rz
177
+ }
178
+
179
+ const dstIndexBase = slot.start * INDICES_PER_QUAD
180
+ const vertexBase = dstVertBase
181
+ for (let i = 0; i < geo.indices.length; i++) {
182
+ this.indices[dstIndexBase + i] = geo.indices[i]! + vertexBase
183
+ }
184
+
185
+ this.sectionSlots.set(sectionKey, slot)
186
+ this.markDirty(slot.start, slot.start + quadCount - 1)
187
+ this.syncDefaultDrawGroups()
188
+ return true
189
+ }
190
+
191
+ updateDrawSpans (visible: VisibleSectionSpan[], mode: 'opaque' | 'sortedBlend'): void {
192
+ const geometry = this.mesh.geometry
193
+ geometry.clearGroups()
194
+
195
+ if (this.highWatermark === 0) {
196
+ geometry.setDrawRange(0, 0)
197
+ return
198
+ }
199
+
200
+ const spans = this._spanScratch
201
+ spans.length = 0
202
+ let visibleQuadCount = 0
203
+
204
+ for (const entry of visible) {
205
+ const slot = this.sectionSlots.get(entry.key)
206
+ if (!slot) continue
207
+ spans.push({ start: slot.start, count: slot.count })
208
+ visibleQuadCount += slot.count
209
+ }
210
+
211
+ if (spans.length === 0) {
212
+ geometry.setDrawRange(0, 0)
213
+ return
214
+ }
215
+
216
+ if (mode === 'opaque') {
217
+ if (visibleQuadCount >= this.highWatermark * FULL_DRAW_VISIBLE_FRACTION) {
218
+ geometry.addGroup(0, this.highWatermark * INDICES_PER_QUAD, 0)
219
+ geometry.setDrawRange(0, this.highWatermark * INDICES_PER_QUAD)
220
+ return
221
+ }
222
+
223
+ spans.sort((a, b) => a.start - b.start)
224
+ this.mergeOpaqueSpans(spans)
225
+ this.capOpaqueSpans(spans)
226
+
227
+ for (const span of spans) {
228
+ geometry.addGroup(span.start * INDICES_PER_QUAD, span.count * INDICES_PER_QUAD, 0)
229
+ }
230
+ } else {
231
+ visible.sort((a, b) => b.distSq - a.distSq)
232
+ for (const entry of visible) {
233
+ const slot = this.sectionSlots.get(entry.key)
234
+ if (!slot) continue
235
+ geometry.addGroup(slot.start * INDICES_PER_QUAD, slot.count * INDICES_PER_QUAD, 0)
236
+ }
237
+ }
238
+
239
+ geometry.setDrawRange(0, this.highWatermark * INDICES_PER_QUAD)
240
+ }
241
+
242
+ private mergeOpaqueSpans (spans: Array<{ start: number, count: number }>): void {
243
+ if (spans.length < 2) return
244
+ let i = 0
245
+ while (i < spans.length - 1) {
246
+ const cur = spans[i]!
247
+ const next = spans[i + 1]!
248
+ const gap = next.start - (cur.start + cur.count)
249
+ if (gap <= SPAN_GAP_TOLERANCE_QUADS) {
250
+ cur.count = next.start + next.count - cur.start
251
+ spans.splice(i + 1, 1)
252
+ } else {
253
+ i++
254
+ }
255
+ }
256
+ }
257
+
258
+ private capOpaqueSpans (spans: Array<{ start: number, count: number }>): void {
259
+ while (spans.length > MAX_OPAQUE_SPANS) {
260
+ let bestIdx = 0
261
+ let bestGap = Infinity
262
+ for (let i = 0; i < spans.length - 1; i++) {
263
+ const gap = spans[i + 1]!.start - (spans[i]!.start + spans[i]!.count)
264
+ if (gap < bestGap) {
265
+ bestGap = gap
266
+ bestIdx = i
267
+ }
268
+ }
269
+ const cur = spans[bestIdx]!
270
+ const next = spans[bestIdx + 1]!
271
+ cur.count = next.start + next.count - cur.start
272
+ spans.splice(bestIdx + 1, 1)
273
+ }
274
+ }
275
+
276
+ hasSection (sectionKey: string): boolean {
277
+ return this.sectionSlots.has(sectionKey)
278
+ }
279
+
280
+ getSectionSlot (sectionKey: string): { start: number, count: number } | undefined {
281
+ return this.sectionSlots.get(sectionKey)
282
+ }
283
+
284
+ takeSectionData (sectionKey: string): LegacySectionGeometryData | undefined {
285
+ const data = this.getSectionGeometryData(sectionKey)
286
+ if (!data) return undefined
287
+ this.removeSection(sectionKey)
288
+ return data
289
+ }
290
+
291
+ getSectionGeometryData (sectionKey: string): LegacySectionGeometryData | undefined {
292
+ const slot = this.sectionSlots.get(sectionKey)
293
+ if (!slot) return undefined
294
+
295
+ const vertCount = slot.count * VERTS_PER_QUAD
296
+ const dstVertBase = slot.start * VERTS_PER_QUAD
297
+ const dstFloatBase = dstVertBase * FLOATS_PER_VERT
298
+ const dstUvBase = dstVertBase * FLOATS_PER_UV_VERT
299
+ const dstIndexBase = slot.start * INDICES_PER_QUAD
300
+ const indexLen = slot.count * INDICES_PER_QUAD
301
+
302
+ const dstLightBase = dstVertBase * FLOATS_PER_LIGHT_VERT
303
+
304
+ const positions = this.positions.slice(dstFloatBase, dstFloatBase + vertCount * FLOATS_PER_VERT)
305
+ const colors = this.colors.slice(dstFloatBase, dstFloatBase + vertCount * FLOATS_PER_VERT)
306
+ const skyLights = this.skyLights.slice(dstLightBase, dstLightBase + vertCount * FLOATS_PER_LIGHT_VERT)
307
+ const blockLights = this.blockLights.slice(dstLightBase, dstLightBase + vertCount * FLOATS_PER_LIGHT_VERT)
308
+ const uvs = this.uvs.slice(dstUvBase, dstUvBase + vertCount * FLOATS_PER_UV_VERT)
309
+ const indices = this.indices.slice(dstIndexBase, dstIndexBase + indexLen)
310
+ const vertexBase = dstVertBase
311
+ for (let i = 0; i < indices.length; i++) {
312
+ indices[i] = indices[i]! - vertexBase
313
+ }
314
+
315
+ const sx = this.aOrigin[dstFloatBase]! + this.renderOrigin.x
316
+ const sy = this.aOrigin[dstFloatBase + 1]! + this.renderOrigin.y
317
+ const sz = this.aOrigin[dstFloatBase + 2]! + this.renderOrigin.z
318
+
319
+ return { positions, colors, skyLights, blockLights, uvs, indices, sx, sy, sz }
320
+ }
321
+
322
+ removeSection (sectionKey: string): void {
323
+ const slot = this.sectionSlots.get(sectionKey)
324
+ if (!slot) return
325
+
326
+ const dstIndexBase = slot.start * INDICES_PER_QUAD
327
+ const indexLen = slot.count * INDICES_PER_QUAD
328
+ for (let i = 0; i < indexLen; i++) {
329
+ this.indices[dstIndexBase + i] = 0
330
+ }
331
+
332
+ this.markDirty(slot.start, slot.start + slot.count - 1)
333
+ this.sectionSlots.delete(sectionKey)
334
+ this.insertFreeSlot(slot)
335
+ this.shrinkHighWatermark()
336
+ this.syncDefaultDrawGroups()
337
+ }
338
+
339
+ hasPendingUploads (): boolean {
340
+ return this.pendingRanges.length > 0
341
+ }
342
+
343
+ uploadDirtyRange (): void {
344
+ const r = this.pendingRanges[0]
345
+ if (!r) return
346
+
347
+ const quadOffset = r.start
348
+ const quadCount = Math.min(r.end - r.start + 1, MAX_UPLOAD_QUADS_PER_FRAME)
349
+ const vertOffset = quadOffset * VERTS_PER_QUAD
350
+ const vertCount = quadCount * VERTS_PER_QUAD
351
+ const indexOffset = quadOffset * INDICES_PER_QUAD
352
+ const indexCount = quadCount * INDICES_PER_QUAD
353
+
354
+ const geometry = this.mesh.geometry
355
+ const posAttr = geometry.getAttribute('position') as THREE.BufferAttribute
356
+ posAttr.clearUpdateRanges()
357
+ posAttr.addUpdateRange(vertOffset * FLOATS_PER_VERT, vertCount * FLOATS_PER_VERT)
358
+ posAttr.needsUpdate = true
359
+
360
+ const colorAttr = geometry.getAttribute('color') as THREE.BufferAttribute
361
+ colorAttr.clearUpdateRanges()
362
+ colorAttr.addUpdateRange(vertOffset * FLOATS_PER_VERT, vertCount * FLOATS_PER_VERT)
363
+ colorAttr.needsUpdate = true
364
+
365
+ const skyAttr = geometry.getAttribute('a_skyLight') as THREE.BufferAttribute
366
+ skyAttr.clearUpdateRanges()
367
+ skyAttr.addUpdateRange(vertOffset * FLOATS_PER_LIGHT_VERT, vertCount * FLOATS_PER_LIGHT_VERT)
368
+ skyAttr.needsUpdate = true
369
+
370
+ const blockAttr = geometry.getAttribute('a_blockLight') as THREE.BufferAttribute
371
+ blockAttr.clearUpdateRanges()
372
+ blockAttr.addUpdateRange(vertOffset * FLOATS_PER_LIGHT_VERT, vertCount * FLOATS_PER_LIGHT_VERT)
373
+ blockAttr.needsUpdate = true
374
+
375
+ const uvAttr = geometry.getAttribute('uv') as THREE.BufferAttribute
376
+ uvAttr.clearUpdateRanges()
377
+ uvAttr.addUpdateRange(vertOffset * FLOATS_PER_UV_VERT, vertCount * FLOATS_PER_UV_VERT)
378
+ uvAttr.needsUpdate = true
379
+
380
+ const originAttr = geometry.getAttribute('a_origin') as THREE.BufferAttribute
381
+ originAttr.clearUpdateRanges()
382
+ originAttr.addUpdateRange(vertOffset * FLOATS_PER_VERT, vertCount * FLOATS_PER_VERT)
383
+ originAttr.needsUpdate = true
384
+
385
+ const indexAttr = geometry.index as THREE.BufferAttribute
386
+ indexAttr.clearUpdateRanges()
387
+ indexAttr.addUpdateRange(indexOffset, indexCount)
388
+ indexAttr.needsUpdate = true
389
+
390
+ if (quadOffset + quadCount > r.end) this.pendingRanges.shift()
391
+ else r.start = quadOffset + quadCount
392
+ }
393
+
394
+ setRenderOrigin (renderOrigin: RenderOrigin): void {
395
+ this.renderOrigin = { ...renderOrigin }
396
+ }
397
+
398
+ rebase (delta: RenderOrigin): void {
399
+ if (this.highWatermark === 0) return
400
+ for (const slot of this.sectionSlots.values()) {
401
+ const dstVertBase = slot.start * VERTS_PER_QUAD
402
+ const vertCount = slot.count * VERTS_PER_QUAD
403
+ const dstFloatBase = dstVertBase * FLOATS_PER_VERT
404
+ for (let v = 0; v < vertCount; v++) {
405
+ const o = dstFloatBase + v * FLOATS_PER_VERT
406
+ this.aOrigin[o]! -= delta.x
407
+ this.aOrigin[o + 1]! -= delta.y
408
+ this.aOrigin[o + 2]! -= delta.z
409
+ }
410
+ }
411
+ this.markDirty(0, this.highWatermark - 1)
412
+ this.renderOrigin.x += delta.x
413
+ this.renderOrigin.y += delta.y
414
+ this.renderOrigin.z += delta.z
415
+ }
416
+
417
+ setCameraOrigin (x: number, y: number, z: number): void {
418
+ const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(this.renderOrigin, x, y, z)
419
+ const u = this.material.uniforms.u_originDelta
420
+ if (u?.value?.set) u.value.set(originDelta.x, originDelta.y, originDelta.z)
421
+ const uf = this.material.uniforms.u_cameraOriginFrac
422
+ if (uf?.value?.set) uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
423
+ }
424
+
425
+ raycastSections (
426
+ raycaster: THREE.Raycaster,
427
+ sectionKeys: Iterable<string>,
428
+ out: THREE.Intersection[],
429
+ ): THREE.Intersection[] {
430
+ const ray = raycaster.ray
431
+ const closest = raycaster.near
432
+ const far = raycaster.far
433
+ _raycastOrigin.copy(ray.origin).sub(_raycastRenderOrigin.set(
434
+ this.renderOrigin.x,
435
+ this.renderOrigin.y,
436
+ this.renderOrigin.z,
437
+ ))
438
+ _raycastRay.origin.copy(_raycastOrigin)
439
+ _raycastRay.direction.copy(ray.direction)
440
+
441
+ for (const key of sectionKeys) {
442
+ const slot = this.sectionSlots.get(key)
443
+ if (!slot) continue
444
+
445
+ const dstVertBase = slot.start * VERTS_PER_QUAD
446
+ const dstFloatBase = dstVertBase * FLOATS_PER_VERT
447
+ const dstIndexBase = slot.start * INDICES_PER_QUAD
448
+ const indexLen = slot.count * INDICES_PER_QUAD
449
+
450
+ for (let i = 0; i < indexLen; i += 3) {
451
+ const i0 = this.indices[dstIndexBase + i]!
452
+ const i1 = this.indices[dstIndexBase + i + 1]!
453
+ const i2 = this.indices[dstIndexBase + i + 2]!
454
+ if (i0 === i1 && i1 === i2) continue
455
+
456
+ const hit = intersectTriangle(
457
+ _raycastRay,
458
+ this.positions, this.aOrigin, dstFloatBase,
459
+ i0, i1, i2,
460
+ closest, far,
461
+ )
462
+ if (hit !== null) {
463
+ out.push({
464
+ distance: hit,
465
+ point: ray.at(hit, new THREE.Vector3()),
466
+ object: this.mesh,
467
+ face: null,
468
+ faceIndex: Math.floor(i / 3),
469
+ })
470
+ }
471
+ }
472
+ }
473
+
474
+ out.sort((a, b) => a.distance - b.distance)
475
+ return out
476
+ }
477
+
478
+ getMemoryBytes (): number {
479
+ const verts = this.capacityQuads * VERTS_PER_QUAD
480
+ return verts * (FLOATS_PER_VERT * 3 + FLOATS_PER_LIGHT_VERT * 2 + FLOATS_PER_UV_VERT) * 4
481
+ + this.capacityQuads * INDICES_PER_QUAD * 4
482
+ }
483
+
484
+ reset (): void {
485
+ this.sectionSlots.clear()
486
+ this.freeList.length = 0
487
+ this.highWatermark = 0
488
+ this.pendingRanges.length = 0
489
+ this.syncDefaultDrawGroups()
490
+ }
491
+
492
+ dispose (): void {
493
+ this.mesh.parent?.remove(this.mesh)
494
+ this.mesh.geometry.dispose()
495
+ this.reset()
496
+ }
497
+
498
+ private markDirty (start: number, end: number): void {
499
+ this.pendingRanges.push({ start, end })
500
+ this.pendingRanges.sort((a, b) => a.start - b.start)
501
+ this.mergePendingRanges()
502
+ }
503
+
504
+ private mergePendingRanges (): void {
505
+ if (this.pendingRanges.length < 2) return
506
+ const merged: Array<{ start: number, end: number }> = []
507
+ let cur = this.pendingRanges[0]!
508
+ for (let i = 1; i < this.pendingRanges.length; i++) {
509
+ const next = this.pendingRanges[i]!
510
+ if (next.start <= cur.end + 1) {
511
+ cur = { start: cur.start, end: Math.max(cur.end, next.end) }
512
+ } else {
513
+ merged.push(cur)
514
+ cur = next
515
+ }
516
+ }
517
+ merged.push(cur)
518
+ this.pendingRanges = merged
519
+ }
520
+
521
+ private takeFreeSlot (count: number): { start: number, count: number } | undefined {
522
+ for (let i = 0; i < this.freeList.length; i++) {
523
+ const slot = this.freeList[i]!
524
+ if (slot.count >= count) {
525
+ this.freeList.splice(i, 1)
526
+ if (slot.count === count) return slot
527
+ const used = { start: slot.start, count }
528
+ this.insertFreeSlot({ start: slot.start + count, count: slot.count - count })
529
+ return used
530
+ }
531
+ }
532
+ return undefined
533
+ }
534
+
535
+ private insertFreeSlot (slot: { start: number, count: number }): void {
536
+ this.freeList.push(slot)
537
+ this.freeList.sort((a, b) => a.start - b.start)
538
+ this.mergeFreeList()
539
+ }
540
+
541
+ private mergeFreeList (): void {
542
+ if (this.freeList.length < 2) return
543
+ const merged: Array<{ start: number, count: number }> = []
544
+ let cur = this.freeList[0]!
545
+ for (let i = 1; i < this.freeList.length; i++) {
546
+ const next = this.freeList[i]!
547
+ if (cur.start + cur.count === next.start) {
548
+ cur = { start: cur.start, count: cur.count + next.count }
549
+ } else {
550
+ merged.push(cur)
551
+ cur = next
552
+ }
553
+ }
554
+ merged.push(cur)
555
+ this.freeList = merged
556
+ }
557
+
558
+ private shrinkHighWatermark (): void {
559
+ while (this.highWatermark > 0) {
560
+ const tail = this.highWatermark - 1
561
+ const free = this.freeList.find(s => s.start <= tail && s.start + s.count > tail)
562
+ if (!free || free.start + free.count !== this.highWatermark) break
563
+ this.highWatermark = free.start
564
+ const idx = this.freeList.indexOf(free)
565
+ this.freeList.splice(idx, 1)
566
+ }
567
+ }
568
+
569
+ private growCapacity (minQuads: number): void {
570
+ let newCap = this.capacityQuads
571
+ while (newCap < minQuads) newCap += this.growthIncrementQuads
572
+
573
+ const oldMaxVerts = this.capacityQuads * VERTS_PER_QUAD
574
+ const newMaxVerts = newCap * VERTS_PER_QUAD
575
+
576
+ const nPos = new Float32Array(newMaxVerts * FLOATS_PER_VERT)
577
+ const nCol = new Float32Array(newMaxVerts * FLOATS_PER_VERT)
578
+ const nSky = new Float32Array(newMaxVerts * FLOATS_PER_LIGHT_VERT)
579
+ const nBlock = new Float32Array(newMaxVerts * FLOATS_PER_LIGHT_VERT)
580
+ const nUv = new Float32Array(newMaxVerts * FLOATS_PER_UV_VERT)
581
+ const nOrigin = new Float32Array(newMaxVerts * FLOATS_PER_VERT)
582
+ const nIdx = new Uint32Array(newCap * INDICES_PER_QUAD)
583
+
584
+ nPos.set(this.positions)
585
+ nCol.set(this.colors)
586
+ nSky.set(this.skyLights)
587
+ nBlock.set(this.blockLights)
588
+ nUv.set(this.uvs)
589
+ nOrigin.set(this.aOrigin)
590
+ nIdx.set(this.indices)
591
+
592
+ this.positions = nPos
593
+ this.colors = nCol
594
+ this.skyLights = nSky
595
+ this.blockLights = nBlock
596
+ this.uvs = nUv
597
+ this.aOrigin = nOrigin
598
+ this.indices = nIdx
599
+ this.capacityQuads = newCap
600
+
601
+ const geometry = this.mesh.geometry
602
+ const replaceAttr = (arr: Float32Array, itemSize: number, name: string) => {
603
+ const prev = geometry.getAttribute(name)
604
+ if (prev) geometry.deleteAttribute(name)
605
+ const attr = new THREE.BufferAttribute(arr, itemSize)
606
+ attr.setUsage(THREE.DynamicDrawUsage)
607
+ geometry.setAttribute(name, attr)
608
+ }
609
+ replaceAttr(this.positions, FLOATS_PER_VERT, 'position')
610
+ replaceAttr(this.colors, FLOATS_PER_VERT, 'color')
611
+ replaceAttr(this.skyLights, FLOATS_PER_LIGHT_VERT, 'a_skyLight')
612
+ replaceAttr(this.blockLights, FLOATS_PER_LIGHT_VERT, 'a_blockLight')
613
+ replaceAttr(this.uvs, FLOATS_PER_UV_VERT, 'uv')
614
+ replaceAttr(this.aOrigin, FLOATS_PER_VERT, 'a_origin')
615
+
616
+ const prevIndex = geometry.index
617
+ if (prevIndex) geometry.setIndex(null)
618
+ const indexAttr = new THREE.BufferAttribute(this.indices, 1)
619
+ indexAttr.setUsage(THREE.DynamicDrawUsage)
620
+ geometry.setIndex(indexAttr)
621
+
622
+ this.pendingRanges.length = 0
623
+ }
624
+ }
625
+
626
+ const _vA = new THREE.Vector3()
627
+ const _vB = new THREE.Vector3()
628
+ const _vC = new THREE.Vector3()
629
+ const _edge1 = new THREE.Vector3()
630
+ const _edge2 = new THREE.Vector3()
631
+ const _normal = new THREE.Vector3()
632
+ const _raycastOrigin = new THREE.Vector3()
633
+ const _raycastRenderOrigin = new THREE.Vector3()
634
+ const _raycastRay = new THREE.Ray()
635
+
636
+ function readWorldVertex (
637
+ positions: Float32Array,
638
+ aOrigin: Float32Array,
639
+ floatBase: number,
640
+ vertIndex: number,
641
+ target: THREE.Vector3,
642
+ ): void {
643
+ const f = floatBase + vertIndex * FLOATS_PER_VERT
644
+ target.set(
645
+ aOrigin[f]! + positions[f]!,
646
+ aOrigin[f + 1]! + positions[f + 1]!,
647
+ aOrigin[f + 2]! + positions[f + 2]!,
648
+ )
649
+ }
650
+
651
+ function intersectTriangle (
652
+ ray: THREE.Ray,
653
+ positions: Float32Array,
654
+ aOrigin: Float32Array,
655
+ floatBase: number,
656
+ i0: number,
657
+ i1: number,
658
+ i2: number,
659
+ near: number,
660
+ far: number,
661
+ ): number | null {
662
+ readWorldVertex(positions, aOrigin, floatBase, i0, _vA)
663
+ readWorldVertex(positions, aOrigin, floatBase, i1, _vB)
664
+ readWorldVertex(positions, aOrigin, floatBase, i2, _vC)
665
+
666
+ _edge1.subVectors(_vB, _vA)
667
+ _edge2.subVectors(_vC, _vA)
668
+ _normal.crossVectors(_edge1, _edge2)
669
+
670
+ const denom = _normal.dot(ray.direction)
671
+ if (Math.abs(denom) < 1e-8) return null
672
+
673
+ const t = _vA.clone().sub(ray.origin).dot(_normal) / denom
674
+ if (t < near || t > far) return null
675
+
676
+ const p = ray.at(t, new THREE.Vector3())
677
+ if (!pointInTriangle(p, _vA, _vB, _vC)) return null
678
+ return t
679
+ }
680
+
681
+ function pointInTriangle (p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3): boolean {
682
+ _edge1.subVectors(b, a)
683
+ _edge2.subVectors(c, a)
684
+ const n = _normal.crossVectors(_edge1, _edge2).normalize()
685
+
686
+ const ab = _edge1
687
+ const ac = _edge2
688
+ const ap = p.clone().sub(a)
689
+
690
+ const d00 = ab.dot(ab)
691
+ const d01 = ab.dot(ac)
692
+ const d11 = ac.dot(ac)
693
+ const d20 = ap.dot(ab)
694
+ const d21 = ap.dot(ac)
695
+ const denom = d00 * d11 - d01 * d01
696
+ if (Math.abs(denom) < 1e-12) return false
697
+ const v = (d11 * d20 - d01 * d21) / denom
698
+ const w = (d00 * d21 - d01 * d20) / denom
699
+ const u = 1 - v - w
700
+ return u >= -1e-4 && v >= -1e-4 && w >= -1e-4
701
+ }