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,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,9 +59,11 @@ import {
|
|
|
59
59
|
type HorseHeadRig
|
|
60
60
|
} from './entity/horseHeadAnimation'
|
|
61
61
|
import { WalkingGeneralSwing } from './entity/animations'
|
|
62
|
+
import { LEG_ANIMATION_USER_DATA_KEY, LEG_RIG_USER_DATA_KEY, applyLegRig, createLegRig, type LegRig } from './entity/legWalkAnimation'
|
|
62
63
|
import { disposeObject, loadNearestFilterTexture, loadTexture, loadThreeJsTextureFromUrl } from './threeJsUtils'
|
|
63
64
|
import { armorModel, armorTextures, elytraTexture } from './entity/armorModels'
|
|
64
65
|
import { WorldRendererThree } from './worldRendererThree'
|
|
66
|
+
import type { EntityLightMeta } from '../lighting/entity/entityLightController'
|
|
65
67
|
import { IndexedData } from 'minecraft-data'
|
|
66
68
|
import { ItemSpecificContextProperties } from '../playerState/types'
|
|
67
69
|
|
|
@@ -380,6 +382,8 @@ function getEntityMesh(
|
|
|
380
382
|
return cube
|
|
381
383
|
}
|
|
382
384
|
|
|
385
|
+
type EquipmentItem = import('prismarine-entity').Entity['equipment'][number]
|
|
386
|
+
|
|
383
387
|
export type SceneEntity = THREE.Object3D & {
|
|
384
388
|
playerObject?: PlayerObjectType
|
|
385
389
|
username?: string
|
|
@@ -469,9 +473,12 @@ export class Entities {
|
|
|
469
473
|
}
|
|
470
474
|
|
|
471
475
|
this.updateEntityEquipment(this.playerEntity, playerData)
|
|
476
|
+
this.registerEntityLight(this.playerEntity, this.playerEntity.originalEntity)
|
|
477
|
+
this.invalidateEntityLight(this.playerEntity)
|
|
472
478
|
}
|
|
473
479
|
|
|
474
480
|
clear() {
|
|
481
|
+
this.worldRenderer.entityLightController?.clear()
|
|
475
482
|
for (const mesh of Object.values(this.entities)) {
|
|
476
483
|
this.worldRenderer.sceneOrigin.removeAndUntrack(mesh)
|
|
477
484
|
disposeObject(mesh)
|
|
@@ -592,18 +599,27 @@ export class Entities {
|
|
|
592
599
|
|
|
593
600
|
const horseHeadRig = entity.userData[HORSE_HEAD_RIG_USER_DATA_KEY] as HorseHeadRig | undefined
|
|
594
601
|
const horseAnimation = entity.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] as HorseHeadAnimationState | undefined
|
|
602
|
+
let limbAnimation: ReturnType<typeof updateHorseHeadAnimationFrame> | undefined
|
|
595
603
|
if (horseHeadRig && horseAnimation) {
|
|
596
|
-
|
|
604
|
+
limbAnimation = updateHorseHeadAnimationFrame(horseAnimation, dt)
|
|
597
605
|
const pose = calculateHorseHeadPose({
|
|
598
606
|
entityPitch: entity.userData._horseHeadPitch ?? 0,
|
|
599
607
|
headYaw: entity.userData._horseHeadYaw ?? entity.userData._horseBodyYaw ?? 0,
|
|
600
608
|
bodyYaw: entity.userData._horseBodyYaw ?? 0,
|
|
601
|
-
limbSwing:
|
|
602
|
-
limbSwingAmount:
|
|
609
|
+
limbSwing: limbAnimation.limbSwing,
|
|
610
|
+
limbSwingAmount: limbAnimation.limbSwingAmount
|
|
603
611
|
})
|
|
604
612
|
applyHorseHeadPose(horseHeadRig, pose)
|
|
605
613
|
}
|
|
606
614
|
|
|
615
|
+
const legRig = entity.userData[LEG_RIG_USER_DATA_KEY] as LegRig | undefined
|
|
616
|
+
const legAnimation = entity.userData[LEG_ANIMATION_USER_DATA_KEY] as HorseHeadAnimationState | undefined
|
|
617
|
+
if (legRig && legAnimation) {
|
|
618
|
+
// Horses share one state between head and legs, it must only be advanced once per frame.
|
|
619
|
+
limbAnimation ??= updateHorseHeadAnimationFrame(legAnimation, dt)
|
|
620
|
+
applyLegRig(legRig, limbAnimation.limbSwing, limbAnimation.limbSwingAmount)
|
|
621
|
+
}
|
|
622
|
+
|
|
607
623
|
if (!isPlayerEntity && playerObject) {
|
|
608
624
|
applyNetworkHeadPitch(playerObject, entity.userData)
|
|
609
625
|
}
|
|
@@ -642,6 +658,7 @@ export class Entities {
|
|
|
642
658
|
this.applyRemoteBoatPassengerRotations()
|
|
643
659
|
this.applyLocalThirdPersonPlayerRotation()
|
|
644
660
|
this.updateBoatPaddleAnimations(dt)
|
|
661
|
+
this.applyEntityLights()
|
|
645
662
|
}
|
|
646
663
|
|
|
647
664
|
private applyRemoteBoatPassengerRotations() {
|
|
@@ -1034,6 +1051,7 @@ export class Entities {
|
|
|
1034
1051
|
if (!playerObject || !playerObject.playerObject?.animation) return
|
|
1035
1052
|
const anim = playerObject.playerObject.animation as any
|
|
1036
1053
|
if (anim.swingArm) {
|
|
1054
|
+
if (isClientLightTraceEnabled()) recordClientLightTrace({ phase: 'inputHand' })
|
|
1037
1055
|
anim.swingArm()
|
|
1038
1056
|
}
|
|
1039
1057
|
}
|
|
@@ -1046,6 +1064,7 @@ export class Entities {
|
|
|
1046
1064
|
if (!anim) return
|
|
1047
1065
|
|
|
1048
1066
|
if (animation === 'oneSwing') {
|
|
1067
|
+
if (isClientLightTraceEnabled()) recordClientLightTrace({ phase: 'inputHand' })
|
|
1049
1068
|
anim.swingArm()
|
|
1050
1069
|
return
|
|
1051
1070
|
}
|
|
@@ -1187,6 +1206,8 @@ export class Entities {
|
|
|
1187
1206
|
e.userData._rotTween?.stop()
|
|
1188
1207
|
e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] = undefined
|
|
1189
1208
|
e.userData[HORSE_HEAD_RIG_USER_DATA_KEY] = undefined
|
|
1209
|
+
e.userData[LEG_ANIMATION_USER_DATA_KEY] = undefined
|
|
1210
|
+
e.userData[LEG_RIG_USER_DATA_KEY] = undefined
|
|
1190
1211
|
const boatMesh = e.children.find(c => c.name === 'mesh')
|
|
1191
1212
|
if (boatMesh) disposeBoatWaterPatch(boatMesh)
|
|
1192
1213
|
if (e.additionalCleanup) e.additionalCleanup()
|
|
@@ -1194,6 +1215,7 @@ export class Entities {
|
|
|
1194
1215
|
if (c['additionalCleanup']) c['additionalCleanup']()
|
|
1195
1216
|
})
|
|
1196
1217
|
this.onRemoveEntity(entity)
|
|
1218
|
+
this.worldRenderer.entityLightController?.unregister(e)
|
|
1197
1219
|
this.worldRenderer.sceneOrigin.removeAndUntrack(e)
|
|
1198
1220
|
disposeObject(e)
|
|
1199
1221
|
// todo dispose textures as well ?
|
|
@@ -1320,6 +1342,11 @@ export class Entities {
|
|
|
1320
1342
|
e.userData[HORSE_HEAD_RIG_USER_DATA_KEY] = horseHeadRig
|
|
1321
1343
|
e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] = createHorseHeadAnimationState()
|
|
1322
1344
|
}
|
|
1345
|
+
const legRig = isPlayerModel ? undefined : createLegRig(mesh, entity.name)
|
|
1346
|
+
if (legRig) {
|
|
1347
|
+
e.userData[LEG_RIG_USER_DATA_KEY] = legRig
|
|
1348
|
+
e.userData[LEG_ANIMATION_USER_DATA_KEY] = e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] ?? createHorseHeadAnimationState()
|
|
1349
|
+
}
|
|
1323
1350
|
} else {
|
|
1324
1351
|
mesh = e.children.find(c => c.name === 'mesh')
|
|
1325
1352
|
if (entity.renderHints) {
|
|
@@ -1518,6 +1545,12 @@ export class Entities {
|
|
|
1518
1545
|
this.updateNameTagVisibility(e)
|
|
1519
1546
|
|
|
1520
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
|
+
}
|
|
1521
1554
|
}
|
|
1522
1555
|
|
|
1523
1556
|
applyEntityRenderHints(e: SceneEntity, entity: SceneEntity['originalEntity']) {
|
|
@@ -1596,7 +1629,7 @@ export class Entities {
|
|
|
1596
1629
|
})
|
|
1597
1630
|
.start()
|
|
1598
1631
|
}
|
|
1599
|
-
const horseAnimation = e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] as HorseHeadAnimationState | undefined
|
|
1632
|
+
const horseAnimation = (e.userData[HORSE_HEAD_ANIMATION_USER_DATA_KEY] ?? e.userData[LEG_ANIMATION_USER_DATA_KEY]) as HorseHeadAnimationState | undefined
|
|
1600
1633
|
if (!headRotationOnly && horseAnimation && Number.isFinite(entity.position.x) && Number.isFinite(entity.position.z)) {
|
|
1601
1634
|
setHorseHeadAnimationPosition(horseAnimation, entity.position)
|
|
1602
1635
|
}
|
|
@@ -1761,7 +1794,7 @@ export class Entities {
|
|
|
1761
1794
|
return texture
|
|
1762
1795
|
}
|
|
1763
1796
|
|
|
1764
|
-
addItemModel(entityMesh: SceneEntity, hand: 'left' | 'right', item:
|
|
1797
|
+
addItemModel(entityMesh: SceneEntity, hand: 'left' | 'right', item: EquipmentItem, isPlayer = false) {
|
|
1765
1798
|
const bedrockParentName = `bone_${hand}item`
|
|
1766
1799
|
const itemName = `custom_item_${hand}`
|
|
1767
1800
|
|
|
@@ -1814,8 +1847,52 @@ export class Entities {
|
|
|
1814
1847
|
}
|
|
1815
1848
|
}
|
|
1816
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
|
+
|
|
1817
1893
|
handleDamageEvent(entityId, damageAmount) {
|
|
1818
|
-
const
|
|
1894
|
+
const entity = this.entities[entityId]
|
|
1895
|
+
const entityMesh = entity?.children.find(c => c.name === 'mesh')
|
|
1819
1896
|
if (entityMesh) {
|
|
1820
1897
|
entityMesh.traverse(child => {
|
|
1821
1898
|
if (child instanceof THREE.Mesh && child.material.clone) {
|
|
@@ -1828,6 +1905,18 @@ export class Entities {
|
|
|
1828
1905
|
}
|
|
1829
1906
|
})
|
|
1830
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
|
+
}
|
|
1831
1920
|
}
|
|
1832
1921
|
|
|
1833
1922
|
raycastSceneDebug() {
|
|
@@ -1921,7 +2010,7 @@ function getSpecificEntityMetadata<T extends keyof EntityMetadataVersions>(name:
|
|
|
1921
2010
|
return getGeneralEntitiesMetadata(entity, mcData) as any
|
|
1922
2011
|
}
|
|
1923
2012
|
|
|
1924
|
-
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) {
|
|
1925
2014
|
if (!item) {
|
|
1926
2015
|
removeArmorModel(entityMesh, slotType)
|
|
1927
2016
|
return
|