minecraft-renderer 0.1.78 → 0.1.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -25
- package/dist/mesher.js +36 -36
- package/dist/mesher.js.map +4 -4
- package/dist/mesherWasm.js +217 -217
- package/dist/minecraft-renderer.js +32 -32
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +415 -415
- package/package.json +5 -2
- package/src/bundler/bundlePrepare.ts +3 -8
- package/src/graphicsBackend/appViewer.ts +10 -7
- package/src/graphicsBackend/config.ts +1 -5
- package/src/graphicsBackend/playerState.ts +35 -34
- package/src/graphicsBackend/preloadWorkers.ts +43 -41
- package/src/graphicsBackend/rendererDefaultOptions.ts +68 -70
- package/src/graphicsBackend/rendererOptionsSync.ts +24 -50
- package/src/graphicsBackend/types.ts +2 -2
- package/src/index.ts +6 -27
- package/src/lib/DebugGui.ts +34 -31
- package/src/lib/animationController.ts +9 -9
- package/src/lib/bakeLegacyLight.ts +1 -6
- package/src/lib/bindAbortableListener.test.ts +32 -12
- package/src/lib/bindAbortableListener.ts +14 -6
- package/src/lib/blockEntityLightRegistry.ts +11 -20
- package/src/lib/blockEntityLighting.test.ts +1 -6
- package/src/lib/blockEntityLighting.ts +5 -5
- package/src/lib/buildSharedConfig.mjs +4 -4
- package/src/lib/buildWorkerMcDataIndexes.test.ts +4 -4
- package/src/lib/buildWorkerMcDataIndexes.ts +11 -19
- package/src/lib/cameraBobbing.ts +1 -1
- package/src/lib/canvas2DOverlay.example.ts +13 -33
- package/src/lib/canvas2DOverlay.quickstart.ts +24 -54
- package/src/lib/canvas2DOverlay.ts +28 -47
- package/src/lib/cleanupDecorator.ts +2 -2
- package/src/lib/createPlayerObject.test.ts +2 -7
- package/src/lib/createPlayerObject.ts +18 -25
- package/src/lib/frameTimingCollector.ts +2 -5
- package/src/lib/guiRenderer.ts +57 -42
- package/src/lib/items.ts +15 -9
- package/src/lib/mesherlogReader.ts +5 -6
- package/src/lib/simpleUtils.ts +5 -5
- package/src/lib/skyLight.ts +3 -3
- package/src/lib/smoothSwitcher.ts +11 -18
- package/src/lib/ui/newStats.ts +10 -12
- package/src/lib/utils/proxy.ts +7 -1
- package/src/lib/utils/skins.ts +4 -4
- package/src/lib/utils.ts +1 -1
- package/src/lib/workerMessageSanitize.ts +5 -12
- package/src/lib/workerProxy.ts +52 -42
- package/src/lib/workerSyncOps.test.ts +58 -41
- package/src/lib/worldrendererCommon.reconfigure.test.ts +24 -21
- package/src/lib/worldrendererCommon.removeColumn.test.ts +11 -13
- package/src/lib/worldrendererCommon.ts +338 -211
- package/src/mesher-legacy/mesher.ts +9 -4
- package/src/mesher-legacy/test/a.ts +1 -1
- package/src/mesher-legacy/test/mesherTester.ts +2 -2
- package/src/mesher-legacy/test/playground.ts +1 -1
- package/src/mesher-legacy/test/run/test-js.ts +1 -1
- package/src/mesher-legacy/test/snapshotUtils.ts +3 -8
- package/src/mesher-legacy/test/test-perf.ts +1 -1
- package/src/mesher-legacy/test/tests.test.ts +2 -2
- package/src/mesher-shared/blockEntityMetadata.test.ts +7 -5
- package/src/mesher-shared/blockEntityMetadata.ts +28 -19
- package/src/mesher-shared/blockPropertiesForMeshing.ts +2 -2
- package/src/mesher-shared/computeHeightmap.ts +2 -4
- package/src/mesher-shared/exportedGeometryTypes.ts +3 -3
- package/src/mesher-shared/faceOcclusion.ts +324 -0
- package/src/mesher-shared/models.ts +80 -131
- package/src/mesher-shared/modelsGeometryCommon.ts +30 -15
- package/src/mesher-shared/shared.ts +46 -46
- package/src/mesher-shared/standaloneRenderer.ts +40 -44
- package/src/mesher-shared/tests/faceOcclusion.test.ts +179 -0
- package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +12 -8
- package/src/mesher-shared/vertexShading.ts +2 -11
- package/src/mesher-shared/world.ts +19 -19
- package/src/mesher-shared/worldLighting.test.ts +2 -2
- package/src/performanceMonitor/PerformanceMonitor.ts +4 -13
- package/src/performanceMonitor/formatPerformanceFactorsDebug.ts +2 -2
- package/src/performanceMonitor/index.ts +1 -5
- package/src/performanceMonitor/types.ts +1 -1
- package/src/playerState/playerState.ts +55 -50
- package/src/playerState/types.ts +2 -8
- package/src/playground/allEntitiesDebug.ts +19 -16
- package/src/playground/baseScene.ts +29 -32
- package/src/playground/mobileControls.tsx +133 -129
- package/src/playground/playground.html +76 -73
- package/src/playground/playground.ts +14 -1
- package/src/playground/playgroundUi.tsx +100 -74
- package/src/playground/reactUtils.ts +3 -7
- package/src/playground/scenes/entities.ts +10 -7
- package/src/playground/scenes/frequentUpdates.ts +5 -5
- package/src/playground/scenes/geometryExport.ts +6 -20
- package/src/playground/scenes/highPosEntityUpdates.ts +2 -10
- package/src/playground/scenes/index.ts +1 -1
- package/src/playground/scenes/lightingStarfield.ts +18 -12
- package/src/playground/scenes/main.ts +15 -11
- package/src/playground/scenes/partialBlockCulling.ts +31 -0
- package/src/playground/scenes/railsCobweb.ts +1 -1
- package/src/playground/scenes/rotationIssue.ts +1 -1
- package/src/playground/scenes/slabsOptimization.ts +1 -2
- package/src/playground/scenes/transparencyIssue.ts +1 -1
- package/src/playground/shared.ts +2 -2
- package/src/resourcesManager/resourcesManager.ts +29 -54
- package/src/resourcesManager/resourcesManager.worker.test.ts +6 -10
- package/src/shims/minecraftData.ts +70 -24
- package/src/sign-renderer/index.html +10 -12
- package/src/sign-renderer/index.ts +29 -29
- package/src/sign-renderer/playground.ts +22 -15
- package/src/sign-renderer/tests.test.ts +16 -13
- package/src/sign-renderer/vite.config.ts +2 -2
- package/src/three/appShared.ts +46 -25
- package/src/three/bannerRenderer.ts +33 -61
- package/src/three/cameraShake.ts +15 -6
- package/src/three/chunkMeshManager.ts +329 -301
- package/src/three/cinimaticScript.ts +26 -15
- package/src/three/cubeDrawSpans.ts +27 -37
- package/src/three/cubeMultiDraw.ts +9 -22
- package/src/three/documentRenderer.ts +15 -18
- package/src/three/entities.ts +175 -150
- package/src/three/entity/EntityMesh.ts +114 -108
- package/src/three/entity/animations.js +3 -3
- package/src/three/entity/armorModels.ts +15 -15
- package/src/three/entity/gltfAnimationUtils.ts +2 -5
- package/src/three/fireworks.ts +34 -32
- package/src/three/fireworksRenderer.ts +32 -29
- package/src/three/globalBlockBuffer.ts +174 -99
- package/src/three/globalLegacyBuffer.ts +465 -136
- package/src/three/graphicsBackendBase.ts +19 -25
- package/src/three/graphicsBackendOffThread.ts +12 -15
- package/src/three/hand.ts +21 -21
- package/src/three/handLegacy.ts +3 -13
- package/src/three/holdingBlock.ts +116 -79
- package/src/three/holdingBlockFactory.ts +1 -4
- package/src/three/holdingBlockItemIdentity.test.ts +25 -17
- package/src/three/holdingBlockItemIdentity.ts +9 -6
- package/src/three/holdingBlockLegacy.ts +113 -132
- package/src/three/itemMesh.ts +20 -29
- package/src/three/legacyMultiDraw.ts +93 -0
- package/src/three/legacySectionCull.ts +8 -14
- package/src/three/menuBackground/activeView.ts +1 -4
- package/src/three/menuBackground/assetUrl.ts +1 -1
- package/src/three/menuBackground/classic.ts +4 -12
- package/src/three/menuBackground/gpuPreference.ts +1 -3
- package/src/three/menuBackground/index.ts +2 -11
- package/src/three/menuBackground/renderer.ts +1 -5
- package/src/three/menuBackground/types.ts +1 -4
- package/src/three/menuBackground/v2.ts +196 -96
- package/src/three/menuBackground/v2Meta.ts +3 -3
- package/src/three/menuBackground/worldBlocks.ts +37 -35
- package/src/three/modules/blockBreakParticles.ts +59 -28
- package/src/three/modules/cameraBobbing.ts +3 -6
- package/src/three/modules/index.ts +1 -1
- package/src/three/modules/rain.ts +10 -23
- package/src/three/modules/sciFiWorldReveal.ts +11 -25
- package/src/three/modules/starfield.ts +7 -13
- package/src/three/renderSlot.ts +28 -16
- package/src/three/rendererModuleSystem.ts +1 -3
- package/src/three/sceneOrigin.ts +96 -33
- package/src/three/sectionRaycastAabb.ts +30 -18
- package/src/three/shaderCubeMesh.ts +6 -17
- package/src/three/shaders/cubeBlockShader.ts +85 -96
- package/src/three/shaders/legacyBlockShader.ts +21 -43
- package/src/three/shaders/textureIndexMapping.ts +89 -95
- package/src/three/shaders/tintPalette.ts +156 -170
- package/src/three/signTextureCache.ts +9 -12
- package/src/three/skyboxRenderer.ts +62 -70
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +133 -25
- package/src/three/tests/cubeDrawSpans.test.ts +55 -25
- package/src/three/tests/globalBlockBuffer.test.ts +217 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +460 -58
- package/src/three/tests/legacyMultiDraw.test.ts +88 -0
- package/src/three/tests/pr3PerFrameCpuCuts.test.ts +370 -0
- package/src/three/tests/signTextureCache.test.ts +4 -4
- package/src/three/threeJsMedia.ts +55 -72
- package/src/three/threeJsParticles.ts +32 -41
- package/src/three/threeJsSound.ts +3 -3
- package/src/three/threeJsUtils.ts +3 -3
- package/src/three/threeWorker.ts +2 -2
- package/src/three/waypointSprite.ts +55 -82
- package/src/three/waypoints.ts +20 -23
- package/src/three/world/cursorBlock.ts +25 -12
- package/src/three/world/vr.ts +11 -17
- package/src/three/worldGeometryExport.ts +15 -34
- package/src/three/worldRendererThree.ts +131 -133
- package/src/wasm-mesher/bridge/convertChunk.ts +3 -9
- package/src/wasm-mesher/bridge/render-from-wasm.ts +204 -229
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +26 -67
- package/src/wasm-mesher/tests/cullingRegression.test.ts +307 -0
- package/src/wasm-mesher/tests/heightmapParity.test.ts +8 -18
- package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +2 -2
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +17 -4
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +103 -141
- package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +14 -22
- package/src/wasm-mesher/worker/mesherWasm.ts +150 -141
- package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +2 -8
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +1 -3
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +1 -5
- package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +42 -42
- package/src/worldView/worldView.spiral.test.ts +1 -1
- package/src/worldView/worldView.ts +34 -39
- package/src/worldView/worldViewWorkerBridge.test.ts +4 -4
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
+
import {
|
|
4
|
+
createLegacyMultiDrawScratch,
|
|
5
|
+
detectLegacyMultiDrawCaps,
|
|
6
|
+
drawLegacySpans,
|
|
7
|
+
logLegacyMultiDrawTierOnce,
|
|
8
|
+
type LegacyDrawSpan,
|
|
9
|
+
type LegacyMultiDrawCaps,
|
|
10
|
+
type LegacyMultiDrawScratch
|
|
11
|
+
} from './legacyMultiDraw'
|
|
3
12
|
import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
4
13
|
|
|
5
14
|
const VERTS_PER_QUAD = 4
|
|
@@ -11,23 +20,85 @@ const FLOATS_PER_LIGHT_VERT = 1
|
|
|
11
20
|
const DEFAULT_INITIAL_CAPACITY_QUADS = 128_000
|
|
12
21
|
const DEFAULT_GROWTH_INCREMENT_QUADS = 128_000
|
|
13
22
|
const MAX_UPLOAD_QUADS_PER_FRAME = 5_000
|
|
23
|
+
const FRAGMENTATION_THRESHOLD = 0.25
|
|
24
|
+
|
|
25
|
+
type PendingMove = { key: string; oldStart: number; newStart: number; count: number }
|
|
26
|
+
type PendingReplace = { oldStart: number; oldCount: number }
|
|
14
27
|
|
|
15
28
|
/** CPU bytes per allocated quad slot (all legacy vertex/index attrs). */
|
|
16
|
-
export const LEGACY_BYTES_PER_QUAD =
|
|
17
|
-
VERTS_PER_QUAD * (FLOATS_PER_VERT * 3 + FLOATS_PER_LIGHT_VERT * 2 + FLOATS_PER_UV_VERT) * 4
|
|
18
|
-
+ 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
|
|
19
30
|
|
|
20
31
|
export const FULL_DRAW_VISIBLE_FRACTION = 0.75
|
|
21
|
-
|
|
32
|
+
/** Initial multi_draw scratch size; arrays auto-grow — not a draw-call cap. */
|
|
22
33
|
export const MAX_OPAQUE_SPANS = 64
|
|
23
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
|
+
|
|
24
95
|
export type GlobalLegacyBufferOptions = {
|
|
25
96
|
name?: string
|
|
26
97
|
initialCapacityQuads?: number
|
|
27
98
|
growthIncrementQuads?: number
|
|
28
99
|
}
|
|
29
100
|
|
|
30
|
-
export type VisibleSectionSpan = { key: string
|
|
101
|
+
export type VisibleSectionSpan = { key: string; distSq: number }
|
|
31
102
|
|
|
32
103
|
export type LegacySectionGeometry = {
|
|
33
104
|
positions: Float32Array
|
|
@@ -44,6 +115,8 @@ export type LegacySectionGeometryData = LegacySectionGeometry & {
|
|
|
44
115
|
sz: number
|
|
45
116
|
}
|
|
46
117
|
|
|
118
|
+
export type { LegacyDrawSpan } from './legacyMultiDraw'
|
|
119
|
+
|
|
47
120
|
/**
|
|
48
121
|
* Single GPU mesh for legacy quads (opaque+cutout or transparent blend).
|
|
49
122
|
* Camera-relative via per-vertex a_origin (relative to render origin) + u_originDelta uniforms.
|
|
@@ -61,18 +134,22 @@ export class GlobalLegacyBuffer {
|
|
|
61
134
|
private uvs: Float32Array
|
|
62
135
|
private aOrigin: Float32Array
|
|
63
136
|
private indices: Uint32Array
|
|
64
|
-
private readonly sectionSlots = new Map<string, { start: number
|
|
65
|
-
private freeList: Array<{ start: number
|
|
137
|
+
private readonly sectionSlots = new Map<string, { start: number; count: number }>()
|
|
138
|
+
private freeList: Array<{ start: number; count: number }> = []
|
|
66
139
|
private highWatermark = 0
|
|
67
|
-
private pendingRanges: Array<{ start: number
|
|
68
|
-
private readonly _spanScratch: Array<{ start: number
|
|
140
|
+
private pendingRanges: Array<{ start: number; end: number }> = []
|
|
141
|
+
private readonly _spanScratch: Array<{ start: number; count: number }> = []
|
|
69
142
|
private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
143
|
+
private layoutVersion = 0
|
|
144
|
+
private pendingMove: PendingMove | null = null
|
|
145
|
+
private readonly pendingReplace = new Map<string, PendingReplace>()
|
|
146
|
+
private uploadEpoch = 0
|
|
147
|
+
private visibleIndexSpans: LegacyDrawSpan[] = []
|
|
148
|
+
private readonly _drawScratch: LegacyMultiDrawScratch = createLegacyMultiDrawScratch()
|
|
149
|
+
private multiDrawCaps: LegacyMultiDrawCaps | null = null
|
|
150
|
+
private debugOverlay = false
|
|
151
|
+
|
|
152
|
+
constructor(material: THREE.ShaderMaterial, scene: THREE.Object3D, opts?: GlobalLegacyBufferOptions) {
|
|
76
153
|
this.material = material
|
|
77
154
|
this.growthIncrementQuads = opts?.growthIncrementQuads ?? DEFAULT_GROWTH_INCREMENT_QUADS
|
|
78
155
|
this.capacityQuads = opts?.initialCapacityQuads ?? DEFAULT_INITIAL_CAPACITY_QUADS
|
|
@@ -114,9 +191,41 @@ export class GlobalLegacyBuffer {
|
|
|
114
191
|
this.mesh.position.set(0, 0, 0)
|
|
115
192
|
scene.add(this.mesh)
|
|
116
193
|
this.syncDefaultDrawGroups()
|
|
194
|
+
|
|
195
|
+
this.mesh.onAfterRender = (renderer, _scene, _camera, _geometry, material) => {
|
|
196
|
+
if (this.visibleIndexSpans.length === 0) return
|
|
197
|
+
const gl = renderer.getContext() as WebGL2RenderingContext
|
|
198
|
+
if (!this.multiDrawCaps) {
|
|
199
|
+
this.multiDrawCaps = detectLegacyMultiDrawCaps(gl)
|
|
200
|
+
logLegacyMultiDrawTierOnce(this.multiDrawCaps.tier, this.debugOverlay)
|
|
201
|
+
}
|
|
202
|
+
drawLegacySpans(gl, this.multiDrawCaps, this.visibleIndexSpans, this._drawScratch)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
setDebugOverlay(enabled: boolean): void {
|
|
207
|
+
this.debugOverlay = enabled
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Suppress three's full-buffer indexed draw; onAfterRender issues visible spans only.
|
|
212
|
+
* setDrawRange(0,0) skips bindingStates.setup — use a minimal non-zero range so
|
|
213
|
+
* program/VAO/ELEMENT_ARRAY_BUFFER stay bound while three draws ~nothing.
|
|
214
|
+
* Draws one triangle from index 0 (usually harmless; if quad 0 is culled, one stray tri).
|
|
215
|
+
*/
|
|
216
|
+
suppressThreeDraw(): void {
|
|
217
|
+
this.mesh.geometry.setDrawRange(0, 3)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
setVisibleIndexSpans(spans: LegacyDrawSpan[]): void {
|
|
221
|
+
this.visibleIndexSpans = spans
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
getVisibleIndexSpans(): readonly LegacyDrawSpan[] {
|
|
225
|
+
return this.visibleIndexSpans
|
|
117
226
|
}
|
|
118
227
|
|
|
119
|
-
private syncDefaultDrawGroups
|
|
228
|
+
private syncDefaultDrawGroups(): void {
|
|
120
229
|
const geometry = this.mesh.geometry
|
|
121
230
|
geometry.clearGroups()
|
|
122
231
|
const indexCount = this.highWatermark * INDICES_PER_QUAD
|
|
@@ -126,13 +235,7 @@ export class GlobalLegacyBuffer {
|
|
|
126
235
|
geometry.setDrawRange(0, indexCount)
|
|
127
236
|
}
|
|
128
237
|
|
|
129
|
-
addSection
|
|
130
|
-
sectionKey: string,
|
|
131
|
-
geo: LegacySectionGeometry,
|
|
132
|
-
sx: number,
|
|
133
|
-
sy: number,
|
|
134
|
-
sz: number,
|
|
135
|
-
): boolean {
|
|
238
|
+
addSection(sectionKey: string, geo: LegacySectionGeometry, sx: number, sy: number, sz: number): boolean {
|
|
136
239
|
const vertCount = geo.positions.length / FLOATS_PER_VERT
|
|
137
240
|
const quadCount = vertCount / VERTS_PER_QUAD
|
|
138
241
|
if (vertCount === 0 || quadCount * VERTS_PER_QUAD !== vertCount) {
|
|
@@ -143,8 +246,23 @@ export class GlobalLegacyBuffer {
|
|
|
143
246
|
return false
|
|
144
247
|
}
|
|
145
248
|
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
}
|
|
148
266
|
}
|
|
149
267
|
|
|
150
268
|
if (quadCount > this.capacityQuads) {
|
|
@@ -171,14 +289,14 @@ export class GlobalLegacyBuffer {
|
|
|
171
289
|
this.uvs.set(geo.uvs, dstUvBase)
|
|
172
290
|
|
|
173
291
|
const originOff = dstFloatBase
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
292
|
+
const ox = sx - this.renderOrigin.x
|
|
293
|
+
const oy = sy - this.renderOrigin.y
|
|
294
|
+
const oz = sz - this.renderOrigin.z
|
|
177
295
|
for (let v = 0; v < vertCount; v++) {
|
|
178
296
|
const o = originOff + v * FLOATS_PER_VERT
|
|
179
|
-
this.aOrigin[o] =
|
|
180
|
-
this.aOrigin[o + 1] =
|
|
181
|
-
this.aOrigin[o + 2] =
|
|
297
|
+
this.aOrigin[o] = ox
|
|
298
|
+
this.aOrigin[o + 1] = oy
|
|
299
|
+
this.aOrigin[o + 2] = oz
|
|
182
300
|
}
|
|
183
301
|
|
|
184
302
|
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
@@ -188,17 +306,106 @@ export class GlobalLegacyBuffer {
|
|
|
188
306
|
}
|
|
189
307
|
|
|
190
308
|
this.sectionSlots.set(sectionKey, slot)
|
|
309
|
+
if (isRemesh && previousSlot) {
|
|
310
|
+
this.pendingReplace.set(sectionKey, { oldStart: previousSlot.start, oldCount: previousSlot.count })
|
|
311
|
+
}
|
|
191
312
|
this.markDirty(slot.start, slot.start + quadCount - 1)
|
|
192
313
|
this.syncDefaultDrawGroups()
|
|
314
|
+
this.layoutVersion++
|
|
193
315
|
return true
|
|
194
316
|
}
|
|
195
317
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
318
|
+
getLayoutVersion(): number {
|
|
319
|
+
return this.layoutVersion
|
|
320
|
+
}
|
|
321
|
+
|
|
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 {
|
|
343
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
344
|
+
if (!slot) return undefined
|
|
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
|
|
349
|
+
return slot.start
|
|
350
|
+
}
|
|
351
|
+
|
|
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 {
|
|
363
|
+
return this.pendingMove
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** One interior-hole move per frame when fragmentation exceeds threshold; deferred shrink. */
|
|
367
|
+
compactStep(): void {
|
|
368
|
+
if (this.pendingMove) {
|
|
369
|
+
const { newStart, count } = this.pendingMove
|
|
370
|
+
if (this.rangeFullyUploaded(newStart, newStart + count - 1)) {
|
|
371
|
+
this.finalizePendingMove()
|
|
372
|
+
}
|
|
373
|
+
return
|
|
374
|
+
}
|
|
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
|
+
|
|
384
|
+
if (this.highWatermark === 0) return
|
|
385
|
+
const interiorFree = this.interiorFreeQuads()
|
|
386
|
+
if (interiorFree / this.highWatermark <= FRAGMENTATION_THRESHOLD) return
|
|
387
|
+
|
|
388
|
+
const section = this.findMovableSection(MAX_UPLOAD_QUADS_PER_FRAME)
|
|
389
|
+
if (!section) return
|
|
390
|
+
|
|
391
|
+
const hole = this.findLowestInteriorHole(section.start, section.count)
|
|
392
|
+
if (!hole) return
|
|
393
|
+
|
|
394
|
+
const reserved = this.reserveFreeSlotAt(hole.index, section.count)
|
|
395
|
+
const oldStart = section.start
|
|
396
|
+
const newStart = reserved.start
|
|
397
|
+
|
|
398
|
+
this.copySectionRange(oldStart, newStart, section.count)
|
|
399
|
+
this.sectionSlots.set(section.key, { start: newStart, count: section.count })
|
|
400
|
+
this.markDirty(newStart, newStart + section.count - 1)
|
|
401
|
+
this.pendingMove = { key: section.key, oldStart, newStart, count: section.count }
|
|
402
|
+
this.layoutVersion++
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
updateDrawSpans(visible: VisibleSectionSpan[], mode: 'opaque' | 'sortedBlend'): void {
|
|
406
|
+
this.visibleIndexSpans = []
|
|
199
407
|
|
|
200
408
|
if (this.highWatermark === 0) {
|
|
201
|
-
geometry.setDrawRange(0, 0)
|
|
202
409
|
return
|
|
203
410
|
}
|
|
204
411
|
|
|
@@ -207,51 +414,62 @@ export class GlobalLegacyBuffer {
|
|
|
207
414
|
let visibleQuadCount = 0
|
|
208
415
|
|
|
209
416
|
for (const entry of visible) {
|
|
417
|
+
const drawStart = this.getSectionDrawStart(entry.key)
|
|
418
|
+
const drawCount = this.getSectionDrawCount(entry.key)
|
|
210
419
|
const slot = this.sectionSlots.get(entry.key)
|
|
211
|
-
if (!slot) continue
|
|
212
|
-
spans.push({ start:
|
|
213
|
-
visibleQuadCount +=
|
|
420
|
+
if (drawStart === undefined || drawCount === undefined || !slot) continue
|
|
421
|
+
spans.push({ start: drawStart, count: drawCount })
|
|
422
|
+
visibleQuadCount += drawCount
|
|
214
423
|
}
|
|
215
424
|
|
|
216
425
|
if (spans.length === 0) {
|
|
217
|
-
geometry.setDrawRange(0, 0)
|
|
218
426
|
return
|
|
219
427
|
}
|
|
220
428
|
|
|
429
|
+
const pushIndexSpan = (quadStart: number, quadCount: number): void => {
|
|
430
|
+
this.visibleIndexSpans.push({
|
|
431
|
+
indexStart: quadStart * INDICES_PER_QUAD,
|
|
432
|
+
indexCount: quadCount * INDICES_PER_QUAD
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
|
|
221
436
|
if (mode === 'opaque') {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
226
446
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
geometry.addGroup(span.start * INDICES_PER_QUAD, span.count * INDICES_PER_QUAD, 0)
|
|
447
|
+
finalQuads = carveSpansAroundPendingRanges(finalQuads, this.pendingRanges)
|
|
448
|
+
if (!usedFullDraw) {
|
|
449
|
+
assertDrawSpansWithinLiveRanges(finalQuads, liveDrawRanges, this.mesh.name)
|
|
450
|
+
}
|
|
451
|
+
for (const span of finalQuads) {
|
|
452
|
+
pushIndexSpan(span.start, span.count)
|
|
234
453
|
}
|
|
235
454
|
} else {
|
|
236
455
|
visible.sort((a, b) => b.distSq - a.distSq)
|
|
237
456
|
for (const entry of visible) {
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
457
|
+
const drawStart = this.getSectionDrawStart(entry.key)
|
|
458
|
+
const drawCount = this.getSectionDrawCount(entry.key)
|
|
459
|
+
if (drawStart === undefined || drawCount === undefined) continue
|
|
460
|
+
pushIndexSpan(drawStart, drawCount)
|
|
241
461
|
}
|
|
242
462
|
}
|
|
243
|
-
|
|
244
|
-
geometry.setDrawRange(0, this.highWatermark * INDICES_PER_QUAD)
|
|
245
463
|
}
|
|
246
464
|
|
|
247
|
-
|
|
465
|
+
/** Merge only physically adjacent section slots (gap === 0). Never bridge interior holes. */
|
|
466
|
+
private mergeOpaqueSpans(spans: Array<{ start: number; count: number }>): void {
|
|
248
467
|
if (spans.length < 2) return
|
|
249
468
|
let i = 0
|
|
250
469
|
while (i < spans.length - 1) {
|
|
251
470
|
const cur = spans[i]!
|
|
252
471
|
const next = spans[i + 1]!
|
|
253
|
-
|
|
254
|
-
if (gap <= SPAN_GAP_TOLERANCE_QUADS) {
|
|
472
|
+
if (cur.start + cur.count === next.start) {
|
|
255
473
|
cur.count = next.start + next.count - cur.start
|
|
256
474
|
spans.splice(i + 1, 1)
|
|
257
475
|
} else {
|
|
@@ -260,40 +478,22 @@ export class GlobalLegacyBuffer {
|
|
|
260
478
|
}
|
|
261
479
|
}
|
|
262
480
|
|
|
263
|
-
|
|
264
|
-
while (spans.length > MAX_OPAQUE_SPANS) {
|
|
265
|
-
let bestIdx = 0
|
|
266
|
-
let bestGap = Infinity
|
|
267
|
-
for (let i = 0; i < spans.length - 1; i++) {
|
|
268
|
-
const gap = spans[i + 1]!.start - (spans[i]!.start + spans[i]!.count)
|
|
269
|
-
if (gap < bestGap) {
|
|
270
|
-
bestGap = gap
|
|
271
|
-
bestIdx = i
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
const cur = spans[bestIdx]!
|
|
275
|
-
const next = spans[bestIdx + 1]!
|
|
276
|
-
cur.count = next.start + next.count - cur.start
|
|
277
|
-
spans.splice(bestIdx + 1, 1)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
hasSection (sectionKey: string): boolean {
|
|
481
|
+
hasSection(sectionKey: string): boolean {
|
|
282
482
|
return this.sectionSlots.has(sectionKey)
|
|
283
483
|
}
|
|
284
484
|
|
|
285
|
-
getSectionSlot
|
|
485
|
+
getSectionSlot(sectionKey: string): { start: number; count: number } | undefined {
|
|
286
486
|
return this.sectionSlots.get(sectionKey)
|
|
287
487
|
}
|
|
288
488
|
|
|
289
|
-
takeSectionData
|
|
489
|
+
takeSectionData(sectionKey: string): LegacySectionGeometryData | undefined {
|
|
290
490
|
const data = this.getSectionGeometryData(sectionKey)
|
|
291
491
|
if (!data) return undefined
|
|
292
492
|
this.removeSection(sectionKey)
|
|
293
493
|
return data
|
|
294
494
|
}
|
|
295
495
|
|
|
296
|
-
getSectionGeometryData
|
|
496
|
+
getSectionGeometryData(sectionKey: string): LegacySectionGeometryData | undefined {
|
|
297
497
|
const slot = this.sectionSlots.get(sectionKey)
|
|
298
498
|
if (!slot) return undefined
|
|
299
499
|
|
|
@@ -324,10 +524,22 @@ export class GlobalLegacyBuffer {
|
|
|
324
524
|
return { positions, colors, skyLights, blockLights, uvs, indices, sx, sy, sz }
|
|
325
525
|
}
|
|
326
526
|
|
|
327
|
-
removeSection
|
|
527
|
+
removeSection(sectionKey: string): void {
|
|
328
528
|
const slot = this.sectionSlots.get(sectionKey)
|
|
329
529
|
if (!slot) return
|
|
330
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
|
+
|
|
537
|
+
if (this.pendingMove?.key === sectionKey) {
|
|
538
|
+
const { oldStart, count } = this.pendingMove
|
|
539
|
+
this.zeroAndFreeSlot(oldStart, count)
|
|
540
|
+
this.pendingMove = null
|
|
541
|
+
}
|
|
542
|
+
|
|
331
543
|
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
332
544
|
const indexLen = slot.count * INDICES_PER_QUAD
|
|
333
545
|
for (let i = 0; i < indexLen; i++) {
|
|
@@ -339,13 +551,14 @@ export class GlobalLegacyBuffer {
|
|
|
339
551
|
this.insertFreeSlot(slot)
|
|
340
552
|
this.shrinkHighWatermark()
|
|
341
553
|
this.syncDefaultDrawGroups()
|
|
554
|
+
this.layoutVersion++
|
|
342
555
|
}
|
|
343
556
|
|
|
344
|
-
hasPendingUploads
|
|
557
|
+
hasPendingUploads(): boolean {
|
|
345
558
|
return this.pendingRanges.length > 0
|
|
346
559
|
}
|
|
347
560
|
|
|
348
|
-
uploadDirtyRange
|
|
561
|
+
uploadDirtyRange(): void {
|
|
349
562
|
const r = this.pendingRanges[0]
|
|
350
563
|
if (!r) return
|
|
351
564
|
|
|
@@ -392,15 +605,19 @@ export class GlobalLegacyBuffer {
|
|
|
392
605
|
indexAttr.addUpdateRange(indexOffset, indexCount)
|
|
393
606
|
indexAttr.needsUpdate = true
|
|
394
607
|
|
|
395
|
-
if (quadOffset + quadCount > r.end)
|
|
396
|
-
|
|
608
|
+
if (quadOffset + quadCount > r.end) {
|
|
609
|
+
this.pendingRanges.shift()
|
|
610
|
+
} else {
|
|
611
|
+
r.start = quadOffset + quadCount
|
|
612
|
+
}
|
|
613
|
+
this.uploadEpoch++
|
|
397
614
|
}
|
|
398
615
|
|
|
399
|
-
setRenderOrigin
|
|
616
|
+
setRenderOrigin(renderOrigin: RenderOrigin): void {
|
|
400
617
|
this.renderOrigin = { ...renderOrigin }
|
|
401
618
|
}
|
|
402
619
|
|
|
403
|
-
rebase
|
|
620
|
+
rebase(delta: RenderOrigin): void {
|
|
404
621
|
if (this.highWatermark === 0) return
|
|
405
622
|
for (const slot of this.sectionSlots.values()) {
|
|
406
623
|
const dstVertBase = slot.start * VERTS_PER_QUAD
|
|
@@ -419,7 +636,7 @@ export class GlobalLegacyBuffer {
|
|
|
419
636
|
this.renderOrigin.z += delta.z
|
|
420
637
|
}
|
|
421
638
|
|
|
422
|
-
setCameraOrigin
|
|
639
|
+
setCameraOrigin(x: number, y: number, z: number): void {
|
|
423
640
|
const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(this.renderOrigin, x, y, z)
|
|
424
641
|
const u = this.material.uniforms.u_originDelta
|
|
425
642
|
if (u?.value?.set) u.value.set(originDelta.x, originDelta.y, originDelta.z)
|
|
@@ -427,19 +644,11 @@ export class GlobalLegacyBuffer {
|
|
|
427
644
|
if (uf?.value?.set) uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
|
|
428
645
|
}
|
|
429
646
|
|
|
430
|
-
raycastSections
|
|
431
|
-
raycaster: THREE.Raycaster,
|
|
432
|
-
sectionKeys: Iterable<string>,
|
|
433
|
-
out: THREE.Intersection[],
|
|
434
|
-
): THREE.Intersection[] {
|
|
647
|
+
raycastSections(raycaster: THREE.Raycaster, sectionKeys: Iterable<string>, out: THREE.Intersection[]): THREE.Intersection[] {
|
|
435
648
|
const ray = raycaster.ray
|
|
436
649
|
const closest = raycaster.near
|
|
437
650
|
const far = raycaster.far
|
|
438
|
-
_raycastOrigin.copy(ray.origin).sub(_raycastRenderOrigin.set(
|
|
439
|
-
this.renderOrigin.x,
|
|
440
|
-
this.renderOrigin.y,
|
|
441
|
-
this.renderOrigin.z,
|
|
442
|
-
))
|
|
651
|
+
_raycastOrigin.copy(ray.origin).sub(_raycastRenderOrigin.set(this.renderOrigin.x, this.renderOrigin.y, this.renderOrigin.z))
|
|
443
652
|
_raycastRay.origin.copy(_raycastOrigin)
|
|
444
653
|
_raycastRay.direction.copy(ray.direction)
|
|
445
654
|
|
|
@@ -458,19 +667,14 @@ export class GlobalLegacyBuffer {
|
|
|
458
667
|
const i2 = this.indices[dstIndexBase + i + 2]!
|
|
459
668
|
if (i0 === i1 && i1 === i2) continue
|
|
460
669
|
|
|
461
|
-
const hit = intersectTriangle(
|
|
462
|
-
_raycastRay,
|
|
463
|
-
this.positions, this.aOrigin, dstFloatBase,
|
|
464
|
-
i0, i1, i2,
|
|
465
|
-
closest, far,
|
|
466
|
-
)
|
|
670
|
+
const hit = intersectTriangle(_raycastRay, this.positions, this.aOrigin, dstFloatBase, i0, i1, i2, closest, far)
|
|
467
671
|
if (hit !== null) {
|
|
468
672
|
out.push({
|
|
469
673
|
distance: hit,
|
|
470
674
|
point: ray.at(hit, new THREE.Vector3()),
|
|
471
675
|
object: this.mesh,
|
|
472
676
|
face: null,
|
|
473
|
-
faceIndex: Math.floor(i / 3)
|
|
677
|
+
faceIndex: Math.floor(i / 3)
|
|
474
678
|
})
|
|
475
679
|
}
|
|
476
680
|
}
|
|
@@ -480,49 +684,53 @@ export class GlobalLegacyBuffer {
|
|
|
480
684
|
return out
|
|
481
685
|
}
|
|
482
686
|
|
|
483
|
-
getHighWatermark
|
|
687
|
+
getHighWatermark(): number {
|
|
484
688
|
return this.highWatermark
|
|
485
689
|
}
|
|
486
690
|
|
|
487
|
-
getCapacityQuads
|
|
691
|
+
getCapacityQuads(): number {
|
|
488
692
|
return this.capacityQuads
|
|
489
693
|
}
|
|
490
694
|
|
|
491
|
-
getSectionCount
|
|
695
|
+
getSectionCount(): number {
|
|
492
696
|
return this.sectionSlots.size
|
|
493
697
|
}
|
|
494
698
|
|
|
495
|
-
getMemoryBytes
|
|
699
|
+
getMemoryBytes(): number {
|
|
496
700
|
return this.capacityQuads * LEGACY_BYTES_PER_QUAD
|
|
497
701
|
}
|
|
498
702
|
|
|
499
|
-
getUsedMemoryBytes
|
|
703
|
+
getUsedMemoryBytes(): number {
|
|
500
704
|
return this.highWatermark * LEGACY_BYTES_PER_QUAD
|
|
501
705
|
}
|
|
502
706
|
|
|
503
|
-
reset
|
|
707
|
+
reset(): void {
|
|
504
708
|
this.sectionSlots.clear()
|
|
505
709
|
this.freeList.length = 0
|
|
506
710
|
this.highWatermark = 0
|
|
507
711
|
this.pendingRanges.length = 0
|
|
712
|
+
this.pendingMove = null
|
|
713
|
+
this.pendingReplace.clear()
|
|
714
|
+
this.uploadEpoch = 0
|
|
715
|
+
this.visibleIndexSpans = []
|
|
508
716
|
this.syncDefaultDrawGroups()
|
|
509
717
|
}
|
|
510
718
|
|
|
511
|
-
dispose
|
|
719
|
+
dispose(): void {
|
|
512
720
|
this.mesh.parent?.remove(this.mesh)
|
|
513
721
|
this.mesh.geometry.dispose()
|
|
514
722
|
this.reset()
|
|
515
723
|
}
|
|
516
724
|
|
|
517
|
-
private markDirty
|
|
725
|
+
private markDirty(start: number, end: number): void {
|
|
518
726
|
this.pendingRanges.push({ start, end })
|
|
519
727
|
this.pendingRanges.sort((a, b) => a.start - b.start)
|
|
520
728
|
this.mergePendingRanges()
|
|
521
729
|
}
|
|
522
730
|
|
|
523
|
-
private mergePendingRanges
|
|
731
|
+
private mergePendingRanges(): void {
|
|
524
732
|
if (this.pendingRanges.length < 2) return
|
|
525
|
-
const merged: Array<{ start: number
|
|
733
|
+
const merged: Array<{ start: number; end: number }> = []
|
|
526
734
|
let cur = this.pendingRanges[0]!
|
|
527
735
|
for (let i = 1; i < this.pendingRanges.length; i++) {
|
|
528
736
|
const next = this.pendingRanges[i]!
|
|
@@ -537,7 +745,7 @@ export class GlobalLegacyBuffer {
|
|
|
537
745
|
this.pendingRanges = merged
|
|
538
746
|
}
|
|
539
747
|
|
|
540
|
-
private takeFreeSlot
|
|
748
|
+
private takeFreeSlot(count: number): { start: number; count: number } | undefined {
|
|
541
749
|
for (let i = 0; i < this.freeList.length; i++) {
|
|
542
750
|
const slot = this.freeList[i]!
|
|
543
751
|
if (slot.count >= count) {
|
|
@@ -551,15 +759,15 @@ export class GlobalLegacyBuffer {
|
|
|
551
759
|
return undefined
|
|
552
760
|
}
|
|
553
761
|
|
|
554
|
-
private insertFreeSlot
|
|
762
|
+
private insertFreeSlot(slot: { start: number; count: number }): void {
|
|
555
763
|
this.freeList.push(slot)
|
|
556
764
|
this.freeList.sort((a, b) => a.start - b.start)
|
|
557
765
|
this.mergeFreeList()
|
|
558
766
|
}
|
|
559
767
|
|
|
560
|
-
private mergeFreeList
|
|
768
|
+
private mergeFreeList(): void {
|
|
561
769
|
if (this.freeList.length < 2) return
|
|
562
|
-
const merged: Array<{ start: number
|
|
770
|
+
const merged: Array<{ start: number; count: number }> = []
|
|
563
771
|
let cur = this.freeList[0]!
|
|
564
772
|
for (let i = 1; i < this.freeList.length; i++) {
|
|
565
773
|
const next = this.freeList[i]!
|
|
@@ -574,7 +782,7 @@ export class GlobalLegacyBuffer {
|
|
|
574
782
|
this.freeList = merged
|
|
575
783
|
}
|
|
576
784
|
|
|
577
|
-
private shrinkHighWatermark
|
|
785
|
+
private shrinkHighWatermark(): void {
|
|
578
786
|
while (this.highWatermark > 0) {
|
|
579
787
|
const tail = this.highWatermark - 1
|
|
580
788
|
const free = this.freeList.find(s => s.start <= tail && s.start + s.count > tail)
|
|
@@ -585,7 +793,138 @@ export class GlobalLegacyBuffer {
|
|
|
585
793
|
}
|
|
586
794
|
}
|
|
587
795
|
|
|
588
|
-
private
|
|
796
|
+
private interiorFreeQuads(): number {
|
|
797
|
+
let total = 0
|
|
798
|
+
for (const slot of this.freeList) {
|
|
799
|
+
if (slot.start < this.highWatermark) total += slot.count
|
|
800
|
+
}
|
|
801
|
+
return total
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private findMovableSection(maxCount: number): { key: string; start: number; count: number } | undefined {
|
|
805
|
+
const sections: Array<{ key: string; start: number; count: number }> = []
|
|
806
|
+
for (const [key, slot] of this.sectionSlots) {
|
|
807
|
+
sections.push({ key, start: slot.start, count: slot.count })
|
|
808
|
+
}
|
|
809
|
+
if (sections.length === 0) return undefined
|
|
810
|
+
|
|
811
|
+
sections.sort((a, b) => {
|
|
812
|
+
if (b.start !== a.start) return b.start - a.start
|
|
813
|
+
return b.start + b.count - (a.start + a.count)
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
const tailmost = sections[0]!
|
|
817
|
+
if (tailmost.count <= maxCount && this.findLowestInteriorHole(tailmost.start, tailmost.count)) {
|
|
818
|
+
return tailmost
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const candidates = sections.filter(s => s.count <= maxCount).sort((a, b) => b.count - a.count)
|
|
822
|
+
|
|
823
|
+
for (const s of candidates) {
|
|
824
|
+
if (this.findLowestInteriorHole(s.start, s.count)) return s
|
|
825
|
+
}
|
|
826
|
+
return undefined
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private findLowestInteriorHole(sectionStart: number, count: number): { start: number; count: number; index: number } | undefined {
|
|
830
|
+
for (let i = 0; i < this.freeList.length; i++) {
|
|
831
|
+
const slot = this.freeList[i]!
|
|
832
|
+
if (slot.start < sectionStart && slot.count >= count) {
|
|
833
|
+
return { start: slot.start, count: slot.count, index: i }
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return undefined
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
private reserveFreeSlotAt(index: number, count: number): { start: number; count: number } {
|
|
840
|
+
const slot = this.freeList[index]!
|
|
841
|
+
this.freeList.splice(index, 1)
|
|
842
|
+
if (slot.count === count) return { start: slot.start, count }
|
|
843
|
+
const used = { start: slot.start, count }
|
|
844
|
+
this.insertFreeSlot({ start: slot.start + count, count: slot.count - count })
|
|
845
|
+
return used
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
private copySectionRange(oldStart: number, newStart: number, quadCount: number): void {
|
|
849
|
+
const oldVertBase = oldStart * VERTS_PER_QUAD
|
|
850
|
+
const newVertBase = newStart * VERTS_PER_QUAD
|
|
851
|
+
const vertCount = quadCount * VERTS_PER_QUAD
|
|
852
|
+
const vertDelta = newVertBase - oldVertBase
|
|
853
|
+
|
|
854
|
+
const oldFloatBase = oldVertBase * FLOATS_PER_VERT
|
|
855
|
+
const newFloatBase = newVertBase * FLOATS_PER_VERT
|
|
856
|
+
const floatLen = vertCount * FLOATS_PER_VERT
|
|
857
|
+
this.positions.copyWithin(newFloatBase, oldFloatBase, oldFloatBase + floatLen)
|
|
858
|
+
this.colors.copyWithin(newFloatBase, oldFloatBase, oldFloatBase + floatLen)
|
|
859
|
+
this.aOrigin.copyWithin(newFloatBase, oldFloatBase, oldFloatBase + floatLen)
|
|
860
|
+
|
|
861
|
+
const oldLightBase = oldVertBase * FLOATS_PER_LIGHT_VERT
|
|
862
|
+
const newLightBase = newVertBase * FLOATS_PER_LIGHT_VERT
|
|
863
|
+
const lightLen = vertCount * FLOATS_PER_LIGHT_VERT
|
|
864
|
+
this.skyLights.copyWithin(newLightBase, oldLightBase, oldLightBase + lightLen)
|
|
865
|
+
this.blockLights.copyWithin(newLightBase, oldLightBase, oldLightBase + lightLen)
|
|
866
|
+
|
|
867
|
+
const oldUvBase = oldVertBase * FLOATS_PER_UV_VERT
|
|
868
|
+
const newUvBase = newVertBase * FLOATS_PER_UV_VERT
|
|
869
|
+
const uvLen = vertCount * FLOATS_PER_UV_VERT
|
|
870
|
+
this.uvs.copyWithin(newUvBase, oldUvBase, oldUvBase + uvLen)
|
|
871
|
+
|
|
872
|
+
const oldIndexBase = oldStart * INDICES_PER_QUAD
|
|
873
|
+
const newIndexBase = newStart * INDICES_PER_QUAD
|
|
874
|
+
const indexLen = quadCount * INDICES_PER_QUAD
|
|
875
|
+
for (let i = 0; i < indexLen; i++) {
|
|
876
|
+
this.indices[newIndexBase + i] = this.indices[oldIndexBase + i]! + vertDelta
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
private rangeFullyUploaded(start: number, end: number): boolean {
|
|
881
|
+
for (const r of this.pendingRanges) {
|
|
882
|
+
if (r.start <= end && r.end >= start) return false
|
|
883
|
+
}
|
|
884
|
+
return true
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private zeroAndFreeSlot(start: number, count: number): void {
|
|
888
|
+
const oldIndexBase = start * INDICES_PER_QUAD
|
|
889
|
+
const oldIndexLen = count * INDICES_PER_QUAD
|
|
890
|
+
for (let i = 0; i < oldIndexLen; i++) {
|
|
891
|
+
this.indices[oldIndexBase + i] = 0
|
|
892
|
+
}
|
|
893
|
+
this.markDirty(start, start + count - 1)
|
|
894
|
+
this.insertFreeSlot({ start, count })
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private finalizePendingReplace(key: string): void {
|
|
898
|
+
const pr = this.pendingReplace.get(key)
|
|
899
|
+
if (!pr) return
|
|
900
|
+
|
|
901
|
+
this.zeroAndFreeSlot(pr.oldStart, pr.oldCount)
|
|
902
|
+
this.pendingReplace.delete(key)
|
|
903
|
+
this.shrinkHighWatermark()
|
|
904
|
+
this.syncDefaultDrawGroups()
|
|
905
|
+
this.layoutVersion++
|
|
906
|
+
this.uploadEpoch++
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
private finalizePendingMove(): void {
|
|
910
|
+
const move = this.pendingMove
|
|
911
|
+
if (!move) return
|
|
912
|
+
|
|
913
|
+
const { oldStart, count } = move
|
|
914
|
+
this.zeroAndFreeSlot(oldStart, count)
|
|
915
|
+
this.shrinkHighWatermark()
|
|
916
|
+
this.syncDefaultDrawGroups()
|
|
917
|
+
this.pendingMove = null
|
|
918
|
+
this.layoutVersion++
|
|
919
|
+
this.uploadEpoch++
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private growCapacity(minQuads: number): void {
|
|
923
|
+
if (this.pendingMove) this.finalizePendingMove()
|
|
924
|
+
for (const key of [...this.pendingReplace.keys()]) {
|
|
925
|
+
this.finalizePendingReplace(key)
|
|
926
|
+
}
|
|
927
|
+
|
|
589
928
|
let newCap = this.capacityQuads
|
|
590
929
|
while (newCap < minQuads) newCap += this.growthIncrementQuads
|
|
591
930
|
|
|
@@ -652,22 +991,12 @@ const _raycastOrigin = new THREE.Vector3()
|
|
|
652
991
|
const _raycastRenderOrigin = new THREE.Vector3()
|
|
653
992
|
const _raycastRay = new THREE.Ray()
|
|
654
993
|
|
|
655
|
-
function readWorldVertex
|
|
656
|
-
positions: Float32Array,
|
|
657
|
-
aOrigin: Float32Array,
|
|
658
|
-
floatBase: number,
|
|
659
|
-
vertIndex: number,
|
|
660
|
-
target: THREE.Vector3,
|
|
661
|
-
): void {
|
|
994
|
+
function readWorldVertex(positions: Float32Array, aOrigin: Float32Array, floatBase: number, vertIndex: number, target: THREE.Vector3): void {
|
|
662
995
|
const f = floatBase + vertIndex * FLOATS_PER_VERT
|
|
663
|
-
target.set(
|
|
664
|
-
aOrigin[f]! + positions[f]!,
|
|
665
|
-
aOrigin[f + 1]! + positions[f + 1]!,
|
|
666
|
-
aOrigin[f + 2]! + positions[f + 2]!,
|
|
667
|
-
)
|
|
996
|
+
target.set(aOrigin[f]! + positions[f]!, aOrigin[f + 1]! + positions[f + 1]!, aOrigin[f + 2]! + positions[f + 2]!)
|
|
668
997
|
}
|
|
669
998
|
|
|
670
|
-
function intersectTriangle
|
|
999
|
+
function intersectTriangle(
|
|
671
1000
|
ray: THREE.Ray,
|
|
672
1001
|
positions: Float32Array,
|
|
673
1002
|
aOrigin: Float32Array,
|
|
@@ -676,7 +1005,7 @@ function intersectTriangle (
|
|
|
676
1005
|
i1: number,
|
|
677
1006
|
i2: number,
|
|
678
1007
|
near: number,
|
|
679
|
-
far: number
|
|
1008
|
+
far: number
|
|
680
1009
|
): number | null {
|
|
681
1010
|
readWorldVertex(positions, aOrigin, floatBase, i0, _vA)
|
|
682
1011
|
readWorldVertex(positions, aOrigin, floatBase, i1, _vB)
|
|
@@ -697,7 +1026,7 @@ function intersectTriangle (
|
|
|
697
1026
|
return t
|
|
698
1027
|
}
|
|
699
1028
|
|
|
700
|
-
function pointInTriangle
|
|
1029
|
+
function pointInTriangle(p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3): boolean {
|
|
701
1030
|
_edge1.subVectors(b, a)
|
|
702
1031
|
_edge2.subVectors(c, a)
|
|
703
1032
|
const n = _normal.crossVectors(_edge1, _edge2).normalize()
|