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
|
@@ -5,14 +5,19 @@ import * as nbt from 'prismarine-nbt'
|
|
|
5
5
|
import { Vec3 } from 'vec3'
|
|
6
6
|
import { MesherGeometryOutput } from '../mesher-shared/shared'
|
|
7
7
|
import { getShaderCubeResources, SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
8
|
-
import { createCubeBlockMaterial } from './shaders/cubeBlockShader'
|
|
8
|
+
import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
|
|
9
|
+
import { computeCameraRelativeUniforms, createGlobalLegacyBlendMaterial, createGlobalLegacyBlockMaterial, createLegacyBlockMaterial, setLegacyCameraOrigin, setLegacySkyLevel, setLegacyLightmapParams, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
10
|
+
import { LEGACY_SECTION_HALF_EXTENT, sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
|
|
9
11
|
import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
|
|
10
12
|
import { GlobalBlockBuffer } from './globalBlockBuffer'
|
|
13
|
+
import { buildVisibleCubeSpans } from './cubeDrawSpans'
|
|
14
|
+
import { GlobalLegacyBuffer, type LegacySectionGeometry } from './globalLegacyBuffer'
|
|
11
15
|
import {
|
|
12
16
|
computeShaderSectionRaycastAabb,
|
|
13
17
|
isPointInsideAabb,
|
|
14
18
|
raycastAabb,
|
|
15
19
|
raycastShaderBlocksAabb,
|
|
20
|
+
sectionAabbIntersectsRay,
|
|
16
21
|
type ShaderSectionRaycastEntry,
|
|
17
22
|
} from './sectionRaycastAabb'
|
|
18
23
|
import { chunkPos } from '../lib/simpleUtils'
|
|
@@ -22,6 +27,7 @@ import type { WorldRendererThree } from './worldRendererThree'
|
|
|
22
27
|
import { armorModel } from './entity/armorModels'
|
|
23
28
|
import { disposeObject } from './threeJsUtils'
|
|
24
29
|
import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
|
|
30
|
+
import { BlockEntityLightRegistry } from '../lib/blockEntityLightRegistry'
|
|
25
31
|
|
|
26
32
|
export interface ChunkMeshPool {
|
|
27
33
|
mesh: THREE.Mesh
|
|
@@ -36,6 +42,12 @@ export interface SectionObject extends THREE.Group {
|
|
|
36
42
|
shaderMesh?: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>
|
|
37
43
|
/** Shader cube words kept for migration to global buffer after reveal. */
|
|
38
44
|
deferredShaderCubes?: { words: Uint32Array, count: number }
|
|
45
|
+
/** Opaque legacy geometry deferred from global buffer during sci-fi reveal. */
|
|
46
|
+
deferredLegacyOpaque?: LegacySectionGeometry
|
|
47
|
+
/** Blend legacy geometry deferred from global buffer during sci-fi reveal. */
|
|
48
|
+
deferredLegacyBlend?: LegacySectionGeometry
|
|
49
|
+
/** Section uses a pooled mesh for blend (reveal defer or invariant fallback). */
|
|
50
|
+
hasBlendMesh?: boolean
|
|
39
51
|
tilesCount?: number
|
|
40
52
|
blocksCount?: number
|
|
41
53
|
|
|
@@ -63,6 +75,11 @@ export interface SectionObject extends THREE.Group {
|
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
export class ChunkMeshManager {
|
|
78
|
+
private static readonly REBASE_THRESHOLD = 65536
|
|
79
|
+
|
|
80
|
+
/** Float64 render origin snapped to section granularity; GPU buffers store origins relative to this. */
|
|
81
|
+
private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
|
|
82
|
+
|
|
66
83
|
private readonly meshPool: ChunkMeshPool[] = []
|
|
67
84
|
private readonly activeSections = new Map<string, ChunkMeshPool>()
|
|
68
85
|
readonly sectionObjects: Record<string, SectionObject> = {}
|
|
@@ -90,6 +107,7 @@ export class ChunkMeshManager {
|
|
|
90
107
|
private maxPoolSize!: number
|
|
91
108
|
private minPoolSize!: number
|
|
92
109
|
private readonly signHeadsRenderer: SignHeadsRenderer
|
|
110
|
+
private readonly blockEntityLightRegistry = new BlockEntityLightRegistry()
|
|
93
111
|
/**
|
|
94
112
|
* Shared transparent material used as the basis for the wireframe chunk
|
|
95
113
|
* border `BoxHelper` created lazily in {@link updateBoxHelper}. Kept on the
|
|
@@ -99,8 +117,26 @@ export class ChunkMeshManager {
|
|
|
99
117
|
private readonly chunkBoxMaterial = new THREE.MeshBasicMaterial({ color: 0x00_00_00, transparent: true, opacity: 0 })
|
|
100
118
|
/** Shared across all sections — atlas/tint uniforms updated via {@link syncCubeShaderUniforms}. */
|
|
101
119
|
private cubeShaderMaterial: THREE.ShaderMaterial | null = null
|
|
120
|
+
/** Per-section blend meshes — atlas + camera origin updated each frame. */
|
|
121
|
+
private legacyShaderMaterial: THREE.ShaderMaterial | null = null
|
|
122
|
+
private globalLegacyShaderMaterial: THREE.ShaderMaterial | null = null
|
|
123
|
+
private globalLegacyBlendShaderMaterial: THREE.ShaderMaterial | null = null
|
|
124
|
+
private readonly _legacyCullFrustum = new THREE.Frustum()
|
|
125
|
+
private readonly _legacyCullProjScreen = new THREE.Matrix4()
|
|
126
|
+
private readonly _legacyCullBox = new THREE.Box3()
|
|
127
|
+
private readonly _legacyCullBoxMin = new THREE.Vector3()
|
|
128
|
+
private readonly _legacyCullBoxMax = new THREE.Vector3()
|
|
129
|
+
private readonly _visibleSectionSpans: Array<{ key: string, distSq: number }> = []
|
|
130
|
+
/** Drives per-frame cull + span rebuild; cleared after updateSectionCullAndSort. */
|
|
131
|
+
cullDirty = true
|
|
132
|
+
private readonly _lastCullCamPos = new THREE.Vector3()
|
|
133
|
+
private readonly _lastCullCamQuat = new THREE.Quaternion()
|
|
134
|
+
private readonly _cullViewQuat = new THREE.Quaternion()
|
|
135
|
+
private _cullCamInitialized = false
|
|
102
136
|
/** One instanced mesh for all shader-cube faces (single draw call). */
|
|
103
137
|
globalBlockBuffer: GlobalBlockBuffer | null = null
|
|
138
|
+
globalLegacyBuffer: GlobalLegacyBuffer | null = null
|
|
139
|
+
globalLegacyBlendBuffer: GlobalLegacyBuffer | null = null
|
|
104
140
|
/** Tight world AABBs for third-person raycast; block word0 read from GlobalBlockBuffer or deferred. */
|
|
105
141
|
private readonly shaderSectionRaycastBoxes = new Map<string, ShaderSectionRaycastEntry>()
|
|
106
142
|
/** Per-raycast block dedup; safe while the eye is inside at most one section aggregate AABB per call. */
|
|
@@ -146,7 +182,7 @@ export class ChunkMeshManager {
|
|
|
146
182
|
// Create initial pool
|
|
147
183
|
for (let i = 0; i < this.poolSize; i++) {
|
|
148
184
|
const geometry = new THREE.BufferGeometry()
|
|
149
|
-
const mesh = new THREE.Mesh(geometry, this.
|
|
185
|
+
const mesh = new THREE.Mesh(geometry, this.getLegacyShaderMaterial())
|
|
150
186
|
mesh.visible = false
|
|
151
187
|
mesh.matrixAutoUpdate = false
|
|
152
188
|
mesh.name = 'pooled-section-mesh'
|
|
@@ -165,6 +201,7 @@ export class ChunkMeshManager {
|
|
|
165
201
|
/** True when section has legacy vertices and/or GPU shader cube instances. */
|
|
166
202
|
sectionHasRenderableContent (geometryData: MesherGeometryOutput): boolean {
|
|
167
203
|
if (geometryData.positions.length > 0) return true
|
|
204
|
+
if ((geometryData.blend?.positions.length ?? 0) > 0) return true
|
|
168
205
|
if (!this.isShaderCubesGpuEnabled()) return false
|
|
169
206
|
return (geometryData.shaderCubes?.count ?? 0) > 0
|
|
170
207
|
}
|
|
@@ -190,6 +227,292 @@ export class ChunkMeshManager {
|
|
|
190
227
|
mat.needsUpdate = true
|
|
191
228
|
}
|
|
192
229
|
|
|
230
|
+
syncLegacyShaderUniforms (): void {
|
|
231
|
+
const atlas = (this.material as THREE.MeshBasicMaterial).map ?? null
|
|
232
|
+
if (this.legacyShaderMaterial) {
|
|
233
|
+
this.legacyShaderMaterial.uniforms.u_atlas.value = atlas
|
|
234
|
+
this.legacyShaderMaterial.needsUpdate = true
|
|
235
|
+
}
|
|
236
|
+
if (this.globalLegacyShaderMaterial) {
|
|
237
|
+
this.globalLegacyShaderMaterial.uniforms.u_atlas.value = atlas
|
|
238
|
+
this.globalLegacyShaderMaterial.needsUpdate = true
|
|
239
|
+
}
|
|
240
|
+
if (this.globalLegacyBlendShaderMaterial) {
|
|
241
|
+
this.globalLegacyBlendShaderMaterial.uniforms.u_atlas.value = atlas
|
|
242
|
+
this.globalLegacyBlendShaderMaterial.needsUpdate = true
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Render-time sky light cap (0–1, from time-of-day / 15). */
|
|
247
|
+
setSkyLevel (value: number): void {
|
|
248
|
+
const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
|
|
249
|
+
if (cube) setCubeSkyLevel(cube, value)
|
|
250
|
+
if (this.legacyShaderMaterial) setLegacySkyLevel(this.legacyShaderMaterial, value)
|
|
251
|
+
if (this.globalLegacyShaderMaterial) setLegacySkyLevel(this.globalLegacyShaderMaterial, value)
|
|
252
|
+
if (this.globalLegacyBlendShaderMaterial) setLegacySkyLevel(this.globalLegacyBlendShaderMaterial, value)
|
|
253
|
+
this.blockEntityLightRegistry.setSkyLevel(value)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Vanilla-like lightmap curve params (live tuning via window.setBlockLightmap). */
|
|
257
|
+
setBlockLightmapParams (params: BlockLightmapParams): void {
|
|
258
|
+
const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
|
|
259
|
+
if (cube) setCubeLightmapParams(cube, params)
|
|
260
|
+
if (this.legacyShaderMaterial) setLegacyLightmapParams(this.legacyShaderMaterial, params)
|
|
261
|
+
if (this.globalLegacyShaderMaterial) setLegacyLightmapParams(this.globalLegacyShaderMaterial, params)
|
|
262
|
+
if (this.globalLegacyBlendShaderMaterial) setLegacyLightmapParams(this.globalLegacyBlendShaderMaterial, params)
|
|
263
|
+
this.blockEntityLightRegistry.setLightmapParams(params)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private getLegacyShaderMaterial (): THREE.ShaderMaterial {
|
|
267
|
+
if (!this.legacyShaderMaterial) {
|
|
268
|
+
this.legacyShaderMaterial = createLegacyBlockMaterial()
|
|
269
|
+
this.syncLegacyShaderUniforms()
|
|
270
|
+
}
|
|
271
|
+
return this.legacyShaderMaterial
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private getGlobalLegacyShaderMaterial (): THREE.ShaderMaterial {
|
|
275
|
+
if (!this.globalLegacyShaderMaterial) {
|
|
276
|
+
this.globalLegacyShaderMaterial = createGlobalLegacyBlockMaterial()
|
|
277
|
+
this.syncLegacyShaderUniforms()
|
|
278
|
+
}
|
|
279
|
+
return this.globalLegacyShaderMaterial
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private getGlobalLegacyBuffer (): GlobalLegacyBuffer {
|
|
283
|
+
if (!this.globalLegacyBuffer) {
|
|
284
|
+
this.globalLegacyBuffer = new GlobalLegacyBuffer(
|
|
285
|
+
this.getGlobalLegacyShaderMaterial(),
|
|
286
|
+
this.scene,
|
|
287
|
+
)
|
|
288
|
+
this.globalLegacyBuffer.setRenderOrigin(this.renderOrigin)
|
|
289
|
+
}
|
|
290
|
+
return this.globalLegacyBuffer
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private getGlobalLegacyBlendShaderMaterial (): THREE.ShaderMaterial {
|
|
294
|
+
if (!this.globalLegacyBlendShaderMaterial) {
|
|
295
|
+
this.globalLegacyBlendShaderMaterial = createGlobalLegacyBlendMaterial()
|
|
296
|
+
this.syncLegacyShaderUniforms()
|
|
297
|
+
}
|
|
298
|
+
return this.globalLegacyBlendShaderMaterial
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private getGlobalLegacyBlendBuffer (): GlobalLegacyBuffer {
|
|
302
|
+
if (!this.globalLegacyBlendBuffer) {
|
|
303
|
+
this.globalLegacyBlendBuffer = new GlobalLegacyBuffer(
|
|
304
|
+
this.getGlobalLegacyBlendShaderMaterial(),
|
|
305
|
+
this.scene,
|
|
306
|
+
{
|
|
307
|
+
name: 'globalLegacyBlend',
|
|
308
|
+
initialCapacityQuads: 32_000,
|
|
309
|
+
growthIncrementQuads: 32_000,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
this.globalLegacyBlendBuffer.setRenderOrigin(this.renderOrigin)
|
|
313
|
+
}
|
|
314
|
+
return this.globalLegacyBlendBuffer
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
getRenderOrigin (): Readonly<RenderOrigin> {
|
|
318
|
+
return this.renderOrigin
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
maybeRebase (camera: RenderOrigin): void {
|
|
322
|
+
const R = this.renderOrigin
|
|
323
|
+
if (
|
|
324
|
+
Math.abs(camera.x - R.x) <= ChunkMeshManager.REBASE_THRESHOLD
|
|
325
|
+
&& Math.abs(camera.y - R.y) <= ChunkMeshManager.REBASE_THRESHOLD
|
|
326
|
+
&& Math.abs(camera.z - R.z) <= ChunkMeshManager.REBASE_THRESHOLD
|
|
327
|
+
) {
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const newOrigin: RenderOrigin = {
|
|
332
|
+
x: Math.round(camera.x / 16) * 16,
|
|
333
|
+
y: Math.round(camera.y / 16) * 16,
|
|
334
|
+
z: Math.round(camera.z / 16) * 16,
|
|
335
|
+
}
|
|
336
|
+
const delta: RenderOrigin = {
|
|
337
|
+
x: newOrigin.x - R.x,
|
|
338
|
+
y: newOrigin.y - R.y,
|
|
339
|
+
z: newOrigin.z - R.z,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.globalLegacyBuffer?.rebase(delta)
|
|
343
|
+
this.globalLegacyBlendBuffer?.rebase(delta)
|
|
344
|
+
|
|
345
|
+
for (const poolEntry of this.activeSections.values()) {
|
|
346
|
+
const sectionKey = poolEntry.sectionKey
|
|
347
|
+
if (!sectionKey) continue
|
|
348
|
+
const sectionObject = this.sectionObjects[sectionKey]
|
|
349
|
+
if (!sectionObject) continue
|
|
350
|
+
setupLegacySectionMatrix(
|
|
351
|
+
poolEntry.mesh,
|
|
352
|
+
sectionObject.worldX ?? 0,
|
|
353
|
+
sectionObject.worldY ?? 0,
|
|
354
|
+
sectionObject.worldZ ?? 0,
|
|
355
|
+
newOrigin,
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.renderOrigin = newOrigin
|
|
360
|
+
this.globalLegacyBuffer?.setRenderOrigin(newOrigin)
|
|
361
|
+
this.globalLegacyBlendBuffer?.setRenderOrigin(newOrigin)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Whether a section still holds a pooled legacy mesh (defer / invariant fallback). */
|
|
365
|
+
sectionUsesPooledLegacyMesh (sectionKey: string): boolean {
|
|
366
|
+
return this.activeSections.has(sectionKey)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Shared section visibility + span groups for global legacy and cube buffers.
|
|
371
|
+
*/
|
|
372
|
+
updateSectionCullAndSort (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
|
|
373
|
+
this._legacyCullProjScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
|
|
374
|
+
this._legacyCullFrustum.setFromProjectionMatrix(this._legacyCullProjScreen)
|
|
375
|
+
|
|
376
|
+
const visible = this._visibleSectionSpans
|
|
377
|
+
visible.length = 0
|
|
378
|
+
|
|
379
|
+
for (const [sectionKey, sectionObject] of Object.entries(this.sectionObjects)) {
|
|
380
|
+
if (sectionObject.worldX === undefined || !sectionObject.visible) continue
|
|
381
|
+
const { visible: inFrustum, distSq } = sectionIntersectsFrustum(
|
|
382
|
+
sectionObject.worldX,
|
|
383
|
+
sectionObject.worldY ?? 0,
|
|
384
|
+
sectionObject.worldZ ?? 0,
|
|
385
|
+
cameraWorldX,
|
|
386
|
+
cameraWorldY,
|
|
387
|
+
cameraWorldZ,
|
|
388
|
+
this._legacyCullFrustum,
|
|
389
|
+
this._legacyCullBox,
|
|
390
|
+
this._legacyCullBoxMin,
|
|
391
|
+
this._legacyCullBoxMax,
|
|
392
|
+
)
|
|
393
|
+
if (inFrustum) {
|
|
394
|
+
visible.push({ key: sectionKey, distSq })
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
this.globalLegacyBuffer?.updateDrawSpans(visible, 'opaque')
|
|
399
|
+
this.globalLegacyBlendBuffer?.updateDrawSpans(visible, 'sortedBlend')
|
|
400
|
+
|
|
401
|
+
const gb = this.globalBlockBuffer
|
|
402
|
+
if (gb) {
|
|
403
|
+
const visibleSlots: Array<{ start: number, count: number }> = []
|
|
404
|
+
gb.forEachSectionSlot((key, slot) => {
|
|
405
|
+
const entry = this.shaderSectionRaycastBoxes.get(key)
|
|
406
|
+
if (!entry) {
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
const { visible: inFrustum } = sectionIntersectsFrustum(
|
|
410
|
+
entry.sectionCenterX,
|
|
411
|
+
entry.sectionCenterY,
|
|
412
|
+
entry.sectionCenterZ,
|
|
413
|
+
cameraWorldX,
|
|
414
|
+
cameraWorldY,
|
|
415
|
+
cameraWorldZ,
|
|
416
|
+
this._legacyCullFrustum,
|
|
417
|
+
this._legacyCullBox,
|
|
418
|
+
this._legacyCullBoxMin,
|
|
419
|
+
this._legacyCullBoxMax,
|
|
420
|
+
)
|
|
421
|
+
if (!inFrustum) {
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
const drawStart = gb.getSectionDrawStart(key)
|
|
425
|
+
if (drawStart !== undefined) {
|
|
426
|
+
visibleSlots.push({ start: drawStart, count: slot.count })
|
|
427
|
+
}
|
|
428
|
+
})
|
|
429
|
+
const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
|
|
430
|
+
gb.setVisibleSpans(spans)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
for (const poolEntry of this.activeSections.values()) {
|
|
434
|
+
const sectionKey = poolEntry.sectionKey
|
|
435
|
+
if (!sectionKey) continue
|
|
436
|
+
const sectionObject = this.sectionObjects[sectionKey]
|
|
437
|
+
if (!sectionObject) continue
|
|
438
|
+
|
|
439
|
+
updateLegacySectionCullState(
|
|
440
|
+
poolEntry.mesh,
|
|
441
|
+
sectionObject.worldX ?? 0,
|
|
442
|
+
sectionObject.worldY ?? 0,
|
|
443
|
+
sectionObject.worldZ ?? 0,
|
|
444
|
+
cameraWorldX,
|
|
445
|
+
cameraWorldY,
|
|
446
|
+
cameraWorldZ,
|
|
447
|
+
this._legacyCullFrustum,
|
|
448
|
+
this._legacyCullBox,
|
|
449
|
+
this._legacyCullBoxMin,
|
|
450
|
+
this._legacyCullBoxMax,
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
markCullDirty (): void {
|
|
456
|
+
this.cullDirty = true
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Compare camera pose; mark cull dirty when position or rotation changed. */
|
|
460
|
+
updateCullDirtyFromCamera (camera: THREE.Camera, cameraWorldX: number, cameraWorldY: number, cameraWorldZ: number): void {
|
|
461
|
+
camera.getWorldQuaternion(this._cullViewQuat)
|
|
462
|
+
if (!this._cullCamInitialized) {
|
|
463
|
+
this._lastCullCamPos.set(cameraWorldX, cameraWorldY, cameraWorldZ)
|
|
464
|
+
this._lastCullCamQuat.copy(this._cullViewQuat)
|
|
465
|
+
this._cullCamInitialized = true
|
|
466
|
+
this.cullDirty = true
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
const posChanged =
|
|
470
|
+
this._lastCullCamPos.x !== cameraWorldX ||
|
|
471
|
+
this._lastCullCamPos.y !== cameraWorldY ||
|
|
472
|
+
this._lastCullCamPos.z !== cameraWorldZ
|
|
473
|
+
const quatChanged =
|
|
474
|
+
this._lastCullCamQuat.x !== this._cullViewQuat.x ||
|
|
475
|
+
this._lastCullCamQuat.y !== this._cullViewQuat.y ||
|
|
476
|
+
this._lastCullCamQuat.z !== this._cullViewQuat.z ||
|
|
477
|
+
this._lastCullCamQuat.w !== this._cullViewQuat.w
|
|
478
|
+
if (posChanged || quatChanged) {
|
|
479
|
+
this._lastCullCamPos.set(cameraWorldX, cameraWorldY, cameraWorldZ)
|
|
480
|
+
this._lastCullCamQuat.copy(this._cullViewQuat)
|
|
481
|
+
this.markCullDirty()
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
clearCullDirty (): void {
|
|
486
|
+
this.cullDirty = false
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
setLegacyCameraOrigin (x: number, y: number, z: number): void {
|
|
490
|
+
const R = this.renderOrigin
|
|
491
|
+
setLegacyCameraOrigin(this.getLegacyShaderMaterial(), R, x, y, z)
|
|
492
|
+
setLegacyCameraOrigin(this.getGlobalLegacyShaderMaterial(), R, x, y, z)
|
|
493
|
+
setLegacyCameraOrigin(this.getGlobalLegacyBlendShaderMaterial(), R, x, y, z)
|
|
494
|
+
this.globalLegacyBuffer?.setCameraOrigin(x, y, z)
|
|
495
|
+
this.globalLegacyBlendBuffer?.setCameraOrigin(x, y, z)
|
|
496
|
+
|
|
497
|
+
const cubeMat = this.cubeShaderMaterial
|
|
498
|
+
if (cubeMat) {
|
|
499
|
+
const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(R, x, y, z)
|
|
500
|
+
const sectionOriginRel = computeSectionOriginRel(R)
|
|
501
|
+
const u = cubeMat.uniforms.u_originDelta
|
|
502
|
+
if (u?.value?.set) {
|
|
503
|
+
u.value.set(originDelta.x, originDelta.y, originDelta.z)
|
|
504
|
+
}
|
|
505
|
+
const uf = cubeMat.uniforms.u_cameraOriginFrac
|
|
506
|
+
if (uf?.value?.set) {
|
|
507
|
+
uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
|
|
508
|
+
}
|
|
509
|
+
const us = cubeMat.uniforms.u_sectionOriginRel
|
|
510
|
+
if (us?.value?.set) {
|
|
511
|
+
us.value.set(sectionOriginRel.x, sectionOriginRel.y, sectionOriginRel.z)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
193
516
|
private getCubeShaderMaterial (): THREE.ShaderMaterial | null {
|
|
194
517
|
if (!this.isShaderCubesGpuEnabled()) return null
|
|
195
518
|
if (!this.cubeShaderMaterial) {
|
|
@@ -208,15 +531,16 @@ export class ChunkMeshManager {
|
|
|
208
531
|
return this.globalBlockBuffer
|
|
209
532
|
}
|
|
210
533
|
|
|
211
|
-
|
|
534
|
+
private shouldDeferLegacyOpaqueToPerSection (sectionKey: string): boolean {
|
|
535
|
+
return this.shouldDeferShaderToPerSection(sectionKey)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Sci-fi reveal keeps geometry off global buffers until the section finishes reveal. */
|
|
212
539
|
private shouldDeferShaderToPerSection (sectionKey: string): boolean {
|
|
213
540
|
const sciFi = this.worldRenderer.getModule<{
|
|
214
|
-
|
|
215
|
-
isInInitialRevealCampaign?: () => boolean
|
|
541
|
+
shouldDeferSectionGeometry?: (key: string) => boolean
|
|
216
542
|
}>('futuristicReveal')
|
|
217
|
-
|
|
218
|
-
if (sciFi.isInInitialRevealCampaign?.()) return true
|
|
219
|
-
return sciFi.shouldUseRevealEffect?.(sectionKey) === true
|
|
543
|
+
return sciFi?.shouldDeferSectionGeometry?.(sectionKey) === true
|
|
220
544
|
}
|
|
221
545
|
|
|
222
546
|
/**
|
|
@@ -237,6 +561,7 @@ export class ChunkMeshManager {
|
|
|
237
561
|
|
|
238
562
|
const global = this.getGlobalBlockBuffer()
|
|
239
563
|
global?.addSection(sectionKey, words, count)
|
|
564
|
+
this.markCullDirty()
|
|
240
565
|
|
|
241
566
|
const hadShaderAsPrimary = section.mesh === (section.shaderMesh as unknown as THREE.Mesh | undefined)
|
|
242
567
|
if (section.shaderMesh) {
|
|
@@ -251,6 +576,104 @@ export class ChunkMeshManager {
|
|
|
251
576
|
}
|
|
252
577
|
}
|
|
253
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Move deferred per-section opaque legacy into the global buffer after reveal completes.
|
|
581
|
+
*/
|
|
582
|
+
migrateDeferredLegacyToGlobal (sectionKey: string): void {
|
|
583
|
+
const section = this.sectionObjects[sectionKey]
|
|
584
|
+
if (!section) return
|
|
585
|
+
|
|
586
|
+
if (section.deferredLegacyOpaque) {
|
|
587
|
+
const { positions, colors, skyLights, blockLights, uvs, indices } = section.deferredLegacyOpaque
|
|
588
|
+
const wx = section.worldX
|
|
589
|
+
const wy = section.worldY
|
|
590
|
+
const wz = section.worldZ
|
|
591
|
+
if (wx !== undefined && wy !== undefined && wz !== undefined) {
|
|
592
|
+
this.getGlobalLegacyBuffer().addSection(
|
|
593
|
+
sectionKey,
|
|
594
|
+
{ positions, colors, skyLights, blockLights, uvs, indices },
|
|
595
|
+
wx,
|
|
596
|
+
wy,
|
|
597
|
+
wz,
|
|
598
|
+
)
|
|
599
|
+
}
|
|
600
|
+
delete section.deferredLegacyOpaque
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (section.deferredLegacyBlend) {
|
|
604
|
+
const { positions, colors, skyLights, blockLights, uvs, indices } = section.deferredLegacyBlend
|
|
605
|
+
const wx = section.worldX
|
|
606
|
+
const wy = section.worldY
|
|
607
|
+
const wz = section.worldZ
|
|
608
|
+
if (wx !== undefined && wy !== undefined && wz !== undefined) {
|
|
609
|
+
this.getGlobalLegacyBlendBuffer().addSection(
|
|
610
|
+
sectionKey,
|
|
611
|
+
{ positions, colors, skyLights, blockLights, uvs, indices },
|
|
612
|
+
wx,
|
|
613
|
+
wy,
|
|
614
|
+
wz,
|
|
615
|
+
)
|
|
616
|
+
}
|
|
617
|
+
delete section.deferredLegacyBlend
|
|
618
|
+
section.hasBlendMesh = false
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (!section.hasBlendMesh) {
|
|
622
|
+
const hadLegacyAsPrimary = section.mesh === this.activeSections.get(sectionKey)?.mesh
|
|
623
|
+
this.releasePooledMesh(sectionKey)
|
|
624
|
+
if (hadLegacyAsPrimary) {
|
|
625
|
+
section.mesh = section.shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined ?? undefined
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
this.markCullDirty()
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
raycastGlobalLegacySections (
|
|
632
|
+
raycaster: THREE.Raycaster,
|
|
633
|
+
origin: THREE.Vector3,
|
|
634
|
+
maxCenterDistance: number,
|
|
635
|
+
): number | undefined {
|
|
636
|
+
const maxDistSq = maxCenterDistance * maxCenterDistance
|
|
637
|
+
const dirX = raycaster.ray.direction.x
|
|
638
|
+
const dirY = raycaster.ray.direction.y
|
|
639
|
+
const dirZ = raycaster.ray.direction.z
|
|
640
|
+
const far = raycaster.far
|
|
641
|
+
const halfExtent = LEGACY_SECTION_HALF_EXTENT + 0.01
|
|
642
|
+
const candidates: string[] = []
|
|
643
|
+
for (const [key, section] of Object.entries(this.sectionObjects)) {
|
|
644
|
+
if (section.worldX === undefined) continue
|
|
645
|
+
const dx = section.worldX - origin.x
|
|
646
|
+
const dy = (section.worldY ?? 0) - origin.y
|
|
647
|
+
const dz = (section.worldZ ?? 0) - origin.z
|
|
648
|
+
if (dx * dx + dy * dy + dz * dz > maxDistSq) continue
|
|
649
|
+
const inOpaque = this.globalLegacyBuffer?.hasSection(key) ?? false
|
|
650
|
+
const inBlend = this.globalLegacyBlendBuffer?.hasSection(key) ?? false
|
|
651
|
+
if (!inOpaque && !inBlend) continue
|
|
652
|
+
if (!sectionAabbIntersectsRay(
|
|
653
|
+
section.worldX,
|
|
654
|
+
section.worldY ?? 0,
|
|
655
|
+
section.worldZ ?? 0,
|
|
656
|
+
origin.x,
|
|
657
|
+
origin.y,
|
|
658
|
+
origin.z,
|
|
659
|
+
dirX,
|
|
660
|
+
dirY,
|
|
661
|
+
dirZ,
|
|
662
|
+
far,
|
|
663
|
+
halfExtent,
|
|
664
|
+
)) continue
|
|
665
|
+
candidates.push(key)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (candidates.length === 0) return undefined
|
|
669
|
+
|
|
670
|
+
const hits: THREE.Intersection[] = []
|
|
671
|
+
this.globalLegacyBuffer?.raycastSections(raycaster, candidates, hits)
|
|
672
|
+
this.globalLegacyBlendBuffer?.raycastSections(raycaster, candidates, hits)
|
|
673
|
+
|
|
674
|
+
return hits[0]?.distance
|
|
675
|
+
}
|
|
676
|
+
|
|
254
677
|
registerShaderSectionRaycastBox (
|
|
255
678
|
sectionKey: string,
|
|
256
679
|
words: Uint32Array,
|
|
@@ -363,8 +786,55 @@ export class ChunkMeshManager {
|
|
|
363
786
|
/**
|
|
364
787
|
* Update or create a section with new geometry data
|
|
365
788
|
*/
|
|
789
|
+
private uploadLegacyPooledMesh (
|
|
790
|
+
poolEntry: ChunkMeshPool,
|
|
791
|
+
geometryData: MesherGeometryOutput | MesherGeometryOutput['blend'],
|
|
792
|
+
sx: number,
|
|
793
|
+
sy: number,
|
|
794
|
+
sz: number,
|
|
795
|
+
): THREE.Mesh<THREE.BufferGeometry, THREE.Material> {
|
|
796
|
+
const { mesh } = poolEntry
|
|
797
|
+
const geo = geometryData!
|
|
798
|
+
this.updateGeometryAttribute(mesh.geometry, 'position', geo.positions, 3)
|
|
799
|
+
this.updateGeometryAttribute(mesh.geometry, 'normal', geo.normals, 3)
|
|
800
|
+
this.updateGeometryAttribute(mesh.geometry, 'color', geo.colors, 3)
|
|
801
|
+
this.updateGeometryAttribute(mesh.geometry, 'a_skyLight', geo.skyLights, 1)
|
|
802
|
+
this.updateGeometryAttribute(mesh.geometry, 'a_blockLight', geo.blockLights, 1)
|
|
803
|
+
this.updateGeometryAttribute(mesh.geometry, 'uv', geo.uvs, 2)
|
|
804
|
+
mesh.geometry.index = new THREE.BufferAttribute(geo.indices as Uint32Array | Uint16Array, 1)
|
|
805
|
+
mesh.geometry.boundingBox = new THREE.Box3(
|
|
806
|
+
new THREE.Vector3(-8, -8, -8),
|
|
807
|
+
new THREE.Vector3(8, 8, 8),
|
|
808
|
+
)
|
|
809
|
+
mesh.geometry.boundingSphere = new THREE.Sphere(
|
|
810
|
+
new THREE.Vector3(0, 0, 0),
|
|
811
|
+
Math.sqrt(3 * 8 ** 2),
|
|
812
|
+
)
|
|
813
|
+
setupLegacySectionMatrix(mesh, sx, sy, sz, this.renderOrigin)
|
|
814
|
+
mesh.visible = false
|
|
815
|
+
mesh.name = 'mesh'
|
|
816
|
+
poolEntry.lastUsedTime = performance.now()
|
|
817
|
+
return mesh as THREE.Mesh<THREE.BufferGeometry, THREE.Material>
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
private acquirePooledSectionMesh (sectionKey: string): ChunkMeshPool | null {
|
|
821
|
+
let poolEntry = this.activeSections.get(sectionKey)
|
|
822
|
+
if (!poolEntry) {
|
|
823
|
+
poolEntry = this.acquireMesh()
|
|
824
|
+
if (!poolEntry) {
|
|
825
|
+
console.warn(`ChunkMeshManager: No available mesh in pool for section ${sectionKey}`)
|
|
826
|
+
return null
|
|
827
|
+
}
|
|
828
|
+
this.activeSections.set(sectionKey, poolEntry)
|
|
829
|
+
poolEntry.sectionKey = sectionKey
|
|
830
|
+
}
|
|
831
|
+
return poolEntry
|
|
832
|
+
}
|
|
833
|
+
|
|
366
834
|
updateSection (sectionKey: string, geometryData: MesherGeometryOutput): SectionObject | null {
|
|
367
|
-
const
|
|
835
|
+
const hasOpaque = geometryData.positions.length > 0
|
|
836
|
+
const hasBlend = (geometryData.blend?.positions.length ?? 0) > 0
|
|
837
|
+
const hasLegacy = hasOpaque || hasBlend
|
|
368
838
|
const shaderData = geometryData.shaderCubes
|
|
369
839
|
const hasShader = this.isShaderCubesGpuEnabled() && (shaderData?.count ?? 0) > 0
|
|
370
840
|
|
|
@@ -376,53 +846,115 @@ export class ChunkMeshManager {
|
|
|
376
846
|
// Remove existing section object from scene if it exists
|
|
377
847
|
let sectionObject = this.sectionObjects[sectionKey]
|
|
378
848
|
if (sectionObject) {
|
|
379
|
-
this.cleanupSection(sectionKey)
|
|
849
|
+
this.cleanupSection(sectionKey, { forRemesh: true })
|
|
380
850
|
}
|
|
381
851
|
|
|
382
|
-
if (!
|
|
852
|
+
if (!hasBlend) {
|
|
383
853
|
this.releasePooledMesh(sectionKey)
|
|
384
854
|
}
|
|
385
855
|
|
|
386
856
|
let legacyMesh: THREE.Mesh<THREE.BufferGeometry, THREE.Material> | undefined
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
857
|
+
let deferredLegacyOpaque: LegacySectionGeometry | undefined
|
|
858
|
+
let deferredLegacyBlend: LegacySectionGeometry | undefined
|
|
859
|
+
let hasBlendMesh = false
|
|
860
|
+
|
|
861
|
+
if (hasOpaque) {
|
|
862
|
+
const opaqueGeo: LegacySectionGeometry = {
|
|
863
|
+
positions: geometryData.positions as Float32Array,
|
|
864
|
+
colors: geometryData.colors as Float32Array,
|
|
865
|
+
skyLights: geometryData.skyLights as Float32Array,
|
|
866
|
+
blockLights: geometryData.blockLights as Float32Array,
|
|
867
|
+
uvs: geometryData.uvs as Float32Array,
|
|
868
|
+
indices: geometryData.indices as Uint32Array | Uint16Array,
|
|
869
|
+
}
|
|
870
|
+
const deferOpaque = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
|
|
871
|
+
if (deferOpaque) {
|
|
872
|
+
deferredLegacyOpaque = {
|
|
873
|
+
positions: new Float32Array(opaqueGeo.positions),
|
|
874
|
+
colors: new Float32Array(opaqueGeo.colors),
|
|
875
|
+
skyLights: new Float32Array(opaqueGeo.skyLights),
|
|
876
|
+
blockLights: new Float32Array(opaqueGeo.blockLights),
|
|
877
|
+
uvs: new Float32Array(opaqueGeo.uvs),
|
|
878
|
+
indices: opaqueGeo.indices instanceof Uint32Array
|
|
879
|
+
? new Uint32Array(opaqueGeo.indices)
|
|
880
|
+
: new Uint16Array(opaqueGeo.indices),
|
|
881
|
+
}
|
|
882
|
+
if (!hasBlend) {
|
|
883
|
+
const poolEntry = this.acquirePooledSectionMesh(sectionKey)
|
|
884
|
+
if (!poolEntry) return null
|
|
885
|
+
legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
|
|
886
|
+
}
|
|
887
|
+
} else {
|
|
888
|
+
const added = this.getGlobalLegacyBuffer().addSection(
|
|
889
|
+
sectionKey,
|
|
890
|
+
opaqueGeo,
|
|
891
|
+
geometryData.sx,
|
|
892
|
+
geometryData.sy,
|
|
893
|
+
geometryData.sz,
|
|
894
|
+
)
|
|
895
|
+
if (!added) {
|
|
896
|
+
const poolEntry = this.acquirePooledSectionMesh(sectionKey)
|
|
897
|
+
if (!poolEntry) return null
|
|
898
|
+
legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
|
|
394
899
|
}
|
|
395
|
-
|
|
396
|
-
this.activeSections.set(sectionKey, poolEntry)
|
|
397
|
-
poolEntry.sectionKey = sectionKey
|
|
398
900
|
}
|
|
901
|
+
}
|
|
399
902
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
903
|
+
if (hasBlend && geometryData.blend) {
|
|
904
|
+
const blendGeo: LegacySectionGeometry = {
|
|
905
|
+
positions: geometryData.blend.positions as Float32Array,
|
|
906
|
+
colors: geometryData.blend.colors as Float32Array,
|
|
907
|
+
skyLights: geometryData.blend.skyLights as Float32Array,
|
|
908
|
+
blockLights: geometryData.blend.blockLights as Float32Array,
|
|
909
|
+
uvs: geometryData.blend.uvs as Float32Array,
|
|
910
|
+
indices: geometryData.blend.indices as Uint32Array | Uint16Array,
|
|
911
|
+
}
|
|
912
|
+
const deferBlend = this.shouldDeferLegacyOpaqueToPerSection(sectionKey)
|
|
913
|
+
if (deferBlend) {
|
|
914
|
+
deferredLegacyBlend = {
|
|
915
|
+
positions: new Float32Array(blendGeo.positions),
|
|
916
|
+
colors: new Float32Array(blendGeo.colors),
|
|
917
|
+
skyLights: new Float32Array(blendGeo.skyLights),
|
|
918
|
+
blockLights: new Float32Array(blendGeo.blockLights),
|
|
919
|
+
uvs: new Float32Array(blendGeo.uvs),
|
|
920
|
+
indices: blendGeo.indices instanceof Uint32Array
|
|
921
|
+
? new Uint32Array(blendGeo.indices)
|
|
922
|
+
: new Uint16Array(blendGeo.indices),
|
|
923
|
+
}
|
|
924
|
+
const poolEntry = this.acquirePooledSectionMesh(sectionKey)
|
|
925
|
+
if (!poolEntry) return null
|
|
926
|
+
const blendMesh = this.uploadLegacyPooledMesh(
|
|
927
|
+
poolEntry,
|
|
928
|
+
geometryData.blend,
|
|
929
|
+
geometryData.sx,
|
|
930
|
+
geometryData.sy,
|
|
931
|
+
geometryData.sz,
|
|
932
|
+
)
|
|
933
|
+
legacyMesh = legacyMesh ?? blendMesh
|
|
934
|
+
hasBlendMesh = true
|
|
935
|
+
} else {
|
|
936
|
+
const added = this.getGlobalLegacyBlendBuffer().addSection(
|
|
937
|
+
sectionKey,
|
|
938
|
+
blendGeo,
|
|
939
|
+
geometryData.sx,
|
|
940
|
+
geometryData.sy,
|
|
941
|
+
geometryData.sz,
|
|
942
|
+
)
|
|
943
|
+
if (!added) {
|
|
944
|
+
console.warn(`ChunkMeshManager: blend invariant violation for section ${sectionKey}, using pooled mesh fallback`)
|
|
945
|
+
const poolEntry = this.acquirePooledSectionMesh(sectionKey)
|
|
946
|
+
if (!poolEntry) return null
|
|
947
|
+
const blendMesh = this.uploadLegacyPooledMesh(
|
|
948
|
+
poolEntry,
|
|
949
|
+
geometryData.blend,
|
|
950
|
+
geometryData.sx,
|
|
951
|
+
geometryData.sy,
|
|
952
|
+
geometryData.sz,
|
|
953
|
+
)
|
|
954
|
+
legacyMesh = legacyMesh ?? blendMesh
|
|
955
|
+
hasBlendMesh = true
|
|
956
|
+
}
|
|
957
|
+
}
|
|
426
958
|
}
|
|
427
959
|
|
|
428
960
|
const cubeMaterial = hasShader ? this.getCubeShaderMaterial() : null
|
|
@@ -455,6 +987,12 @@ export class ChunkMeshManager {
|
|
|
455
987
|
sectionObject.mesh = shaderMesh as unknown as THREE.Mesh<THREE.BufferGeometry, THREE.Material>
|
|
456
988
|
}
|
|
457
989
|
}
|
|
990
|
+
if (deferredLegacyOpaque) {
|
|
991
|
+
sectionObject.deferredLegacyOpaque = deferredLegacyOpaque
|
|
992
|
+
}
|
|
993
|
+
if (deferredLegacyBlend) {
|
|
994
|
+
sectionObject.deferredLegacyBlend = deferredLegacyBlend
|
|
995
|
+
}
|
|
458
996
|
if (hasShader && shaderData) {
|
|
459
997
|
this.registerShaderSectionRaycastBox(
|
|
460
998
|
sectionKey,
|
|
@@ -467,13 +1005,17 @@ export class ChunkMeshManager {
|
|
|
467
1005
|
}
|
|
468
1006
|
|
|
469
1007
|
let tilesCount = 0
|
|
470
|
-
if (
|
|
1008
|
+
if (hasOpaque) {
|
|
471
1009
|
tilesCount += geometryData.positions.length / 3 / 4
|
|
472
1010
|
}
|
|
1011
|
+
if (hasBlend && geometryData.blend) {
|
|
1012
|
+
tilesCount += geometryData.blend.positions.length / 3 / 4
|
|
1013
|
+
}
|
|
473
1014
|
if (hasShader && shaderData) {
|
|
474
1015
|
tilesCount += shaderData.count
|
|
475
1016
|
}
|
|
476
1017
|
sectionObject.tilesCount = tilesCount
|
|
1018
|
+
sectionObject.hasBlendMesh = hasBlendMesh
|
|
477
1019
|
sectionObject.blocksCount = geometryData.blocksCount
|
|
478
1020
|
sectionObject.worldX = geometryData.sx
|
|
479
1021
|
sectionObject.worldY = geometryData.sy
|
|
@@ -526,13 +1068,30 @@ export class ChunkMeshManager {
|
|
|
526
1068
|
bannersContainer.name = 'banners'
|
|
527
1069
|
sectionObject.bannersContainer = bannersContainer
|
|
528
1070
|
sectionObject.add(bannersContainer)
|
|
529
|
-
for (const [posKey,
|
|
1071
|
+
for (const [posKey, bannerMeta] of Object.entries(geometryData.banners)) {
|
|
1072
|
+
const { isWall, rotation, blockName, blockLightNorm = 0, skyLightNorm = 1 } = bannerMeta
|
|
530
1073
|
const bannerBlockEntity = this.worldRenderer.blockEntities[posKey]
|
|
531
1074
|
if (!bannerBlockEntity) continue
|
|
532
1075
|
const [x, y, z] = posKey.split(',')
|
|
533
1076
|
const bannerTexture = getBannerTexture(this.worldRenderer, blockName, nbt.simplify(bannerBlockEntity))
|
|
534
1077
|
if (!bannerTexture) continue
|
|
535
|
-
const
|
|
1078
|
+
const skyLevel = this.blockEntityLightRegistry.getSkyLevel()
|
|
1079
|
+
const banner = createBannerMesh(
|
|
1080
|
+
new Vec3(+x, +y, +z),
|
|
1081
|
+
rotation,
|
|
1082
|
+
isWall,
|
|
1083
|
+
bannerTexture,
|
|
1084
|
+
blockLightNorm,
|
|
1085
|
+
skyLightNorm,
|
|
1086
|
+
skyLevel,
|
|
1087
|
+
)
|
|
1088
|
+
if (banner.bannerMaterial) {
|
|
1089
|
+
this.blockEntityLightRegistry.register({
|
|
1090
|
+
material: banner.bannerMaterial,
|
|
1091
|
+
blockLightNorm,
|
|
1092
|
+
skyLightNorm,
|
|
1093
|
+
})
|
|
1094
|
+
}
|
|
536
1095
|
const { x: bwx, y: bwy, z: bwz } = banner.position
|
|
537
1096
|
this.worldRenderer.sceneOrigin.track(banner)
|
|
538
1097
|
banner.position.set(bwx, bwy, bwz)
|
|
@@ -574,6 +1133,7 @@ export class ChunkMeshManager {
|
|
|
574
1133
|
if (!list.includes(sectionKey)) list.push(sectionKey)
|
|
575
1134
|
}
|
|
576
1135
|
|
|
1136
|
+
this.markCullDirty()
|
|
577
1137
|
return sectionObject
|
|
578
1138
|
}
|
|
579
1139
|
|
|
@@ -747,7 +1307,7 @@ export class ChunkMeshManager {
|
|
|
747
1307
|
}
|
|
748
1308
|
}
|
|
749
1309
|
|
|
750
|
-
cleanupSection (sectionKey: string) {
|
|
1310
|
+
cleanupSection (sectionKey: string, opts?: { forRemesh?: boolean }) {
|
|
751
1311
|
// Remove section object from scene
|
|
752
1312
|
const sectionObject = this.sectionObjects[sectionKey]
|
|
753
1313
|
if (sectionObject) {
|
|
@@ -765,15 +1325,24 @@ export class ChunkMeshManager {
|
|
|
765
1325
|
}
|
|
766
1326
|
// Cleanup banner textures before disposing
|
|
767
1327
|
if (sectionObject.bannersContainer) {
|
|
768
|
-
sectionObject.bannersContainer.
|
|
769
|
-
|
|
770
|
-
|
|
1328
|
+
for (const child of sectionObject.bannersContainer.children) {
|
|
1329
|
+
const banner = child as THREE.Group & { bannerMaterial?: THREE.MeshBasicMaterial, bannerTexture?: THREE.Texture }
|
|
1330
|
+
if (banner.bannerMaterial) {
|
|
1331
|
+
this.blockEntityLightRegistry.unregister(banner.bannerMaterial)
|
|
771
1332
|
}
|
|
772
|
-
|
|
1333
|
+
if (banner.bannerTexture) {
|
|
1334
|
+
releaseBannerTexture(banner.bannerTexture)
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
773
1337
|
this.disposeContainer(sectionObject.bannersContainer)
|
|
774
1338
|
}
|
|
775
1339
|
this.globalBlockBuffer?.removeSection(sectionKey)
|
|
1340
|
+
this.globalLegacyBuffer?.removeSection(sectionKey)
|
|
1341
|
+
this.globalLegacyBlendBuffer?.removeSection(sectionKey)
|
|
776
1342
|
this.unregisterShaderSectionRaycastBox(sectionKey)
|
|
1343
|
+
this.markCullDirty()
|
|
1344
|
+
delete sectionObject.deferredLegacyOpaque
|
|
1345
|
+
delete sectionObject.deferredLegacyBlend
|
|
777
1346
|
if (sectionObject.shaderMesh) {
|
|
778
1347
|
disposeShaderCubeMesh(sectionObject.shaderMesh)
|
|
779
1348
|
sectionObject.shaderMesh = undefined
|
|
@@ -781,7 +1350,7 @@ export class ChunkMeshManager {
|
|
|
781
1350
|
delete sectionObject.deferredShaderCubes
|
|
782
1351
|
// Dispose signs and heads containers
|
|
783
1352
|
if (sectionObject.signsContainer) {
|
|
784
|
-
this.disposeContainer(sectionObject.signsContainer)
|
|
1353
|
+
this.disposeContainer(sectionObject.signsContainer, false)
|
|
785
1354
|
}
|
|
786
1355
|
if (sectionObject.headsContainer) {
|
|
787
1356
|
this.disposeContainer(sectionObject.headsContainer)
|
|
@@ -804,6 +1373,10 @@ export class ChunkMeshManager {
|
|
|
804
1373
|
sectionObject.boxHelper = undefined
|
|
805
1374
|
}
|
|
806
1375
|
delete this.sectionObjects[sectionKey]
|
|
1376
|
+
if (!opts?.forRemesh) {
|
|
1377
|
+
this.worldRenderer.getModule<{ onSectionRemoved?: (key: string) => void }>('futuristicReveal')
|
|
1378
|
+
?.onSectionRemoved?.(sectionKey)
|
|
1379
|
+
}
|
|
807
1380
|
}
|
|
808
1381
|
}
|
|
809
1382
|
|
|
@@ -860,7 +1433,7 @@ export class ChunkMeshManager {
|
|
|
860
1433
|
*/
|
|
861
1434
|
updateBoxHelper (sectionKey: string, showChunkBorders: boolean, chunkBoxMaterial: THREE.Material = this.chunkBoxMaterial) {
|
|
862
1435
|
const sectionObject = this.sectionObjects[sectionKey]
|
|
863
|
-
if (!sectionObject
|
|
1436
|
+
if (!sectionObject) return
|
|
864
1437
|
|
|
865
1438
|
if (showChunkBorders) {
|
|
866
1439
|
if (!sectionObject.boxHelper) {
|
|
@@ -1002,6 +1575,11 @@ export class ChunkMeshManager {
|
|
|
1002
1575
|
}
|
|
1003
1576
|
}
|
|
1004
1577
|
|
|
1578
|
+
const legacyGlobalBytes = this.globalLegacyBuffer?.getMemoryBytes() ?? 0
|
|
1579
|
+
const legacyBlendGlobalBytes = this.globalLegacyBlendBuffer?.getMemoryBytes() ?? 0
|
|
1580
|
+
totalBytes += legacyGlobalBytes + legacyBlendGlobalBytes
|
|
1581
|
+
positionBytes += legacyGlobalBytes + legacyBlendGlobalBytes
|
|
1582
|
+
|
|
1005
1583
|
for (const sectionObject of Object.values(this.sectionObjects)) {
|
|
1006
1584
|
const geom = sectionObject.shaderMesh?.geometry
|
|
1007
1585
|
if (!geom) continue
|
|
@@ -1094,8 +1672,18 @@ export class ChunkMeshManager {
|
|
|
1094
1672
|
this.shaderSectionRaycastBoxes.clear()
|
|
1095
1673
|
this.globalBlockBuffer?.dispose()
|
|
1096
1674
|
this.globalBlockBuffer = null
|
|
1675
|
+
this.globalLegacyBuffer?.dispose()
|
|
1676
|
+
this.globalLegacyBuffer = null
|
|
1677
|
+
this.globalLegacyBlendBuffer?.dispose()
|
|
1678
|
+
this.globalLegacyBlendBuffer = null
|
|
1097
1679
|
this.cubeShaderMaterial?.dispose()
|
|
1098
1680
|
this.cubeShaderMaterial = null
|
|
1681
|
+
this.legacyShaderMaterial?.dispose()
|
|
1682
|
+
this.legacyShaderMaterial = null
|
|
1683
|
+
this.globalLegacyShaderMaterial?.dispose()
|
|
1684
|
+
this.globalLegacyShaderMaterial = null
|
|
1685
|
+
this.globalLegacyBlendShaderMaterial?.dispose()
|
|
1686
|
+
this.globalLegacyBlendShaderMaterial = null
|
|
1099
1687
|
// Drop any pending near-first reveal state and cancel safety timers.
|
|
1100
1688
|
this.pendingNearReveal.clear()
|
|
1101
1689
|
for (const timer of this.nearRevealTimers.values()) clearTimeout(timer)
|
|
@@ -1109,7 +1697,7 @@ export class ChunkMeshManager {
|
|
|
1109
1697
|
private acquireMesh (): ChunkMeshPool | undefined {
|
|
1110
1698
|
if (this.bypassPooling) {
|
|
1111
1699
|
const entry: ChunkMeshPool = {
|
|
1112
|
-
mesh: new THREE.Mesh(new THREE.BufferGeometry(), this.
|
|
1700
|
+
mesh: new THREE.Mesh(new THREE.BufferGeometry(), this.getLegacyShaderMaterial()),
|
|
1113
1701
|
inUse: true,
|
|
1114
1702
|
lastUsedTime: performance.now()
|
|
1115
1703
|
}
|
|
@@ -1160,7 +1748,7 @@ export class ChunkMeshManager {
|
|
|
1160
1748
|
// Add new meshes to pool
|
|
1161
1749
|
for (let i = currentLength; i < newSize; i++) {
|
|
1162
1750
|
const geometry = new THREE.BufferGeometry()
|
|
1163
|
-
const mesh = new THREE.Mesh(geometry, this.
|
|
1751
|
+
const mesh = new THREE.Mesh(geometry, this.getLegacyShaderMaterial())
|
|
1164
1752
|
mesh.visible = false
|
|
1165
1753
|
mesh.matrixAutoUpdate = false
|
|
1166
1754
|
mesh.name = 'pooled-section-mesh'
|
|
@@ -1195,7 +1783,7 @@ export class ChunkMeshManager {
|
|
|
1195
1783
|
}
|
|
1196
1784
|
|
|
1197
1785
|
private clearGeometry (geometry: THREE.BufferGeometry) {
|
|
1198
|
-
const attributes = ['position', 'normal', 'color', 'uv']
|
|
1786
|
+
const attributes = ['position', 'normal', 'color', 'a_skyLight', 'a_blockLight', 'uv']
|
|
1199
1787
|
for (const name of attributes) {
|
|
1200
1788
|
if (geometry.hasAttribute(name)) {
|
|
1201
1789
|
geometry.deleteAttribute(name)
|
|
@@ -1228,8 +1816,8 @@ export class ChunkMeshManager {
|
|
|
1228
1816
|
}
|
|
1229
1817
|
}
|
|
1230
1818
|
|
|
1231
|
-
private disposeContainer (container: THREE.Group) {
|
|
1232
|
-
disposeObject(container,
|
|
1819
|
+
private disposeContainer (container: THREE.Group, cleanTextures = true) {
|
|
1820
|
+
disposeObject(container, cleanTextures)
|
|
1233
1821
|
}
|
|
1234
1822
|
|
|
1235
1823
|
/**
|
|
@@ -1335,8 +1923,10 @@ export class ChunkMeshManager {
|
|
|
1335
1923
|
}
|
|
1336
1924
|
|
|
1337
1925
|
|
|
1926
|
+
type SignTextureCacheEntry = { tex: THREE.Texture, signature: string }
|
|
1927
|
+
|
|
1338
1928
|
class SignHeadsRenderer {
|
|
1339
|
-
chunkTextures = new Map<string, { [pos: string]:
|
|
1929
|
+
chunkTextures = new Map<string, { [pos: string]: SignTextureCacheEntry }>()
|
|
1340
1930
|
|
|
1341
1931
|
constructor (public worldRendererThree: WorldRendererThree) {
|
|
1342
1932
|
}
|
|
@@ -1344,7 +1934,7 @@ class SignHeadsRenderer {
|
|
|
1344
1934
|
dispose () {
|
|
1345
1935
|
for (const [, textures] of this.chunkTextures) {
|
|
1346
1936
|
for (const key of Object.keys(textures)) {
|
|
1347
|
-
textures[key].dispose()
|
|
1937
|
+
textures[key]!.tex.dispose()
|
|
1348
1938
|
}
|
|
1349
1939
|
}
|
|
1350
1940
|
this.chunkTextures.clear()
|
|
@@ -1441,8 +2031,11 @@ class SignHeadsRenderer {
|
|
|
1441
2031
|
this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
|
|
1442
2032
|
}
|
|
1443
2033
|
const texturekey = `${position.x},${position.y},${position.z}`
|
|
1444
|
-
|
|
1445
|
-
|
|
2034
|
+
const signature = JSON.stringify(blockEntity) + '|' + isHanging + '|' + backSide
|
|
2035
|
+
const cached = textures[texturekey]
|
|
2036
|
+
if (cached && cached.signature === signature) return cached.tex
|
|
2037
|
+
|
|
2038
|
+
if (cached?.tex) cached.tex.dispose()
|
|
1446
2039
|
|
|
1447
2040
|
const PrismarineChat = PrismarineChatLoader(this.worldRendererThree.version)
|
|
1448
2041
|
const canvas = renderSign(blockEntity, isHanging, PrismarineChat)
|
|
@@ -1451,7 +2044,7 @@ class SignHeadsRenderer {
|
|
|
1451
2044
|
tex.magFilter = THREE.NearestFilter
|
|
1452
2045
|
tex.minFilter = THREE.NearestFilter
|
|
1453
2046
|
tex.needsUpdate = true
|
|
1454
|
-
textures[texturekey] = tex
|
|
2047
|
+
textures[texturekey] = { tex, signature }
|
|
1455
2048
|
return tex
|
|
1456
2049
|
}
|
|
1457
2050
|
|
|
@@ -1466,8 +2059,9 @@ class SignHeadsRenderer {
|
|
|
1466
2059
|
const key = `${Math.floor(x / 16)},${Math.floor(z / 16)}`
|
|
1467
2060
|
const textures = this.chunkTextures.get(key)
|
|
1468
2061
|
if (!textures) return
|
|
1469
|
-
|
|
1470
|
-
|
|
2062
|
+
const disposedKeys = Object.keys(textures)
|
|
2063
|
+
for (const k of disposedKeys) {
|
|
2064
|
+
textures[k]!.tex.dispose()
|
|
1471
2065
|
delete textures[k]
|
|
1472
2066
|
}
|
|
1473
2067
|
}
|