minecraft-renderer 0.1.79 → 0.1.81

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 (205) hide show
  1. package/README.md +66 -26
  2. package/dist/mesher.js +36 -36
  3. package/dist/mesher.js.map +4 -4
  4. package/dist/mesherWasm.js +217 -217
  5. package/dist/minecraft-renderer.js +66 -66
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +450 -450
  8. package/package.json +5 -2
  9. package/src/bundler/bundlePrepare.ts +3 -8
  10. package/src/graphicsBackend/appViewer.ts +10 -7
  11. package/src/graphicsBackend/config.ts +1 -5
  12. package/src/graphicsBackend/playerState.ts +35 -34
  13. package/src/graphicsBackend/preloadWorkers.ts +43 -41
  14. package/src/graphicsBackend/rendererDefaultOptions.ts +68 -70
  15. package/src/graphicsBackend/rendererOptionsSync.ts +24 -50
  16. package/src/graphicsBackend/types.ts +2 -2
  17. package/src/index.ts +6 -27
  18. package/src/lib/DebugGui.ts +34 -31
  19. package/src/lib/animationController.ts +9 -9
  20. package/src/lib/bakeLegacyLight.ts +1 -6
  21. package/src/lib/bindAbortableListener.test.ts +32 -12
  22. package/src/lib/bindAbortableListener.ts +14 -6
  23. package/src/lib/blockEntityLightRegistry.ts +11 -20
  24. package/src/lib/blockEntityLighting.test.ts +1 -6
  25. package/src/lib/blockEntityLighting.ts +5 -5
  26. package/src/lib/buildSharedConfig.mjs +4 -4
  27. package/src/lib/buildWorkerMcDataIndexes.test.ts +4 -4
  28. package/src/lib/buildWorkerMcDataIndexes.ts +11 -19
  29. package/src/lib/cameraBobbing.ts +1 -1
  30. package/src/lib/canvas2DOverlay.example.ts +13 -33
  31. package/src/lib/canvas2DOverlay.quickstart.ts +24 -54
  32. package/src/lib/canvas2DOverlay.ts +28 -47
  33. package/src/lib/cleanupDecorator.ts +2 -2
  34. package/src/lib/createPlayerObject.test.ts +2 -7
  35. package/src/lib/createPlayerObject.ts +18 -25
  36. package/src/lib/frameTimingCollector.ts +2 -5
  37. package/src/lib/guiRenderer.ts +57 -42
  38. package/src/lib/items.ts +15 -9
  39. package/src/lib/mesherlogReader.ts +5 -6
  40. package/src/lib/simpleUtils.ts +5 -5
  41. package/src/lib/skyLight.ts +3 -3
  42. package/src/lib/smoothSwitcher.ts +11 -18
  43. package/src/lib/ui/newStats.ts +10 -12
  44. package/src/lib/utils/proxy.ts +7 -1
  45. package/src/lib/utils/skins.ts +4 -4
  46. package/src/lib/utils.ts +1 -1
  47. package/src/lib/workerMessageSanitize.ts +5 -12
  48. package/src/lib/workerProxy.ts +52 -42
  49. package/src/lib/workerSyncOps.test.ts +58 -41
  50. package/src/lib/worldrendererCommon.reconfigure.test.ts +24 -21
  51. package/src/lib/worldrendererCommon.removeColumn.test.ts +11 -13
  52. package/src/lib/worldrendererCommon.ts +349 -211
  53. package/src/mesher-legacy/mesher.ts +9 -4
  54. package/src/mesher-legacy/test/a.ts +1 -1
  55. package/src/mesher-legacy/test/mesherTester.ts +2 -2
  56. package/src/mesher-legacy/test/playground.ts +1 -1
  57. package/src/mesher-legacy/test/run/test-js.ts +1 -1
  58. package/src/mesher-legacy/test/snapshotUtils.ts +3 -8
  59. package/src/mesher-legacy/test/test-perf.ts +1 -1
  60. package/src/mesher-legacy/test/tests.test.ts +2 -2
  61. package/src/mesher-shared/blockEntityMetadata.test.ts +7 -5
  62. package/src/mesher-shared/blockEntityMetadata.ts +28 -19
  63. package/src/mesher-shared/blockPropertiesForMeshing.ts +2 -2
  64. package/src/mesher-shared/computeHeightmap.ts +2 -4
  65. package/src/mesher-shared/exportedGeometryTypes.ts +3 -3
  66. package/src/mesher-shared/faceOcclusion.ts +324 -0
  67. package/src/mesher-shared/models.ts +80 -131
  68. package/src/mesher-shared/modelsGeometryCommon.ts +30 -15
  69. package/src/mesher-shared/shared.ts +46 -46
  70. package/src/mesher-shared/standaloneRenderer.ts +40 -44
  71. package/src/mesher-shared/tests/faceOcclusion.test.ts +179 -0
  72. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +12 -8
  73. package/src/mesher-shared/vertexShading.ts +2 -11
  74. package/src/mesher-shared/world.ts +19 -19
  75. package/src/mesher-shared/worldLighting.test.ts +2 -2
  76. package/src/performanceMonitor/PerformanceMonitor.ts +4 -13
  77. package/src/performanceMonitor/formatPerformanceFactorsDebug.ts +2 -2
  78. package/src/performanceMonitor/index.ts +1 -5
  79. package/src/performanceMonitor/types.ts +1 -1
  80. package/src/playerState/playerState.ts +55 -50
  81. package/src/playerState/types.ts +2 -8
  82. package/src/playground/allEntitiesDebug.ts +19 -16
  83. package/src/playground/baseScene.ts +29 -32
  84. package/src/playground/mobileControls.tsx +133 -129
  85. package/src/playground/playground.html +76 -73
  86. package/src/playground/playground.ts +14 -1
  87. package/src/playground/playgroundUi.tsx +100 -74
  88. package/src/playground/reactUtils.ts +3 -7
  89. package/src/playground/scenes/entities.ts +10 -7
  90. package/src/playground/scenes/frequentUpdates.ts +5 -5
  91. package/src/playground/scenes/geometryExport.ts +6 -20
  92. package/src/playground/scenes/highPosEntityUpdates.ts +2 -10
  93. package/src/playground/scenes/index.ts +1 -1
  94. package/src/playground/scenes/lightingStarfield.ts +18 -12
  95. package/src/playground/scenes/main.ts +15 -11
  96. package/src/playground/scenes/partialBlockCulling.ts +31 -0
  97. package/src/playground/scenes/railsCobweb.ts +1 -1
  98. package/src/playground/scenes/rotationIssue.ts +1 -1
  99. package/src/playground/scenes/slabsOptimization.ts +1 -2
  100. package/src/playground/scenes/transparencyIssue.ts +1 -1
  101. package/src/playground/shared.ts +2 -2
  102. package/src/resourcesManager/resourcesManager.ts +29 -54
  103. package/src/resourcesManager/resourcesManager.worker.test.ts +6 -10
  104. package/src/shims/minecraftData.ts +70 -24
  105. package/src/sign-renderer/index.html +10 -12
  106. package/src/sign-renderer/index.ts +29 -29
  107. package/src/sign-renderer/playground.ts +22 -15
  108. package/src/sign-renderer/tests.test.ts +16 -13
  109. package/src/sign-renderer/vite.config.ts +2 -2
  110. package/src/three/appShared.ts +46 -25
  111. package/src/three/bannerRenderer.ts +33 -61
  112. package/src/three/cameraCollisionBlockCache.ts +156 -0
  113. package/src/three/cameraCollisionSolidity.ts +50 -0
  114. package/src/three/cameraShake.ts +15 -6
  115. package/src/three/chunkMeshManager.ts +223 -390
  116. package/src/three/cinimaticScript.ts +26 -15
  117. package/src/three/cubeDrawSpans.ts +27 -37
  118. package/src/three/cubeMultiDraw.ts +9 -22
  119. package/src/three/documentRenderer.ts +15 -18
  120. package/src/three/entities.ts +175 -150
  121. package/src/three/entity/EntityMesh.ts +114 -108
  122. package/src/three/entity/animations.js +3 -3
  123. package/src/three/entity/armorModels.ts +15 -15
  124. package/src/three/entity/gltfAnimationUtils.ts +2 -5
  125. package/src/three/fireworks.ts +34 -32
  126. package/src/three/fireworksRenderer.ts +32 -29
  127. package/src/three/globalBlockBuffer.ts +167 -100
  128. package/src/three/globalLegacyBuffer.ts +248 -264
  129. package/src/three/graphicsBackendBase.ts +19 -25
  130. package/src/three/graphicsBackendOffThread.ts +12 -15
  131. package/src/three/hand.ts +21 -21
  132. package/src/three/handLegacy.ts +3 -13
  133. package/src/three/holdingBlock.ts +116 -79
  134. package/src/three/holdingBlockFactory.ts +1 -4
  135. package/src/three/holdingBlockItemIdentity.test.ts +25 -17
  136. package/src/three/holdingBlockItemIdentity.ts +9 -6
  137. package/src/three/holdingBlockLegacy.ts +113 -132
  138. package/src/three/itemMesh.ts +20 -29
  139. package/src/three/legacyMultiDraw.ts +10 -16
  140. package/src/three/legacySectionCull.ts +8 -14
  141. package/src/three/menuBackground/activeView.ts +1 -4
  142. package/src/three/menuBackground/assetUrl.ts +1 -1
  143. package/src/three/menuBackground/classic.ts +4 -12
  144. package/src/three/menuBackground/gpuPreference.ts +1 -3
  145. package/src/three/menuBackground/index.ts +2 -11
  146. package/src/three/menuBackground/renderer.ts +1 -5
  147. package/src/three/menuBackground/types.ts +1 -4
  148. package/src/three/menuBackground/v2.ts +196 -96
  149. package/src/three/menuBackground/v2Meta.ts +3 -3
  150. package/src/three/menuBackground/worldBlocks.ts +37 -35
  151. package/src/three/modules/blockBreakParticles.ts +59 -28
  152. package/src/three/modules/cameraBobbing.ts +3 -6
  153. package/src/three/modules/index.ts +1 -1
  154. package/src/three/modules/rain.ts +10 -23
  155. package/src/three/modules/sciFiWorldReveal.ts +11 -25
  156. package/src/three/modules/starfield.ts +7 -13
  157. package/src/three/renderSlot.ts +28 -16
  158. package/src/three/rendererModuleSystem.ts +1 -3
  159. package/src/three/sceneOrigin.ts +96 -33
  160. package/src/three/sectionRaycastAabb.ts +24 -110
  161. package/src/three/shaderCubeMesh.ts +6 -17
  162. package/src/three/shaders/cubeBlockShader.ts +85 -96
  163. package/src/three/shaders/legacyBlockShader.ts +21 -43
  164. package/src/three/shaders/textureIndexMapping.ts +89 -95
  165. package/src/three/shaders/tintPalette.ts +156 -170
  166. package/src/three/signTextureCache.ts +9 -12
  167. package/src/three/skyboxRenderer.ts +62 -70
  168. package/src/three/tests/chunkMeshManagerLegacy.test.ts +34 -64
  169. package/src/three/tests/cubeDrawSpans.test.ts +55 -25
  170. package/src/three/tests/globalBlockBuffer.test.ts +217 -0
  171. package/src/three/tests/globalLegacyBuffer.test.ts +300 -41
  172. package/src/three/tests/legacyMultiDraw.test.ts +8 -29
  173. package/src/three/tests/pr3PerFrameCpuCuts.test.ts +106 -29
  174. package/src/three/tests/signTextureCache.test.ts +4 -4
  175. package/src/three/thirdPersonVoxelRaycast.ts +120 -0
  176. package/src/three/threeJsMedia.ts +55 -72
  177. package/src/three/threeJsParticles.ts +32 -41
  178. package/src/three/threeJsSound.ts +3 -3
  179. package/src/three/threeJsUtils.ts +3 -3
  180. package/src/three/threeWorker.ts +2 -2
  181. package/src/three/waypointSprite.ts +55 -82
  182. package/src/three/waypoints.ts +20 -23
  183. package/src/three/world/cursorBlock.ts +25 -12
  184. package/src/three/world/vr.ts +11 -17
  185. package/src/three/worldGeometryExport.ts +15 -34
  186. package/src/three/worldRendererThree.ts +144 -166
  187. package/src/wasm-mesher/bridge/convertChunk.ts +3 -9
  188. package/src/wasm-mesher/bridge/render-from-wasm.ts +204 -229
  189. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +26 -67
  190. package/src/wasm-mesher/tests/cameraCollisionBlockCache.test.ts +82 -0
  191. package/src/wasm-mesher/tests/cullingRegression.test.ts +307 -0
  192. package/src/wasm-mesher/tests/heightmapParity.test.ts +8 -18
  193. package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +2 -2
  194. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +1 -93
  195. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +103 -141
  196. package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +14 -22
  197. package/src/wasm-mesher/tests/thirdPersonVoxelRaycast.test.ts +63 -0
  198. package/src/wasm-mesher/worker/mesherWasm.ts +150 -141
  199. package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +2 -8
  200. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +1 -3
  201. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +1 -5
  202. package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +42 -42
  203. package/src/worldView/worldView.spiral.test.ts +1 -1
  204. package/src/worldView/worldView.ts +34 -39
  205. package/src/worldView/worldViewWorkerBridge.test.ts +4 -4
