minecraft-renderer 0.1.71 → 0.1.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/mesher.js +81 -81
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +1183 -943
- package/dist/minecraft-renderer.js +250 -79
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +1732 -1001
- package/package.json +3 -3
- package/src/graphicsBackend/rendererDefaultOptions.ts +5 -10
- package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
- package/src/lib/bakeLegacyLight.ts +17 -0
- package/src/lib/blockEntityLightRegistry.test.ts +18 -0
- package/src/lib/blockEntityLightRegistry.ts +75 -0
- package/src/lib/blockEntityLighting.test.ts +30 -0
- package/src/lib/blockEntityLighting.ts +53 -0
- package/src/lib/worldrendererCommon.reconfigure.test.ts +202 -0
- package/src/lib/worldrendererCommon.ts +152 -22
- package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
- package/src/mesher-shared/blockEntityMetadata.ts +19 -3
- package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
- package/src/mesher-shared/models.ts +161 -92
- package/src/mesher-shared/shared.ts +15 -4
- package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
- package/src/mesher-shared/world.ts +12 -0
- package/src/mesher-shared/worldLighting.test.ts +54 -0
- package/src/playground/baseScene.ts +1 -1
- package/src/three/bannerRenderer.ts +10 -3
- package/src/three/chunkMeshManager.ts +663 -69
- package/src/three/cubeDrawSpans.ts +74 -0
- package/src/three/cubeMultiDraw.ts +119 -0
- package/src/three/documentRenderer.ts +0 -2
- package/src/three/entities.ts +5 -6
- package/src/three/entity/EntityMesh.ts +7 -5
- package/src/three/entity/gltfAnimationUtils.ts +5 -3
- package/src/three/globalBlockBuffer.ts +208 -12
- package/src/three/globalLegacyBuffer.ts +701 -0
- package/src/three/graphicsBackendOffThread.ts +16 -1
- package/src/three/itemMesh.ts +5 -2
- package/src/three/legacySectionCull.ts +85 -0
- package/src/three/modules/sciFiWorldReveal.ts +347 -703
- package/src/three/modules/starfield.ts +3 -2
- package/src/three/sectionRaycastAabb.ts +25 -0
- package/src/three/shaders/cubeBlockShader.ts +80 -17
- package/src/three/shaders/legacyBlockShader.ts +292 -0
- package/src/three/skyboxRenderer.ts +1 -1
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
- package/src/three/tests/cubeDrawSpans.test.ts +73 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
- package/src/three/tests/legacySectionCull.test.ts +80 -0
- package/src/three/tests/signTextureCache.test.ts +83 -0
- package/src/three/threeJsMedia.ts +2 -2
- package/src/three/waypointSprite.ts +2 -2
- package/src/three/world/cursorBlock.ts +1 -0
- package/src/three/world/vr.ts +2 -2
- package/src/three/worldGeometryExport.ts +83 -26
- package/src/three/worldRendererThree.ts +94 -25
- package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +67 -5
- package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
- package/src/worldView/worldView.ts +11 -0
|
@@ -1,847 +1,491 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Wireframe-to-solid chunk reveal.
|
|
4
|
+
* Ported from web-client-2/renderer/viewer/three/sciFiWorldReveal.ts
|
|
5
|
+
*/
|
|
2
6
|
import * as THREE from 'three'
|
|
3
7
|
import type { WorldRendererThree } from '../worldRendererThree'
|
|
4
8
|
import type { RendererModuleController, RendererModuleManifest } from '../rendererModuleSystem'
|
|
5
9
|
import type { MesherGeometryOutput } from '../../mesher-shared/shared'
|
|
6
|
-
import type {
|
|
10
|
+
import type { SectionObject } from '../chunkMeshManager'
|
|
7
11
|
|
|
8
12
|
const SCI_FI_CYAN = new THREE.Color(13 / 255, 234 / 255, 238 / 255)
|
|
13
|
+
|
|
9
14
|
const CHUNKS_THRESHOLD = 9
|
|
10
|
-
const
|
|
11
|
-
const
|
|
15
|
+
const GLOBAL_START_FALLBACK_MS = 12_000
|
|
16
|
+
const WAVE_SPREAD_MS = 1500
|
|
17
|
+
const MAX_SECTION_LIFETIME_MS = 10_000
|
|
12
18
|
|
|
13
19
|
const INITIAL_WIREFRAME_MS = 350
|
|
14
|
-
const
|
|
15
|
-
const INITIAL_WAVE_SPREAD_MS = 650
|
|
16
|
-
|
|
20
|
+
const INITIAL_FADE_MS = 650
|
|
17
21
|
const CHUNK_WIREFRAME_MS = 120
|
|
18
|
-
const
|
|
22
|
+
const CHUNK_FADE_MS = 280
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
type RevealPhase = 'queued' | 'wireframe' | 'fade' | 'done'
|
|
25
|
+
|
|
26
|
+
interface SectionReveal {
|
|
21
27
|
key: string
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
renderMeshRefs: THREE.Mesh[]
|
|
27
|
-
/** Shader cubes temporarily removed from globalBlockBuffer during reveal. */
|
|
28
|
-
globalShaderRestore?: GlobalBlockBufferShaderData
|
|
28
|
+
geometry: MesherGeometryOutput
|
|
29
|
+
phase: RevealPhase
|
|
30
|
+
phaseStartMs: number
|
|
31
|
+
revealAtMs: number
|
|
29
32
|
wireframeMs: number
|
|
30
|
-
|
|
33
|
+
fadeMs: number
|
|
34
|
+
wireframeGroup: THREE.Group | null
|
|
35
|
+
mesh: THREE.Mesh | null
|
|
36
|
+
savedMaterial: THREE.Material | null
|
|
37
|
+
pulseOffset: number
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* SciFiWorldReveal - Creates a futuristic wireframe-to-solid reveal effect
|
|
36
|
-
*
|
|
37
|
-
* When chunks load, they first appear as glowing cyan wireframes that pulse
|
|
38
|
-
* and emanate from the camera, then gradually transition to solid geometry.
|
|
39
|
-
*/
|
|
40
40
|
export class SciFiWorldRevealModule implements RendererModuleController {
|
|
41
|
-
private
|
|
42
|
-
private readonly
|
|
43
|
-
private
|
|
44
|
-
private revealTriggered = false
|
|
45
|
-
private revealStartTime = 0
|
|
46
|
-
private enabled = false
|
|
47
|
-
|
|
48
|
-
private onWorldSwitchedCb: (() => void) | null = null
|
|
49
|
-
private patched = false
|
|
50
|
-
private initialWaveDone = false
|
|
51
|
-
/** True after every section from the first reveal wave has finished animating. */
|
|
52
|
-
private initialRevealWaveSettled = false
|
|
41
|
+
private enabled = true
|
|
42
|
+
private readonly sections = new Map<string, SectionReveal>()
|
|
43
|
+
private readonly completed = new Set<string>()
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
// For pulsing animation
|
|
45
|
+
private globalWaveStarted = false
|
|
46
|
+
private globalWaveStartMs = 0
|
|
47
|
+
private finishedChunkCount = 0
|
|
48
|
+
private firstQueuedMs: number | null = null
|
|
59
49
|
private pulseTime = 0
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
51
|
+
private readonly wireframeMaterial = new THREE.LineBasicMaterial({
|
|
52
|
+
color: SCI_FI_CYAN,
|
|
53
|
+
transparent: true,
|
|
54
|
+
opacity: 1,
|
|
55
|
+
blending: THREE.AdditiveBlending,
|
|
56
|
+
depthWrite: false,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
private readonly wireframeGlowMaterial = new THREE.LineBasicMaterial({
|
|
60
|
+
color: SCI_FI_CYAN,
|
|
61
|
+
transparent: true,
|
|
62
|
+
opacity: 0.55,
|
|
63
|
+
blending: THREE.AdditiveBlending,
|
|
64
|
+
depthWrite: false,
|
|
65
|
+
})
|
|
69
66
|
|
|
70
67
|
constructor(private readonly worldRenderer: WorldRendererThree) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
opacity: 1,
|
|
75
|
-
blending: THREE.AdditiveBlending,
|
|
76
|
-
depthWrite: false,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
this.wireframeGlowMaterial = new THREE.LineBasicMaterial({
|
|
80
|
-
color: SCI_FI_CYAN,
|
|
81
|
-
transparent: true,
|
|
82
|
-
opacity: 0.55,
|
|
83
|
-
blending: THREE.AdditiveBlending,
|
|
84
|
-
depthWrite: false,
|
|
85
|
-
})
|
|
68
|
+
if (worldRenderer.worldRendererConfig.futuristicReveal !== true) {
|
|
69
|
+
this.enabled = false
|
|
70
|
+
}
|
|
86
71
|
}
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
isFuturisticRevealConfigured (): boolean {
|
|
73
|
+
autoEnableCheck(): boolean {
|
|
90
74
|
return this.worldRenderer.worldRendererConfig.futuristicReveal === true
|
|
91
75
|
}
|
|
92
76
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
enable(): void {
|
|
78
|
+
if (!this.autoEnableCheck()) return
|
|
79
|
+
this.setEnabled(true)
|
|
96
80
|
}
|
|
97
81
|
|
|
98
|
-
|
|
99
|
-
|
|
82
|
+
disable(): void {
|
|
83
|
+
this.setEnabled(false)
|
|
100
84
|
}
|
|
101
85
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.patchWorldRenderer()
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
this.enabled = true
|
|
110
|
-
this.patchWorldRenderer()
|
|
86
|
+
dispose(): void {
|
|
87
|
+
this.reset()
|
|
88
|
+
this.wireframeMaterial.dispose()
|
|
89
|
+
this.wireframeGlowMaterial.dispose()
|
|
111
90
|
}
|
|
112
91
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.enabled = false
|
|
116
|
-
this.unpatchWorldRenderer()
|
|
117
|
-
this.reset()
|
|
92
|
+
shouldDeferSectionGeometry(sectionKey: string): boolean {
|
|
93
|
+
return this.enabled && !this.completed.has(sectionKey)
|
|
118
94
|
}
|
|
119
95
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.enable()
|
|
125
|
-
}
|
|
126
|
-
return this.enabled
|
|
96
|
+
onSectionMeshed(key: string, geometry: MesherGeometryOutput, sectionObject: SectionObject): void {
|
|
97
|
+
const mesh = sectionObject.children.find(c => c.name === 'mesh')
|
|
98
|
+
if (!(mesh instanceof THREE.Mesh)) return
|
|
99
|
+
this.onSectionMeshedMesh(key, geometry, mesh)
|
|
127
100
|
}
|
|
128
101
|
|
|
129
|
-
|
|
102
|
+
onChunkFinished(): void {
|
|
130
103
|
if (!this.enabled) return
|
|
131
|
-
this.
|
|
104
|
+
this.finishedChunkCount++
|
|
105
|
+
this.tryStartGlobalWave(performance.now())
|
|
132
106
|
}
|
|
133
107
|
|
|
134
|
-
|
|
135
|
-
this.
|
|
136
|
-
this.
|
|
137
|
-
this.wireframeGlowMaterial.dispose()
|
|
108
|
+
onSectionRemoved(key: string): void {
|
|
109
|
+
this.cancelSection(key, true)
|
|
110
|
+
this.completed.delete(key)
|
|
138
111
|
}
|
|
139
112
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
private patchWorldRenderer(): void {
|
|
144
|
-
if (this.patched) return
|
|
145
|
-
this.patched = true
|
|
146
|
-
const wr = this.worldRenderer
|
|
147
|
-
|
|
148
|
-
// Hook into onWorldSwitched
|
|
149
|
-
this.onWorldSwitchedCb = () => this.reset()
|
|
150
|
-
wr.onWorldSwitched.push(this.onWorldSwitchedCb)
|
|
151
|
-
|
|
113
|
+
onWorldSwitched(): void {
|
|
114
|
+
this.reset()
|
|
115
|
+
}
|
|
152
116
|
|
|
153
|
-
|
|
154
|
-
this.
|
|
155
|
-
wr.finishChunk = (chunkKey: string) => {
|
|
156
|
-
this.originalFinishChunk!(chunkKey)
|
|
157
|
-
this.onChunkFinished(chunkKey)
|
|
158
|
-
}
|
|
117
|
+
tick(deltaMs: number, now = performance.now()): void {
|
|
118
|
+
if (!this.enabled) return
|
|
159
119
|
|
|
160
|
-
|
|
161
|
-
this.originalDestroy = wr.destroy.bind(wr)
|
|
162
|
-
wr.destroy = () => {
|
|
163
|
-
this.dispose()
|
|
164
|
-
this.originalDestroy!()
|
|
165
|
-
}
|
|
120
|
+
this.tryStartGlobalWave(now)
|
|
166
121
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
this.registerSection(data.key, data.geometry)
|
|
176
|
-
} catch (err) {
|
|
177
|
-
console.error('[SciFiReveal] registerSection failed', err)
|
|
178
|
-
}
|
|
179
|
-
})
|
|
180
|
-
}
|
|
122
|
+
if (
|
|
123
|
+
!this.globalWaveStarted &&
|
|
124
|
+
this.firstQueuedMs !== null &&
|
|
125
|
+
this.sections.size > 0 &&
|
|
126
|
+
now - this.firstQueuedMs >= GLOBAL_START_FALLBACK_MS
|
|
127
|
+
) {
|
|
128
|
+
this.startGlobalWave(now)
|
|
181
129
|
}
|
|
182
130
|
|
|
131
|
+
if (this.sections.size === 0) return
|
|
183
132
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
wr.scene.add = (...objects: THREE.Object3D[]): THREE.Scene => {
|
|
187
|
-
// Call original add first
|
|
188
|
-
const result = this.originalSceneAdd!(...objects)
|
|
133
|
+
this.pulseTime += deltaMs * 0.001
|
|
134
|
+
const toFinish: SectionReveal[] = []
|
|
189
135
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
136
|
+
for (const section of this.sections.values()) {
|
|
137
|
+
if (now - section.phaseStartMs > MAX_SECTION_LIFETIME_MS) {
|
|
138
|
+
toFinish.push(section)
|
|
139
|
+
continue
|
|
193
140
|
}
|
|
194
141
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
*/
|
|
202
|
-
private unpatchWorldRenderer(): void {
|
|
203
|
-
const wr = this.worldRenderer
|
|
142
|
+
if (section.phase === 'queued') {
|
|
143
|
+
if (this.globalWaveStarted && now >= section.revealAtMs) {
|
|
144
|
+
this.beginWireframe(section, now)
|
|
145
|
+
}
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
204
148
|
|
|
205
|
-
|
|
206
|
-
wr.finishChunk = this.originalFinishChunk
|
|
207
|
-
this.originalFinishChunk = null
|
|
208
|
-
}
|
|
149
|
+
const phaseElapsed = now - section.phaseStartMs
|
|
209
150
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.originalHandleWorkerMessage = null
|
|
218
|
-
}
|
|
151
|
+
if (section.phase === 'wireframe') {
|
|
152
|
+
this.animateWireframe(section, phaseElapsed)
|
|
153
|
+
if (phaseElapsed >= section.wireframeMs) {
|
|
154
|
+
this.beginFade(section, now)
|
|
155
|
+
}
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
219
158
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
159
|
+
if (section.phase === 'fade') {
|
|
160
|
+
const progress = Math.min(1, phaseElapsed / section.fadeMs)
|
|
161
|
+
this.animateFade(section, progress)
|
|
162
|
+
if (progress >= 1) {
|
|
163
|
+
toFinish.push(section)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
223
166
|
}
|
|
224
167
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (i !== -1) wr.onWorldSwitched.splice(i, 1)
|
|
228
|
-
this.onWorldSwitchedCb = null
|
|
168
|
+
for (const section of toFinish) {
|
|
169
|
+
this.finishSection(section)
|
|
229
170
|
}
|
|
230
|
-
this.patched = false
|
|
231
171
|
}
|
|
232
172
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const sectionKey = this.findSectionKeyForMesh(obj)
|
|
239
|
-
if (sectionKey && this.shouldUseRevealEffect(sectionKey)) {
|
|
240
|
-
obj.visible = false
|
|
241
|
-
; (obj as any).hiddenByReveal = true
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Recursively check children
|
|
246
|
-
for (const child of obj.children) {
|
|
247
|
-
this.checkAndPatchMesh(child)
|
|
173
|
+
private setEnabled(enabled: boolean): void {
|
|
174
|
+
if (enabled === this.enabled) return
|
|
175
|
+
this.enabled = enabled
|
|
176
|
+
if (!enabled) {
|
|
177
|
+
this.forceFinishAll()
|
|
248
178
|
}
|
|
249
179
|
}
|
|
250
180
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return sectionKey
|
|
181
|
+
private onSectionMeshedMesh(key: string, geometry: MesherGeometryOutput, mesh: THREE.Mesh): void {
|
|
182
|
+
if (!this.enabled || !geometry.positions?.length) return
|
|
183
|
+
if (this.completed.has(key)) return
|
|
184
|
+
|
|
185
|
+
const existing = this.sections.get(key)
|
|
186
|
+
if (existing) {
|
|
187
|
+
existing.mesh = mesh
|
|
188
|
+
existing.geometry = geometry
|
|
189
|
+
if (existing.phase === 'wireframe' || existing.phase === 'fade') {
|
|
190
|
+
this.finishSection(existing)
|
|
262
191
|
}
|
|
263
|
-
|
|
192
|
+
return
|
|
264
193
|
}
|
|
265
194
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const wp = this.worldRenderer.sceneOrigin.getWorldPosition(mesh)
|
|
270
|
-
const worldX = wp?.x ?? this.worldRenderer.sceneOrigin.toWorldX(mesh.position.x)
|
|
271
|
-
const worldY = wp?.y ?? this.worldRenderer.sceneOrigin.toWorldY(mesh.position.y)
|
|
272
|
-
const worldZ = wp?.z ?? this.worldRenderer.sceneOrigin.toWorldZ(mesh.position.z)
|
|
273
|
-
const CHUNK_SIZE = 16
|
|
274
|
-
const sectionHeight = this.worldRenderer.getSectionHeight()
|
|
275
|
-
const sectionX = Math.floor(worldX / CHUNK_SIZE) * CHUNK_SIZE
|
|
276
|
-
const sectionY = Math.floor(worldY / sectionHeight) * sectionHeight
|
|
277
|
-
const sectionZ = Math.floor(worldZ / CHUNK_SIZE) * CHUNK_SIZE
|
|
278
|
-
const derivedKey = `${sectionX},${sectionY},${sectionZ}`
|
|
279
|
-
|
|
280
|
-
// Verify this key exists in sectionObjects
|
|
281
|
-
if (this.worldRenderer.chunkMeshManager.sectionObjects[derivedKey]) {
|
|
282
|
-
return derivedKey
|
|
195
|
+
const now = performance.now()
|
|
196
|
+
if (this.firstQueuedMs === null) {
|
|
197
|
+
this.firstQueuedMs = now
|
|
283
198
|
}
|
|
284
199
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
200
|
+
const useChunkTimings = this.globalWaveStarted
|
|
201
|
+
this.sections.set(key, {
|
|
202
|
+
key,
|
|
203
|
+
geometry,
|
|
204
|
+
phase: 'queued',
|
|
205
|
+
phaseStartMs: now,
|
|
206
|
+
revealAtMs: this.globalWaveStarted ? now : 0,
|
|
207
|
+
wireframeMs: useChunkTimings ? CHUNK_WIREFRAME_MS : INITIAL_WIREFRAME_MS,
|
|
208
|
+
fadeMs: useChunkTimings ? CHUNK_FADE_MS : INITIAL_FADE_MS,
|
|
209
|
+
wireframeGroup: null,
|
|
210
|
+
mesh,
|
|
211
|
+
savedMaterial: null,
|
|
212
|
+
pulseOffset: Math.random() * Math.PI * 2,
|
|
213
|
+
})
|
|
294
214
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
private getCameraPosition(): THREE.Vector3 {
|
|
299
|
-
return this.worldRenderer.getCameraPosition()
|
|
215
|
+
mesh.visible = false
|
|
216
|
+
this.setShaderMeshesVisible(key, false)
|
|
217
|
+
this.tryStartGlobalWave(now)
|
|
300
218
|
}
|
|
301
219
|
|
|
302
|
-
private
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
220
|
+
private reset(): void {
|
|
221
|
+
this.forceFinishAll()
|
|
222
|
+
this.sections.clear()
|
|
223
|
+
this.completed.clear()
|
|
224
|
+
this.globalWaveStarted = false
|
|
225
|
+
this.globalWaveStartMs = 0
|
|
226
|
+
this.finishedChunkCount = 0
|
|
227
|
+
this.firstQueuedMs = null
|
|
228
|
+
this.pulseTime = 0
|
|
306
229
|
}
|
|
307
230
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (!sectionObject) return []
|
|
312
|
-
const meshes: THREE.Mesh[] = []
|
|
313
|
-
for (const name of ['mesh', 'shaderMesh'] as const) {
|
|
314
|
-
const child = sectionObject.children.find(c => c.name === name)
|
|
315
|
-
if (child instanceof THREE.Mesh) meshes.push(child)
|
|
231
|
+
private forceFinishAll(): void {
|
|
232
|
+
for (const section of [...this.sections.values()]) {
|
|
233
|
+
this.finishSection(section)
|
|
316
234
|
}
|
|
317
|
-
|
|
235
|
+
this.sections.clear()
|
|
236
|
+
this.unhideAllSectionMeshes()
|
|
318
237
|
}
|
|
319
238
|
|
|
320
|
-
private
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
m.visible = false
|
|
324
|
-
;(m as any).hiddenByReveal = true
|
|
325
|
-
}
|
|
326
|
-
return meshes
|
|
327
|
-
}
|
|
239
|
+
private tryStartGlobalWave(now: number): void {
|
|
240
|
+
if (this.globalWaveStarted) return
|
|
241
|
+
if (this.sections.size === 0) return
|
|
328
242
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (mesh.name === 'shaderMesh') {
|
|
332
|
-
mesh.visible = opacity > 0.001
|
|
243
|
+
if (this.finishedChunkCount >= CHUNKS_THRESHOLD) {
|
|
244
|
+
this.startGlobalWave(now)
|
|
333
245
|
return
|
|
334
246
|
}
|
|
335
|
-
const mat = mesh.material
|
|
336
|
-
if (Array.isArray(mat)) return
|
|
337
|
-
if (!(mat as any).originalMaterial) {
|
|
338
|
-
;(mat as any).originalMaterial = mat
|
|
339
|
-
const fadeMat = mat.clone()
|
|
340
|
-
fadeMat.transparent = true
|
|
341
|
-
fadeMat.opacity = opacity
|
|
342
|
-
fadeMat.needsUpdate = true
|
|
343
|
-
mesh.material = fadeMat
|
|
344
|
-
} else {
|
|
345
|
-
mat.opacity = opacity
|
|
346
|
-
mat.transparent = true
|
|
347
|
-
mat.needsUpdate = true
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
247
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (!originalMat) return
|
|
354
|
-
const currentMat = mesh.material as THREE.Material
|
|
355
|
-
mesh.material = originalMat
|
|
356
|
-
if (currentMat !== originalMat) currentMat.dispose()
|
|
357
|
-
delete (mesh as any).originalMaterial
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Call this when a chunk finishes loading
|
|
362
|
-
*/
|
|
363
|
-
onChunkFinished(_chunkKey: string): void {
|
|
364
|
-
this.finishedChunkCount++
|
|
365
|
-
|
|
366
|
-
if (!this.revealTriggered && this.finishedChunkCount >= CHUNKS_THRESHOLD) {
|
|
367
|
-
this.triggerReveal()
|
|
248
|
+
if (this.worldRenderer.allChunksFinished) {
|
|
249
|
+
this.startGlobalWave(now)
|
|
368
250
|
}
|
|
369
251
|
}
|
|
370
252
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// If already revealed or currently revealing, skip
|
|
376
|
-
if (this.revealedChunks.has(key) || this.revealingSections.has(key)) return
|
|
253
|
+
private startGlobalWave(now: number): void {
|
|
254
|
+
if (this.globalWaveStarted) return
|
|
255
|
+
this.globalWaveStarted = true
|
|
256
|
+
this.globalWaveStartMs = now
|
|
377
257
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return
|
|
382
|
-
}
|
|
258
|
+
const cameraPos = this.worldRenderer.getCameraPosition()
|
|
259
|
+
let maxDistance = 1
|
|
260
|
+
const distances = new Map<string, number>()
|
|
383
261
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
262
|
+
for (const [key, section] of this.sections) {
|
|
263
|
+
if (section.phase !== 'queued') continue
|
|
264
|
+
const { sx, sy, sz } = section.geometry
|
|
265
|
+
const distance = Math.hypot(sx - cameraPos.x, sy - cameraPos.y, sz - cameraPos.z)
|
|
266
|
+
distances.set(key, distance)
|
|
267
|
+
maxDistance = Math.max(maxDistance, distance)
|
|
390
268
|
}
|
|
391
|
-
}
|
|
392
269
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (this.revealTriggered && this.initialWaveDone) return false
|
|
400
|
-
return !this.revealedChunks.has(key) && !this.revealingSections.has(key)
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Trigger the reveal sequence
|
|
405
|
-
*/
|
|
406
|
-
private triggerReveal(): void {
|
|
407
|
-
this.revealTriggered = true
|
|
408
|
-
this.initialWaveDone = true
|
|
409
|
-
|
|
410
|
-
this.revealStartTime = performance.now()
|
|
411
|
-
|
|
412
|
-
const cameraPos = this.getCameraPosition()
|
|
413
|
-
|
|
414
|
-
// Copy and clear pending geometries before processing
|
|
415
|
-
const toProcess = [...this.pendingGeometries.entries()]
|
|
416
|
-
this.pendingGeometries.clear()
|
|
417
|
-
|
|
418
|
-
// Sort by distance from camera for wave effect
|
|
419
|
-
const sorted = toProcess
|
|
420
|
-
.map(([key, geometry]) => {
|
|
421
|
-
const distance = Math.hypot(
|
|
422
|
-
(geometry.sx - cameraPos.x),
|
|
423
|
-
(geometry.sy - cameraPos.y),
|
|
424
|
-
(geometry.sz - cameraPos.z)
|
|
425
|
-
)
|
|
426
|
-
return { key, geometry, distance }
|
|
427
|
-
})
|
|
428
|
-
.sort((a, b) => a.distance - b.distance)
|
|
429
|
-
|
|
430
|
-
const maxDistance = sorted.at(-1)?.distance || 1
|
|
431
|
-
|
|
432
|
-
// Start reveal for each section with staggered timing
|
|
433
|
-
for (const { key, geometry, distance } of sorted) {
|
|
434
|
-
const delay = (distance / maxDistance) * 1500 // 1500ms spread for wave effect
|
|
435
|
-
setTimeout(() => {
|
|
436
|
-
// Double check the section hasn't been revealed already
|
|
437
|
-
if (!this.revealedChunks.has(key) && !this.revealingSections.has(key)) {
|
|
438
|
-
this.startSectionReveal(key, geometry)
|
|
439
|
-
}
|
|
440
|
-
}, delay)
|
|
270
|
+
for (const [, section] of this.sections) {
|
|
271
|
+
if (section.phase !== 'queued') continue
|
|
272
|
+
const distance = distances.get(section.key) ?? 0
|
|
273
|
+
section.revealAtMs = now + (distance / maxDistance) * WAVE_SPREAD_MS
|
|
274
|
+
section.wireframeMs = CHUNK_WIREFRAME_MS
|
|
275
|
+
section.fadeMs = CHUNK_FADE_MS
|
|
441
276
|
}
|
|
442
277
|
}
|
|
443
278
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (this.revealingSections.has(key) || this.revealedChunks.has(key)) return
|
|
452
|
-
|
|
453
|
-
// Create wireframe geometry
|
|
454
|
-
const wireframeGeom = this.createWireframeGeometry(geometry)
|
|
455
|
-
|
|
456
|
-
const global = this.worldRenderer.chunkMeshManager.globalBlockBuffer
|
|
457
|
-
const globalShaderRestore = global?.hasSection(key) ? global.takeSectionData(key) : undefined
|
|
279
|
+
private beginWireframe(section: SectionReveal, now: number): void {
|
|
280
|
+
const mesh = section.mesh ?? this.getSectionMesh(section.key)
|
|
281
|
+
section.mesh = mesh
|
|
282
|
+
if (!mesh) {
|
|
283
|
+
this.sections.delete(section.key)
|
|
284
|
+
return
|
|
285
|
+
}
|
|
458
286
|
|
|
459
|
-
const
|
|
460
|
-
// Main wireframe
|
|
287
|
+
const wireframeGeom = this.createWireframeGeometry(section.geometry)
|
|
461
288
|
const wireframe = new THREE.LineSegments(wireframeGeom, this.wireframeMaterial.clone())
|
|
462
289
|
this.worldRenderer.sceneOrigin.track(wireframe)
|
|
463
|
-
wireframe.position.set(geometry.sx, geometry.sy, geometry.sz)
|
|
290
|
+
wireframe.position.set(section.geometry.sx, section.geometry.sy, section.geometry.sz)
|
|
464
291
|
wireframe.name = 'scifi-wireframe'
|
|
465
292
|
wireframe.renderOrder = 1000
|
|
466
293
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
glowWireframe.renderOrder = 999
|
|
294
|
+
const glow = new THREE.LineSegments(wireframeGeom.clone(), this.wireframeGlowMaterial.clone())
|
|
295
|
+
this.worldRenderer.sceneOrigin.track(glow)
|
|
296
|
+
glow.position.copy(wireframe.position)
|
|
297
|
+
glow.scale.setScalar(1.02)
|
|
298
|
+
glow.name = 'scifi-glow'
|
|
299
|
+
glow.renderOrder = 999
|
|
474
300
|
|
|
475
301
|
const group = new THREE.Group()
|
|
476
|
-
group.add(wireframe)
|
|
477
|
-
group.add(glowWireframe)
|
|
478
302
|
group.name = 'scifi-reveal-group'
|
|
479
|
-
|
|
480
|
-
|
|
303
|
+
group.add(wireframe, glow)
|
|
304
|
+
this.worldRenderer.realScene.add(group)
|
|
481
305
|
|
|
482
|
-
|
|
306
|
+
mesh.visible = false
|
|
307
|
+
this.setShaderMeshesVisible(section.key, false)
|
|
483
308
|
|
|
484
|
-
|
|
485
|
-
|
|
309
|
+
section.wireframeGroup = group
|
|
310
|
+
section.phase = 'wireframe'
|
|
311
|
+
section.phaseStartMs = now
|
|
312
|
+
}
|
|
486
313
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
314
|
+
private beginFade(section: SectionReveal, now: number): void {
|
|
315
|
+
const mesh = section.mesh ?? this.getSectionMesh(section.key)
|
|
316
|
+
section.mesh = mesh
|
|
317
|
+
if (mesh) {
|
|
318
|
+
mesh.visible = true
|
|
319
|
+
const rawMat = mesh.material
|
|
320
|
+
if (!Array.isArray(rawMat) && rawMat && typeof rawMat.clone === 'function') {
|
|
321
|
+
section.savedMaterial = rawMat
|
|
322
|
+
const fadeMat = rawMat.clone()
|
|
323
|
+
fadeMat.transparent = true
|
|
324
|
+
fadeMat.opacity = 0
|
|
325
|
+
fadeMat.needsUpdate = true
|
|
326
|
+
mesh.material = fadeMat
|
|
327
|
+
}
|
|
496
328
|
}
|
|
329
|
+
this.setShaderMeshesVisible(section.key, true)
|
|
330
|
+
section.phase = 'fade'
|
|
331
|
+
section.phaseStartMs = now
|
|
332
|
+
}
|
|
497
333
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
334
|
+
private animateWireframe(section: SectionReveal, phaseElapsed: number): void {
|
|
335
|
+
if (!section.wireframeGroup) return
|
|
336
|
+
const wireframe = section.wireframeGroup.children[0] as THREE.LineSegments
|
|
337
|
+
const glow = section.wireframeGroup.children[1] as THREE.LineSegments
|
|
338
|
+
const basePulse = 0.6 + 0.4 * Math.sin(this.pulseTime * 4 + section.pulseOffset)
|
|
339
|
+
if (wireframe?.material) {
|
|
340
|
+
const mat = wireframe.material as THREE.LineBasicMaterial
|
|
341
|
+
mat.opacity = basePulse
|
|
342
|
+
const intensity = 0.85 + 0.15 * Math.sin(this.pulseTime * 6 + phaseElapsed * 0.002)
|
|
343
|
+
mat.color.setRGB((13 / 255) * intensity, (234 / 255) * intensity, (238 / 255) * intensity)
|
|
344
|
+
}
|
|
345
|
+
if (glow?.material) {
|
|
346
|
+
(glow.material as THREE.LineBasicMaterial).opacity = basePulse * 0.4
|
|
347
|
+
}
|
|
348
|
+
}
|
|
505
349
|
|
|
506
|
-
|
|
350
|
+
private animateFade(section: SectionReveal, progress: number): void {
|
|
351
|
+
const eased = 1 - (1 - progress) ** 3
|
|
352
|
+
if (section.wireframeGroup) {
|
|
353
|
+
const wireframe = section.wireframeGroup.children[0] as THREE.LineSegments
|
|
354
|
+
const glow = section.wireframeGroup.children[1] as THREE.LineSegments
|
|
355
|
+
if (wireframe?.material) (wireframe.material as THREE.LineBasicMaterial).opacity = 1 - eased
|
|
356
|
+
if (glow?.material) (glow.material as THREE.LineBasicMaterial).opacity = (1 - eased) * 0.55
|
|
357
|
+
}
|
|
358
|
+
const mesh = section.mesh
|
|
359
|
+
if (mesh && section.savedMaterial && !Array.isArray(mesh.material)) {
|
|
360
|
+
(mesh.material as THREE.Material).opacity = eased
|
|
361
|
+
}
|
|
362
|
+
this.setShaderMeshesVisible(section.key, eased > 0.001)
|
|
507
363
|
}
|
|
508
364
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
[4, 5], [5, 7], [7, 6], [6, 4],
|
|
520
|
-
[0, 4], [1, 5], [2, 6], [3, 7],
|
|
521
|
-
]
|
|
522
|
-
const linePositions: number[] = []
|
|
523
|
-
for (const [a, b] of edges) {
|
|
524
|
-
linePositions.push(...c[a]!, ...c[b]!)
|
|
365
|
+
private finishSection(section: SectionReveal): void {
|
|
366
|
+
const mesh = section.mesh ?? this.getSectionMesh(section.key)
|
|
367
|
+
if (mesh) {
|
|
368
|
+
if (section.savedMaterial) {
|
|
369
|
+
const fadeMat = mesh.material as THREE.Material
|
|
370
|
+
mesh.material = section.savedMaterial
|
|
371
|
+
fadeMat.dispose()
|
|
372
|
+
section.savedMaterial = null
|
|
373
|
+
}
|
|
374
|
+
mesh.visible = true
|
|
525
375
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
376
|
+
this.setShaderMeshesVisible(section.key, true)
|
|
377
|
+
|
|
378
|
+
const { chunkMeshManager } = this.worldRenderer
|
|
379
|
+
chunkMeshManager.migrateDeferredShaderToGlobal(section.key)
|
|
380
|
+
chunkMeshManager.migrateDeferredLegacyToGlobal(section.key)
|
|
381
|
+
|
|
382
|
+
if (section.wireframeGroup) {
|
|
383
|
+
this.disposeWireframeGroup(section.wireframeGroup)
|
|
384
|
+
section.wireframeGroup = null
|
|
385
|
+
}
|
|
386
|
+
this.sections.delete(section.key)
|
|
387
|
+
this.completed.add(section.key)
|
|
529
388
|
}
|
|
530
389
|
|
|
531
|
-
private
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
390
|
+
private cancelSection(key: string, unhide: boolean): void {
|
|
391
|
+
const section = this.sections.get(key)
|
|
392
|
+
if (!section) return
|
|
393
|
+
if (section.wireframeGroup) {
|
|
394
|
+
this.disposeWireframeGroup(section.wireframeGroup)
|
|
536
395
|
}
|
|
396
|
+
if (unhide) {
|
|
397
|
+
const mesh = section.mesh ?? this.getSectionMesh(key)
|
|
398
|
+
if (mesh) {
|
|
399
|
+
if (section.savedMaterial) {
|
|
400
|
+
const fadeMat = mesh.material as THREE.Material
|
|
401
|
+
mesh.material = section.savedMaterial
|
|
402
|
+
fadeMat.dispose()
|
|
403
|
+
}
|
|
404
|
+
mesh.visible = true
|
|
405
|
+
}
|
|
406
|
+
this.setShaderMeshesVisible(key, true)
|
|
407
|
+
}
|
|
408
|
+
this.sections.delete(key)
|
|
409
|
+
}
|
|
537
410
|
|
|
538
|
-
|
|
539
|
-
const
|
|
411
|
+
private getSectionMesh(key: string): THREE.Mesh | null {
|
|
412
|
+
const sectionObject = this.worldRenderer.chunkMeshManager.sectionObjects[key]
|
|
413
|
+
if (!sectionObject) return null
|
|
414
|
+
const mesh = sectionObject.children.find(c => c.name === 'mesh')
|
|
415
|
+
return mesh instanceof THREE.Mesh ? mesh : null
|
|
416
|
+
}
|
|
540
417
|
|
|
541
|
-
|
|
542
|
-
|
|
418
|
+
private getShaderMesh(key: string): THREE.Mesh | null {
|
|
419
|
+
const sectionObject = this.worldRenderer.chunkMeshManager.sectionObjects[key]
|
|
420
|
+
if (!sectionObject) return null
|
|
421
|
+
const mesh = sectionObject.children.find(c => c.name === 'shaderMesh')
|
|
422
|
+
return mesh instanceof THREE.Mesh ? mesh : null
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private setShaderMeshesVisible(key: string, visible: boolean): void {
|
|
426
|
+
const shaderMesh = this.getShaderMesh(key)
|
|
427
|
+
if (shaderMesh) shaderMesh.visible = visible
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private unhideAllSectionMeshes(): void {
|
|
431
|
+
for (const obj of Object.values(this.worldRenderer.chunkMeshManager.sectionObjects)) {
|
|
432
|
+
if (!obj) continue
|
|
433
|
+
obj.traverse((child) => {
|
|
434
|
+
if (child instanceof THREE.Mesh && (child.name === 'mesh' || child.name === 'shaderMesh')) {
|
|
435
|
+
child.visible = true
|
|
436
|
+
}
|
|
437
|
+
})
|
|
543
438
|
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private disposeWireframeGroup(group: THREE.Group): void {
|
|
442
|
+
this.worldRenderer.sceneOrigin.removeAndUntrackAll(group)
|
|
443
|
+
this.worldRenderer.realScene.remove(group)
|
|
444
|
+
group.traverse((child) => {
|
|
445
|
+
const line = child as THREE.LineSegments
|
|
446
|
+
line.geometry?.dispose()
|
|
447
|
+
const mat = line.material
|
|
448
|
+
if (Array.isArray(mat)) mat.forEach(m => m.dispose())
|
|
449
|
+
else mat?.dispose()
|
|
450
|
+
})
|
|
451
|
+
group.clear()
|
|
452
|
+
}
|
|
544
453
|
|
|
454
|
+
private createWireframeGeometry(geometry: MesherGeometryOutput): THREE.BufferGeometry {
|
|
455
|
+
const positions = geometry.positions as Float32Array
|
|
456
|
+
const indices = geometry.indices as Uint32Array | Uint16Array
|
|
545
457
|
const linePositions: number[] = []
|
|
546
458
|
const edgeSet = new Set<string>()
|
|
547
|
-
|
|
548
|
-
// Create edges from triangles
|
|
549
459
|
for (let i = 0; i < indices.length; i += 3) {
|
|
550
460
|
const i0 = indices[i]!
|
|
551
461
|
const i1 = indices[i + 1]!
|
|
552
462
|
const i2 = indices[i + 2]!
|
|
553
|
-
|
|
554
463
|
this.addEdge(positions, i0, i1, linePositions, edgeSet)
|
|
555
464
|
this.addEdge(positions, i1, i2, linePositions, edgeSet)
|
|
556
465
|
this.addEdge(positions, i2, i0, linePositions, edgeSet)
|
|
557
466
|
}
|
|
558
|
-
|
|
559
467
|
const wireframeGeom = new THREE.BufferGeometry()
|
|
560
468
|
wireframeGeom.setAttribute('position', new THREE.Float32BufferAttribute(linePositions, 3))
|
|
561
|
-
|
|
562
469
|
return wireframeGeom
|
|
563
470
|
}
|
|
564
471
|
|
|
565
|
-
/**
|
|
566
|
-
* Add edge to line positions if not duplicate
|
|
567
|
-
*/
|
|
568
472
|
private addEdge(
|
|
569
473
|
positions: Float32Array,
|
|
570
474
|
i0: number,
|
|
571
475
|
i1: number,
|
|
572
476
|
linePositions: number[],
|
|
573
|
-
edgeSet: Set<string
|
|
477
|
+
edgeSet: Set<string>,
|
|
574
478
|
): void {
|
|
575
479
|
const minI = Math.min(i0, i1)
|
|
576
480
|
const maxI = Math.max(i0, i1)
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
edgeSet.add(key)
|
|
581
|
-
|
|
481
|
+
const edgeKey = `${minI}-${maxI}`
|
|
482
|
+
if (edgeSet.has(edgeKey)) return
|
|
483
|
+
edgeSet.add(edgeKey)
|
|
582
484
|
linePositions.push(
|
|
583
485
|
positions[i0 * 3]!, positions[i0 * 3 + 1]!, positions[i0 * 3 + 2]!,
|
|
584
|
-
positions[i1 * 3]!, positions[i1 * 3 + 1]!, positions[i1 * 3 + 2]
|
|
486
|
+
positions[i1 * 3]!, positions[i1 * 3 + 1]!, positions[i1 * 3 + 2]!,
|
|
585
487
|
)
|
|
586
488
|
}
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* Update the reveal animation - call this every frame
|
|
590
|
-
*/
|
|
591
|
-
update(deltaTime: number): void {
|
|
592
|
-
if (!this.enabled || this.revealingSections.size === 0) return
|
|
593
|
-
|
|
594
|
-
this.pulseTime += deltaTime * 0.001 // Convert to seconds
|
|
595
|
-
const currentTime = performance.now()
|
|
596
|
-
|
|
597
|
-
// Pulse effect parameters
|
|
598
|
-
const basePulse = 0.6 + 0.4 * Math.sin(this.pulseTime * 4)
|
|
599
|
-
|
|
600
|
-
const toComplete: RevealingSection[] = []
|
|
601
|
-
|
|
602
|
-
for (const [key, section] of this.revealingSections) {
|
|
603
|
-
const elapsed = currentTime - section.revealStartTime
|
|
604
|
-
|
|
605
|
-
if (section.phase === 'wireframe') {
|
|
606
|
-
// Animate wireframe
|
|
607
|
-
const wireframe = section.wireframeGroup.children[0] as THREE.LineSegments
|
|
608
|
-
const glow = section.wireframeGroup.children[1] as THREE.LineSegments
|
|
609
|
-
|
|
610
|
-
if (wireframe?.material) {
|
|
611
|
-
const mat = wireframe.material as THREE.LineBasicMaterial
|
|
612
|
-
mat.opacity = basePulse
|
|
613
|
-
|
|
614
|
-
// Color pulse with slight variation
|
|
615
|
-
const colorIntensity = 0.85 + 0.15 * Math.sin(this.pulseTime * 6 + elapsed * 0.002)
|
|
616
|
-
mat.color.setRGB(
|
|
617
|
-
(13 / 255) * colorIntensity,
|
|
618
|
-
(234 / 255) * colorIntensity,
|
|
619
|
-
(238 / 255) * colorIntensity
|
|
620
|
-
)
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if (glow?.material) {
|
|
624
|
-
const glowMat = glow.material as THREE.LineBasicMaterial
|
|
625
|
-
glowMat.opacity = basePulse * 0.4
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Transition to fading phase
|
|
629
|
-
if (elapsed > section.wireframeMs) {
|
|
630
|
-
section.phase = 'transitioning'
|
|
631
|
-
|
|
632
|
-
section.renderMeshRefs = this.getSectionRenderMeshes(key)
|
|
633
|
-
for (const mesh of section.renderMeshRefs) {
|
|
634
|
-
mesh.visible = true
|
|
635
|
-
this.setMeshFadeOpacity(mesh, 0)
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
} else if (section.phase === 'transitioning') {
|
|
639
|
-
const transitionElapsed = elapsed - section.wireframeMs
|
|
640
|
-
const progress = Math.min(1, transitionElapsed / section.revealMs)
|
|
641
|
-
|
|
642
|
-
// Smooth ease-out curve
|
|
643
|
-
const eased = 1 - (1 - progress) ** 3
|
|
644
|
-
|
|
645
|
-
// Fade out wireframe
|
|
646
|
-
const wireframe = section.wireframeGroup.children[0] as THREE.LineSegments
|
|
647
|
-
const glow = section.wireframeGroup.children[1] as THREE.LineSegments
|
|
648
|
-
|
|
649
|
-
if (wireframe?.material) {
|
|
650
|
-
const mat = wireframe.material as THREE.LineBasicMaterial
|
|
651
|
-
mat.opacity = (1 - eased)
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
if (glow?.material) {
|
|
655
|
-
const glowMat = glow.material as THREE.LineBasicMaterial
|
|
656
|
-
glowMat.opacity = (1 - eased) * 0.55
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
for (const mesh of section.renderMeshRefs) {
|
|
660
|
-
this.setMeshFadeOpacity(mesh, eased)
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Complete transition
|
|
664
|
-
if (progress >= 1) {
|
|
665
|
-
section.phase = 'complete'
|
|
666
|
-
toComplete.push(section)
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Complete all finished sections after iteration
|
|
672
|
-
for (const section of toComplete) {
|
|
673
|
-
this.completeReveal(section)
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
/**
|
|
678
|
-
* Complete the reveal and clean up
|
|
679
|
-
*/
|
|
680
|
-
private completeReveal(section: RevealingSection): void {
|
|
681
|
-
// Remove from map first to prevent re-processing
|
|
682
|
-
this.revealingSections.delete(section.key)
|
|
683
|
-
this.revealedChunks.add(section.key)
|
|
684
|
-
|
|
685
|
-
if (this.revealTriggered && this.revealingSections.size === 0) {
|
|
686
|
-
this.initialRevealWaveSettled = true
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
for (const mesh of section.renderMeshRefs) {
|
|
690
|
-
this.restoreMeshMaterial(mesh)
|
|
691
|
-
mesh.visible = true
|
|
692
|
-
delete (mesh as any).hiddenByReveal
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
this.worldRenderer.chunkMeshManager.migrateDeferredShaderToGlobal(section.key)
|
|
696
|
-
|
|
697
|
-
if (section.globalShaderRestore) {
|
|
698
|
-
this.worldRenderer.chunkMeshManager.globalBlockBuffer?.addSection(
|
|
699
|
-
section.key,
|
|
700
|
-
section.globalShaderRestore.words,
|
|
701
|
-
section.globalShaderRestore.count,
|
|
702
|
-
)
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Clean up wireframe group
|
|
706
|
-
this.disposeWireframeGroup(section.wireframeGroup)
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* Dispose a wireframe group and remove from scene
|
|
711
|
-
*/
|
|
712
|
-
private disposeWireframeGroup(group: THREE.Group): void {
|
|
713
|
-
this.worldRenderer.sceneOrigin.removeAndUntrackAll(group)
|
|
714
|
-
|
|
715
|
-
// Collect all objects to dispose
|
|
716
|
-
const toDispose: THREE.Object3D[] = []
|
|
717
|
-
group.traverse((child) => {
|
|
718
|
-
toDispose.push(child)
|
|
719
|
-
})
|
|
720
|
-
|
|
721
|
-
// Dispose all collected objects
|
|
722
|
-
for (const child of toDispose) {
|
|
723
|
-
const lineSegments = child as THREE.LineSegments
|
|
724
|
-
if (lineSegments.geometry) {
|
|
725
|
-
lineSegments.geometry.dispose()
|
|
726
|
-
}
|
|
727
|
-
if (lineSegments.material) {
|
|
728
|
-
const mat = lineSegments.material
|
|
729
|
-
if (Array.isArray(mat)) {
|
|
730
|
-
for (const m of mat) m.dispose()
|
|
731
|
-
} else if (mat && typeof mat.dispose === 'function') {
|
|
732
|
-
mat.dispose()
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// Clear children
|
|
738
|
-
group.clear()
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Reset the reveal system
|
|
743
|
-
*/
|
|
744
|
-
reset(): void {
|
|
745
|
-
// Clean up all revealing sections
|
|
746
|
-
for (const section of this.revealingSections.values()) {
|
|
747
|
-
this.disposeWireframeGroup(section.wireframeGroup)
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
this.pendingGeometries.clear()
|
|
751
|
-
this.revealingSections.clear()
|
|
752
|
-
this.revealedChunks.clear()
|
|
753
|
-
this.finishedChunkCount = 0
|
|
754
|
-
this.revealTriggered = false
|
|
755
|
-
this.initialWaveDone = false
|
|
756
|
-
this.initialRevealWaveSettled = false
|
|
757
|
-
this.revealStartTime = 0
|
|
758
|
-
this.pulseTime = 0
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Force complete all reveals (skip animation)
|
|
763
|
-
*/
|
|
764
|
-
forceCompleteAll(): void {
|
|
765
|
-
const sections = [...this.revealingSections.values()]
|
|
766
|
-
for (const section of sections) {
|
|
767
|
-
section.renderMeshRefs = this.getSectionRenderMeshes(section.key)
|
|
768
|
-
for (const mesh of section.renderMeshRefs) {
|
|
769
|
-
this.restoreMeshMaterial(mesh)
|
|
770
|
-
mesh.visible = true
|
|
771
|
-
delete (mesh as any).hiddenByReveal
|
|
772
|
-
}
|
|
773
|
-
this.completeReveal(section)
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// ============ DEBUG METHODS ============
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Debug: Get all wireframe groups still in scene
|
|
781
|
-
*/
|
|
782
|
-
debugGetWireframeGroups(): THREE.Group[] {
|
|
783
|
-
const groups: THREE.Group[] = []
|
|
784
|
-
this.scene.traverse((child) => {
|
|
785
|
-
if (child.name === 'scifi-reveal-group') {
|
|
786
|
-
groups.push(child as THREE.Group)
|
|
787
|
-
}
|
|
788
|
-
})
|
|
789
|
-
return groups
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* Debug: Force remove all wireframe groups from scene
|
|
794
|
-
*/
|
|
795
|
-
debugForceCleanup(): void {
|
|
796
|
-
const groups = this.debugGetWireframeGroups()
|
|
797
|
-
console.log(`[SciFiReveal] Found ${groups.length} wireframe groups in scene`)
|
|
798
|
-
|
|
799
|
-
for (const group of groups) {
|
|
800
|
-
console.log(`[SciFiReveal] Removing group:`, group)
|
|
801
|
-
this.disposeWireframeGroup(group)
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Also clean up any tracked sections
|
|
805
|
-
for (const section of this.revealingSections.values()) {
|
|
806
|
-
this.disposeWireframeGroup(section.wireframeGroup)
|
|
807
|
-
}
|
|
808
|
-
this.revealingSections.clear()
|
|
809
|
-
|
|
810
|
-
console.log(`[SciFiReveal] Cleanup complete. Remaining groups: ${this.debugGetWireframeGroups().length}`)
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* Debug: Get status of the reveal system
|
|
815
|
-
*/
|
|
816
|
-
debugStatus() {
|
|
817
|
-
const wireframeGroups = this.debugGetWireframeGroups()
|
|
818
|
-
const trackedKeys = new Set(this.revealingSections.keys())
|
|
819
|
-
const orphanedGroups = wireframeGroups.filter(g => !trackedKeys.has((g as any).sectionKey))
|
|
820
|
-
|
|
821
|
-
return {
|
|
822
|
-
revealTriggered: this.revealTriggered,
|
|
823
|
-
finishedChunkCount: this.finishedChunkCount,
|
|
824
|
-
pendingGeometries: this.pendingGeometries.size,
|
|
825
|
-
revealingSections: this.revealingSections.size,
|
|
826
|
-
revealedChunks: this.revealedChunks.size,
|
|
827
|
-
wireframeGroupsInScene: wireframeGroups.length,
|
|
828
|
-
orphanedWireframeGroups: orphanedGroups.length,
|
|
829
|
-
orphanedKeys: orphanedGroups.map(g => (g as any).sectionKey),
|
|
830
|
-
sections: [...this.revealingSections.entries()].map(([key, s]) => ({
|
|
831
|
-
key,
|
|
832
|
-
phase: s.phase,
|
|
833
|
-
renderMeshCount: s.renderMeshRefs.length,
|
|
834
|
-
wireframeInScene: s.wireframeGroup.parent !== null
|
|
835
|
-
}))
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Debug: Log current status to console
|
|
841
|
-
*/
|
|
842
|
-
debugLog(): void {
|
|
843
|
-
console.log('[SciFiReveal] Status:', this.debugStatus())
|
|
844
|
-
}
|
|
845
489
|
}
|
|
846
490
|
|
|
847
491
|
export const sciFiWorldRevealManifest: RendererModuleManifest = {
|