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
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
4
|
+
|
|
5
|
+
const VERTS_PER_QUAD = 4
|
|
6
|
+
const INDICES_PER_QUAD = 6
|
|
7
|
+
const FLOATS_PER_VERT = 3
|
|
8
|
+
const FLOATS_PER_UV_VERT = 2
|
|
9
|
+
const FLOATS_PER_LIGHT_VERT = 1
|
|
10
|
+
|
|
11
|
+
const DEFAULT_INITIAL_CAPACITY_QUADS = 128_000
|
|
12
|
+
const DEFAULT_GROWTH_INCREMENT_QUADS = 128_000
|
|
13
|
+
const MAX_UPLOAD_QUADS_PER_FRAME = 5_000
|
|
14
|
+
|
|
15
|
+
export const FULL_DRAW_VISIBLE_FRACTION = 0.75
|
|
16
|
+
export const SPAN_GAP_TOLERANCE_QUADS = 256
|
|
17
|
+
export const MAX_OPAQUE_SPANS = 64
|
|
18
|
+
|
|
19
|
+
export type GlobalLegacyBufferOptions = {
|
|
20
|
+
name?: string
|
|
21
|
+
initialCapacityQuads?: number
|
|
22
|
+
growthIncrementQuads?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type VisibleSectionSpan = { key: string, distSq: number }
|
|
26
|
+
|
|
27
|
+
export type LegacySectionGeometry = {
|
|
28
|
+
positions: Float32Array
|
|
29
|
+
colors: Float32Array
|
|
30
|
+
skyLights: Float32Array
|
|
31
|
+
blockLights: Float32Array
|
|
32
|
+
uvs: Float32Array
|
|
33
|
+
indices: Uint32Array | Uint16Array
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type LegacySectionGeometryData = LegacySectionGeometry & {
|
|
37
|
+
sx: number
|
|
38
|
+
sy: number
|
|
39
|
+
sz: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Single GPU mesh for legacy quads (opaque+cutout or transparent blend).
|
|
44
|
+
* Camera-relative via per-vertex a_origin (relative to render origin) + u_originDelta uniforms.
|
|
45
|
+
*/
|
|
46
|
+
export class GlobalLegacyBuffer {
|
|
47
|
+
readonly mesh: THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial | THREE.ShaderMaterial[]>
|
|
48
|
+
readonly material: THREE.ShaderMaterial
|
|
49
|
+
|
|
50
|
+
private readonly growthIncrementQuads: number
|
|
51
|
+
private capacityQuads: number
|
|
52
|
+
private positions: Float32Array
|
|
53
|
+
private colors: Float32Array
|
|
54
|
+
private skyLights: Float32Array
|
|
55
|
+
private blockLights: Float32Array
|
|
56
|
+
private uvs: Float32Array
|
|
57
|
+
private aOrigin: Float32Array
|
|
58
|
+
private indices: Uint32Array
|
|
59
|
+
private readonly sectionSlots = new Map<string, { start: number, count: number }>()
|
|
60
|
+
private freeList: Array<{ start: number, count: number }> = []
|
|
61
|
+
private highWatermark = 0
|
|
62
|
+
private pendingRanges: Array<{ start: number, end: number }> = []
|
|
63
|
+
private readonly _spanScratch: Array<{ start: number, count: number }> = []
|
|
64
|
+
private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
|
|
65
|
+
|
|
66
|
+
constructor (
|
|
67
|
+
material: THREE.ShaderMaterial,
|
|
68
|
+
scene: THREE.Object3D,
|
|
69
|
+
opts?: GlobalLegacyBufferOptions,
|
|
70
|
+
) {
|
|
71
|
+
this.material = material
|
|
72
|
+
this.growthIncrementQuads = opts?.growthIncrementQuads ?? DEFAULT_GROWTH_INCREMENT_QUADS
|
|
73
|
+
this.capacityQuads = opts?.initialCapacityQuads ?? DEFAULT_INITIAL_CAPACITY_QUADS
|
|
74
|
+
const maxVerts = this.capacityQuads * VERTS_PER_QUAD
|
|
75
|
+
this.positions = new Float32Array(maxVerts * FLOATS_PER_VERT)
|
|
76
|
+
this.colors = new Float32Array(maxVerts * FLOATS_PER_VERT)
|
|
77
|
+
this.skyLights = new Float32Array(maxVerts * FLOATS_PER_LIGHT_VERT)
|
|
78
|
+
this.blockLights = new Float32Array(maxVerts * FLOATS_PER_LIGHT_VERT)
|
|
79
|
+
this.uvs = new Float32Array(maxVerts * FLOATS_PER_UV_VERT)
|
|
80
|
+
this.aOrigin = new Float32Array(maxVerts * FLOATS_PER_VERT)
|
|
81
|
+
this.indices = new Uint32Array(this.capacityQuads * INDICES_PER_QUAD)
|
|
82
|
+
|
|
83
|
+
const geometry = new THREE.BufferGeometry()
|
|
84
|
+
const mkAttr = (arr: Float32Array, itemSize: number, name: string) => {
|
|
85
|
+
const attr = new THREE.BufferAttribute(arr, itemSize)
|
|
86
|
+
attr.setUsage(THREE.DynamicDrawUsage)
|
|
87
|
+
geometry.setAttribute(name, attr)
|
|
88
|
+
return attr
|
|
89
|
+
}
|
|
90
|
+
mkAttr(this.positions, FLOATS_PER_VERT, 'position')
|
|
91
|
+
mkAttr(this.colors, FLOATS_PER_VERT, 'color')
|
|
92
|
+
mkAttr(this.skyLights, FLOATS_PER_LIGHT_VERT, 'a_skyLight')
|
|
93
|
+
mkAttr(this.blockLights, FLOATS_PER_LIGHT_VERT, 'a_blockLight')
|
|
94
|
+
mkAttr(this.uvs, FLOATS_PER_UV_VERT, 'uv')
|
|
95
|
+
mkAttr(this.aOrigin, FLOATS_PER_VERT, 'a_origin')
|
|
96
|
+
|
|
97
|
+
const indexAttr = new THREE.BufferAttribute(this.indices, 1)
|
|
98
|
+
indexAttr.setUsage(THREE.DynamicDrawUsage)
|
|
99
|
+
geometry.setIndex(indexAttr)
|
|
100
|
+
|
|
101
|
+
geometry.setDrawRange(0, 0)
|
|
102
|
+
geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(), Infinity)
|
|
103
|
+
|
|
104
|
+
this.mesh = new THREE.Mesh(geometry, [material])
|
|
105
|
+
this.mesh.name = opts?.name ?? 'globalLegacyOpaque'
|
|
106
|
+
this.mesh.frustumCulled = false
|
|
107
|
+
this.mesh.matrixAutoUpdate = false
|
|
108
|
+
this.mesh.matrix.identity()
|
|
109
|
+
this.mesh.position.set(0, 0, 0)
|
|
110
|
+
scene.add(this.mesh)
|
|
111
|
+
this.syncDefaultDrawGroups()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private syncDefaultDrawGroups (): void {
|
|
115
|
+
const geometry = this.mesh.geometry
|
|
116
|
+
geometry.clearGroups()
|
|
117
|
+
const indexCount = this.highWatermark * INDICES_PER_QUAD
|
|
118
|
+
if (indexCount > 0) {
|
|
119
|
+
geometry.addGroup(0, indexCount, 0)
|
|
120
|
+
}
|
|
121
|
+
geometry.setDrawRange(0, indexCount)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
addSection (
|
|
125
|
+
sectionKey: string,
|
|
126
|
+
geo: LegacySectionGeometry,
|
|
127
|
+
sx: number,
|
|
128
|
+
sy: number,
|
|
129
|
+
sz: number,
|
|
130
|
+
): boolean {
|
|
131
|
+
const vertCount = geo.positions.length / FLOATS_PER_VERT
|
|
132
|
+
const quadCount = vertCount / VERTS_PER_QUAD
|
|
133
|
+
if (vertCount === 0 || quadCount * VERTS_PER_QUAD !== vertCount) {
|
|
134
|
+
this.removeSection(sectionKey)
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
if (geo.indices.length % INDICES_PER_QUAD !== 0 || geo.indices.length / INDICES_PER_QUAD !== quadCount) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (this.sectionSlots.has(sectionKey)) {
|
|
142
|
+
this.removeSection(sectionKey)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (quadCount > this.capacityQuads) {
|
|
146
|
+
this.growCapacity(quadCount)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let slot = this.takeFreeSlot(quadCount)
|
|
150
|
+
if (!slot) {
|
|
151
|
+
if (this.highWatermark + quadCount > this.capacityQuads) {
|
|
152
|
+
this.growCapacity(this.highWatermark + quadCount)
|
|
153
|
+
}
|
|
154
|
+
slot = { start: this.highWatermark, count: quadCount }
|
|
155
|
+
this.highWatermark += quadCount
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const dstVertBase = slot.start * VERTS_PER_QUAD
|
|
159
|
+
const dstFloatBase = dstVertBase * FLOATS_PER_VERT
|
|
160
|
+
const dstUvBase = dstVertBase * FLOATS_PER_UV_VERT
|
|
161
|
+
const dstLightBase = dstVertBase * FLOATS_PER_LIGHT_VERT
|
|
162
|
+
this.positions.set(geo.positions, dstFloatBase)
|
|
163
|
+
this.colors.set(geo.colors, dstFloatBase)
|
|
164
|
+
this.skyLights.set(geo.skyLights, dstLightBase)
|
|
165
|
+
this.blockLights.set(geo.blockLights, dstLightBase)
|
|
166
|
+
this.uvs.set(geo.uvs, dstUvBase)
|
|
167
|
+
|
|
168
|
+
const originOff = dstFloatBase
|
|
169
|
+
const rx = this.renderOrigin.x
|
|
170
|
+
const ry = this.renderOrigin.y
|
|
171
|
+
const rz = this.renderOrigin.z
|
|
172
|
+
for (let v = 0; v < vertCount; v++) {
|
|
173
|
+
const o = originOff + v * FLOATS_PER_VERT
|
|
174
|
+
this.aOrigin[o] = sx - rx
|
|
175
|
+
this.aOrigin[o + 1] = sy - ry
|
|
176
|
+
this.aOrigin[o + 2] = sz - rz
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
180
|
+
const vertexBase = dstVertBase
|
|
181
|
+
for (let i = 0; i < geo.indices.length; i++) {
|
|
182
|
+
this.indices[dstIndexBase + i] = geo.indices[i]! + vertexBase
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.sectionSlots.set(sectionKey, slot)
|
|
186
|
+
this.markDirty(slot.start, slot.start + quadCount - 1)
|
|
187
|
+
this.syncDefaultDrawGroups()
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
updateDrawSpans (visible: VisibleSectionSpan[], mode: 'opaque' | 'sortedBlend'): void {
|
|
192
|
+
const geometry = this.mesh.geometry
|
|
193
|
+
geometry.clearGroups()
|
|
194
|
+
|
|
195
|
+
if (this.highWatermark === 0) {
|
|
196
|
+
geometry.setDrawRange(0, 0)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const spans = this._spanScratch
|
|
201
|
+
spans.length = 0
|
|
202
|
+
let visibleQuadCount = 0
|
|
203
|
+
|
|
204
|
+
for (const entry of visible) {
|
|
205
|
+
const slot = this.sectionSlots.get(entry.key)
|
|
206
|
+
if (!slot) continue
|
|
207
|
+
spans.push({ start: slot.start, count: slot.count })
|
|
208
|
+
visibleQuadCount += slot.count
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (spans.length === 0) {
|
|
212
|
+
geometry.setDrawRange(0, 0)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (mode === 'opaque') {
|
|
217
|
+
if (visibleQuadCount >= this.highWatermark * FULL_DRAW_VISIBLE_FRACTION) {
|
|
218
|
+
geometry.addGroup(0, this.highWatermark * INDICES_PER_QUAD, 0)
|
|
219
|
+
geometry.setDrawRange(0, this.highWatermark * INDICES_PER_QUAD)
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
spans.sort((a, b) => a.start - b.start)
|
|
224
|
+
this.mergeOpaqueSpans(spans)
|
|
225
|
+
this.capOpaqueSpans(spans)
|
|
226
|
+
|
|
227
|
+
for (const span of spans) {
|
|
228
|
+
geometry.addGroup(span.start * INDICES_PER_QUAD, span.count * INDICES_PER_QUAD, 0)
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
visible.sort((a, b) => b.distSq - a.distSq)
|
|
232
|
+
for (const entry of visible) {
|
|
233
|
+
const slot = this.sectionSlots.get(entry.key)
|
|
234
|
+
if (!slot) continue
|
|
235
|
+
geometry.addGroup(slot.start * INDICES_PER_QUAD, slot.count * INDICES_PER_QUAD, 0)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
geometry.setDrawRange(0, this.highWatermark * INDICES_PER_QUAD)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private mergeOpaqueSpans (spans: Array<{ start: number, count: number }>): void {
|
|
243
|
+
if (spans.length < 2) return
|
|
244
|
+
let i = 0
|
|
245
|
+
while (i < spans.length - 1) {
|
|
246
|
+
const cur = spans[i]!
|
|
247
|
+
const next = spans[i + 1]!
|
|
248
|
+
const gap = next.start - (cur.start + cur.count)
|
|
249
|
+
if (gap <= SPAN_GAP_TOLERANCE_QUADS) {
|
|
250
|
+
cur.count = next.start + next.count - cur.start
|
|
251
|
+
spans.splice(i + 1, 1)
|
|
252
|
+
} else {
|
|
253
|
+
i++
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private capOpaqueSpans (spans: Array<{ start: number, count: number }>): void {
|
|
259
|
+
while (spans.length > MAX_OPAQUE_SPANS) {
|
|
260
|
+
let bestIdx = 0
|
|
261
|
+
let bestGap = Infinity
|
|
262
|
+
for (let i = 0; i < spans.length - 1; i++) {
|
|
263
|
+
const gap = spans[i + 1]!.start - (spans[i]!.start + spans[i]!.count)
|
|
264
|
+
if (gap < bestGap) {
|
|
265
|
+
bestGap = gap
|
|
266
|
+
bestIdx = i
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const cur = spans[bestIdx]!
|
|
270
|
+
const next = spans[bestIdx + 1]!
|
|
271
|
+
cur.count = next.start + next.count - cur.start
|
|
272
|
+
spans.splice(bestIdx + 1, 1)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
hasSection (sectionKey: string): boolean {
|
|
277
|
+
return this.sectionSlots.has(sectionKey)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getSectionSlot (sectionKey: string): { start: number, count: number } | undefined {
|
|
281
|
+
return this.sectionSlots.get(sectionKey)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
takeSectionData (sectionKey: string): LegacySectionGeometryData | undefined {
|
|
285
|
+
const data = this.getSectionGeometryData(sectionKey)
|
|
286
|
+
if (!data) return undefined
|
|
287
|
+
this.removeSection(sectionKey)
|
|
288
|
+
return data
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getSectionGeometryData (sectionKey: string): LegacySectionGeometryData | undefined {
|
|
292
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
293
|
+
if (!slot) return undefined
|
|
294
|
+
|
|
295
|
+
const vertCount = slot.count * VERTS_PER_QUAD
|
|
296
|
+
const dstVertBase = slot.start * VERTS_PER_QUAD
|
|
297
|
+
const dstFloatBase = dstVertBase * FLOATS_PER_VERT
|
|
298
|
+
const dstUvBase = dstVertBase * FLOATS_PER_UV_VERT
|
|
299
|
+
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
300
|
+
const indexLen = slot.count * INDICES_PER_QUAD
|
|
301
|
+
|
|
302
|
+
const dstLightBase = dstVertBase * FLOATS_PER_LIGHT_VERT
|
|
303
|
+
|
|
304
|
+
const positions = this.positions.slice(dstFloatBase, dstFloatBase + vertCount * FLOATS_PER_VERT)
|
|
305
|
+
const colors = this.colors.slice(dstFloatBase, dstFloatBase + vertCount * FLOATS_PER_VERT)
|
|
306
|
+
const skyLights = this.skyLights.slice(dstLightBase, dstLightBase + vertCount * FLOATS_PER_LIGHT_VERT)
|
|
307
|
+
const blockLights = this.blockLights.slice(dstLightBase, dstLightBase + vertCount * FLOATS_PER_LIGHT_VERT)
|
|
308
|
+
const uvs = this.uvs.slice(dstUvBase, dstUvBase + vertCount * FLOATS_PER_UV_VERT)
|
|
309
|
+
const indices = this.indices.slice(dstIndexBase, dstIndexBase + indexLen)
|
|
310
|
+
const vertexBase = dstVertBase
|
|
311
|
+
for (let i = 0; i < indices.length; i++) {
|
|
312
|
+
indices[i] = indices[i]! - vertexBase
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const sx = this.aOrigin[dstFloatBase]! + this.renderOrigin.x
|
|
316
|
+
const sy = this.aOrigin[dstFloatBase + 1]! + this.renderOrigin.y
|
|
317
|
+
const sz = this.aOrigin[dstFloatBase + 2]! + this.renderOrigin.z
|
|
318
|
+
|
|
319
|
+
return { positions, colors, skyLights, blockLights, uvs, indices, sx, sy, sz }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
removeSection (sectionKey: string): void {
|
|
323
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
324
|
+
if (!slot) return
|
|
325
|
+
|
|
326
|
+
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
327
|
+
const indexLen = slot.count * INDICES_PER_QUAD
|
|
328
|
+
for (let i = 0; i < indexLen; i++) {
|
|
329
|
+
this.indices[dstIndexBase + i] = 0
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this.markDirty(slot.start, slot.start + slot.count - 1)
|
|
333
|
+
this.sectionSlots.delete(sectionKey)
|
|
334
|
+
this.insertFreeSlot(slot)
|
|
335
|
+
this.shrinkHighWatermark()
|
|
336
|
+
this.syncDefaultDrawGroups()
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
hasPendingUploads (): boolean {
|
|
340
|
+
return this.pendingRanges.length > 0
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
uploadDirtyRange (): void {
|
|
344
|
+
const r = this.pendingRanges[0]
|
|
345
|
+
if (!r) return
|
|
346
|
+
|
|
347
|
+
const quadOffset = r.start
|
|
348
|
+
const quadCount = Math.min(r.end - r.start + 1, MAX_UPLOAD_QUADS_PER_FRAME)
|
|
349
|
+
const vertOffset = quadOffset * VERTS_PER_QUAD
|
|
350
|
+
const vertCount = quadCount * VERTS_PER_QUAD
|
|
351
|
+
const indexOffset = quadOffset * INDICES_PER_QUAD
|
|
352
|
+
const indexCount = quadCount * INDICES_PER_QUAD
|
|
353
|
+
|
|
354
|
+
const geometry = this.mesh.geometry
|
|
355
|
+
const posAttr = geometry.getAttribute('position') as THREE.BufferAttribute
|
|
356
|
+
posAttr.clearUpdateRanges()
|
|
357
|
+
posAttr.addUpdateRange(vertOffset * FLOATS_PER_VERT, vertCount * FLOATS_PER_VERT)
|
|
358
|
+
posAttr.needsUpdate = true
|
|
359
|
+
|
|
360
|
+
const colorAttr = geometry.getAttribute('color') as THREE.BufferAttribute
|
|
361
|
+
colorAttr.clearUpdateRanges()
|
|
362
|
+
colorAttr.addUpdateRange(vertOffset * FLOATS_PER_VERT, vertCount * FLOATS_PER_VERT)
|
|
363
|
+
colorAttr.needsUpdate = true
|
|
364
|
+
|
|
365
|
+
const skyAttr = geometry.getAttribute('a_skyLight') as THREE.BufferAttribute
|
|
366
|
+
skyAttr.clearUpdateRanges()
|
|
367
|
+
skyAttr.addUpdateRange(vertOffset * FLOATS_PER_LIGHT_VERT, vertCount * FLOATS_PER_LIGHT_VERT)
|
|
368
|
+
skyAttr.needsUpdate = true
|
|
369
|
+
|
|
370
|
+
const blockAttr = geometry.getAttribute('a_blockLight') as THREE.BufferAttribute
|
|
371
|
+
blockAttr.clearUpdateRanges()
|
|
372
|
+
blockAttr.addUpdateRange(vertOffset * FLOATS_PER_LIGHT_VERT, vertCount * FLOATS_PER_LIGHT_VERT)
|
|
373
|
+
blockAttr.needsUpdate = true
|
|
374
|
+
|
|
375
|
+
const uvAttr = geometry.getAttribute('uv') as THREE.BufferAttribute
|
|
376
|
+
uvAttr.clearUpdateRanges()
|
|
377
|
+
uvAttr.addUpdateRange(vertOffset * FLOATS_PER_UV_VERT, vertCount * FLOATS_PER_UV_VERT)
|
|
378
|
+
uvAttr.needsUpdate = true
|
|
379
|
+
|
|
380
|
+
const originAttr = geometry.getAttribute('a_origin') as THREE.BufferAttribute
|
|
381
|
+
originAttr.clearUpdateRanges()
|
|
382
|
+
originAttr.addUpdateRange(vertOffset * FLOATS_PER_VERT, vertCount * FLOATS_PER_VERT)
|
|
383
|
+
originAttr.needsUpdate = true
|
|
384
|
+
|
|
385
|
+
const indexAttr = geometry.index as THREE.BufferAttribute
|
|
386
|
+
indexAttr.clearUpdateRanges()
|
|
387
|
+
indexAttr.addUpdateRange(indexOffset, indexCount)
|
|
388
|
+
indexAttr.needsUpdate = true
|
|
389
|
+
|
|
390
|
+
if (quadOffset + quadCount > r.end) this.pendingRanges.shift()
|
|
391
|
+
else r.start = quadOffset + quadCount
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
setRenderOrigin (renderOrigin: RenderOrigin): void {
|
|
395
|
+
this.renderOrigin = { ...renderOrigin }
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
rebase (delta: RenderOrigin): void {
|
|
399
|
+
if (this.highWatermark === 0) return
|
|
400
|
+
for (const slot of this.sectionSlots.values()) {
|
|
401
|
+
const dstVertBase = slot.start * VERTS_PER_QUAD
|
|
402
|
+
const vertCount = slot.count * VERTS_PER_QUAD
|
|
403
|
+
const dstFloatBase = dstVertBase * FLOATS_PER_VERT
|
|
404
|
+
for (let v = 0; v < vertCount; v++) {
|
|
405
|
+
const o = dstFloatBase + v * FLOATS_PER_VERT
|
|
406
|
+
this.aOrigin[o]! -= delta.x
|
|
407
|
+
this.aOrigin[o + 1]! -= delta.y
|
|
408
|
+
this.aOrigin[o + 2]! -= delta.z
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
this.markDirty(0, this.highWatermark - 1)
|
|
412
|
+
this.renderOrigin.x += delta.x
|
|
413
|
+
this.renderOrigin.y += delta.y
|
|
414
|
+
this.renderOrigin.z += delta.z
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
setCameraOrigin (x: number, y: number, z: number): void {
|
|
418
|
+
const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(this.renderOrigin, x, y, z)
|
|
419
|
+
const u = this.material.uniforms.u_originDelta
|
|
420
|
+
if (u?.value?.set) u.value.set(originDelta.x, originDelta.y, originDelta.z)
|
|
421
|
+
const uf = this.material.uniforms.u_cameraOriginFrac
|
|
422
|
+
if (uf?.value?.set) uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
raycastSections (
|
|
426
|
+
raycaster: THREE.Raycaster,
|
|
427
|
+
sectionKeys: Iterable<string>,
|
|
428
|
+
out: THREE.Intersection[],
|
|
429
|
+
): THREE.Intersection[] {
|
|
430
|
+
const ray = raycaster.ray
|
|
431
|
+
const closest = raycaster.near
|
|
432
|
+
const far = raycaster.far
|
|
433
|
+
_raycastOrigin.copy(ray.origin).sub(_raycastRenderOrigin.set(
|
|
434
|
+
this.renderOrigin.x,
|
|
435
|
+
this.renderOrigin.y,
|
|
436
|
+
this.renderOrigin.z,
|
|
437
|
+
))
|
|
438
|
+
_raycastRay.origin.copy(_raycastOrigin)
|
|
439
|
+
_raycastRay.direction.copy(ray.direction)
|
|
440
|
+
|
|
441
|
+
for (const key of sectionKeys) {
|
|
442
|
+
const slot = this.sectionSlots.get(key)
|
|
443
|
+
if (!slot) continue
|
|
444
|
+
|
|
445
|
+
const dstVertBase = slot.start * VERTS_PER_QUAD
|
|
446
|
+
const dstFloatBase = dstVertBase * FLOATS_PER_VERT
|
|
447
|
+
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
448
|
+
const indexLen = slot.count * INDICES_PER_QUAD
|
|
449
|
+
|
|
450
|
+
for (let i = 0; i < indexLen; i += 3) {
|
|
451
|
+
const i0 = this.indices[dstIndexBase + i]!
|
|
452
|
+
const i1 = this.indices[dstIndexBase + i + 1]!
|
|
453
|
+
const i2 = this.indices[dstIndexBase + i + 2]!
|
|
454
|
+
if (i0 === i1 && i1 === i2) continue
|
|
455
|
+
|
|
456
|
+
const hit = intersectTriangle(
|
|
457
|
+
_raycastRay,
|
|
458
|
+
this.positions, this.aOrigin, dstFloatBase,
|
|
459
|
+
i0, i1, i2,
|
|
460
|
+
closest, far,
|
|
461
|
+
)
|
|
462
|
+
if (hit !== null) {
|
|
463
|
+
out.push({
|
|
464
|
+
distance: hit,
|
|
465
|
+
point: ray.at(hit, new THREE.Vector3()),
|
|
466
|
+
object: this.mesh,
|
|
467
|
+
face: null,
|
|
468
|
+
faceIndex: Math.floor(i / 3),
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
out.sort((a, b) => a.distance - b.distance)
|
|
475
|
+
return out
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
getMemoryBytes (): number {
|
|
479
|
+
const verts = this.capacityQuads * VERTS_PER_QUAD
|
|
480
|
+
return verts * (FLOATS_PER_VERT * 3 + FLOATS_PER_LIGHT_VERT * 2 + FLOATS_PER_UV_VERT) * 4
|
|
481
|
+
+ this.capacityQuads * INDICES_PER_QUAD * 4
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
reset (): void {
|
|
485
|
+
this.sectionSlots.clear()
|
|
486
|
+
this.freeList.length = 0
|
|
487
|
+
this.highWatermark = 0
|
|
488
|
+
this.pendingRanges.length = 0
|
|
489
|
+
this.syncDefaultDrawGroups()
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
dispose (): void {
|
|
493
|
+
this.mesh.parent?.remove(this.mesh)
|
|
494
|
+
this.mesh.geometry.dispose()
|
|
495
|
+
this.reset()
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private markDirty (start: number, end: number): void {
|
|
499
|
+
this.pendingRanges.push({ start, end })
|
|
500
|
+
this.pendingRanges.sort((a, b) => a.start - b.start)
|
|
501
|
+
this.mergePendingRanges()
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
private mergePendingRanges (): void {
|
|
505
|
+
if (this.pendingRanges.length < 2) return
|
|
506
|
+
const merged: Array<{ start: number, end: number }> = []
|
|
507
|
+
let cur = this.pendingRanges[0]!
|
|
508
|
+
for (let i = 1; i < this.pendingRanges.length; i++) {
|
|
509
|
+
const next = this.pendingRanges[i]!
|
|
510
|
+
if (next.start <= cur.end + 1) {
|
|
511
|
+
cur = { start: cur.start, end: Math.max(cur.end, next.end) }
|
|
512
|
+
} else {
|
|
513
|
+
merged.push(cur)
|
|
514
|
+
cur = next
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
merged.push(cur)
|
|
518
|
+
this.pendingRanges = merged
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private takeFreeSlot (count: number): { start: number, count: number } | undefined {
|
|
522
|
+
for (let i = 0; i < this.freeList.length; i++) {
|
|
523
|
+
const slot = this.freeList[i]!
|
|
524
|
+
if (slot.count >= count) {
|
|
525
|
+
this.freeList.splice(i, 1)
|
|
526
|
+
if (slot.count === count) return slot
|
|
527
|
+
const used = { start: slot.start, count }
|
|
528
|
+
this.insertFreeSlot({ start: slot.start + count, count: slot.count - count })
|
|
529
|
+
return used
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return undefined
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private insertFreeSlot (slot: { start: number, count: number }): void {
|
|
536
|
+
this.freeList.push(slot)
|
|
537
|
+
this.freeList.sort((a, b) => a.start - b.start)
|
|
538
|
+
this.mergeFreeList()
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private mergeFreeList (): void {
|
|
542
|
+
if (this.freeList.length < 2) return
|
|
543
|
+
const merged: Array<{ start: number, count: number }> = []
|
|
544
|
+
let cur = this.freeList[0]!
|
|
545
|
+
for (let i = 1; i < this.freeList.length; i++) {
|
|
546
|
+
const next = this.freeList[i]!
|
|
547
|
+
if (cur.start + cur.count === next.start) {
|
|
548
|
+
cur = { start: cur.start, count: cur.count + next.count }
|
|
549
|
+
} else {
|
|
550
|
+
merged.push(cur)
|
|
551
|
+
cur = next
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
merged.push(cur)
|
|
555
|
+
this.freeList = merged
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private shrinkHighWatermark (): void {
|
|
559
|
+
while (this.highWatermark > 0) {
|
|
560
|
+
const tail = this.highWatermark - 1
|
|
561
|
+
const free = this.freeList.find(s => s.start <= tail && s.start + s.count > tail)
|
|
562
|
+
if (!free || free.start + free.count !== this.highWatermark) break
|
|
563
|
+
this.highWatermark = free.start
|
|
564
|
+
const idx = this.freeList.indexOf(free)
|
|
565
|
+
this.freeList.splice(idx, 1)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private growCapacity (minQuads: number): void {
|
|
570
|
+
let newCap = this.capacityQuads
|
|
571
|
+
while (newCap < minQuads) newCap += this.growthIncrementQuads
|
|
572
|
+
|
|
573
|
+
const oldMaxVerts = this.capacityQuads * VERTS_PER_QUAD
|
|
574
|
+
const newMaxVerts = newCap * VERTS_PER_QUAD
|
|
575
|
+
|
|
576
|
+
const nPos = new Float32Array(newMaxVerts * FLOATS_PER_VERT)
|
|
577
|
+
const nCol = new Float32Array(newMaxVerts * FLOATS_PER_VERT)
|
|
578
|
+
const nSky = new Float32Array(newMaxVerts * FLOATS_PER_LIGHT_VERT)
|
|
579
|
+
const nBlock = new Float32Array(newMaxVerts * FLOATS_PER_LIGHT_VERT)
|
|
580
|
+
const nUv = new Float32Array(newMaxVerts * FLOATS_PER_UV_VERT)
|
|
581
|
+
const nOrigin = new Float32Array(newMaxVerts * FLOATS_PER_VERT)
|
|
582
|
+
const nIdx = new Uint32Array(newCap * INDICES_PER_QUAD)
|
|
583
|
+
|
|
584
|
+
nPos.set(this.positions)
|
|
585
|
+
nCol.set(this.colors)
|
|
586
|
+
nSky.set(this.skyLights)
|
|
587
|
+
nBlock.set(this.blockLights)
|
|
588
|
+
nUv.set(this.uvs)
|
|
589
|
+
nOrigin.set(this.aOrigin)
|
|
590
|
+
nIdx.set(this.indices)
|
|
591
|
+
|
|
592
|
+
this.positions = nPos
|
|
593
|
+
this.colors = nCol
|
|
594
|
+
this.skyLights = nSky
|
|
595
|
+
this.blockLights = nBlock
|
|
596
|
+
this.uvs = nUv
|
|
597
|
+
this.aOrigin = nOrigin
|
|
598
|
+
this.indices = nIdx
|
|
599
|
+
this.capacityQuads = newCap
|
|
600
|
+
|
|
601
|
+
const geometry = this.mesh.geometry
|
|
602
|
+
const replaceAttr = (arr: Float32Array, itemSize: number, name: string) => {
|
|
603
|
+
const prev = geometry.getAttribute(name)
|
|
604
|
+
if (prev) geometry.deleteAttribute(name)
|
|
605
|
+
const attr = new THREE.BufferAttribute(arr, itemSize)
|
|
606
|
+
attr.setUsage(THREE.DynamicDrawUsage)
|
|
607
|
+
geometry.setAttribute(name, attr)
|
|
608
|
+
}
|
|
609
|
+
replaceAttr(this.positions, FLOATS_PER_VERT, 'position')
|
|
610
|
+
replaceAttr(this.colors, FLOATS_PER_VERT, 'color')
|
|
611
|
+
replaceAttr(this.skyLights, FLOATS_PER_LIGHT_VERT, 'a_skyLight')
|
|
612
|
+
replaceAttr(this.blockLights, FLOATS_PER_LIGHT_VERT, 'a_blockLight')
|
|
613
|
+
replaceAttr(this.uvs, FLOATS_PER_UV_VERT, 'uv')
|
|
614
|
+
replaceAttr(this.aOrigin, FLOATS_PER_VERT, 'a_origin')
|
|
615
|
+
|
|
616
|
+
const prevIndex = geometry.index
|
|
617
|
+
if (prevIndex) geometry.setIndex(null)
|
|
618
|
+
const indexAttr = new THREE.BufferAttribute(this.indices, 1)
|
|
619
|
+
indexAttr.setUsage(THREE.DynamicDrawUsage)
|
|
620
|
+
geometry.setIndex(indexAttr)
|
|
621
|
+
|
|
622
|
+
this.pendingRanges.length = 0
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const _vA = new THREE.Vector3()
|
|
627
|
+
const _vB = new THREE.Vector3()
|
|
628
|
+
const _vC = new THREE.Vector3()
|
|
629
|
+
const _edge1 = new THREE.Vector3()
|
|
630
|
+
const _edge2 = new THREE.Vector3()
|
|
631
|
+
const _normal = new THREE.Vector3()
|
|
632
|
+
const _raycastOrigin = new THREE.Vector3()
|
|
633
|
+
const _raycastRenderOrigin = new THREE.Vector3()
|
|
634
|
+
const _raycastRay = new THREE.Ray()
|
|
635
|
+
|
|
636
|
+
function readWorldVertex (
|
|
637
|
+
positions: Float32Array,
|
|
638
|
+
aOrigin: Float32Array,
|
|
639
|
+
floatBase: number,
|
|
640
|
+
vertIndex: number,
|
|
641
|
+
target: THREE.Vector3,
|
|
642
|
+
): void {
|
|
643
|
+
const f = floatBase + vertIndex * FLOATS_PER_VERT
|
|
644
|
+
target.set(
|
|
645
|
+
aOrigin[f]! + positions[f]!,
|
|
646
|
+
aOrigin[f + 1]! + positions[f + 1]!,
|
|
647
|
+
aOrigin[f + 2]! + positions[f + 2]!,
|
|
648
|
+
)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function intersectTriangle (
|
|
652
|
+
ray: THREE.Ray,
|
|
653
|
+
positions: Float32Array,
|
|
654
|
+
aOrigin: Float32Array,
|
|
655
|
+
floatBase: number,
|
|
656
|
+
i0: number,
|
|
657
|
+
i1: number,
|
|
658
|
+
i2: number,
|
|
659
|
+
near: number,
|
|
660
|
+
far: number,
|
|
661
|
+
): number | null {
|
|
662
|
+
readWorldVertex(positions, aOrigin, floatBase, i0, _vA)
|
|
663
|
+
readWorldVertex(positions, aOrigin, floatBase, i1, _vB)
|
|
664
|
+
readWorldVertex(positions, aOrigin, floatBase, i2, _vC)
|
|
665
|
+
|
|
666
|
+
_edge1.subVectors(_vB, _vA)
|
|
667
|
+
_edge2.subVectors(_vC, _vA)
|
|
668
|
+
_normal.crossVectors(_edge1, _edge2)
|
|
669
|
+
|
|
670
|
+
const denom = _normal.dot(ray.direction)
|
|
671
|
+
if (Math.abs(denom) < 1e-8) return null
|
|
672
|
+
|
|
673
|
+
const t = _vA.clone().sub(ray.origin).dot(_normal) / denom
|
|
674
|
+
if (t < near || t > far) return null
|
|
675
|
+
|
|
676
|
+
const p = ray.at(t, new THREE.Vector3())
|
|
677
|
+
if (!pointInTriangle(p, _vA, _vB, _vC)) return null
|
|
678
|
+
return t
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function pointInTriangle (p: THREE.Vector3, a: THREE.Vector3, b: THREE.Vector3, c: THREE.Vector3): boolean {
|
|
682
|
+
_edge1.subVectors(b, a)
|
|
683
|
+
_edge2.subVectors(c, a)
|
|
684
|
+
const n = _normal.crossVectors(_edge1, _edge2).normalize()
|
|
685
|
+
|
|
686
|
+
const ab = _edge1
|
|
687
|
+
const ac = _edge2
|
|
688
|
+
const ap = p.clone().sub(a)
|
|
689
|
+
|
|
690
|
+
const d00 = ab.dot(ab)
|
|
691
|
+
const d01 = ab.dot(ac)
|
|
692
|
+
const d11 = ac.dot(ac)
|
|
693
|
+
const d20 = ap.dot(ab)
|
|
694
|
+
const d21 = ap.dot(ac)
|
|
695
|
+
const denom = d00 * d11 - d01 * d01
|
|
696
|
+
if (Math.abs(denom) < 1e-12) return false
|
|
697
|
+
const v = (d11 * d20 - d01 * d21) / denom
|
|
698
|
+
const w = (d00 * d21 - d01 * d20) / denom
|
|
699
|
+
const u = 1 - v - w
|
|
700
|
+
return u >= -1e-4 && v >= -1e-4 && w >= -1e-4
|
|
701
|
+
}
|