minecraft-renderer 0.1.106 → 0.1.108
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/dist/lightOwnerWorker.js +9 -0
- package/dist/lightOwnerWorker.js.map +7 -0
- package/dist/mesher.js +1 -1
- package/dist/mesher.js.map +2 -2
- package/dist/mesherWasm.js +222 -222
- package/dist/minecraft-renderer.js +105 -76
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +537 -508
- package/package.json +1 -1
- package/src/bundler/bundlePrepare.test.ts +25 -0
- package/src/bundler/bundlePrepare.ts +1 -1
- package/src/graphicsBackend/appViewer.ts +15 -0
- package/src/graphicsBackend/config.ts +8 -0
- package/src/graphicsBackend/rendererDefaultOptions.test.ts +51 -0
- package/src/graphicsBackend/rendererDefaultOptions.ts +18 -1
- package/src/graphicsBackend/rendererOptionsSync.ts +4 -3
- package/src/lib/blockEntityLightRegistry.ts +4 -0
- package/src/lib/skyLight.ts +17 -0
- package/src/lib/worldrendererCommon.lightOwner.test.ts +916 -0
- package/src/lib/worldrendererCommon.removeColumn.test.ts +16 -0
- package/src/lib/worldrendererCommon.ts +340 -67
- package/src/lighting/cache/rendererLightCache.test.ts +158 -0
- package/src/lighting/cache/rendererLightCache.ts +248 -0
- package/src/lighting/coordinator/clientLightCoordinator.test.ts +102 -0
- package/src/lighting/coordinator/clientLightCoordinator.ts +574 -0
- package/src/lighting/debug/clientLightProbe.test.ts +24 -0
- package/src/lighting/debug/clientLightProbe.ts +20 -0
- package/src/lighting/debug/clientLightTrace.test.ts +271 -0
- package/src/lighting/debug/clientLightTrace.ts +335 -0
- package/src/lighting/entity/entityLightController.test.ts +216 -0
- package/src/lighting/entity/entityLightController.ts +322 -0
- package/src/lighting/owner/clientLightOwner.test.ts +467 -0
- package/src/lighting/owner/clientLightOwner.ts +523 -0
- package/src/lighting/owner/lightOwnerHost.test.ts +78 -0
- package/src/lighting/owner/lightOwnerHost.ts +202 -0
- package/src/lighting/owner/lightOwnerWorker.ts +154 -0
- package/src/lighting/owner/updateLight1171OwnerPath.test.ts +68 -0
- package/src/lighting/owner/updateLightToOwnerEvents.ts +49 -0
- package/src/lighting/tables/lightTables1171.generated.json +10 -0
- package/src/lighting/tables/lightTables1171.test.ts +168 -0
- package/src/lighting/tables/lightTables1171.ts +59 -0
- package/src/lighting/versioning/clientLightVersions.test.ts +241 -0
- package/src/lighting/versioning/clientLightVersions.ts +203 -0
- package/src/lighting/versioning/pendingSectionFlush.test.ts +214 -0
- package/src/lighting/versioning/pendingSectionFlush.ts +123 -0
- package/src/mesher-shared/lightNibblePack.ts +26 -0
- package/src/mesher-shared/mcDataWorkerShim.cjs +9 -7
- package/src/mesher-shared/modelsGeometryCommon.test.ts +6 -1
- package/src/mesher-shared/shared.ts +48 -1
- package/src/three/chunkMeshManager.ts +26 -0
- package/src/three/entities.ts +97 -8
- package/src/three/entity/EntityMesh.ts +1 -0
- package/src/three/entity/legWalkAnimation.test.ts +154 -0
- package/src/three/entity/legWalkAnimation.ts +193 -0
- package/src/three/globalBlockBuffer.ts +94 -2
- package/src/three/globalLegacyBuffer.ts +94 -2
- package/src/three/graphicsBackendBase.ts +2 -9
- package/src/three/modules/clouds.ts +191 -0
- package/src/three/modules/index.ts +4 -0
- package/src/three/modules/sunMoon.ts +151 -0
- package/src/three/skyboxRenderer.ts +15 -8
- package/src/three/tests/clouds.test.ts +59 -0
- package/src/three/tests/globalBlockBuffer.test.ts +98 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +108 -0
- package/src/three/tests/waterFog.test.ts +25 -0
- package/src/three/waterFog.ts +28 -0
- package/src/three/worldRendererThree.ts +84 -39
- package/src/wasm-mesher/bridge/render-from-wasm.ts +46 -1
- package/src/wasm-mesher/runtime-build/wasm_mesher.d.ts +31 -5
- package/src/wasm-mesher/runtime-build/wasm_mesher.js +180 -5
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm.d.ts +11 -0
- package/src/wasm-mesher/tests/mesherLightDependencies.test.ts +83 -0
- package/src/wasm-mesher/tests/mesherWasmColumnTickSelect.test.ts +39 -0
- package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +30 -1
- package/src/wasm-mesher/tests/mesherWasmTickParseCache.test.ts +73 -0
- package/src/wasm-mesher/tests/mesherWasmTickSchedule.test.ts +51 -0
- package/src/wasm-mesher/tests/mesherWasmTopologyPostCache.test.ts +78 -0
- package/src/wasm-mesher/worker/clientLightDarkPublication.probe.test.ts +32 -0
- package/src/wasm-mesher/worker/mesherLightDependencies.ts +43 -0
- package/src/wasm-mesher/worker/mesherWasm.ownerLightSync.test.ts +198 -0
- package/src/wasm-mesher/worker/mesherWasm.ts +506 -96
- package/src/wasm-mesher/worker/mesherWasmChunkBuffer.ts +1 -0
- package/src/wasm-mesher/worker/mesherWasmColumnTickSelect.ts +47 -0
- package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +16 -5
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +17 -1
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +10 -0
- package/src/wasm-mesher/worker/mesherWasmLightMerge.test.ts +208 -0
- package/src/wasm-mesher/worker/mesherWasmLightMerge.ts +228 -0
- package/src/wasm-mesher/worker/mesherWasmOwnerLight.test.ts +161 -0
- package/src/wasm-mesher/worker/mesherWasmOwnerLight.ts +112 -0
- package/src/wasm-mesher/worker/mesherWasmTickParseCache.ts +45 -0
- package/src/wasm-mesher/worker/mesherWasmTickSchedule.ts +44 -0
- package/src/wasm-mesher/worker/mesherWasmTopologyPostCache.ts +63 -0
- package/src/worldView/types.ts +3 -0
- package/src/worldView/worldView.geometryReload.test.ts +68 -0
- package/src/worldView/worldView.spiral.test.ts +1 -1
- package/src/worldView/worldView.ts +49 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import { expect, test } from 'vitest'
|
|
4
|
+
import { applyLegRig, assignLegPhases, calculateLegSwing, createLegRig } from './legWalkAnimation'
|
|
5
|
+
|
|
6
|
+
const close = (a: THREE.Vector3, b: THREE.Vector3) => a.distanceTo(b) < 1e-4
|
|
7
|
+
|
|
8
|
+
/** Mirrors `getMesh`: bones carry absolute pivots even when parented, vertices are authored in mesh space. */
|
|
9
|
+
// bound before scaling like `getMesh`, so skinned vertices come back in 1/16 block units
|
|
10
|
+
function makeBedrockQuadruped() {
|
|
11
|
+
const legs = [
|
|
12
|
+
{ name: 'leg0', pivot: [-4, 12, 7] },
|
|
13
|
+
{ name: 'leg1', pivot: [4, 12, 7] },
|
|
14
|
+
{ name: 'leg2', pivot: [-4, 12, -6] },
|
|
15
|
+
{ name: 'leg3', pivot: [4, 12, -6] }
|
|
16
|
+
]
|
|
17
|
+
const body = new THREE.Bone()
|
|
18
|
+
body.name = 'bone_body'
|
|
19
|
+
body.position.set(0, 5, 2)
|
|
20
|
+
const bones = [body]
|
|
21
|
+
const positions: number[] = []
|
|
22
|
+
const skinIndices: number[] = []
|
|
23
|
+
const skinWeights: number[] = []
|
|
24
|
+
for (const [i, leg] of legs.entries()) {
|
|
25
|
+
const bone = new THREE.Bone()
|
|
26
|
+
bone.name = `bone_${leg.name}`
|
|
27
|
+
bone.position.set(leg.pivot[0], leg.pivot[1], leg.pivot[2])
|
|
28
|
+
body.add(bone)
|
|
29
|
+
bones.push(bone)
|
|
30
|
+
// single foot vertex 12px under the pivot
|
|
31
|
+
positions.push(leg.pivot[0], leg.pivot[1] - 12, leg.pivot[2])
|
|
32
|
+
skinIndices.push(i + 1, 0, 0, 0)
|
|
33
|
+
skinWeights.push(1, 0, 0, 0)
|
|
34
|
+
}
|
|
35
|
+
const geometry = new THREE.BufferGeometry()
|
|
36
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
|
|
37
|
+
geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(skinIndices, 4))
|
|
38
|
+
geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(skinWeights, 4))
|
|
39
|
+
const mesh = new THREE.SkinnedMesh(geometry, new THREE.MeshBasicMaterial())
|
|
40
|
+
mesh.add(body)
|
|
41
|
+
mesh.bind(new THREE.Skeleton(bones))
|
|
42
|
+
mesh.scale.set(1 / 16, 1 / 16, 1 / 16)
|
|
43
|
+
const root = new THREE.Object3D()
|
|
44
|
+
root.add(mesh)
|
|
45
|
+
return { root, mesh, legs }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test('calculateLegSwing follows vanilla gait and is zero at rest', () => {
|
|
49
|
+
expect(calculateLegSwing(5, 0, 0)).toBe(0)
|
|
50
|
+
expect(calculateLegSwing(0, 1, 0)).toBeCloseTo(1.4)
|
|
51
|
+
expect(calculateLegSwing(0, 0.5, Math.PI)).toBeCloseTo(-0.7)
|
|
52
|
+
expect(calculateLegSwing(Number.NaN, 1, 0)).toBe(0)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('assignLegPhases alternates bipeds and pairs quadruped diagonals', () => {
|
|
56
|
+
expect(
|
|
57
|
+
assignLegPhases([
|
|
58
|
+
{ x: -1.9, z: 0 },
|
|
59
|
+
{ x: 1.9, z: 0 }
|
|
60
|
+
])
|
|
61
|
+
).toEqual([Math.PI, 0])
|
|
62
|
+
// leg0..leg3 as hind-right, hind-left, front-right, front-left
|
|
63
|
+
const quad = assignLegPhases([
|
|
64
|
+
{ x: -4, z: 7 },
|
|
65
|
+
{ x: 4, z: 7 },
|
|
66
|
+
{ x: -4, z: -6 },
|
|
67
|
+
{ x: 4, z: -6 }
|
|
68
|
+
])!
|
|
69
|
+
expect(quad[0]).toBe(quad[3])
|
|
70
|
+
expect(quad[1]).toBe(quad[2])
|
|
71
|
+
expect(quad[0]).not.toBe(quad[1])
|
|
72
|
+
expect(assignLegPhases([{ x: 0, z: 0 }])).toBeUndefined()
|
|
73
|
+
expect(assignLegPhases(Array.from({ length: 8 }, (_, i) => ({ x: i, z: i % 2 })))).toBeUndefined()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('bedrock legs rotate about their own pivot even when parented to an offset body bone', () => {
|
|
77
|
+
const { root, mesh, legs } = makeBedrockQuadruped()
|
|
78
|
+
const rig = createLegRig(root, 'cow')!
|
|
79
|
+
expect(rig.legs).toHaveLength(4)
|
|
80
|
+
|
|
81
|
+
applyLegRig(rig, 0.4 * Math.PI, 1)
|
|
82
|
+
mesh.updateWorldMatrix(true, true)
|
|
83
|
+
mesh.skeleton.update()
|
|
84
|
+
|
|
85
|
+
const vertex = new THREE.Vector3()
|
|
86
|
+
for (const [i, leg] of legs.entries()) {
|
|
87
|
+
const pivot = new THREE.Vector3(...(leg.pivot as [number, number, number]))
|
|
88
|
+
const expectedAngle = calculateLegSwing(0.4 * Math.PI, 1, rig.legs[i].phase)
|
|
89
|
+
const foot = new THREE.Vector3(pivot.x, pivot.y - 12, pivot.z)
|
|
90
|
+
const expected = foot
|
|
91
|
+
.clone()
|
|
92
|
+
.sub(pivot)
|
|
93
|
+
.applyAxisAngle(new THREE.Vector3(1, 0, 0), expectedAngle)
|
|
94
|
+
.add(pivot)
|
|
95
|
+
mesh.getVertexPosition(i, vertex).multiplyScalar(16)
|
|
96
|
+
expect(close(vertex, expected), `leg ${i}`).toBe(true)
|
|
97
|
+
// the pivot itself never moves
|
|
98
|
+
expect(Math.abs(vertex.distanceTo(pivot) - 12)).toBeLessThan(1e-4)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('legs settle back to the exact rest pose once the mob stops', () => {
|
|
103
|
+
const { root, mesh, legs } = makeBedrockQuadruped()
|
|
104
|
+
const rig = createLegRig(root, 'pig')!
|
|
105
|
+
applyLegRig(rig, 3, 0.8)
|
|
106
|
+
applyLegRig(rig, 3, 0)
|
|
107
|
+
mesh.updateWorldMatrix(true, true)
|
|
108
|
+
mesh.skeleton.update()
|
|
109
|
+
const vertex = new THREE.Vector3()
|
|
110
|
+
for (const [i, leg] of legs.entries()) {
|
|
111
|
+
mesh.getVertexPosition(i, vertex).multiplyScalar(16)
|
|
112
|
+
expect(close(vertex, new THREE.Vector3(leg.pivot[0], leg.pivot[1] - 12, leg.pivot[2]))).toBe(true)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
function makeObjBiped() {
|
|
117
|
+
const root = new THREE.Object3D()
|
|
118
|
+
const makeLeg = (name: string, x: number) => {
|
|
119
|
+
const geometry = new THREE.BoxGeometry(0.25, 0.75, 0.25)
|
|
120
|
+
geometry.translate(x, 0.375, 0)
|
|
121
|
+
const mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial())
|
|
122
|
+
mesh.name = name
|
|
123
|
+
return mesh
|
|
124
|
+
}
|
|
125
|
+
const left = makeLeg('LeftLeg', 0.125)
|
|
126
|
+
const right = makeLeg('RightLeg', -0.125)
|
|
127
|
+
root.add(left, right)
|
|
128
|
+
return { root, left, right }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
test('OBJ legs pivot at the top of their box without moving at rest', () => {
|
|
132
|
+
const { root, left, right } = makeObjBiped()
|
|
133
|
+
const rig = createLegRig(root, 'enderman')!
|
|
134
|
+
expect(rig.legs).toHaveLength(2)
|
|
135
|
+
|
|
136
|
+
left.updateMatrix()
|
|
137
|
+
left.geometry.computeBoundingBox()
|
|
138
|
+
const restBox = left.geometry.boundingBox!.clone().applyMatrix4(left.matrix)
|
|
139
|
+
expect(restBox.min.y).toBeCloseTo(0)
|
|
140
|
+
expect(restBox.max.y).toBeCloseTo(0.75)
|
|
141
|
+
|
|
142
|
+
applyLegRig(rig, 0, 1)
|
|
143
|
+
expect(left.rotation.x).toBeCloseTo(calculateLegSwing(0, 1, rig.legs.find(l => l.kind === 'mesh' && l.meshes[0] === left)!.phase))
|
|
144
|
+
expect(left.rotation.x).toBeCloseTo(-right.rotation.x)
|
|
145
|
+
// the hinge (original top centre of the leg) stays put while the leg rotates
|
|
146
|
+
left.updateMatrix()
|
|
147
|
+
const hinge = new THREE.Vector3(0, 0, 0).applyMatrix4(left.matrix)
|
|
148
|
+
expect(close(hinge, new THREE.Vector3(0.125, 0.75, 0))).toBe(true)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('excluded and unrecognised models get no rig', () => {
|
|
152
|
+
expect(createLegRig(makeObjBiped().root, 'parrot')).toBeUndefined()
|
|
153
|
+
expect(createLegRig(new THREE.Object3D(), 'cow')).toBeUndefined()
|
|
154
|
+
})
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
|
|
4
|
+
export const LEG_RIG_USER_DATA_KEY = '_legRig'
|
|
5
|
+
export const LEG_ANIMATION_USER_DATA_KEY = '_legAnimation'
|
|
6
|
+
|
|
7
|
+
/** Vanilla ModelBiped / ModelQuadruped constants. */
|
|
8
|
+
export const LEG_SWING_FREQUENCY = 0.6662
|
|
9
|
+
export const LEG_SWING_AMPLITUDE = 1.4
|
|
10
|
+
|
|
11
|
+
/** Entities whose legs are not driven by ground walking (fly, hop, swim or use bespoke animations). */
|
|
12
|
+
const LEG_ANIMATION_EXCLUDED = new Set(['armor_stand', 'axolotl', 'ender_dragon', 'frog', 'parrot', 'rabbit', 'turtle'])
|
|
13
|
+
|
|
14
|
+
const LEG_NAME_PATTERN = /leg/i
|
|
15
|
+
|
|
16
|
+
type BoneLeg = {
|
|
17
|
+
kind: 'bone'
|
|
18
|
+
bone: THREE.Bone
|
|
19
|
+
/** Pivot in mesh space, the point the vertices were authored around. */
|
|
20
|
+
pivot: THREE.Vector3
|
|
21
|
+
/** Parent bone matrix relative to the skinned mesh at bind time. */
|
|
22
|
+
parentMatrix: THREE.Matrix4
|
|
23
|
+
parentMatrixInverse: THREE.Matrix4
|
|
24
|
+
/** Local bone matrix at bind time. */
|
|
25
|
+
restMatrix: THREE.Matrix4
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type MeshLeg = {
|
|
29
|
+
kind: 'mesh'
|
|
30
|
+
meshes: THREE.Mesh[]
|
|
31
|
+
restRotationX: number[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type LegRigLeg = (BoneLeg | MeshLeg) & { phase: number }
|
|
35
|
+
|
|
36
|
+
export type LegRig = {
|
|
37
|
+
legs: LegRigLeg[]
|
|
38
|
+
/** True while legs are rotated away from rest, so settling back is written exactly once. */
|
|
39
|
+
posed: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type LegCandidate = { name: string; pivot: THREE.Vector3; build: (phase: number) => LegRigLeg }
|
|
43
|
+
|
|
44
|
+
/** Vanilla leg swing angle (radians around the model X axis). */
|
|
45
|
+
export function calculateLegSwing(limbSwing: number, limbSwingAmount: number, phase: number): number {
|
|
46
|
+
const amount = Number.isFinite(limbSwingAmount) ? Math.max(0, limbSwingAmount) : 0
|
|
47
|
+
if (amount === 0 || !Number.isFinite(limbSwing)) return 0
|
|
48
|
+
return Math.cos(limbSwing * LEG_SWING_FREQUENCY + phase) * LEG_SWING_AMPLITUDE * amount
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Diagonal pairs move together on four legged mobs and opposite legs alternate on bipeds.
|
|
53
|
+
* Derived from pivot positions so it works for any bone naming (leg0, rightLeg, left_hind_leg...).
|
|
54
|
+
* Returns the phase offset per candidate, or undefined if the leg layout isn't recognised.
|
|
55
|
+
*/
|
|
56
|
+
export function assignLegPhases(pivots: Array<{ x: number; z: number }>): number[] | undefined {
|
|
57
|
+
if (pivots.length !== 2 && pivots.length !== 4) return undefined
|
|
58
|
+
|
|
59
|
+
const meanX = pivots.reduce((sum, p) => sum + p.x, 0) / pivots.length
|
|
60
|
+
const meanZ = pivots.reduce((sum, p) => sum + p.z, 0) / pivots.length
|
|
61
|
+
const xs = pivots.map(p => p.x - meanX)
|
|
62
|
+
const zs = pivots.map(p => p.z - meanZ)
|
|
63
|
+
const xSpread = Math.max(...xs) - Math.min(...xs)
|
|
64
|
+
const zSpread = Math.max(...zs) - Math.min(...zs)
|
|
65
|
+
|
|
66
|
+
// Legs side by side: alternate left/right.
|
|
67
|
+
if (pivots.length === 2 || zSpread < xSpread * 0.5) {
|
|
68
|
+
if (xSpread === 0) return undefined
|
|
69
|
+
return xs.map(x => (x > 0 ? 0 : Math.PI))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (xSpread === 0) return undefined
|
|
73
|
+
return xs.map((x, i) => (x > 0 === zs[i] > 0 ? 0 : Math.PI))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function collectBoneCandidates(skinned: THREE.SkinnedMesh): LegCandidate[] {
|
|
77
|
+
skinned.updateWorldMatrix(true, true)
|
|
78
|
+
const meshInverse = skinned.matrixWorld.clone().invert()
|
|
79
|
+
const candidates: LegCandidate[] = []
|
|
80
|
+
for (const bone of skinned.skeleton.bones) {
|
|
81
|
+
const name = bone.name.replace(/^bone_/, '')
|
|
82
|
+
if (!LEG_NAME_PATTERN.test(name)) continue
|
|
83
|
+
// Sub-bones such as `Leg1B` hang off the same pivot and would be double counted.
|
|
84
|
+
if (/^leg\d[b-z]$/i.test(name)) continue
|
|
85
|
+
const pivot = bone.position.clone()
|
|
86
|
+
const parentMatrix = bone.parent instanceof THREE.Bone ? meshInverse.clone().multiply(bone.parent.matrixWorld) : new THREE.Matrix4()
|
|
87
|
+
candidates.push({
|
|
88
|
+
name,
|
|
89
|
+
pivot,
|
|
90
|
+
build: phase => ({
|
|
91
|
+
kind: 'bone',
|
|
92
|
+
phase,
|
|
93
|
+
bone,
|
|
94
|
+
pivot,
|
|
95
|
+
parentMatrix,
|
|
96
|
+
parentMatrixInverse: parentMatrix.clone().invert(),
|
|
97
|
+
restMatrix: bone.matrix.clone()
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
return candidates
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function collectMeshCandidates(root: THREE.Object3D): LegCandidate[] {
|
|
105
|
+
const groups = new Map<string, THREE.Mesh[]>()
|
|
106
|
+
for (const child of root.children) {
|
|
107
|
+
if (!(child instanceof THREE.Mesh) || child instanceof THREE.SkinnedMesh) continue
|
|
108
|
+
if (!LEG_NAME_PATTERN.test(child.name)) continue
|
|
109
|
+
const group = groups.get(child.name)
|
|
110
|
+
if (group) group.push(child)
|
|
111
|
+
else groups.set(child.name, [child])
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const candidates: LegCandidate[] = []
|
|
115
|
+
for (const [name, meshes] of groups) {
|
|
116
|
+
// Overlay layers share a name, so the pivot is the union of them.
|
|
117
|
+
const box = new THREE.Box3()
|
|
118
|
+
for (const mesh of meshes) {
|
|
119
|
+
mesh.updateMatrix()
|
|
120
|
+
mesh.geometry.computeBoundingBox()
|
|
121
|
+
box.union(mesh.geometry.boundingBox!.clone().applyMatrix4(mesh.matrix))
|
|
122
|
+
}
|
|
123
|
+
// Legs hang from the top of their box and swing around it.
|
|
124
|
+
const pivot = new THREE.Vector3((box.min.x + box.max.x) / 2, box.max.y, (box.min.z + box.max.z) / 2)
|
|
125
|
+
candidates.push({
|
|
126
|
+
name,
|
|
127
|
+
pivot,
|
|
128
|
+
build: phase => {
|
|
129
|
+
for (const mesh of meshes) {
|
|
130
|
+
mesh.geometry.translate(-pivot.x, -pivot.y, -pivot.z)
|
|
131
|
+
mesh.position.add(pivot)
|
|
132
|
+
}
|
|
133
|
+
return { kind: 'mesh', phase, meshes, restRotationX: meshes.map(mesh => mesh.rotation.x) }
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
return candidates
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildLegs(candidates: LegCandidate[]): LegRigLeg[] {
|
|
141
|
+
const phases = assignLegPhases(candidates.map(c => c.pivot))
|
|
142
|
+
if (!phases) return []
|
|
143
|
+
return candidates.map((candidate, i) => candidate.build(phases[i]))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Finds the legs of a mob model (bedrock bones or OBJ parts) and prepares them to be swung
|
|
148
|
+
* around their pivots. Returns undefined when the model has no recognisable 2 or 4 leg layout.
|
|
149
|
+
*/
|
|
150
|
+
export function createLegRig(root: THREE.Object3D, entityName?: string): LegRig | undefined {
|
|
151
|
+
if (entityName && LEG_ANIMATION_EXCLUDED.has(entityName)) return undefined
|
|
152
|
+
|
|
153
|
+
const legs: LegRigLeg[] = []
|
|
154
|
+
root.traverse(child => {
|
|
155
|
+
if (child instanceof THREE.SkinnedMesh) legs.push(...buildLegs(collectBoneCandidates(child)))
|
|
156
|
+
})
|
|
157
|
+
legs.push(...buildLegs(collectMeshCandidates(root)))
|
|
158
|
+
|
|
159
|
+
if (legs.length === 0) return undefined
|
|
160
|
+
return { legs, posed: false }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tmpPivotMatrix = new THREE.Matrix4()
|
|
164
|
+
const tmpRotation = new THREE.Matrix4()
|
|
165
|
+
const tmpDeform = new THREE.Matrix4()
|
|
166
|
+
const tmpLocal = new THREE.Matrix4()
|
|
167
|
+
|
|
168
|
+
function applyBoneLeg(leg: BoneLeg, angle: number): void {
|
|
169
|
+
// Skinning deforms vertices by D = T(pivot) * Rx * T(-pivot) in mesh space. Bones are parented with absolute
|
|
170
|
+
// pivots, so the local rotation is conjugated by the parent bind matrix to produce exactly that deformation.
|
|
171
|
+
tmpDeform.makeTranslation(leg.pivot.x, leg.pivot.y, leg.pivot.z)
|
|
172
|
+
tmpDeform.multiply(tmpRotation.makeRotationX(angle))
|
|
173
|
+
tmpDeform.multiply(tmpPivotMatrix.makeTranslation(-leg.pivot.x, -leg.pivot.y, -leg.pivot.z))
|
|
174
|
+
|
|
175
|
+
tmpLocal.copy(leg.parentMatrixInverse).multiply(tmpDeform).multiply(leg.parentMatrix).multiply(leg.restMatrix)
|
|
176
|
+
tmpLocal.decompose(leg.bone.position, leg.bone.quaternion, leg.bone.scale)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Poses all legs of the rig. Cheap no-op while the mob stays still. */
|
|
180
|
+
export function applyLegRig(rig: LegRig, limbSwing: number, limbSwingAmount: number): void {
|
|
181
|
+
const moving = Number.isFinite(limbSwingAmount) && limbSwingAmount >= 1e-4
|
|
182
|
+
if (!moving && !rig.posed) return
|
|
183
|
+
rig.posed = moving
|
|
184
|
+
|
|
185
|
+
for (const leg of rig.legs) {
|
|
186
|
+
const angle = moving ? calculateLegSwing(limbSwing, limbSwingAmount, leg.phase) : 0
|
|
187
|
+
if (leg.kind === 'bone') {
|
|
188
|
+
applyBoneLeg(leg, angle)
|
|
189
|
+
} else {
|
|
190
|
+
for (const [i, mesh] of leg.meshes.entries()) mesh.rotation.x = leg.restRotationX[i] + angle
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { VERTICES_PER_FACE, computeSectionOriginRel } from './shaders/cubeBlockShader'
|
|
13
13
|
import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
14
14
|
import { packWord2Empty } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
15
|
+
import { isClientLightTraceEnabled, recordClientLightTrace, recordClientLightTraceGpuSample } from '../lighting/debug/clientLightTrace'
|
|
15
16
|
|
|
16
17
|
type WebGLRendererInternals = THREE.WebGLRenderer & {
|
|
17
18
|
properties: {
|
|
@@ -162,9 +163,9 @@ export class GlobalBlockBuffer {
|
|
|
162
163
|
getSectionDrawStart(sectionKey: string): number | undefined {
|
|
163
164
|
const slot = this.sectionSlots.get(sectionKey)
|
|
164
165
|
if (!slot) return undefined
|
|
165
|
-
if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
|
|
166
166
|
const replace = this.pendingReplace.get(sectionKey)
|
|
167
167
|
if (replace) return replace.oldStart
|
|
168
|
+
if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
|
|
168
169
|
if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) return undefined
|
|
169
170
|
return slot.start
|
|
170
171
|
}
|
|
@@ -172,13 +173,37 @@ export class GlobalBlockBuffer {
|
|
|
172
173
|
getSectionDrawCount(sectionKey: string): number | undefined {
|
|
173
174
|
const slot = this.sectionSlots.get(sectionKey)
|
|
174
175
|
if (!slot) return undefined
|
|
175
|
-
if (this.pendingMove?.key === sectionKey) return this.pendingMove.count
|
|
176
176
|
const replace = this.pendingReplace.get(sectionKey)
|
|
177
177
|
if (replace) return replace.oldCount
|
|
178
|
+
if (this.pendingMove?.key === sectionKey) return this.pendingMove.count
|
|
178
179
|
if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) return undefined
|
|
179
180
|
return slot.count
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
getGpuSlotTrace(sectionKey: string):
|
|
184
|
+
| {
|
|
185
|
+
displayedStart?: number
|
|
186
|
+
displayedCount?: number
|
|
187
|
+
candidateStart: number
|
|
188
|
+
candidateCount: number
|
|
189
|
+
pendingReplace: boolean
|
|
190
|
+
pendingMove: boolean
|
|
191
|
+
unuploadedRanges: number
|
|
192
|
+
}
|
|
193
|
+
| undefined {
|
|
194
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
195
|
+
if (!slot) return undefined
|
|
196
|
+
return {
|
|
197
|
+
displayedStart: this.getSectionDrawStart(sectionKey),
|
|
198
|
+
displayedCount: this.getSectionDrawCount(sectionKey),
|
|
199
|
+
candidateStart: slot.start,
|
|
200
|
+
candidateCount: slot.count,
|
|
201
|
+
pendingReplace: this.pendingReplace.has(sectionKey),
|
|
202
|
+
pendingMove: this.pendingMove?.key === sectionKey,
|
|
203
|
+
unuploadedRanges: this.pendingRanges.length
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
182
207
|
getUploadEpoch(): number {
|
|
183
208
|
return this.uploadEpoch
|
|
184
209
|
}
|
|
@@ -282,6 +307,20 @@ export class GlobalBlockBuffer {
|
|
|
282
307
|
this.markDirty(slot.start, slot.start + faceCount - 1)
|
|
283
308
|
this.mesh.geometry.instanceCount = this.highWatermark
|
|
284
309
|
this.layoutVersion++
|
|
310
|
+
if (isClientLightTraceEnabled()) {
|
|
311
|
+
const gpu = this.getGpuSlotTrace(sectionKey)
|
|
312
|
+
recordClientLightTrace({
|
|
313
|
+
phase: 'gpuStaged',
|
|
314
|
+
sectionKey,
|
|
315
|
+
displayedStart: gpu?.displayedStart,
|
|
316
|
+
displayedCount: gpu?.displayedCount,
|
|
317
|
+
candidateStart: gpu?.candidateStart,
|
|
318
|
+
candidateCount: gpu?.candidateCount,
|
|
319
|
+
pendingReplace: gpu?.pendingReplace,
|
|
320
|
+
pendingMove: gpu?.pendingMove,
|
|
321
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
322
|
+
})
|
|
323
|
+
}
|
|
285
324
|
}
|
|
286
325
|
|
|
287
326
|
getLayoutVersion(): number {
|
|
@@ -388,6 +427,20 @@ export class GlobalBlockBuffer {
|
|
|
388
427
|
this.markDirty(newStart, newStart + section.count - 1)
|
|
389
428
|
this.pendingMove = { key: section.key, oldStart, newStart, count: section.count }
|
|
390
429
|
this.layoutVersion++
|
|
430
|
+
if (isClientLightTraceEnabled()) {
|
|
431
|
+
const gpu = this.getGpuSlotTrace(section.key)
|
|
432
|
+
recordClientLightTrace({
|
|
433
|
+
phase: 'gpuStaged',
|
|
434
|
+
sectionKey: section.key,
|
|
435
|
+
displayedStart: gpu?.displayedStart,
|
|
436
|
+
displayedCount: gpu?.displayedCount,
|
|
437
|
+
candidateStart: gpu?.candidateStart,
|
|
438
|
+
candidateCount: gpu?.candidateCount,
|
|
439
|
+
pendingReplace: gpu?.pendingReplace,
|
|
440
|
+
pendingMove: true,
|
|
441
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
442
|
+
})
|
|
443
|
+
}
|
|
391
444
|
}
|
|
392
445
|
|
|
393
446
|
uploadDirtyRange(): void {
|
|
@@ -411,6 +464,13 @@ export class GlobalBlockBuffer {
|
|
|
411
464
|
r.start = offset + count
|
|
412
465
|
}
|
|
413
466
|
this.uploadEpoch++
|
|
467
|
+
if (isClientLightTraceEnabled()) {
|
|
468
|
+
recordClientLightTraceGpuSample('gpuUploaded', this.highWatermark, () => ({
|
|
469
|
+
phase: 'gpuUploaded',
|
|
470
|
+
unuploadedRanges: this.pendingRanges.length,
|
|
471
|
+
drawableFaces: this.highWatermark
|
|
472
|
+
}))
|
|
473
|
+
}
|
|
414
474
|
}
|
|
415
475
|
|
|
416
476
|
setCameraOrigin(renderOrigin: RenderOrigin, x: number, y: number, z: number): void {
|
|
@@ -608,6 +668,9 @@ export class GlobalBlockBuffer {
|
|
|
608
668
|
private findMovableSection(maxCount: number): { key: string; start: number; count: number } | undefined {
|
|
609
669
|
const sections: Array<{ key: string; start: number; count: number }> = []
|
|
610
670
|
for (const [key, slot] of this.sectionSlots) {
|
|
671
|
+
if (this.pendingReplace.has(key)) continue
|
|
672
|
+
if (this.pendingMove?.key === key) continue
|
|
673
|
+
if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) continue
|
|
611
674
|
sections.push({ key, start: slot.start, count: slot.count })
|
|
612
675
|
}
|
|
613
676
|
if (sections.length === 0) return undefined
|
|
@@ -684,11 +747,26 @@ export class GlobalBlockBuffer {
|
|
|
684
747
|
this.mesh.geometry.instanceCount = this.highWatermark
|
|
685
748
|
this.layoutVersion++
|
|
686
749
|
this.uploadEpoch++
|
|
750
|
+
if (isClientLightTraceEnabled()) {
|
|
751
|
+
const gpu = this.getGpuSlotTrace(key)
|
|
752
|
+
recordClientLightTrace({
|
|
753
|
+
phase: 'gpuCommitted',
|
|
754
|
+
sectionKey: key,
|
|
755
|
+
displayedStart: gpu?.displayedStart,
|
|
756
|
+
displayedCount: gpu?.displayedCount,
|
|
757
|
+
candidateStart: gpu?.candidateStart,
|
|
758
|
+
candidateCount: gpu?.candidateCount,
|
|
759
|
+
pendingReplace: false,
|
|
760
|
+
pendingMove: gpu?.pendingMove,
|
|
761
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
762
|
+
})
|
|
763
|
+
}
|
|
687
764
|
}
|
|
688
765
|
|
|
689
766
|
private finalizePendingMove(): void {
|
|
690
767
|
const move = this.pendingMove
|
|
691
768
|
if (!move) return
|
|
769
|
+
const movedKey = move.key
|
|
692
770
|
|
|
693
771
|
const { oldStart, count } = move
|
|
694
772
|
this.zeroAndFreeSlot(oldStart, count)
|
|
@@ -697,6 +775,20 @@ export class GlobalBlockBuffer {
|
|
|
697
775
|
this.pendingMove = null
|
|
698
776
|
this.layoutVersion++
|
|
699
777
|
this.uploadEpoch++
|
|
778
|
+
if (isClientLightTraceEnabled()) {
|
|
779
|
+
const gpu = this.getGpuSlotTrace(movedKey)
|
|
780
|
+
recordClientLightTrace({
|
|
781
|
+
phase: 'gpuCommitted',
|
|
782
|
+
sectionKey: movedKey,
|
|
783
|
+
displayedStart: gpu?.displayedStart,
|
|
784
|
+
displayedCount: gpu?.displayedCount,
|
|
785
|
+
candidateStart: gpu?.candidateStart,
|
|
786
|
+
candidateCount: gpu?.candidateCount,
|
|
787
|
+
pendingReplace: gpu?.pendingReplace,
|
|
788
|
+
pendingMove: false,
|
|
789
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
790
|
+
})
|
|
791
|
+
}
|
|
700
792
|
}
|
|
701
793
|
|
|
702
794
|
private shrinkHighWatermark(): void {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type LegacyMultiDrawScratch
|
|
11
11
|
} from './legacyMultiDraw'
|
|
12
12
|
import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
13
|
+
import { isClientLightTraceEnabled, recordClientLightTrace, recordClientLightTraceGpuSample } from '../lighting/debug/clientLightTrace'
|
|
13
14
|
|
|
14
15
|
const VERTS_PER_QUAD = 4
|
|
15
16
|
const INDICES_PER_QUAD = 6
|
|
@@ -337,6 +338,20 @@ export class GlobalLegacyBuffer {
|
|
|
337
338
|
this.markDirty(slot.start, slot.start + quadCount - 1)
|
|
338
339
|
this.syncDefaultDrawGroups()
|
|
339
340
|
this.layoutVersion++
|
|
341
|
+
if (isClientLightTraceEnabled()) {
|
|
342
|
+
const gpu = this.getGpuSlotTrace(sectionKey)
|
|
343
|
+
recordClientLightTrace({
|
|
344
|
+
phase: 'gpuStaged',
|
|
345
|
+
sectionKey,
|
|
346
|
+
displayedStart: gpu?.displayedStart,
|
|
347
|
+
displayedCount: gpu?.displayedCount,
|
|
348
|
+
candidateStart: gpu?.candidateStart,
|
|
349
|
+
candidateCount: gpu?.candidateCount,
|
|
350
|
+
pendingReplace: gpu?.pendingReplace,
|
|
351
|
+
pendingMove: gpu?.pendingMove,
|
|
352
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
353
|
+
})
|
|
354
|
+
}
|
|
340
355
|
return true
|
|
341
356
|
}
|
|
342
357
|
|
|
@@ -367,9 +382,9 @@ export class GlobalLegacyBuffer {
|
|
|
367
382
|
getSectionDrawStart(sectionKey: string): number | undefined {
|
|
368
383
|
const slot = this.sectionSlots.get(sectionKey)
|
|
369
384
|
if (!slot) return undefined
|
|
370
|
-
if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
|
|
371
385
|
const replace = this.pendingReplace.get(sectionKey)
|
|
372
386
|
if (replace) return replace.oldStart
|
|
387
|
+
if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
|
|
373
388
|
if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) return undefined
|
|
374
389
|
return slot.start
|
|
375
390
|
}
|
|
@@ -377,13 +392,37 @@ export class GlobalLegacyBuffer {
|
|
|
377
392
|
getSectionDrawCount(sectionKey: string): number | undefined {
|
|
378
393
|
const slot = this.sectionSlots.get(sectionKey)
|
|
379
394
|
if (!slot) return undefined
|
|
380
|
-
if (this.pendingMove?.key === sectionKey) return this.pendingMove.count
|
|
381
395
|
const replace = this.pendingReplace.get(sectionKey)
|
|
382
396
|
if (replace) return replace.oldCount
|
|
397
|
+
if (this.pendingMove?.key === sectionKey) return this.pendingMove.count
|
|
383
398
|
if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) return undefined
|
|
384
399
|
return slot.count
|
|
385
400
|
}
|
|
386
401
|
|
|
402
|
+
getGpuSlotTrace(sectionKey: string):
|
|
403
|
+
| {
|
|
404
|
+
displayedStart?: number
|
|
405
|
+
displayedCount?: number
|
|
406
|
+
candidateStart: number
|
|
407
|
+
candidateCount: number
|
|
408
|
+
pendingReplace: boolean
|
|
409
|
+
pendingMove: boolean
|
|
410
|
+
unuploadedRanges: number
|
|
411
|
+
}
|
|
412
|
+
| undefined {
|
|
413
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
414
|
+
if (!slot) return undefined
|
|
415
|
+
return {
|
|
416
|
+
displayedStart: this.getSectionDrawStart(sectionKey),
|
|
417
|
+
displayedCount: this.getSectionDrawCount(sectionKey),
|
|
418
|
+
candidateStart: slot.start,
|
|
419
|
+
candidateCount: slot.count,
|
|
420
|
+
pendingReplace: this.pendingReplace.has(sectionKey),
|
|
421
|
+
pendingMove: this.pendingMove?.key === sectionKey,
|
|
422
|
+
unuploadedRanges: this.pendingRanges.length
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
387
426
|
getPendingMove(): PendingMove | null {
|
|
388
427
|
return this.pendingMove
|
|
389
428
|
}
|
|
@@ -425,6 +464,20 @@ export class GlobalLegacyBuffer {
|
|
|
425
464
|
this.markDirty(newStart, newStart + section.count - 1)
|
|
426
465
|
this.pendingMove = { key: section.key, oldStart, newStart, count: section.count }
|
|
427
466
|
this.layoutVersion++
|
|
467
|
+
if (isClientLightTraceEnabled()) {
|
|
468
|
+
const gpu = this.getGpuSlotTrace(section.key)
|
|
469
|
+
recordClientLightTrace({
|
|
470
|
+
phase: 'gpuStaged',
|
|
471
|
+
sectionKey: section.key,
|
|
472
|
+
displayedStart: gpu?.displayedStart,
|
|
473
|
+
displayedCount: gpu?.displayedCount,
|
|
474
|
+
candidateStart: gpu?.candidateStart,
|
|
475
|
+
candidateCount: gpu?.candidateCount,
|
|
476
|
+
pendingReplace: gpu?.pendingReplace,
|
|
477
|
+
pendingMove: true,
|
|
478
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
479
|
+
})
|
|
480
|
+
}
|
|
428
481
|
}
|
|
429
482
|
|
|
430
483
|
updateDrawSpans(visible: VisibleSectionSpan[], mode: 'opaque' | 'sortedBlend'): void {
|
|
@@ -696,6 +749,13 @@ export class GlobalLegacyBuffer {
|
|
|
696
749
|
r.start = quadOffset + quadCount
|
|
697
750
|
}
|
|
698
751
|
this.uploadEpoch++
|
|
752
|
+
if (isClientLightTraceEnabled()) {
|
|
753
|
+
recordClientLightTraceGpuSample('gpuUploaded', this.highWatermark, () => ({
|
|
754
|
+
phase: 'gpuUploaded',
|
|
755
|
+
unuploadedRanges: this.pendingRanges.length,
|
|
756
|
+
drawableFaces: this.highWatermark
|
|
757
|
+
}))
|
|
758
|
+
}
|
|
699
759
|
}
|
|
700
760
|
|
|
701
761
|
uploadDirtyIndexRange(): void {
|
|
@@ -898,6 +958,9 @@ export class GlobalLegacyBuffer {
|
|
|
898
958
|
private findMovableSection(maxCount: number): { key: string; start: number; count: number } | undefined {
|
|
899
959
|
const sections: Array<{ key: string; start: number; count: number }> = []
|
|
900
960
|
for (const [key, slot] of this.sectionSlots) {
|
|
961
|
+
if (this.pendingReplace.has(key)) continue
|
|
962
|
+
if (this.pendingMove?.key === key) continue
|
|
963
|
+
if (!this.rangeFullyUploaded(slot.start, slot.start + slot.count - 1)) continue
|
|
901
964
|
sections.push({ key, start: slot.start, count: slot.count })
|
|
902
965
|
}
|
|
903
966
|
if (sections.length === 0) return undefined
|
|
@@ -1006,11 +1069,26 @@ export class GlobalLegacyBuffer {
|
|
|
1006
1069
|
this.syncDefaultDrawGroups()
|
|
1007
1070
|
this.layoutVersion++
|
|
1008
1071
|
this.uploadEpoch++
|
|
1072
|
+
if (isClientLightTraceEnabled()) {
|
|
1073
|
+
const gpu = this.getGpuSlotTrace(key)
|
|
1074
|
+
recordClientLightTrace({
|
|
1075
|
+
phase: 'gpuCommitted',
|
|
1076
|
+
sectionKey: key,
|
|
1077
|
+
displayedStart: gpu?.displayedStart,
|
|
1078
|
+
displayedCount: gpu?.displayedCount,
|
|
1079
|
+
candidateStart: gpu?.candidateStart,
|
|
1080
|
+
candidateCount: gpu?.candidateCount,
|
|
1081
|
+
pendingReplace: false,
|
|
1082
|
+
pendingMove: gpu?.pendingMove,
|
|
1083
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
1084
|
+
})
|
|
1085
|
+
}
|
|
1009
1086
|
}
|
|
1010
1087
|
|
|
1011
1088
|
private finalizePendingMove(): void {
|
|
1012
1089
|
const move = this.pendingMove
|
|
1013
1090
|
if (!move) return
|
|
1091
|
+
const movedKey = move.key
|
|
1014
1092
|
|
|
1015
1093
|
const { oldStart, count } = move
|
|
1016
1094
|
this.zeroAndFreeSlot(oldStart, count)
|
|
@@ -1019,6 +1097,20 @@ export class GlobalLegacyBuffer {
|
|
|
1019
1097
|
this.pendingMove = null
|
|
1020
1098
|
this.layoutVersion++
|
|
1021
1099
|
this.uploadEpoch++
|
|
1100
|
+
if (isClientLightTraceEnabled()) {
|
|
1101
|
+
const gpu = this.getGpuSlotTrace(movedKey)
|
|
1102
|
+
recordClientLightTrace({
|
|
1103
|
+
phase: 'gpuCommitted',
|
|
1104
|
+
sectionKey: movedKey,
|
|
1105
|
+
displayedStart: gpu?.displayedStart,
|
|
1106
|
+
displayedCount: gpu?.displayedCount,
|
|
1107
|
+
candidateStart: gpu?.candidateStart,
|
|
1108
|
+
candidateCount: gpu?.candidateCount,
|
|
1109
|
+
pendingReplace: gpu?.pendingReplace,
|
|
1110
|
+
pendingMove: false,
|
|
1111
|
+
unuploadedRanges: gpu?.unuploadedRanges
|
|
1112
|
+
})
|
|
1113
|
+
}
|
|
1022
1114
|
}
|
|
1023
1115
|
|
|
1024
1116
|
private growCapacity(minQuads: number): void {
|