minecraft-renderer 0.1.107 → 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 +80 -80
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +513 -513
- 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 +5 -0
- package/src/graphicsBackend/rendererDefaultOptions.test.ts +51 -0
- package/src/graphicsBackend/rendererDefaultOptions.ts +13 -1
- package/src/graphicsBackend/rendererOptionsSync.ts +3 -3
- package/src/lib/blockEntityLightRegistry.ts +4 -0
- package/src/lib/worldrendererCommon.lightOwner.test.ts +916 -0
- package/src/lib/worldrendererCommon.removeColumn.test.ts +16 -0
- package/src/lib/worldrendererCommon.ts +329 -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 +77 -11
- package/src/three/entity/EntityMesh.ts +1 -0
- package/src/three/entity/legWalkAnimation.test.ts +11 -2
- package/src/three/entity/legWalkAnimation.ts +1 -9
- package/src/three/globalBlockBuffer.ts +94 -2
- package/src/three/globalLegacyBuffer.ts +94 -2
- package/src/three/graphicsBackendBase.ts +2 -9
- package/src/three/tests/globalBlockBuffer.test.ts +98 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +108 -0
- package/src/three/worldRendererThree.ts +75 -34
- 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 +1 -0
- package/src/worldView/worldView.geometryReload.test.ts +68 -0
- package/src/worldView/worldView.spiral.test.ts +1 -1
- package/src/worldView/worldView.ts +46 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Flush policy for buffered section geometry (`pendingSectionUpdates`).
|
|
4
|
+
*
|
|
5
|
+
* A remesh of an already-visible section must not be installed alone while a
|
|
6
|
+
* face neighbour is still being re-meshed: the neighbour's faces towards this
|
|
7
|
+
* section were culled against the OLD block state, so installing one side
|
|
8
|
+
* first exposes a see-through hole (the "sky flash" on dig/place).
|
|
9
|
+
*
|
|
10
|
+
* The policy therefore works on *groups*: pending sections that are face
|
|
11
|
+
* adjacent form a connected group, and a group is installed only when every
|
|
12
|
+
* outstanding neighbour of every member has arrived. When the buffer deadline
|
|
13
|
+
* expires we still flush, but we flush the whole group in one batch — never a
|
|
14
|
+
* single member, which is exactly the hole the buffer exists to prevent.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type PendingFlushQuery = {
|
|
18
|
+
/** Keys currently buffered, in insertion order. */
|
|
19
|
+
pendingKeys: Iterable<string>
|
|
20
|
+
/** When each key was first buffered. */
|
|
21
|
+
startedAt: (key: string) => number | undefined
|
|
22
|
+
now: number
|
|
23
|
+
maxBufferMs: number
|
|
24
|
+
sectionHeight: number
|
|
25
|
+
/** Section is dirty on a worker and its geometry has not arrived yet. */
|
|
26
|
+
isOutstanding: (key: string) => boolean
|
|
27
|
+
/**
|
|
28
|
+
* When set, neighbour waits use topology outstanding instead of covering/light
|
|
29
|
+
* outstanding. Omit for bit-for-bit legacy flush behavior.
|
|
30
|
+
*/
|
|
31
|
+
isTopologyOutstanding?: (key: string) => boolean
|
|
32
|
+
/** Section currently has geometry on screen (an air section has none). */
|
|
33
|
+
hasSectionObject: (key: string) => boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function faceNeighborKeys(key: string, sectionHeight: number): string[] {
|
|
37
|
+
const [sx, sy, sz] = key.split(',').map(Number)
|
|
38
|
+
return [
|
|
39
|
+
`${sx - 16},${sy},${sz}`,
|
|
40
|
+
`${sx + 16},${sy},${sz}`,
|
|
41
|
+
`${sx},${sy - sectionHeight},${sz}`,
|
|
42
|
+
`${sx},${sy + sectionHeight},${sz}`,
|
|
43
|
+
`${sx},${sy},${sz - 16}`,
|
|
44
|
+
`${sx},${sy},${sz + 16}`
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Connected components of the buffered sections under face adjacency. Members
|
|
50
|
+
* of one component must be installed in the same frame.
|
|
51
|
+
*/
|
|
52
|
+
export function pendingSectionGroups(pendingKeys: Iterable<string>, sectionHeight: number): string[][] {
|
|
53
|
+
const remaining = new Set(pendingKeys)
|
|
54
|
+
const groups: string[][] = []
|
|
55
|
+
for (const seed of [...remaining]) {
|
|
56
|
+
if (!remaining.has(seed)) continue
|
|
57
|
+
const group: string[] = []
|
|
58
|
+
const stack = [seed]
|
|
59
|
+
remaining.delete(seed)
|
|
60
|
+
while (stack.length > 0) {
|
|
61
|
+
const key = stack.pop()!
|
|
62
|
+
group.push(key)
|
|
63
|
+
for (const neighbor of faceNeighborKeys(key, sectionHeight)) {
|
|
64
|
+
if (remaining.delete(neighbor)) stack.push(neighbor)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
groups.push(group)
|
|
68
|
+
}
|
|
69
|
+
return groups
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type PendingFlushReason = 'group-complete' | 'deadline'
|
|
73
|
+
|
|
74
|
+
export type ReadySectionFlush = {
|
|
75
|
+
key: string
|
|
76
|
+
reason: PendingFlushReason
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Keys to install this frame. Either a group is complete (no member still
|
|
81
|
+
* waiting on a visible neighbour) or its oldest member has run out of buffer
|
|
82
|
+
* time — in both cases the whole group goes out together.
|
|
83
|
+
*/
|
|
84
|
+
export function selectReadySectionFlushes(query: PendingFlushQuery): ReadySectionFlush[] {
|
|
85
|
+
const { now, maxBufferMs, sectionHeight } = query
|
|
86
|
+
const ready: ReadySectionFlush[] = []
|
|
87
|
+
|
|
88
|
+
for (const group of pendingSectionGroups(query.pendingKeys, sectionHeight)) {
|
|
89
|
+
const members = new Set(group)
|
|
90
|
+
|
|
91
|
+
let oldestStart = now
|
|
92
|
+
for (const key of group) {
|
|
93
|
+
const startedAt = query.startedAt(key) ?? now
|
|
94
|
+
if (startedAt < oldestStart) oldestStart = startedAt
|
|
95
|
+
}
|
|
96
|
+
const expired = now - oldestStart >= maxBufferMs
|
|
97
|
+
|
|
98
|
+
let waitingOnNeighbor = false
|
|
99
|
+
if (!expired) {
|
|
100
|
+
for (const key of group) {
|
|
101
|
+
for (const neighbor of faceNeighborKeys(key, sectionHeight)) {
|
|
102
|
+
if (members.has(neighbor)) continue
|
|
103
|
+
const neighborOutstanding = query.isTopologyOutstanding ? query.isTopologyOutstanding(neighbor) : query.isOutstanding(neighbor)
|
|
104
|
+
if (neighborOutstanding && query.hasSectionObject(neighbor)) {
|
|
105
|
+
waitingOnNeighbor = true
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (waitingOnNeighbor) break
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (waitingOnNeighbor) continue
|
|
114
|
+
const reason: PendingFlushReason = expired ? 'deadline' : 'group-complete'
|
|
115
|
+
for (const key of group) ready.push({ key, reason })
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return ready
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function selectReadySectionUpdates(query: PendingFlushQuery): string[] {
|
|
122
|
+
return selectReadySectionFlushes(query).map(item => item.key)
|
|
123
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/** Packed 2048-byte nibble section ↔ unpacked 4096-byte per-block values. */
|
|
3
|
+
|
|
4
|
+
export const PACKED_LIGHT_SECTION_BYTES = 2048
|
|
5
|
+
export const UNPACKED_LIGHT_SECTION_BYTES = 4096
|
|
6
|
+
|
|
7
|
+
export function packUnpackedLightSection(unpacked: Uint8Array): Uint8Array {
|
|
8
|
+
const packed = new Uint8Array(PACKED_LIGHT_SECTION_BYTES)
|
|
9
|
+
const n = Math.min(unpacked.length, UNPACKED_LIGHT_SECTION_BYTES)
|
|
10
|
+
for (let i = 0; i < n; i++) {
|
|
11
|
+
const v = unpacked[i]! & 0x0f
|
|
12
|
+
const byte = i >> 1
|
|
13
|
+
packed[byte] = i & 1 ? (packed[byte]! & 0x0f) | (v << 4) : (packed[byte]! & 0xf0) | v
|
|
14
|
+
}
|
|
15
|
+
return packed
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function unpackPackedLightSection(packed: Uint8Array): Uint8Array {
|
|
19
|
+
const unpacked = new Uint8Array(UNPACKED_LIGHT_SECTION_BYTES)
|
|
20
|
+
const n = Math.min(packed.length, PACKED_LIGHT_SECTION_BYTES)
|
|
21
|
+
for (let i = 0; i < n * 2 && i < UNPACKED_LIGHT_SECTION_BYTES; i++) {
|
|
22
|
+
const byte = packed[i >> 1] ?? 0
|
|
23
|
+
unpacked[i] = i & 1 ? byte >> 4 : byte & 0x0f
|
|
24
|
+
}
|
|
25
|
+
return unpacked
|
|
26
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const mcDataToNode = require('minecraft-data/lib/loader')
|
|
12
12
|
const indexer = require('minecraft-data/lib/indexer.js')
|
|
13
13
|
|
|
14
|
-
function createVersion
|
|
14
|
+
function createVersion(type, suppliedVersion, protocolVersions) {
|
|
15
15
|
const versions = indexer.buildIndexFromArray(protocolVersions, 'minecraftVersion') ?? {}
|
|
16
16
|
// Allows comparisons against majorVersion even if `other` is not present in the versions list (e.g. 1.17.0 exists but not 1.17)
|
|
17
17
|
for (const key in versions) {
|
|
@@ -23,12 +23,14 @@ function createVersion (type, suppliedVersion, protocolVersions) {
|
|
|
23
23
|
// the supplied dataVersion is authoritative: the current version doesn't have to be present in the supplied list
|
|
24
24
|
self.dataVersion = suppliedVersion.dataVersion ?? versions[suppliedVersion.minecraftVersion]?.dataVersion
|
|
25
25
|
const v1 = self.dataVersion ?? 0
|
|
26
|
-
const raise = other => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
self['
|
|
30
|
-
self['
|
|
31
|
-
self['
|
|
26
|
+
const raise = other => {
|
|
27
|
+
throw new RangeError(`Version '${other}' not found in protocolVersions supplied by the main thread for ${type}`)
|
|
28
|
+
}
|
|
29
|
+
self['>='] = other => (versions[other] ? v1 >= versions[other].dataVersion : raise(other))
|
|
30
|
+
self['>'] = other => (versions[other] ? v1 > versions[other].dataVersion : raise(other))
|
|
31
|
+
self['<'] = other => (versions[other] ? v1 < versions[other].dataVersion : raise(other))
|
|
32
|
+
self['<='] = other => (versions[other] ? v1 <= versions[other].dataVersion : raise(other))
|
|
33
|
+
self['=='] = other => (versions[other] ? v1 === versions[other].dataVersion : raise(other))
|
|
32
34
|
self.type = type
|
|
33
35
|
self.majorVersion = suppliedVersion.majorVersion
|
|
34
36
|
return self
|
|
@@ -44,7 +44,12 @@ describe('buildElementRotation', () => {
|
|
|
44
44
|
|
|
45
45
|
it('treats a rotation without any angle as no rotation', () => {
|
|
46
46
|
const { localMatrix, localShift } = buildElementRotation({ origin })
|
|
47
|
-
localMatrix.forEach((row, i) =>
|
|
47
|
+
localMatrix.forEach((row, i) =>
|
|
48
|
+
close(
|
|
49
|
+
row,
|
|
50
|
+
[0, 1, 2].map(j => (i === j ? 1 : 0))
|
|
51
|
+
)
|
|
52
|
+
)
|
|
48
53
|
close(localShift, [0, 0, 0])
|
|
49
54
|
})
|
|
50
55
|
})
|
|
@@ -87,8 +87,53 @@ export type MesherGeometryOutput = {
|
|
|
87
87
|
visibilitySet?: number
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export type MeshGeometryMode = 'owner' | 'legacyBootstrap'
|
|
91
|
+
|
|
92
|
+
export type MesherGeometryVersionFields = {
|
|
93
|
+
sessionEpoch?: number
|
|
94
|
+
columnIncarnation?: number
|
|
95
|
+
requestId?: number
|
|
96
|
+
topologyRevision?: number
|
|
97
|
+
lightPublicationVersion?: number
|
|
98
|
+
worldGeneration?: number
|
|
99
|
+
neighborTopologyRevisions?: Array<{ key: string; topologyRevision: number }>
|
|
100
|
+
meshMode?: MeshGeometryMode
|
|
101
|
+
clientLightRequestId?: number
|
|
102
|
+
clientLightEditSeq?: number
|
|
103
|
+
clientLightSessionEpoch?: number
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type MesherWorkerDirtyMessage = {
|
|
107
|
+
type: 'dirty'
|
|
108
|
+
x: number
|
|
109
|
+
y: number
|
|
110
|
+
z: number
|
|
111
|
+
value?: boolean
|
|
112
|
+
config?: MesherConfig
|
|
113
|
+
} & MesherGeometryVersionFields
|
|
114
|
+
|
|
115
|
+
export type MesherWorkerOwnerPublicationMessage = {
|
|
116
|
+
type: 'applyOwnerLightPublication'
|
|
117
|
+
worldGeneration: number
|
|
118
|
+
publicationVersion: number
|
|
119
|
+
sessionEpoch?: number
|
|
120
|
+
sections: Array<{ sx: number; sy: number; sz: number; blockLight: Uint8Array; skyLight?: Uint8Array }>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type MesherWorkerRevertOwnerMessage = {
|
|
124
|
+
type: 'revertOwnerLightToIncoming'
|
|
125
|
+
sessionEpoch: number
|
|
126
|
+
worldGeneration: number
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type MesherWorkerOwnerFenceMessage = {
|
|
130
|
+
type: 'setOwnerAcceptFence'
|
|
131
|
+
sessionEpoch: number
|
|
132
|
+
rejectOwnerPublications: boolean
|
|
133
|
+
}
|
|
134
|
+
|
|
90
135
|
export interface MesherMainEvents {
|
|
91
|
-
geometry: { type: 'geometry'; key: string; geometry: MesherGeometryOutput; workerIndex: number }
|
|
136
|
+
geometry: { type: 'geometry'; key: string; geometry: MesherGeometryOutput; workerIndex: number } & MesherGeometryVersionFields
|
|
92
137
|
sectionFinished: {
|
|
93
138
|
type: 'sectionFinished'
|
|
94
139
|
key: string
|
|
@@ -108,6 +153,8 @@ export interface MesherMainEvents {
|
|
|
108
153
|
// Per-event counts for the column-mode conversion cache.
|
|
109
154
|
preCacheHits?: number
|
|
110
155
|
preCacheMisses?: number
|
|
156
|
+
preWasmParseHits?: number
|
|
157
|
+
preWasmParseMisses?: number
|
|
111
158
|
chunkCount?: number
|
|
112
159
|
worldColumns3x3?: number
|
|
113
160
|
parsedCache3x3?: number
|
|
@@ -35,6 +35,7 @@ import { disposeObject } from './threeJsUtils'
|
|
|
35
35
|
import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
|
|
36
36
|
import { getSignTexture, releaseSignTexture, disposeAllSignTextures } from './signTextureCache'
|
|
37
37
|
import { BlockEntityLightRegistry } from '../lib/blockEntityLightRegistry'
|
|
38
|
+
import { isClientLightTraceEnabled, recordClientLightTraceGpuSample } from '../lighting/debug/clientLightTrace'
|
|
38
39
|
import { SectionOcclusionCull, hsvToRgb } from './occlusion/sectionOcclusionCull'
|
|
39
40
|
|
|
40
41
|
export interface ChunkMeshPool {
|
|
@@ -285,6 +286,15 @@ export class ChunkMeshManager {
|
|
|
285
286
|
if (this.globalLegacyShaderMaterial) setLegacySkyLevel(this.globalLegacyShaderMaterial, value)
|
|
286
287
|
if (this.globalLegacyBlendShaderMaterial) setLegacySkyLevel(this.globalLegacyBlendShaderMaterial, value)
|
|
287
288
|
this.blockEntityLightRegistry.setSkyLevel(value)
|
|
289
|
+
this.worldRenderer.entityLightController?.setSkyLevel(value)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
getSkyLevel(): number {
|
|
293
|
+
return this.blockEntityLightRegistry.getSkyLevel()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getLightmapParams(): BlockLightmapParams {
|
|
297
|
+
return this.blockEntityLightRegistry.getLightmapParams()
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
setShadingTheme(theme: 'vanilla' | 'high-contrast', cardinalLight: string): void {
|
|
@@ -300,6 +310,7 @@ export class ChunkMeshManager {
|
|
|
300
310
|
if (this.globalLegacyShaderMaterial) setLegacyLightmapParams(this.globalLegacyShaderMaterial, params)
|
|
301
311
|
if (this.globalLegacyBlendShaderMaterial) setLegacyLightmapParams(this.globalLegacyBlendShaderMaterial, params)
|
|
302
312
|
this.blockEntityLightRegistry.setLightmapParams(params)
|
|
313
|
+
this.worldRenderer.entityLightController?.setLightmapParams(params)
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
private getLegacyShaderMaterial(): THREE.ShaderMaterial {
|
|
@@ -591,6 +602,21 @@ export class ChunkMeshManager {
|
|
|
591
602
|
gb.setVisibleSpans(spans)
|
|
592
603
|
}
|
|
593
604
|
|
|
605
|
+
if (isClientLightTraceEnabled()) {
|
|
606
|
+
const cubeFaces = gb?.getVisibleFaceCount() ?? 0
|
|
607
|
+
const legacyQuads = (opaqueBuf?.getVisibleQuadCount() ?? 0) + (blendBuf?.getVisibleQuadCount() ?? 0)
|
|
608
|
+
const drawableFaces = cubeFaces + legacyQuads
|
|
609
|
+
recordClientLightTraceGpuSample('gpuDrawn', drawableFaces, () => ({
|
|
610
|
+
phase: 'gpuDrawn',
|
|
611
|
+
drawableFaces,
|
|
612
|
+
pendingReplace: !!(gb?.hasPendingReplace() || opaqueBuf?.hasPendingReplace() || blendBuf?.hasPendingReplace()),
|
|
613
|
+
pendingMove: !!(gb?.getPendingMove() || opaqueBuf?.getPendingMove() || blendBuf?.getPendingMove()),
|
|
614
|
+
unuploadedRanges:
|
|
615
|
+
(gb?.getPendingDirtyRanges().length ?? 0) + (opaqueBuf?.getPendingDirtyRanges().length ?? 0) + (blendBuf?.getPendingDirtyRanges().length ?? 0),
|
|
616
|
+
cullReason: smartCull ? 'frustum+occlusion' : 'frustum'
|
|
617
|
+
}))
|
|
618
|
+
}
|
|
619
|
+
|
|
594
620
|
this.lastBufferStateKey = this.bufferStateKey()
|
|
595
621
|
this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ, smartCull, occlusionVisible)
|
|
596
622
|
}
|
package/src/three/entities.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { inferModelType, loadCapeToCanvas, loadEarsToCanvasFromSkin } from 'skin
|
|
|
9
9
|
import { flat, fromFormattedString } from '@xmcl/text-component'
|
|
10
10
|
import mojangson from 'mojangson'
|
|
11
11
|
import { snakeCase } from 'change-case'
|
|
12
|
-
import { Item } from 'prismarine-item'
|
|
13
12
|
import { isEntityAttackable } from 'mineflayer-mouse/dist/attackableEntity'
|
|
14
13
|
import { Team } from 'mineflayer'
|
|
15
14
|
import PrismarineChatLoader from 'prismarine-chat'
|
|
@@ -34,6 +33,7 @@ import { releaseVehiclePassengerPosition } from './entity/vehiclePassengerRender
|
|
|
34
33
|
import { updateVehiclePassengerPositions as applyVehiclePassengerPositions } from './entity/vehiclePassengerUpdate'
|
|
35
34
|
import { applyNetworkHeadPitch, storeNetworkHeadPitch, storeNetworkHeadYaw } from './entity/networkHeadPitchRendering'
|
|
36
35
|
import { processRemoteBoatPassengerRotations, type RemoteBoatPassengerEntity } from './entity/remoteBoatPassengerRotation'
|
|
36
|
+
import { isClientLightTraceEnabled, recordClientLightTrace } from '../lighting/debug/clientLightTrace'
|
|
37
37
|
import {
|
|
38
38
|
ENTITY_TWEEN_DURATION_MS,
|
|
39
39
|
applyLocalHorseCameraYawLock,
|
|
@@ -59,16 +59,11 @@ import {
|
|
|
59
59
|
type HorseHeadRig
|
|
60
60
|
} from './entity/horseHeadAnimation'
|
|
61
61
|
import { WalkingGeneralSwing } from './entity/animations'
|
|
62
|
-
import {
|
|
63
|
-
LEG_ANIMATION_USER_DATA_KEY,
|
|
64
|
-
LEG_RIG_USER_DATA_KEY,
|
|
65
|
-
applyLegRig,
|
|
66
|
-
createLegRig,
|
|
67
|
-
type LegRig
|
|
68
|
-
} from './entity/legWalkAnimation'
|
|
62
|
+
import { LEG_ANIMATION_USER_DATA_KEY, LEG_RIG_USER_DATA_KEY, applyLegRig, createLegRig, type LegRig } from './entity/legWalkAnimation'
|
|
69
63
|
import { disposeObject, loadNearestFilterTexture, loadTexture, loadThreeJsTextureFromUrl } from './threeJsUtils'
|
|
70
64
|
import { armorModel, armorTextures, elytraTexture } from './entity/armorModels'
|
|
71
65
|
import { WorldRendererThree } from './worldRendererThree'
|
|
66
|
+
import type { EntityLightMeta } from '../lighting/entity/entityLightController'
|
|
72
67
|
import { IndexedData } from 'minecraft-data'
|
|
73
68
|
import { ItemSpecificContextProperties } from '../playerState/types'
|
|
74
69
|
|
|
@@ -387,6 +382,8 @@ function getEntityMesh(
|
|
|
387
382
|
return cube
|
|
388
383
|
}
|
|
389
384
|
|
|
385
|
+
type EquipmentItem = import('prismarine-entity').Entity['equipment'][number]
|
|
386
|
+
|
|
390
387
|
export type SceneEntity = THREE.Object3D & {
|
|
391
388
|
playerObject?: PlayerObjectType
|
|
392
389
|
username?: string
|
|
@@ -476,9 +473,12 @@ export class Entities {
|
|
|
476
473
|
}
|
|
477
474
|
|
|
478
475
|
this.updateEntityEquipment(this.playerEntity, playerData)
|
|
476
|
+
this.registerEntityLight(this.playerEntity, this.playerEntity.originalEntity)
|
|
477
|
+
this.invalidateEntityLight(this.playerEntity)
|
|
479
478
|
}
|
|
480
479
|
|
|
481
480
|
clear() {
|
|
481
|
+
this.worldRenderer.entityLightController?.clear()
|
|
482
482
|
for (const mesh of Object.values(this.entities)) {
|
|
483
483
|
this.worldRenderer.sceneOrigin.removeAndUntrack(mesh)
|
|
484
484
|
disposeObject(mesh)
|
|
@@ -658,6 +658,7 @@ export class Entities {
|
|
|
658
658
|
this.applyRemoteBoatPassengerRotations()
|
|
659
659
|
this.applyLocalThirdPersonPlayerRotation()
|
|
660
660
|
this.updateBoatPaddleAnimations(dt)
|
|
661
|
+
this.applyEntityLights()
|
|
661
662
|
}
|
|
662
663
|
|
|
663
664
|
private applyRemoteBoatPassengerRotations() {
|
|
@@ -1050,6 +1051,7 @@ export class Entities {
|
|
|
1050
1051
|
if (!playerObject || !playerObject.playerObject?.animation) return
|
|
1051
1052
|
const anim = playerObject.playerObject.animation as any
|
|
1052
1053
|
if (anim.swingArm) {
|
|
1054
|
+
if (isClientLightTraceEnabled()) recordClientLightTrace({ phase: 'inputHand' })
|
|
1053
1055
|
anim.swingArm()
|
|
1054
1056
|
}
|
|
1055
1057
|
}
|
|
@@ -1062,6 +1064,7 @@ export class Entities {
|
|
|
1062
1064
|
if (!anim) return
|
|
1063
1065
|
|
|
1064
1066
|
if (animation === 'oneSwing') {
|
|
1067
|
+
if (isClientLightTraceEnabled()) recordClientLightTrace({ phase: 'inputHand' })
|
|
1065
1068
|
anim.swingArm()
|
|
1066
1069
|
return
|
|
1067
1070
|
}
|
|
@@ -1212,6 +1215,7 @@ export class Entities {
|
|
|
1212
1215
|
if (c['additionalCleanup']) c['additionalCleanup']()
|
|
1213
1216
|
})
|
|
1214
1217
|
this.onRemoveEntity(entity)
|
|
1218
|
+
this.worldRenderer.entityLightController?.unregister(e)
|
|
1215
1219
|
this.worldRenderer.sceneOrigin.removeAndUntrack(e)
|
|
1216
1220
|
disposeObject(e)
|
|
1217
1221
|
// todo dispose textures as well ?
|
|
@@ -1541,6 +1545,12 @@ export class Entities {
|
|
|
1541
1545
|
this.updateNameTagVisibility(e)
|
|
1542
1546
|
|
|
1543
1547
|
this.updateEntityPosition(entity, justAdded, overrides)
|
|
1548
|
+
if (justAdded) {
|
|
1549
|
+
this.registerEntityLight(e, entity)
|
|
1550
|
+
} else {
|
|
1551
|
+
this.worldRenderer.entityLightController?.setMeta(e, this.entityLightMeta(entity))
|
|
1552
|
+
this.invalidateEntityLight(e)
|
|
1553
|
+
}
|
|
1544
1554
|
}
|
|
1545
1555
|
|
|
1546
1556
|
applyEntityRenderHints(e: SceneEntity, entity: SceneEntity['originalEntity']) {
|
|
@@ -1784,7 +1794,7 @@ export class Entities {
|
|
|
1784
1794
|
return texture
|
|
1785
1795
|
}
|
|
1786
1796
|
|
|
1787
|
-
addItemModel(entityMesh: SceneEntity, hand: 'left' | 'right', item:
|
|
1797
|
+
addItemModel(entityMesh: SceneEntity, hand: 'left' | 'right', item: EquipmentItem, isPlayer = false) {
|
|
1788
1798
|
const bedrockParentName = `bone_${hand}item`
|
|
1789
1799
|
const itemName = `custom_item_${hand}`
|
|
1790
1800
|
|
|
@@ -1837,8 +1847,52 @@ export class Entities {
|
|
|
1837
1847
|
}
|
|
1838
1848
|
}
|
|
1839
1849
|
|
|
1850
|
+
onEntityMeshReady(mesh: THREE.Object3D) {
|
|
1851
|
+
this.worldRenderer.entityLightController?.invalidateFromDescendant(mesh)
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
private entityLightSharedMaterials() {
|
|
1855
|
+
return new Set<THREE.Material>([this.worldRenderer.material])
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
private entityLightMeta(entity: SceneEntity['originalEntity']): EntityLightMeta {
|
|
1859
|
+
const localPlayerId = this.playerEntity?.originalEntity.id
|
|
1860
|
+
const useLocalEyeHeight = entity.name === 'player' && localPlayerId !== undefined && entity.id === localPlayerId
|
|
1861
|
+
return {
|
|
1862
|
+
name: entity.name,
|
|
1863
|
+
height: entity.height,
|
|
1864
|
+
eyeHeight: useLocalEyeHeight ? this.worldRenderer.playerStateReactive.eyeHeight : undefined
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
private registerEntityLight(root: SceneEntity, entity: SceneEntity['originalEntity']) {
|
|
1869
|
+
this.worldRenderer.entityLightController?.register(root, this.entityLightMeta(entity), this.entityLightSharedMaterials())
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
private invalidateEntityLight(root: THREE.Object3D) {
|
|
1873
|
+
this.worldRenderer.entityLightController?.invalidateMaterials(root)
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
private applyEntityLights() {
|
|
1877
|
+
const controller = this.worldRenderer.entityLightController
|
|
1878
|
+
if (!controller) return
|
|
1879
|
+
const shared = this.entityLightSharedMaterials()
|
|
1880
|
+
const apply = (root: SceneEntity | null) => {
|
|
1881
|
+
if (!root) return
|
|
1882
|
+
if (root === this.playerEntity) {
|
|
1883
|
+
controller.setMeta(root, this.entityLightMeta(root.originalEntity))
|
|
1884
|
+
}
|
|
1885
|
+
const worldPos = this.worldRenderer.sceneOrigin.getWorldPosition(root)
|
|
1886
|
+
if (!worldPos) return
|
|
1887
|
+
controller.update(root, worldPos, shared)
|
|
1888
|
+
}
|
|
1889
|
+
for (const entity of Object.values(this.entities)) apply(entity)
|
|
1890
|
+
apply(this.playerEntity)
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1840
1893
|
handleDamageEvent(entityId, damageAmount) {
|
|
1841
|
-
const
|
|
1894
|
+
const entity = this.entities[entityId]
|
|
1895
|
+
const entityMesh = entity?.children.find(c => c.name === 'mesh')
|
|
1842
1896
|
if (entityMesh) {
|
|
1843
1897
|
entityMesh.traverse(child => {
|
|
1844
1898
|
if (child instanceof THREE.Mesh && child.material.clone) {
|
|
@@ -1851,6 +1905,18 @@ export class Entities {
|
|
|
1851
1905
|
}
|
|
1852
1906
|
})
|
|
1853
1907
|
}
|
|
1908
|
+
if (entity) {
|
|
1909
|
+
this.worldRenderer.entityLightController?.markFlash(entity)
|
|
1910
|
+
this.invalidateEntityLight(entity)
|
|
1911
|
+
setTimeout(() => {
|
|
1912
|
+
if (!this.entities[entityId]) return
|
|
1913
|
+
this.worldRenderer.entityLightController?.endFlash(entity)
|
|
1914
|
+
const worldPos = this.worldRenderer.sceneOrigin.getWorldPosition(entity)
|
|
1915
|
+
if (worldPos) {
|
|
1916
|
+
this.worldRenderer.entityLightController?.update(entity, worldPos, this.entityLightSharedMaterials())
|
|
1917
|
+
}
|
|
1918
|
+
}, 500)
|
|
1919
|
+
}
|
|
1854
1920
|
}
|
|
1855
1921
|
|
|
1856
1922
|
raycastSceneDebug() {
|
|
@@ -1944,7 +2010,7 @@ function getSpecificEntityMetadata<T extends keyof EntityMetadataVersions>(name:
|
|
|
1944
2010
|
return getGeneralEntitiesMetadata(entity, mcData) as any
|
|
1945
2011
|
}
|
|
1946
2012
|
|
|
1947
|
-
function addArmorModel(worldRenderer: WorldRendererThree, entityMesh: THREE.Object3D, slotType: string, item:
|
|
2013
|
+
function addArmorModel(worldRenderer: WorldRendererThree, entityMesh: THREE.Object3D, slotType: string, item: EquipmentItem, layer = 1, overlay = false) {
|
|
1948
2014
|
if (!item) {
|
|
1949
2015
|
removeArmorModel(entityMesh, slotType)
|
|
1950
2016
|
return
|
|
@@ -53,7 +53,12 @@ test('calculateLegSwing follows vanilla gait and is zero at rest', () => {
|
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
test('assignLegPhases alternates bipeds and pairs quadruped diagonals', () => {
|
|
56
|
-
expect(
|
|
56
|
+
expect(
|
|
57
|
+
assignLegPhases([
|
|
58
|
+
{ x: -1.9, z: 0 },
|
|
59
|
+
{ x: 1.9, z: 0 }
|
|
60
|
+
])
|
|
61
|
+
).toEqual([Math.PI, 0])
|
|
57
62
|
// leg0..leg3 as hind-right, hind-left, front-right, front-left
|
|
58
63
|
const quad = assignLegPhases([
|
|
59
64
|
{ x: -4, z: 7 },
|
|
@@ -82,7 +87,11 @@ test('bedrock legs rotate about their own pivot even when parented to an offset
|
|
|
82
87
|
const pivot = new THREE.Vector3(...(leg.pivot as [number, number, number]))
|
|
83
88
|
const expectedAngle = calculateLegSwing(0.4 * Math.PI, 1, rig.legs[i].phase)
|
|
84
89
|
const foot = new THREE.Vector3(pivot.x, pivot.y - 12, pivot.z)
|
|
85
|
-
const expected = foot
|
|
90
|
+
const expected = foot
|
|
91
|
+
.clone()
|
|
92
|
+
.sub(pivot)
|
|
93
|
+
.applyAxisAngle(new THREE.Vector3(1, 0, 0), expectedAngle)
|
|
94
|
+
.add(pivot)
|
|
86
95
|
mesh.getVertexPosition(i, vertex).multiplyScalar(16)
|
|
87
96
|
expect(close(vertex, expected), `leg ${i}`).toBe(true)
|
|
88
97
|
// the pivot itself never moves
|
|
@@ -9,15 +9,7 @@ export const LEG_SWING_FREQUENCY = 0.6662
|
|
|
9
9
|
export const LEG_SWING_AMPLITUDE = 1.4
|
|
10
10
|
|
|
11
11
|
/** Entities whose legs are not driven by ground walking (fly, hop, swim or use bespoke animations). */
|
|
12
|
-
const LEG_ANIMATION_EXCLUDED = new Set([
|
|
13
|
-
'armor_stand',
|
|
14
|
-
'axolotl',
|
|
15
|
-
'ender_dragon',
|
|
16
|
-
'frog',
|
|
17
|
-
'parrot',
|
|
18
|
-
'rabbit',
|
|
19
|
-
'turtle'
|
|
20
|
-
])
|
|
12
|
+
const LEG_ANIMATION_EXCLUDED = new Set(['armor_stand', 'axolotl', 'ender_dragon', 'frog', 'parrot', 'rabbit', 'turtle'])
|
|
21
13
|
|
|
22
14
|
const LEG_NAME_PATTERN = /leg/i
|
|
23
15
|
|