@@ -7,7 +7,7 @@ import {
7
7
  logLegacyMultiDrawTierOnce,
8
8
  type LegacyDrawSpan,
9
9
  type LegacyMultiDrawCaps,
10
- type LegacyMultiDrawScratch,
10
+ type LegacyMultiDrawScratch
11
11
  } from './legacyMultiDraw'
12
12
  import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
13
13
 
@@ -22,24 +22,83 @@ const DEFAULT_GROWTH_INCREMENT_QUADS = 128_000
22
22
  const MAX_UPLOAD_QUADS_PER_FRAME = 5_000
23
23
  const FRAGMENTATION_THRESHOLD = 0.25
24
24
 
25
- type PendingMove = { key: string, oldStart: number, newStart: number, count: number }
25
+ type PendingMove = { key: string; oldStart: number; newStart: number; count: number }
26
+ type PendingReplace = { oldStart: number; oldCount: number }
26
27
 
27
28
  /** CPU bytes per allocated quad slot (all legacy vertex/index attrs). */
28
- export const LEGACY_BYTES_PER_QUAD =
29
- VERTS_PER_QUAD * (FLOATS_PER_VERT * 3 + FLOATS_PER_LIGHT_VERT * 2 + FLOATS_PER_UV_VERT) * 4
30
- + INDICES_PER_QUAD * 4
29
+ export const LEGACY_BYTES_PER_QUAD = VERTS_PER_QUAD * (FLOATS_PER_VERT * 3 + FLOATS_PER_LIGHT_VERT * 2 + FLOATS_PER_UV_VERT) * 4 + INDICES_PER_QUAD * 4
31
30
 
