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,74 @@
1
+ //@ts-nocheck
2
+ import {
3
+ FULL_DRAW_VISIBLE_FRACTION,
4
+ MAX_OPAQUE_SPANS,
5
+ SPAN_GAP_TOLERANCE_QUADS,
6
+ } from './globalLegacyBuffer'
7
+
8
+ export const MAX_CUBE_SPANS = MAX_OPAQUE_SPANS
9
+ export const SPAN_GAP_TOLERANCE_FACES = SPAN_GAP_TOLERANCE_QUADS
10
+ export { FULL_DRAW_VISIBLE_FRACTION }
11
+
12
+ export type CubeDrawSpan = { start: number, count: number }
13
+
14
+ export type VisibleCubeSlot = { start: number, count: number }
15
+
16
+ function mergeCubeSpans (spans: CubeDrawSpan[]): void {
17
+ if (spans.length < 2) return
18
+ let i = 0
19
+ while (i < spans.length - 1) {
20
+ const cur = spans[i]!
21
+ const next = spans[i + 1]!
22
+ const gap = next.start - (cur.start + cur.count)
23
+ if (gap <= SPAN_GAP_TOLERANCE_FACES) {
24
+ cur.count = next.start + next.count - cur.start
25
+ spans.splice(i + 1, 1)
26
+ } else {
27
+ i++
28
+ }
29
+ }
30
+ }
31
+
32
+ function capCubeSpans (spans: CubeDrawSpan[]): void {
33
+ while (spans.length > MAX_CUBE_SPANS) {
34
+ let bestIdx = 0
35
+ let bestGap = Infinity
36
+ for (let i = 0; i < spans.length - 1; i++) {
37
+ const gap = spans[i + 1]!.start - (spans[i]!.start + spans[i]!.count)
38
+ if (gap < bestGap) {
39
+ bestGap = gap
40
+ bestIdx = i
41
+ }
42
+ }
43
+ const cur = spans[bestIdx]!
44
+ const next = spans[bestIdx + 1]!
45
+ cur.count = next.start + next.count - cur.start
46
+ spans.splice(bestIdx + 1, 1)
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Merge/cap visible face-instance ranges for WEBGL_multi_draw (option B).
52
+ * Instance indices — no *6 scaling on span values.
53
+ */
54
+ export function buildVisibleCubeSpans (
55
+ visibleSlots: VisibleCubeSlot[],
56
+ highWatermark: number,
57
+ ): CubeDrawSpan[] {
58
+ if (visibleSlots.length === 0 || highWatermark === 0) return []
59
+
60
+ let visibleFaceCount = 0
61
+ for (const slot of visibleSlots) {
62
+ visibleFaceCount += slot.count
63
+ }
64
+
65
+ if (visibleFaceCount >= highWatermark * FULL_DRAW_VISIBLE_FRACTION) {
66
+ return [{ start: 0, count: highWatermark }]
67
+ }
68
+
69
+ const spans = visibleSlots.map(s => ({ start: s.start, count: s.count }))
70
+ spans.sort((a, b) => a.start - b.start)
71
+ mergeCubeSpans(spans)
72
+ capCubeSpans(spans)
73
+ return spans
74
+ }
@@ -0,0 +1,119 @@
1
+ //@ts-nocheck
2
+ import type { CubeDrawSpan } from './cubeDrawSpans'
3
+ import { MAX_CUBE_SPANS } from './cubeDrawSpans'
4
+ import { VERTICES_PER_FACE } from './shaders/cubeBlockShader'
5
+
6
+ export type MultiDrawTier = 'A' | 'B' | 'C'
7
+
8
+ export type MultiDrawCaps = {
9
+ tier: MultiDrawTier
10
+ ext: MultiDrawInstancedExt | DrawInstancedBaseExt | null
11
+ }
12
+
13
+ type MultiDrawInstancedExt = {
14
+ multiDrawArraysInstancedBaseInstanceWEBGL: (
15
+ mode: number,
16
+ firsts: Int32Array,
17
+ firstsOffset: number,
18
+ counts: Int32Array,
19
+ countsOffset: number,
20
+ instanceCounts: Int32Array,
21
+ instanceCountsOffset: number,
22
+ baseInstances: Int32Array,
23
+ baseInstancesOffset: number,
24
+ drawCount: number,
25
+ ) => void
26
+ }
27
+
28
+ type DrawInstancedBaseExt = {
29
+ drawArraysInstancedBaseInstanceWEBGL: (
30
+ mode: number,
31
+ first: number,
32
+ count: number,
33
+ instanceCount: number,
34
+ baseInstance: number,
35
+ ) => void
36
+ }
37
+
38
+ export type CubeMultiDrawScratch = {
39
+ firsts: Int32Array
40
+ counts: Int32Array
41
+ instanceCounts: Int32Array
42
+ baseInstances: Int32Array
43
+ }
44
+
45
+ export function createCubeMultiDrawScratch (): CubeMultiDrawScratch {
46
+ return {
47
+ firsts: new Int32Array(MAX_CUBE_SPANS),
48
+ counts: new Int32Array(MAX_CUBE_SPANS),
49
+ instanceCounts: new Int32Array(MAX_CUBE_SPANS),
50
+ baseInstances: new Int32Array(MAX_CUBE_SPANS),
51
+ }
52
+ }
53
+
54
+ export function detectMultiDrawCaps (gl: WebGL2RenderingContext): MultiDrawCaps {
55
+ const tierA = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance')
56
+ if (tierA) {
57
+ return { tier: 'A', ext: tierA as MultiDrawInstancedExt }
58
+ }
59
+ const tierB = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance')
60
+ if (tierB) {
61
+ return { tier: 'B', ext: tierB as DrawInstancedBaseExt }
62
+ }
63
+ return { tier: 'C', ext: null }
64
+ }
65
+
66
+ let tierLogged = false
67
+
68
+ export function logMultiDrawTierOnce (tier: MultiDrawTier, debug: boolean): void {
69
+ if (tierLogged || !debug) return
70
+ tierLogged = true
71
+ console.info('[globalBlockBuffer] cube multi_draw tier', tier)
72
+ }
73
+
74
+ /**
75
+ * Issue instanced cube draws for visible spans. Tier C delegates to buffer-owned VAO path.
76
+ */
77
+ export function drawCubeSpans (
78
+ gl: WebGL2RenderingContext,
79
+ caps: MultiDrawCaps,
80
+ spans: readonly CubeDrawSpan[],
81
+ scratch: CubeMultiDrawScratch,
82
+ tierCDraw?: (gl: WebGL2RenderingContext, spans: readonly CubeDrawSpan[]) => void,
83
+ ): void {
84
+ const drawCount = spans.length
85
+ if (drawCount === 0) return
86
+
87
+ const mode = gl.TRIANGLES
88
+
89
+ if (caps.tier === 'A' && caps.ext) {
90
+ const ext = caps.ext as MultiDrawInstancedExt
91
+ for (let i = 0; i < drawCount; i++) {
92
+ const span = spans[i]!
93
+ scratch.firsts[i] = 0
94
+ scratch.counts[i] = VERTICES_PER_FACE
95
+ scratch.instanceCounts[i] = span.count
96
+ scratch.baseInstances[i] = span.start
97
+ }
98
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL(
99
+ mode,
100
+ scratch.firsts, 0,
101
+ scratch.counts, 0,
102
+ scratch.instanceCounts, 0,
103
+ scratch.baseInstances, 0,
104
+ drawCount,
105
+ )
106
+ return
107
+ }
108
+
109
+ if (caps.tier === 'B' && caps.ext) {
110
+ const ext = caps.ext as DrawInstancedBaseExt
111
+ for (let i = 0; i < drawCount; i++) {
112
+ const span = spans[i]!
113
+ ext.drawArraysInstancedBaseInstanceWEBGL(mode, 0, VERTICES_PER_FACE, span.count, span.start)
114
+ }
115
+ return
116
+ }
117
+
118
+ tierCDraw?.(gl, spans)
119
+ }
@@ -216,8 +216,6 @@ export class DocumentRenderer {
216
216
  throw err
217
217
  }
218
218
 
219
- // @ts-ignore - legacy lights API
220
- this.renderer.useLegacyLights = true
221
219
  this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace
222
220
 
223
221
  if (!externalCanvas) {
@@ -265,7 +265,7 @@ export class Entities {
265
265
  }
266
266
  debugMode: string
267
267
  onSkinUpdate: () => void
268
- clock = new THREE.Clock()
268
+ clock = new THREE.Timer()
269
269
  currentlyRendering = true
270
270
  cachedMapsImages = {} as Record<number, string>
271
271
  itemFrameMaps = {} as Record<number, Array<THREE.Mesh<THREE.PlaneGeometry, THREE.MeshLambertMaterial>>>
@@ -405,6 +405,7 @@ export class Entities {
405
405
  this.setRendering(renderEntitiesConfig)
406
406
  }
407
407
 
408
+ this.clock.update(performance.now())
408
409
  const dt = Math.min(this.clock.getDelta(), 1 / 30)
409
410
  const botPos = this.worldRenderer.viewerChunkPosition
410
411
  const VISIBLE_DISTANCE = 10 * 10
@@ -678,7 +679,6 @@ export class Entities {
678
679
  earsTexture.magFilter = THREE.NearestFilter
679
680
  earsTexture.minFilter = THREE.NearestFilter
680
681
  earsTexture.needsUpdate = true
681
- //@ts-expect-error
682
682
  playerObject.ears.map = earsTexture
683
683
  playerObject.ears.visible = true
684
684
  } else {
@@ -706,10 +706,8 @@ export class Entities {
706
706
  capeTexture.magFilter = THREE.NearestFilter
707
707
  capeTexture.minFilter = THREE.NearestFilter
708
708
  capeTexture.needsUpdate = true
709
- //@ts-expect-error
710
709
  playerObject.cape.map = capeTexture
711
710
  playerObject.cape.visible = true
712
- //@ts-expect-error
713
711
  playerObject.elytra.map = capeTexture
714
712
  this.onSkinUpdate?.()
715
713
 
@@ -902,9 +900,10 @@ export class Entities {
902
900
  // mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5)
903
901
  // viewer.scene.add(mesh)
904
902
  if (entity.name === 'item') {
905
- const clock = new THREE.Clock()
903
+ const itemTimer = new THREE.Timer()
906
904
  mesh.onBeforeRender = () => {
907
- const delta = clock.getDelta()
905
+ itemTimer.update(performance.now())
906
+ const delta = itemTimer.getDelta()
908
907
  mesh!.rotation.y += delta
909
908
  }
910
909
  }
@@ -261,7 +261,7 @@ export function getMesh(
261
261
  let textureHeight = jsonModel.textureheight ?? 64
262
262
  let textureOffset: number[] | undefined
263
263
  const useBlockTexture = texture.startsWith('block:')
264
- const blocksTexture = worldRenderer?.material.map
264
+ const blocksTexture = worldRenderer?.material.map as THREE.Texture<HTMLImageElement | ImageBitmap> | null | undefined
265
265
  if (useBlockTexture) {
266
266
  if (!worldRenderer) throw new Error('worldRenderer is required for block textures')
267
267
  const blockName = texture.slice(6)
@@ -369,13 +369,15 @@ export function getMesh(
369
369
  material.map = loadedTexture
370
370
  }, () => {
371
371
  // This callback runs after the texture is fully loaded
372
- const actualWidth = material.map!.image.width
372
+ const map = material.map as THREE.Texture<HTMLImageElement | ImageBitmap> | null
373
+ if (!map) return
374
+ const actualWidth = map.image.width
373
375
  if (actualWidth && textureWidth !== actualWidth) {
374
- material.map!.repeat.x = textureWidth / actualWidth
376
+ map.repeat.x = textureWidth / actualWidth
375
377
  }
376
- const actualHeight = material.map!.image.height
378
+ const actualHeight = map.image.height
377
379
  if (actualHeight && textureHeight !== actualHeight) {
378
- material.map!.repeat.y = textureHeight / actualHeight
380
+ map.repeat.y = textureHeight / actualHeight
379
381
  }
380
382
  material.needsUpdate = true
381
383
  })
@@ -10,7 +10,7 @@ export interface AnimationState {
10
10
  }
11
11
 
12
12
  export class AnimationManager {
13
- private readonly clock = new THREE.Clock()
13
+ private readonly timer = new THREE.Timer()
14
14
  private readonly animatedObjects = new Set<THREE.Object3D>()
15
15
  state!: AnimationState
16
16
 
@@ -47,7 +47,8 @@ export class AnimationManager {
47
47
  if (child instanceof THREE.Mesh || child instanceof THREE.Line || child instanceof THREE.Points || child instanceof THREE.Sprite) {
48
48
  const originalOnBeforeRender = child.onBeforeRender
49
49
  child.onBeforeRender = (renderer, scene, camera, geometry, material, group) => {
50
- const delta = this.clock.getDelta()
50
+ this.timer.update(performance.now())
51
+ const delta = this.timer.getDelta()
51
52
  mixer.update(delta)
52
53
  // Call original onBeforeRender if it existed
53
54
  originalOnBeforeRender?.(renderer, scene, camera, geometry, material, group)
@@ -123,7 +124,8 @@ export class AnimationManager {
123
124
  * Gets the current clock delta (useful for manual updates)
124
125
  */
125
126
  getDelta(): number {
126
- return this.clock.getDelta()
127
+ this.timer.update(performance.now())
128
+ return this.timer.getDelta()
127
129
  }
128
130
  }
129
131
 
@@ -1,8 +1,32 @@
1
1
  //@ts-nocheck
2
2
  import * as THREE from 'three'
3
- import { VERTICES_PER_FACE } from './shaders/cubeBlockShader'
3
+ import type { CubeDrawSpan } from './cubeDrawSpans'
4
+ import {
5
+ createCubeMultiDrawScratch,
6
+ detectMultiDrawCaps,
7
+ drawCubeSpans,
8
+ logMultiDrawTierOnce,
9
+ type CubeMultiDrawScratch,
10
+ type MultiDrawCaps,
11
+ } from './cubeMultiDraw'
12
+ import { VERTICES_PER_FACE, computeSectionOriginRel } from './shaders/cubeBlockShader'
13
+ import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
4
14
  import { packWord2Empty } from '../wasm-mesher/bridge/shaderCubeBridge'
5
15
 
16
+ type WebGLRendererInternals = THREE.WebGLRenderer & {
17
+ properties: {
18
+ get: (material: THREE.Material) => { currentProgram: { program: WebGLProgram } }
19
+ }
20
+ }
21
+
22
+ type TierCAttrBinding = { loc: number, buffer: WebGLBuffer }
23
+ type TierCAttrState = {
24
+ w0: TierCAttrBinding
25
+ w1: TierCAttrBinding
26
+ w2: TierCAttrBinding
27
+ w3: TierCAttrBinding
28
+ }
29
+
6
30
  // Linear growth (NOT doubling) to keep iOS allocation spikes bounded to one increment.
7
31
  // Reference: prismarine-web-client PR #90 (webgl) and #120 (webgpu) both grow by +1M faces.
8
32
  const INITIAL_CAPACITY_FACES = 512_000 // ~8 MB up front (4 words × 4 B), well under 1M
@@ -20,7 +44,7 @@ export type GlobalBlockBufferShaderData = {
20
44
 
21
45
  /**
22
46
  * Single GPU instanced mesh for all shader-cube faces in the world.
23
- * Camera-relative positioning via u_cameraOrigin; no sceneOrigin tracking.
47
+ * Camera-relative positioning via u_originDelta + u_sectionOriginRel; no sceneOrigin tracking.
24
48
  */
25
49
  export class GlobalBlockBuffer {
26
50
  readonly mesh: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>
@@ -35,6 +59,13 @@ export class GlobalBlockBuffer {
35
59
  private highWatermark = 0
36
60
  private pendingRanges: Array<{ start: number, end: number }> = []
37
61
  private pendingMove: PendingMove | null = null
62
+ private visibleSpans: CubeDrawSpan[] = []
63
+ private readonly _drawScratch: CubeMultiDrawScratch = createCubeMultiDrawScratch()
64
+ private multiDrawCaps: MultiDrawCaps | null = null
65
+ private tierCVao: WebGLVertexArrayObject | null = null
66
+ private tierCAttrs: TierCAttrState | null = null
67
+ private tierCGl: WebGL2RenderingContext | null = null
68
+ private debugOverlay = false
38
69
 
39
70
  constructor (
40
71
  material: THREE.ShaderMaterial,
@@ -70,6 +101,73 @@ export class GlobalBlockBuffer {
70
101
  this.mesh.matrix.identity()
71
102
  this.mesh.position.set(0, 0, 0)
72
103
  scene.add(this.mesh)
104
+
105
+ this.mesh.onAfterRender = (renderer, _scene, _camera, _geometry, material) => {
106
+ if (this.visibleSpans.length === 0) return
107
+ const shaderMaterial = material as THREE.ShaderMaterial
108
+ const gl = renderer.getContext() as WebGL2RenderingContext
109
+ if (!this.multiDrawCaps) {
110
+ this.multiDrawCaps = detectMultiDrawCaps(gl)
111
+ logMultiDrawTierOnce(this.multiDrawCaps.tier, this.debugOverlay)
112
+ }
113
+ drawCubeSpans(
114
+ gl,
115
+ this.multiDrawCaps,
116
+ this.visibleSpans,
117
+ this._drawScratch,
118
+ this.multiDrawCaps.tier === 'C'
119
+ ? (g, spans) => this.drawTierCSpans(g, spans, renderer, shaderMaterial)
120
+ : undefined,
121
+ )
122
+ }
123
+ }
124
+
125
+ setDebugOverlay (enabled: boolean): void {
126
+ this.debugOverlay = enabled
127
+ }
128
+
129
+ /**
130
+ * Suppress three's full-buffer instanced draw; onAfterRender issues visible spans only.
131
+ * setDrawRange(0,0) skips bindingStates.setup in r0.184 — use 6 verts + instanceCount=0
132
+ * so program/VAO stay bound while renderInstances no-ops at primcount===0.
133
+ */
134
+ suppressThreeDraw (): void {
135
+ const geometry = this.mesh.geometry
136
+ geometry.setDrawRange(0, VERTICES_PER_FACE)
137
+ geometry.instanceCount = 0
138
+ }
139
+
140
+ setVisibleSpans (spans: CubeDrawSpan[]): void {
141
+ this.visibleSpans = spans
142
+ }
143
+
144
+ getVisibleSpans (): readonly CubeDrawSpan[] {
145
+ return this.visibleSpans
146
+ }
147
+
148
+ forEachSectionSlot (cb: (key: string, slot: { start: number, count: number }) => void): void {
149
+ for (const [key, slot] of this.sectionSlots) {
150
+ cb(key, slot)
151
+ }
152
+ }
153
+
154
+ getSectionDrawStart (sectionKey: string): number | undefined {
155
+ const slot = this.sectionSlots.get(sectionKey)
156
+ if (!slot) return undefined
157
+ if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
158
+ return slot.start
159
+ }
160
+
161
+ getHighWatermark (): number {
162
+ return this.highWatermark
163
+ }
164
+
165
+ hasPendingUploads (): boolean {
166
+ return this.pendingRanges.length > 0
167
+ }
168
+
169
+ getPendingMove (): PendingMove | null {
170
+ return this.pendingMove
73
171
  }
74
172
 
75
173
  addSection (sectionKey: string, words: Uint32Array, faceCount: number): void {
@@ -213,8 +311,8 @@ export class GlobalBlockBuffer {
213
311
 
214
312
  for (const name of ['a_w0', 'a_w1', 'a_w2', 'a_w3'] as const) {
215
313
  const attr = geometry.getAttribute(name) as THREE.InstancedBufferAttribute
216
- attr.updateRange.offset = offset
217
- attr.updateRange.count = count
314
+ attr.clearUpdateRanges()
315
+ attr.addUpdateRange(offset, count)
218
316
  attr.needsUpdate = true
219
317
  }
220
318
 
@@ -222,18 +320,20 @@ export class GlobalBlockBuffer {
222
320
  else r.start = offset + count
223
321
  }
224
322
 
225
- setCameraOrigin (x: number, y: number, z: number): void {
226
- // Integer + fractional parts see cubeBlockShader position math.
227
- const ix = Math.floor(x)
228
- const iy = Math.floor(y)
229
- const iz = Math.floor(z)
230
- const u = this.mesh.material.uniforms.u_cameraOrigin
323
+ setCameraOrigin (renderOrigin: RenderOrigin, x: number, y: number, z: number): void {
324
+ const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(renderOrigin, x, y, z)
325
+ const sectionOriginRel = computeSectionOriginRel(renderOrigin)
326
+ const u = this.mesh.material.uniforms.u_originDelta
231
327
  if (u?.value?.set) {
232
- u.value.set(ix, iy, iz)
328
+ u.value.set(originDelta.x, originDelta.y, originDelta.z)
233
329
  }
234
330
  const uf = this.mesh.material.uniforms.u_cameraOriginFrac
235
331
  if (uf?.value?.set) {
236
- uf.value.set(x - ix, y - iy, z - iz)
332
+ uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
333
+ }
334
+ const us = this.mesh.material.uniforms.u_sectionOriginRel
335
+ if (us?.value?.set) {
336
+ us.value.set(sectionOriginRel.x, sectionOriginRel.y, sectionOriginRel.z)
237
337
  }
238
338
  }
239
339
 
@@ -243,19 +343,114 @@ export class GlobalBlockBuffer {
243
343
  this.highWatermark = 0
244
344
  this.pendingRanges.length = 0
245
345
  this.pendingMove = null
346
+ this.visibleSpans = []
246
347
  this.w0.fill(0)
247
348
  this.w1.fill(0)
248
349
  this.w2.fill(EMPTY_W2)
249
350
  this.w3.fill(0)
250
351
  this.mesh.geometry.instanceCount = 0
352
+ this.invalidateTierCVao()
251
353
  }
252
354
 
253
355
  dispose (): void {
356
+ this.invalidateTierCVao()
254
357
  this.mesh.parent?.remove(this.mesh)
255
358
  this.mesh.geometry.dispose()
256
359
  this.reset()
257
360
  }
258
361
 
362
+ private invalidateTierCVao (): void {
363
+ if (this.tierCVao && this.tierCGl) {
364
+ this.tierCGl.deleteVertexArray(this.tierCVao)
365
+ }
366
+ this.tierCVao = null
367
+ this.tierCAttrs = null
368
+ }
369
+
370
+ private drawTierCSpans (
371
+ gl: WebGL2RenderingContext,
372
+ spans: readonly CubeDrawSpan[],
373
+ renderer: THREE.WebGLRenderer,
374
+ material: THREE.ShaderMaterial,
375
+ ): void {
376
+ this.ensureTierCVao(gl, renderer, material)
377
+ if (!this.tierCVao || !this.tierCAttrs) return
378
+
379
+ const attrs = this.tierCAttrs
380
+ gl.bindVertexArray(this.tierCVao)
381
+ for (const span of spans) {
382
+ const byteOffset = span.start * 4
383
+ const repointWord = (binding: TierCAttrBinding) => {
384
+ gl.bindBuffer(gl.ARRAY_BUFFER, binding.buffer)
385
+ gl.vertexAttribIPointer(binding.loc, 1, gl.UNSIGNED_INT, 4, byteOffset)
386
+ }
387
+ repointWord(attrs.w0)
388
+ repointWord(attrs.w1)
389
+ repointWord(attrs.w2)
390
+ repointWord(attrs.w3)
391
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, VERTICES_PER_FACE, span.count)
392
+ }
393
+ gl.bindVertexArray(null)
394
+ }
395
+
396
+ private ensureTierCVao (
397
+ gl: WebGL2RenderingContext,
398
+ renderer: THREE.WebGLRenderer,
399
+ material: THREE.ShaderMaterial,
400
+ ): void {
401
+ this.tierCGl = gl
402
+ const internals = renderer as WebGLRendererInternals
403
+ const materialProps = internals.properties.get(material) as { currentProgram: { program: WebGLProgram } }
404
+ const program = materialProps.currentProgram.program
405
+
406
+ const w0Loc = gl.getAttribLocation(program, 'a_w0')
407
+ const w1Loc = gl.getAttribLocation(program, 'a_w1')
408
+ const w2Loc = gl.getAttribLocation(program, 'a_w2')
409
+ const w3Loc = gl.getAttribLocation(program, 'a_w3')
410
+
411
+ const readBoundBuffer = (loc: number): WebGLBuffer | null => {
412
+ if (loc < 0) return null
413
+ return gl.getVertexAttrib(loc, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING) as WebGLBuffer | null
414
+ }
415
+
416
+ const liveW0 = readBoundBuffer(w0Loc)
417
+ if (this.tierCVao && this.tierCAttrs && liveW0 && this.tierCAttrs.w0.buffer !== liveW0) {
418
+ this.invalidateTierCVao()
419
+ }
420
+ if (this.tierCVao) return
421
+
422
+ const w0Buf = readBoundBuffer(w0Loc)
423
+ const w1Buf = readBoundBuffer(w1Loc)
424
+ const w2Buf = readBoundBuffer(w2Loc)
425
+ const w3Buf = readBoundBuffer(w3Loc)
426
+ if (!w0Buf || !w1Buf || !w2Buf || !w3Buf) return
427
+
428
+ const vao = gl.createVertexArray()
429
+ if (!vao) return
430
+
431
+ gl.bindVertexArray(vao)
432
+
433
+ const bindInstancedWord = (loc: number, buffer: WebGLBuffer) => {
434
+ gl.enableVertexAttribArray(loc)
435
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
436
+ gl.vertexAttribIPointer(loc, 1, gl.UNSIGNED_INT, 4, 0)
437
+ gl.vertexAttribDivisor(loc, 1)
438
+ }
439
+ bindInstancedWord(w0Loc, w0Buf)
440
+ bindInstancedWord(w1Loc, w1Buf)
441
+ bindInstancedWord(w2Loc, w2Buf)
442
+ bindInstancedWord(w3Loc, w3Buf)
443
+
444
+ gl.bindVertexArray(null)
445
+ this.tierCVao = vao
446
+ this.tierCAttrs = {
447
+ w0: { loc: w0Loc, buffer: w0Buf },
448
+ w1: { loc: w1Loc, buffer: w1Buf },
449
+ w2: { loc: w2Loc, buffer: w2Buf },
450
+ w3: { loc: w3Loc, buffer: w3Buf },
451
+ }
452
+ }
453
+
259
454
  private markDirty (start: number, end: number): void {
260
455
  this.pendingRanges.push({ start, end })
261
456
  this.pendingRanges.sort((a, b) => a.start - b.start)
@@ -462,5 +657,6 @@ export class GlobalBlockBuffer {
462
657
  mkAttr(this.w3, 'a_w3')
463
658
 
464
659
  this.pendingRanges.length = 0
660
+ this.invalidateTierCVao()
465
661
  }
466
662
  }