32
31
  export const FULL_DRAW_VISIBLE_FRACTION = 0.75
33
- export const SPAN_GAP_TOLERANCE_QUADS = 256
32
+ /** Initial multi_draw scratch size; arrays auto-grow — not a draw-call cap. */
34
33
  export const MAX_OPAQUE_SPANS = 64
35
34
 
35
+ /** Dev assert: every quad in draw spans must lie in a live section's drawable range. */
36
+ export function assertDrawSpansWithinLiveRanges(
37
+ spans: ReadonlyArray<{ start: number; count: number }>,
38
+ liveRanges: ReadonlyArray<{ start: number; count: number }>,
39
+ bufferName: string
40
+ ): void {
41
+ for (const span of spans) {
42
+ for (let q = span.start; q < span.start + span.count; q++) {
43
+ let inLive = false
44
+ for (const live of liveRanges) {
45
+ if (q >= live.start && q < live.start + live.count) {
46
+ inLive = true
47
+ break
48
+ }
49
+ }
50
+ if (!inLive) {
51
+ console.error('[GlobalLegacyBuffer] draw span covers non-live quad', {
52
+ buffer: bufferName,
53
+ quad: q,
54
+ span,
55
+ liveRanges
56
+ })
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ export type DirtyRange = { start: number; end: number }
63
+
64
+ /** Split draw spans to exclude quad/face ranges still in pendingRanges (not yet on GPU). */
65
+ export function carveSpansAroundPendingRanges(
66
+ spans: Array<{ start: number; count: number }>,
67
+ pendingRanges: ReadonlyArray<DirtyRange>
68
+ ): Array<{ start: number; count: number }> {
69
+ if (pendingRanges.length === 0) return spans
70
+ const out: Array<{ start: number; count: number }> = []
71
+ for (const span of spans) {
72
+ let segments: Array<{ start: number; count: number }> = [span]
73
+ for (const pr of pendingRanges) {
74
+ const next: Array<{ start: number; count: number }> = []
75
+ for (const seg of segments) {
76
+ const segEnd = seg.start + seg.count - 1
77
+ if (pr.end < seg.start || pr.start > segEnd) {
78
+ next.push(seg)
79
+ continue
80
+ }
81
+ if (pr.start > seg.start) {
82
+ next.push({ start: seg.start, count: pr.start - seg.start })
83
+ }
84
+ if (pr.end < segEnd) {
85
+ next.push({ start: pr.end + 1, count: segEnd - pr.end })
86
+ }
87
+ }
88
+ segments = next
89
+ }
90
+ out.push(...segments)
91
+ }
92
+ return out.filter(s => s.count > 0)
93
+ }
94
+
36
95
  export type GlobalLegacyBufferOptions = {
37
96
  name?: string
38
97
  initialCapacityQuads?: number
39
98
  growthIncrementQuads?: number
40
99
  }
41
100
 
42
- export type VisibleSectionSpan = { key: string, distSq: number }
101
+ export type VisibleSectionSpan = { key: string; distSq: number }
43
102
 
44
103
  export type LegacySectionGeometry = {
45
104
  positions: Float32Array
@@ -75,24 +134,22 @@ export class GlobalLegacyBuffer {
75
134
  private uvs: Float32Array
76
135
  private aOrigin: Float32Array
77
136
  private indices: Uint32Array
78
- private readonly sectionSlots = new Map<string, { start: number, count: number }>()
79
- private freeList: Array<{ start: number, count: number }> = []
137
+ private readonly sectionSlots = new Map<string, { start: number; count: number }>()
138
+ private freeList: Array<{ start: number; count: number }> = []
80
139
  private highWatermark = 0
81
- private pendingRanges: Array<{ start: number, end: number }> = []
82
- private readonly _spanScratch: Array<{ start: number, count: number }> = []
140
+ private pendingRanges: Array<{ start: number; end: number }> = []
141
+ private readonly _spanScratch: Array<{ start: number; count: number }> = []
83
142
  private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
84
143
  private layoutVersion = 0
85
144
  private pendingMove: PendingMove | null = null
145
+ private readonly pendingReplace = new Map<string, PendingReplace>()
146
+ private uploadEpoch = 0
86
147
  private visibleIndexSpans: LegacyDrawSpan[] = []
87
148
  private readonly _drawScratch: LegacyMultiDrawScratch = createLegacyMultiDrawScratch()
88
149
  private multiDrawCaps: LegacyMultiDrawCaps | null = null
89
150
  private debugOverlay = false
90
151
 
91
- constructor (
92
- material: THREE.ShaderMaterial,
93
- scene: THREE.Object3D,
94
- opts?: GlobalLegacyBufferOptions,
95
- ) {
152
+ constructor(material: THREE.ShaderMaterial, scene: THREE.Object3D, opts?: GlobalLegacyBufferOptions) {
96
153
  this.material = material
97
154
  this.growthIncrementQuads = opts?.growthIncrementQuads ?? DEFAULT_GROWTH_INCREMENT_QUADS
98
155
  this.capacityQuads = opts?.initialCapacityQuads ?? DEFAULT_INITIAL_CAPACITY_QUADS
@@ -146,7 +203,7 @@ export class GlobalLegacyBuffer {
146
203
  }
147
204
  }
148
205
 
149
- setDebugOverlay (enabled: boolean): void {
206
+ setDebugOverlay(enabled: boolean): void {
150
207
  this.debugOverlay = enabled
151
208
  }
152
209
 
@@ -156,19 +213,19 @@ export class GlobalLegacyBuffer {
156
213
  * program/VAO/ELEMENT_ARRAY_BUFFER stay bound while three draws ~nothing.
157
214
  * Draws one triangle from index 0 (usually harmless; if quad 0 is culled, one stray tri).
158
215
  */
159
- suppressThreeDraw (): void {
216
+ suppressThreeDraw(): void {
160
217
  this.mesh.geometry.setDrawRange(0, 3)
161
218
  }
162
219
 
163
- setVisibleIndexSpans (spans: LegacyDrawSpan[]): void {
220
+ setVisibleIndexSpans(spans: LegacyDrawSpan[]): void {
164
221
  this.visibleIndexSpans = spans
165
222
  }
166
223
 
167
- getVisibleIndexSpans (): readonly LegacyDrawSpan[] {
224
+ getVisibleIndexSpans(): readonly LegacyDrawSpan[] {
168
225
  return this.visibleIndexSpans
169
226
  }
170
227
 
171
- private syncDefaultDrawGroups (): void {
228
+ private syncDefaultDrawGroups(): void {
172
229
  const geometry = this.mesh.geometry
173
230
  geometry.clearGroups()
174
231
  const indexCount = this.highWatermark * INDICES_PER_QUAD
@@ -178,13 +235,7 @@ export class GlobalLegacyBuffer {
178
235
  geometry.setDrawRange(0, indexCount)
179
236
  }
180
237
 
181
- addSection (
182
- sectionKey: string,
183
- geo: LegacySectionGeometry,
184
- sx: number,
185
- sy: number,
186
- sz: number,
187
- ): boolean {
238
+ addSection(sectionKey: string, geo: LegacySectionGeometry, sx: number, sy: number, sz: number): boolean {
188
239
  const vertCount = geo.positions.length / FLOATS_PER_VERT
189
240
  const quadCount = vertCount / VERTS_PER_QUAD
190
241
  if (vertCount === 0 || quadCount * VERTS_PER_QUAD !== vertCount) {
@@ -195,8 +246,23 @@ export class GlobalLegacyBuffer {
195
246
  return false
196
247
  }
197
248
 
198
- if (this.sectionSlots.has(sectionKey)) {
199
- this.removeSection(sectionKey)
249
+ const isRemesh = this.sectionSlots.has(sectionKey)
250
+ let previousSlot: { start: number; count: number } | undefined
251
+ if (isRemesh) {
252
+ const currentSlot = this.sectionSlots.get(sectionKey)!
253
+ const inflightReplace = this.pendingReplace.get(sectionKey)
254
+ const inflightMove = this.pendingMove?.key === sectionKey ? this.pendingMove : undefined
255
+ if (inflightReplace) {
256
+ this.zeroAndFreeSlot(currentSlot.start, currentSlot.count)
257
+ previousSlot = { start: inflightReplace.oldStart, count: inflightReplace.oldCount }
258
+ this.pendingReplace.delete(sectionKey)
259
+ } else if (inflightMove) {
260
+ this.zeroAndFreeSlot(currentSlot.start, currentSlot.count)
261
+ previousSlot = { start: inflightMove.oldStart, count: inflightMove.count }
262
+ this.pendingMove = null
263
+ } else {
264
+ previousSlot = currentSlot
265
+ }
200
266
  }
201
267
 
202
268
  if (quadCount > this.capacityQuads) {
@@ -240,29 +306,65 @@ export class GlobalLegacyBuffer {
240
306
  }
241
307
 
242
308
  this.sectionSlots.set(sectionKey, slot)
309
+ if (isRemesh && previousSlot) {
310
+ this.pendingReplace.set(sectionKey, { oldStart: previousSlot.start, oldCount: previousSlot.count })
311
+ }
243
312
  this.markDirty(slot.start, slot.start + quadCount - 1)
244
313
  this.syncDefaultDrawGroups()
245
314
  this.layoutVersion++
246
315
  return true
247
316
  }
248
317
 
249
- getLayoutVersion (): number {
318
+ getLayoutVersion(): number {
250
319
  return this.layoutVersion
251
320
  }
252
321
 
253
- getSectionDrawStart (sectionKey: string): number | undefined {
322
+ getUploadEpoch(): number {
323
+ return this.uploadEpoch
324
+ }
325
+
326
+ hasPendingReplace(): boolean {
327
+ return this.pendingReplace.size > 0
328
+ }
329
+
330
+ canUseFullDrawShortcut(): boolean {
331
+ return this.pendingRanges.length === 0 && this.interiorFreeQuads() === 0 && this.pendingMove === null && this.pendingReplace.size === 0
332
+ }
333
+
334
+ isRangeFullyUploaded(start: number, end: number): boolean {
335
+ return this.rangeFullyUploaded(start, end)
336
+ }
337
+
338
+ getPendingDirtyRanges(): ReadonlyArray<DirtyRange> {
339
+ return this.pendingRanges
340
+ }
341
+
342
+ getSectionDrawStart(sectionKey: string): number | undefined {
254
343
  const slot = this.sectionSlots.get(sectionKey)
255
344
  if (!slot) return undefined
256
345
  if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
346
+ const replace = this.pendingReplace.get(sectionKey)
347
+ if (replace) return replace.oldStart
348
+ if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) return undefined
257
349
  return slot.start
258
350
  }
259
351
 
260
- getPendingMove (): PendingMove | null {
352
+ getSectionDrawCount(sectionKey: string): number | undefined {
353
+ const slot = this.sectionSlots.get(sectionKey)
354
+ if (!slot) return undefined
355
+ if (this.pendingMove?.key === sectionKey) return this.pendingMove.count
356
+ const replace = this.pendingReplace.get(sectionKey)
357
+ if (replace) return replace.oldCount
358
+ if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) return undefined
359
+ return slot.count
360
+ }
361
+
362
+ getPendingMove(): PendingMove | null {
261
363
  return this.pendingMove
262
364
  }
263
365
 
264
366
  /** One interior-hole move per frame when fragmentation exceeds threshold; deferred shrink. */
265
- compactStep (): void {
367
+ compactStep(): void {
266
368
  if (this.pendingMove) {
267
369
  const { newStart, count } = this.pendingMove
268
370
  if (this.rangeFullyUploaded(newStart, newStart + count - 1)) {
@@ -271,6 +373,14 @@ export class GlobalLegacyBuffer {
271
373
  return
272
374
  }
273
375
 
376
+ for (const key of [...this.pendingReplace.keys()]) {
377
+ const slot = this.sectionSlots.get(key)
378
+ if (!slot) continue
379
+ if (this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) {
380
+ this.finalizePendingReplace(key)
381
+ }
382
+ }
383
+
274
384
  if (this.highWatermark === 0) return
275
385
  const interiorFree = this.interiorFreeQuads()
276
386
  if (interiorFree / this.highWatermark <= FRAGMENTATION_THRESHOLD) return
@@ -292,7 +402,7 @@ export class GlobalLegacyBuffer {
292
402
  this.layoutVersion++
293
403
  }
294
404
 
295
- updateDrawSpans (visible: VisibleSectionSpan[], mode: 'opaque' | 'sortedBlend'): void {
405
+ updateDrawSpans(visible: VisibleSectionSpan[], mode: 'opaque' | 'sortedBlend'): void {
296
406
  this.visibleIndexSpans = []
297
407
 
298
408
  if (this.highWatermark === 0) {
@@ -305,10 +415,11 @@ export class GlobalLegacyBuffer {
305
415
 
306
416
  for (const entry of visible) {
307
417
  const drawStart = this.getSectionDrawStart(entry.key)
418
+ const drawCount = this.getSectionDrawCount(entry.key)
308
419
  const slot = this.sectionSlots.get(entry.key)
309
- if (drawStart === undefined || !slot) continue
310
- spans.push({ start: drawStart, count: slot.count })
311
- visibleQuadCount += slot.count
420
+ if (drawStart === undefined || drawCount === undefined || !slot) continue
421
+ spans.push({ start: drawStart, count: drawCount })
422
+ visibleQuadCount += drawCount
312
423
  }
313
424
 
314
425
  if (spans.length === 0) {
@@ -318,45 +429,47 @@ export class GlobalLegacyBuffer {
318
429
  const pushIndexSpan = (quadStart: number, quadCount: number): void => {
319
430
  this.visibleIndexSpans.push({
320
431
  indexStart: quadStart * INDICES_PER_QUAD,
321
- indexCount: quadCount * INDICES_PER_QUAD,
432
+ indexCount: quadCount * INDICES_PER_QUAD
322
433
  })
323
434
  }
324
435
 
325
436
  if (mode === 'opaque') {
326
- if (visibleQuadCount >= this.highWatermark * FULL_DRAW_VISIBLE_FRACTION) {
327
- this.visibleIndexSpans.push({
328
- indexStart: 0,
329
- indexCount: this.highWatermark * INDICES_PER_QUAD,
330
- })
331
- return
437
+ let finalQuads: Array<{ start: number; count: number }>
438
+ const liveDrawRanges = spans.map(s => ({ ...s }))
439
+ const usedFullDraw = this.canUseFullDrawShortcut() && visibleQuadCount >= this.highWatermark * FULL_DRAW_VISIBLE_FRACTION
440
+ if (usedFullDraw) {
441
+ finalQuads = [{ start: 0, count: this.highWatermark }]
442
+ } else {
443
+ spans.sort((a, b) => a.start - b.start)
444
+ this.mergeOpaqueSpans(spans)
445
+ finalQuads = spans
332
446
  }
333
-
334
- spans.sort((a, b) => a.start - b.start)
335
- this.mergeOpaqueSpans(spans)
336
- this.capOpaqueSpans(spans)
337
-
338
- for (const span of spans) {
447
+ finalQuads = carveSpansAroundPendingRanges(finalQuads, this.pendingRanges)
448
+ if (!usedFullDraw) {
449
+ assertDrawSpansWithinLiveRanges(finalQuads, liveDrawRanges, this.mesh.name)
450
+ }
451
+ for (const span of finalQuads) {
339
452
  pushIndexSpan(span.start, span.count)
340
453
  }
341
454
  } else {
342
455
  visible.sort((a, b) => b.distSq - a.distSq)
343
456
  for (const entry of visible) {
344
457
  const drawStart = this.getSectionDrawStart(entry.key)
345
- const slot = this.sectionSlots.get(entry.key)
346
- if (drawStart === undefined || !slot) continue
347
- pushIndexSpan(drawStart, slot.count)
458
+ const drawCount = this.getSectionDrawCount(entry.key)
459
+ if (drawStart === undefined || drawCount === undefined) continue
460
+ pushIndexSpan(drawStart, drawCount)
348
461
  }
349
462
  }
350
463
  }
351
464
 
352
- private mergeOpaqueSpans (spans: Array<{ start: number, count: number }>): void {
465
+ /** Merge only physically adjacent section slots (gap === 0). Never bridge interior holes. */
466
+ private mergeOpaqueSpans(spans: Array<{ start: number; count: number }>): void {
353
467
  if (spans.length < 2) return
354
468
  let i = 0
355
469
  while (i < spans.length - 1) {
356
470
  const cur = spans[i]!
357
471
  const next = spans[i + 1]!
358
- const gap = next.start - (cur.start + cur.count)
359
- if (gap <= SPAN_GAP_TOLERANCE_QUADS) {
472
+ if (cur.start + cur.count === next.start) {
360
473
  cur.count = next.start + next.count - cur.start
361
474
  spans.splice(i + 1, 1)
362
475
  } else {
@@ -365,40 +478,22 @@ export class GlobalLegacyBuffer {
365
478
  }
366
479
  }
367
480
 
368
- private capOpaqueSpans (spans: Array<{ start: number, count: number }>): void {
369
- while (spans.length > MAX_OPAQUE_SPANS) {
370
- let bestIdx = 0
371
- let bestGap = Infinity
372
- for (let i = 0; i < spans.length - 1; i++) {
373
- const gap = spans[i + 1]!.start - (spans[i]!.start + spans[i]!.count)
374
- if (gap < bestGap) {
375
- bestGap = gap
376
- bestIdx = i
377
- }
378
- }
379
- const cur = spans[bestIdx]!
380
- const next = spans[bestIdx + 1]!
381
- cur.count = next.start + next.count - cur.start
382
- spans.splice(bestIdx + 1, 1)
383
- }
384
- }
385
-
386
- hasSection (sectionKey: string): boolean {
481
+ hasSection(sectionKey: string): boolean {
387
482
  return this.sectionSlots.has(sectionKey)
388
483
  }
389
484
 
390
- getSectionSlot (sectionKey: string): { start: number, count: number } | undefined {
485
+ getSectionSlot(sectionKey: string): { start: number; count: number } | undefined {
391
486
  return this.sectionSlots.get(sectionKey)
392
487
  }
393
488
 
394
- takeSectionData (sectionKey: string): LegacySectionGeometryData | undefined {
489
+ takeSectionData(sectionKey: string): LegacySectionGeometryData | undefined {
395
490
  const data = this.getSectionGeometryData(sectionKey)
396
491
  if (!data) return undefined
397
492
  this.removeSection(sectionKey)
398
493
  return data
399
494
  }
400
495
 
401
- getSectionGeometryData (sectionKey: string): LegacySectionGeometryData | undefined {
496
+ getSectionGeometryData(sectionKey: string): LegacySectionGeometryData | undefined {
402
497
  const slot = this.sectionSlots.get(sectionKey)
403
498
  if (!slot) return undefined
404
499
 
@@ -429,19 +524,19 @@ export class GlobalLegacyBuffer {
429
524
  return { positions, colors, skyLights, blockLights, uvs, indices, sx, sy, sz }
430
525
  }
431
526
 
432
- removeSection (sectionKey: string): void {
527
+ removeSection(sectionKey: string): void {
433
528
  const slot = this.sectionSlots.get(sectionKey)
434
529
  if (!slot) return
435
530
 
531
+ if (this.pendingReplace.has(sectionKey)) {
532
+ const pr = this.pendingReplace.get(sectionKey)!
533
+ this.zeroAndFreeSlot(pr.oldStart, pr.oldCount)
534
+ this.pendingReplace.delete(sectionKey)
535
+ }
536
+
436
537
  if (this.pendingMove?.key === sectionKey) {
437
538
  const { oldStart, count } = this.pendingMove
438
- const oldIndexBase = oldStart * INDICES_PER_QUAD
439
- const oldIndexLen = count * INDICES_PER_QUAD
440
- for (let i = 0; i < oldIndexLen; i++) {
441
- this.indices[oldIndexBase + i] = 0
442
- }
443
- this.markDirty(oldStart, oldStart + count - 1)
444
- this.insertFreeSlot({ start: oldStart, count })
539
+ this.zeroAndFreeSlot(oldStart, count)
445
540
  this.pendingMove = null
446
541
  }
447
542
 
@@ -459,11 +554,11 @@ export class GlobalLegacyBuffer {
459
554
  this.layoutVersion++
460
555
  }
461
556
 
462
- hasPendingUploads (): boolean {
557
+ hasPendingUploads(): boolean {
463
558
  return this.pendingRanges.length > 0
464
559
  }
465
560
 
466
- uploadDirtyRange (): void {
561
+ uploadDirtyRange(): void {
467
562
  const r = this.pendingRanges[0]
468
563
  if (!r) return
469
564
 
@@ -510,15 +605,19 @@ export class GlobalLegacyBuffer {
510
605
  indexAttr.addUpdateRange(indexOffset, indexCount)
511
606
  indexAttr.needsUpdate = true
512
607
 
513
- if (quadOffset + quadCount > r.end) this.pendingRanges.shift()
514
- else r.start = quadOffset + quadCount
608
+ if (quadOffset + quadCount > r.end) {
609
+ this.pendingRanges.shift()
610
+ } else {
611
+ r.start = quadOffset + quadCount
612
+ }
613
+ this.uploadEpoch++
515
614
  }
516
615
 
517
- setRenderOrigin (renderOrigin: RenderOrigin): void {
616
+ setRenderOrigin(renderOrigin: RenderOrigin): void {
518
617
  this.renderOrigin = { ...renderOrigin }
519
618
  }
520
619
 
521
- rebase (delta: RenderOrigin): void {
620
+ rebase(delta: RenderOrigin): void {
522
621
  if (this.highWatermark === 0) return
523
622
  for (const slot of this.sectionSlots.values()) {
524
623
  const dstVertBase = slot.start * VERTS_PER_QUAD
@@ -537,7 +636,7 @@ export class GlobalLegacyBuffer {
537
636
  this.renderOrigin.z += delta.z
538
637
  }
539
638
 
540
- setCameraOrigin (x: number, y: number, z: number): void {
639
+ setCameraOrigin(x: number, y: number, z: number): void {
541
640
  const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(this.renderOrigin, x, y, z)
542
641
  const u = this.material.uniforms.u_originDelta
543
642
  if (u?.value?.set) u.value.set(originDelta.x, originDelta.y, originDelta.z)
@@ -545,104 +644,53 @@ export class GlobalLegacyBuffer {
545
644
  if (uf?.value?.set) uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
546
645
  }
547
646
 
548
- raycastSections (
549
- raycaster: THREE.Raycaster,
550
- sectionKeys: Iterable<string>,
551
- out: THREE.Intersection[],
552
- ): THREE.Intersection[] {
553
- const ray = raycaster.ray
554
- const closest = raycaster.near
555
- const far = raycaster.far
556
- _raycastOrigin.copy(ray.origin).sub(_raycastRenderOrigin.set(
557
- this.renderOrigin.x,
558
- this.renderOrigin.y,
559
- this.renderOrigin.z,
560
- ))
561
- _raycastRay.origin.copy(_raycastOrigin)
562
- _raycastRay.direction.copy(ray.direction)
563
-
564
- for (const key of sectionKeys) {
565
- const slot = this.sectionSlots.get(key)
566
- if (!slot) continue
567
-
568
- const dstVertBase = slot.start * VERTS_PER_QUAD
569
- const dstFloatBase = dstVertBase * FLOATS_PER_VERT
570
- const dstIndexBase = slot.start * INDICES_PER_QUAD
571
- const indexLen = slot.count * INDICES_PER_QUAD
572
-
573
- for (let i = 0; i < indexLen; i += 3) {
574
- const i0 = this.indices[dstIndexBase + i]!
575
- const i1 = this.indices[dstIndexBase + i + 1]!
576
- const i2 = this.indices[dstIndexBase + i + 2]!
577
- if (i0 === i1 && i1 === i2) continue
578
-
579
- const hit = intersectTriangle(
580
- _raycastRay,
581
- this.positions, this.aOrigin, dstFloatBase,
582
- i0, i1, i2,
583
- closest, far,
584
- )
585
- if (hit !== null) {
586
- out.push({
587
- distance: hit,
588
- point: ray.at(hit, new THREE.Vector3()),
589
- object: this.mesh,
590
- face: null,
591
- faceIndex: Math.floor(i / 3),
592
- })
593
- }
594
- }
595
- }
596
-
597
- out.sort((a, b) => a.distance - b.distance)
598
- return out
599
- }
600
-
601
- getHighWatermark (): number {
647
+ getHighWatermark(): number {
602
648
  return this.highWatermark
603
649
  }
604
650
 
605
- getCapacityQuads (): number {
651
+ getCapacityQuads(): number {
606
652
  return this.capacityQuads
607
653
  }
608
654
 
609
- getSectionCount (): number {
655
+ getSectionCount(): number {
610
656
  return this.sectionSlots.size
611
657
  }
612
658
 
613
- getMemoryBytes (): number {
659
+ getMemoryBytes(): number {
614
660
  return this.capacityQuads * LEGACY_BYTES_PER_QUAD
615
661
  }
616
662
 
617
- getUsedMemoryBytes (): number {
663
+ getUsedMemoryBytes(): number {
618
664
  return this.highWatermark * LEGACY_BYTES_PER_QUAD
619
665
  }
620
666
 
621
- reset (): void {
667
+ reset(): void {
622
668
  this.sectionSlots.clear()
623
669
  this.freeList.length = 0
624
670
  this.highWatermark = 0
625
671
  this.pendingRanges.length = 0
626
672
  this.pendingMove = null
673
+ this.pendingReplace.clear()
674
+ this.uploadEpoch = 0
627
675
  this.visibleIndexSpans = []
628
676
  this.syncDefaultDrawGroups()
629
677
  }
630
678
 
631
- dispose (): void {
679
+ dispose(): void {
632
680
  this.mesh.parent?.remove(this.mesh)
633
681
  this.mesh.geometry.dispose()
634
682
  this.reset()
635
683
  }
636
684
 
637
- private markDirty (start: number, end: number): void {
685
+ private markDirty(start: number, end: number): void {
638
686
  this.pendingRanges.push({ start, end })
639
687
  this.pendingRanges.sort((a, b) => a.start - b.start)
640
688
  this.mergePendingRanges()
641
689
  }
642
690
 
643
- private mergePendingRanges (): void {
691
+ private mergePendingRanges(): void {
644
692
  if (this.pendingRanges.length < 2) return
645
- const merged: Array<{ start: number, end: number }> = []
693
+ const merged: Array<{ start: number; end: number }> = []
646
694
  let cur = this.pendingRanges[0]!
647
695
  for (let i = 1; i < this.pendingRanges.length; i++) {
648
696
  const next = this.pendingRanges[i]!
@@ -657,7 +705,7 @@ export class GlobalLegacyBuffer {
657
705
  this.pendingRanges = merged
658
706
  }
659
707
 
660
- private takeFreeSlot (count: number): { start: number, count: number } | undefined {
708
+ private takeFreeSlot(count: number): { start: number; count: number } | undefined {
661
709
  for (let i = 0; i < this.freeList.length; i++) {
662
710
  const slot = this.freeList[i]!
663
711
  if (slot.count >= count) {
@@ -671,15 +719,15 @@ export class GlobalLegacyBuffer {
671
719
  return undefined
672
720
  }
673
721
 
674
- private insertFreeSlot (slot: { start: number, count: number }): void {
722
+ private insertFreeSlot(slot: { start: number; count: number }): void {
675
723
  this.freeList.push(slot)
676
724
  this.freeList.sort((a, b) => a.start - b.start)
677
725
  this.mergeFreeList()
678
726
  }
679
727
 
680
- private mergeFreeList (): void {
728
+ private mergeFreeList(): void {
681
729
  if (this.freeList.length < 2) return
682
- const merged: Array<{ start: number, count: number }> = []
730
+ const merged: Array<{ start: number; count: number }> = []
683
731
  let cur = this.freeList[0]!
684
732
  for (let i = 1; i < this.freeList.length; i++) {
685
733
  const next = this.freeList[i]!
@@ -694,7 +742,7 @@ export class GlobalLegacyBuffer {
694
742
  this.freeList = merged
695
743
  }
696
744
 
697
- private shrinkHighWatermark (): void {
745
+ private shrinkHighWatermark(): void {
698
746
  while (this.highWatermark > 0) {
699
747
  const tail = this.highWatermark - 1
700
748
  const free = this.freeList.find(s => s.start <= tail && s.start + s.count > tail)
@@ -705,7 +753,7 @@ export class GlobalLegacyBuffer {
705
753
  }
706
754
  }
707
755
 
708
- private interiorFreeQuads (): number {
756
+ private interiorFreeQuads(): number {
709
757
  let total = 0
710
758
  for (const slot of this.freeList) {
711
759
  if (slot.start < this.highWatermark) total += slot.count
@@ -713,8 +761,8 @@ export class GlobalLegacyBuffer {
713
761
  return total
714
762
  }
715
763
 
716
- private findMovableSection (maxCount: number): { key: string, start: number, count: number } | undefined {
717
- const sections: Array<{ key: string, start: number, count: number }> = []
764
+ private findMovableSection(maxCount: number): { key: string; start: number; count: number } | undefined {
765
+ const sections: Array<{ key: string; start: number; count: number }> = []
718
766
  for (const [key, slot] of this.sectionSlots) {
719
767
  sections.push({ key, start: slot.start, count: slot.count })
720
768
  }
@@ -722,7 +770,7 @@ export class GlobalLegacyBuffer {
722
770
 
723
771
  sections.sort((a, b) => {
724
772
  if (b.start !== a.start) return b.start - a.start
725
- return (b.start + b.count) - (a.start + a.count)
773
+ return b.start + b.count - (a.start + a.count)
726
774
  })
727
775
 
728
776
  const tailmost = sections[0]!
@@ -730,9 +778,7 @@ export class GlobalLegacyBuffer {
730
778
  return tailmost
731
779
  }
732
780
 
733
- const candidates = sections
734
- .filter(s => s.count <= maxCount)
735
- .sort((a, b) => b.count - a.count)
781
+ const candidates = sections.filter(s => s.count <= maxCount).sort((a, b) => b.count - a.count)
736
782
 
737
783
  for (const s of candidates) {
738
784
  if (this.findLowestInteriorHole(s.start, s.count)) return s
@@ -740,10 +786,7 @@ export class GlobalLegacyBuffer {
740
786
  return undefined
741
787
  }
742
788
 
743
- private findLowestInteriorHole (
744
- sectionStart: number,
745
- count: number,
746
- ): { start: number, count: number, index: number } | undefined {
789
+ private findLowestInteriorHole(sectionStart: number, count: number): { start: number; count: number; index: number } | undefined {
747
790
  for (let i = 0; i < this.freeList.length; i++) {
748
791
  const slot = this.freeList[i]!
749
792
  if (slot.start < sectionStart && slot.count >= count) {
@@ -753,7 +796,7 @@ export class GlobalLegacyBuffer {
753
796
  return undefined
754
797
  }
755
798
 
756
- private reserveFreeSlotAt (index: number, count: number): { start: number, count: number } {
799
+ private reserveFreeSlotAt(index: number, count: number): { start: number; count: number } {
757
800
  const slot = this.freeList[index]!
758
801
  this.freeList.splice(index, 1)
759
802
  if (slot.count === count) return { start: slot.start, count }
@@ -762,7 +805,7 @@ export class GlobalLegacyBuffer {
762
805
  return used
763
806
  }
764
807
 
765
- private copySectionRange (oldStart: number, newStart: number, quadCount: number): void {
808
+ private copySectionRange(oldStart: number, newStart: number, quadCount: number): void {
766
809
  const oldVertBase = oldStart * VERTS_PER_QUAD
767
810
  const newVertBase = newStart * VERTS_PER_QUAD
768
811
  const vertCount = quadCount * VERTS_PER_QUAD
@@ -794,35 +837,53 @@ export class GlobalLegacyBuffer {
794
837
  }
795
838
  }
796
839
 
797
- private rangeFullyUploaded (start: number, end: number): boolean {
840
+ private rangeFullyUploaded(start: number, end: number): boolean {
798
841
  for (const r of this.pendingRanges) {
799
842
  if (r.start <= end && r.end >= start) return false
800
843
  }
801
844
  return true
802
845
  }
803
846
 
804
- private finalizePendingMove (): void {
805
- const move = this.pendingMove
806
- if (!move) return
807
-
808
- const { oldStart, count } = move
809
- const oldIndexBase = oldStart * INDICES_PER_QUAD
847
+ private zeroAndFreeSlot(start: number, count: number): void {
848
+ const oldIndexBase = start * INDICES_PER_QUAD
810
849
  const oldIndexLen = count * INDICES_PER_QUAD
811
850
  for (let i = 0; i < oldIndexLen; i++) {
812
851
  this.indices[oldIndexBase + i] = 0
813
852
  }
814
- this.insertFreeSlot({ start: oldStart, count })
853
+ this.markDirty(start, start + count - 1)
854
+ this.insertFreeSlot({ start, count })
855
+ }
856
+
857
+ private finalizePendingReplace(key: string): void {
858
+ const pr = this.pendingReplace.get(key)
859
+ if (!pr) return
860
+
861
+ this.zeroAndFreeSlot(pr.oldStart, pr.oldCount)
862
+ this.pendingReplace.delete(key)
863
+ this.shrinkHighWatermark()
864
+ this.syncDefaultDrawGroups()
865
+ this.layoutVersion++
866
+ this.uploadEpoch++
867
+ }
868
+
869
+ private finalizePendingMove(): void {
870
+ const move = this.pendingMove
871
+ if (!move) return
872
+
873
+ const { oldStart, count } = move
874
+ this.zeroAndFreeSlot(oldStart, count)
815
875
  this.shrinkHighWatermark()
816
- if (oldStart < this.highWatermark) {
817
- this.markDirty(oldStart, oldStart + count - 1)
818
- }
819
876
  this.syncDefaultDrawGroups()
820
877
  this.pendingMove = null
821
878
  this.layoutVersion++
879
+ this.uploadEpoch++
822
880
  }
823
881
 
824
- private growCapacity (minQuads: number): void {
882
+ private growCapacity(minQuads: number): void {
825
883
  if (this.pendingMove) this.finalizePendingMove()
884
+ for (const key of [...this.pendingReplace.keys()]) {
885
+ this.finalizePendingReplace(key)
886
+ }
826
887
 
827
888
  let newCap = this.capacityQuads
828
889
  while (newCap < minQuads) newCap += this.growthIncrementQuads
@@ -879,80 +940,3 @@ export class GlobalLegacyBuffer {
879
940
  this.pendingRanges.length = 0
880
941
  }
881
942
  }
882
-
883
- const _vA = new THREE.Vector3()
884
- const _vB = new THREE.Vector3()
885
- const _vC = new THREE.Vector3()
886
- const _edge1 = new THREE.Vector3()
887
- const _edge2 = new THREE.Vector3()
888
- const _normal = new THREE.Vector3()
889
- const _raycastOrigin = new THREE.Vector3()
890
- const _raycastRenderOrigin = new THREE.Vector3()
891
- const _raycastRay = new THREE.Ray()
892
-
893
- function readWorldVertex (
894
- positions: Float32Array,
895
- aOrigin: Float32Array,
896
- floatBase: number,
897
- vertIndex: number,
898
- target: THREE.Vector3,
899
- ): void {
900
- const f = floatBase + vertIndex * FLOATS_PER_VERT
901
- target.set(
902
- aOrigin[f]! + positions[f]!,
903
- aOrigin[f + 1]! + positions[f + 1]!,
904
- aOrigin[f + 2]! + positions[f + 2]!,
905
- )
906
- }
907
-
908
- function intersectTriangle (
909
- ray: THREE.Ray,
910
- positions: Float32Array,
911
- aOrigin: Float32Array,
912
- floatBase: number,
913
- i0: number,
914
- i1: number,
915
- i2: number,
916
- near: number,
917
- far: number,
918
- ): number | null {
919
- readWorldVertex(positions, aOrigin, floatBase, i0, _vA)
920
- readWorldVertex(positions, aOrigin, floatBase, i1, _vB)
921
- readWorldVertex(positions, aOrigin, floatBase, i2, _vC)
922
-
923
- _edge1.subVectors(_vB, _vA)
924
- _edge2.subVectors(_vC, _vA)
925
- _normal.crossVectors(_edge1, _edge2)
926
-
927
- const denom = _normal.dot(ray.direction)
928
- if (Math.abs(denom) < 1e-8) return null
929
-
930
- const t = _vA.clone().sub(ray.origin).dot(_normal) / denom
931
- if (t < near || t > far) return null
932
-
933
- const p = ray.at(t, new THREE.Vector3())
934
- if (!pointInTriangle(p, _vA, _vB, _vC)) return null
935
- return t
936
- }
937
-
938
- function pointInTriangle (p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3): boolean {
939
- _edge1.subVectors(b, a)
940
- _edge2.subVectors(c, a)
941
- const n = _normal.crossVectors(_edge1, _edge2).normalize()
942
-
943
- const ab = _edge1
944
- const ac = _edge2
945
- const ap = p.clone().sub(a)
946
-
947
- const d00 = ab.dot(ab)
948
- const d01 = ab.dot(ac)
949
- const d11 = ac.dot(ac)
950
- const d20 = ap.dot(ab)
951
- const d21 = ap.dot(ac)
952
- const denom = d00 * d11 - d01 * d01
953
- if (Math.abs(denom) < 1e-12) return false
954
- const v = (d11 * d20 - d01 * d21) / denom
955
- const w = (d00 * d21 - d01 * d20) / denom
956
- const u = 1 - v - w
957
- return u >= -1e-4 && v >= -1e-4 && w >= -1e-4
958
- }