minecraft-renderer 0.1.54 → 0.1.56
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/mesher.js +54 -54
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +46 -46
- package/dist/minecraft-renderer.js +61 -61
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +972 -972
- package/package.json +1 -1
- package/src/lib/buildWorkerMcDataIndexes.test.ts +48 -0
- package/src/lib/buildWorkerMcDataIndexes.ts +121 -0
- package/src/lib/items.ts +4 -3
- package/src/lib/utils.ts +1 -1
- package/src/lib/workerMessageSanitize.test.ts +14 -0
- package/src/lib/workerMessageSanitize.ts +48 -0
- package/src/lib/workerProxy.restore.test.ts +29 -0
- package/src/lib/workerProxy.ts +110 -36
- package/src/lib/worldrendererCommon.ts +25 -0
- package/src/resourcesManager/resourcesManager.ts +97 -11
- package/src/resourcesManager/resourcesManager.worker.test.ts +61 -0
- package/src/three/appShared.ts +1 -1
- package/src/three/chunkMeshManager.ts +4 -3
- package/src/three/entities.ts +27 -19
- package/src/three/entity/EntityMesh.ts +4 -10
- package/src/three/graphicsBackendBase.ts +25 -15
- package/src/three/graphicsBackendOffThread.ts +27 -3
- package/src/three/hand.ts +3 -3
- package/src/three/itemMesh.ts +20 -7
- package/src/three/menuBackground/assetUrl.ts +15 -0
- package/src/three/menuBackground/classic.ts +11 -5
- package/src/three/menuBackground/futuristic.ts +3 -0
- package/src/three/threeJsMedia.ts +31 -4
- package/src/three/threeJsUtils.ts +11 -0
- package/src/three/threeWorker.ts +12 -11
- package/src/three/worldRendererThree.ts +7 -0
- package/src/worldView/worldView.ts +2 -1
|
@@ -12,8 +12,10 @@ import itemsAtlasLegacy from 'mc-assets/dist/itemsAtlasLegacy.png'
|
|
|
12
12
|
import christmasPack from 'mc-assets/dist/textureReplacements/christmas'
|
|
13
13
|
import { AtlasParser, ItemsAtlasesOutputJson } from 'mc-assets/dist/atlasParser'
|
|
14
14
|
import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
15
|
+
import { isWebWorker } from '../three/documentRenderer'
|
|
15
16
|
import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer'
|
|
16
|
-
import { getLoadedItemDefinitionsStore } from 'mc-assets'
|
|
17
|
+
import { getLoadedItemDefinitionsStore } from 'mc-assets/dist/stores'
|
|
18
|
+
import { sanitizeWorkerEventArgs } from '../lib/workerMessageSanitize'
|
|
17
19
|
|
|
18
20
|
type ResourceManagerEvents = {
|
|
19
21
|
assetsTexturesUpdated: () => void
|
|
@@ -21,16 +23,43 @@ type ResourceManagerEvents = {
|
|
|
21
23
|
assetsInventoryReady: () => void
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
type ItemDefinitionsStore = ReturnType<typeof getLoadedItemDefinitionsStore>
|
|
27
|
+
|
|
28
|
+
let workerItemDefinitionsStore: ItemDefinitionsStore | null = null
|
|
29
|
+
|
|
30
|
+
export function getItemsDefinitionsStoreForRender (
|
|
31
|
+
resources: LoadedResourcesTransferrable
|
|
32
|
+
): ItemDefinitionsStore {
|
|
33
|
+
if (!isWebWorker) {
|
|
34
|
+
ensureItemsDefinitionsStore(resources)
|
|
35
|
+
return resources.itemsDefinitionsStore
|
|
36
|
+
}
|
|
37
|
+
if (!workerItemDefinitionsStore || typeof workerItemDefinitionsStore.get !== 'function') {
|
|
38
|
+
workerItemDefinitionsStore = getLoadedItemDefinitionsStore(itemDefinitionsJson)
|
|
39
|
+
}
|
|
40
|
+
return workerItemDefinitionsStore
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function ensureItemsDefinitionsStore (resources: LoadedResourcesTransferrable): void {
|
|
44
|
+
if (isWebWorker) return
|
|
45
|
+
const store = resources.itemsDefinitionsStore as { get?: unknown }
|
|
46
|
+
if (typeof store?.get === 'function') return
|
|
47
|
+
resources.itemsDefinitionsStore = getLoadedItemDefinitionsStore(
|
|
48
|
+
resources.sourceItemDefinitionsJson ?? itemDefinitionsJson
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
24
52
|
export class LoadedResourcesTransferrable {
|
|
25
53
|
// todo transfer instead!
|
|
26
54
|
readonly sourceItemDefinitionsJson: any = itemDefinitionsJson
|
|
27
|
-
|
|
55
|
+
itemsDefinitionsStore = getLoadedItemDefinitionsStore(this.sourceItemDefinitionsJson)
|
|
28
56
|
|
|
29
57
|
allReady = false
|
|
30
58
|
// Atlas parsers
|
|
31
59
|
itemsAtlasImage!: ImageBitmap
|
|
32
60
|
blocksAtlasImage!: ImageBitmap
|
|
33
61
|
blocksAtlasJson!: ItemsAtlasesOutputJson
|
|
62
|
+
itemsAtlasJson!: ItemsAtlasesOutputJson
|
|
34
63
|
// User data (specific to current resourcepack/version)
|
|
35
64
|
customBlockStates?: Record<string, any>
|
|
36
65
|
customModels?: Record<string, any>
|
|
@@ -54,8 +83,20 @@ export class LoadedResourcesTransferrable {
|
|
|
54
83
|
|
|
55
84
|
constructor(data?: any) {
|
|
56
85
|
if (data) {
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
const safe = { ...data }
|
|
87
|
+
delete safe.itemsDefinitionsStore
|
|
88
|
+
delete safe.sourceItemDefinitionsJson
|
|
89
|
+
Object.assign(this, safe)
|
|
90
|
+
ensureItemsDefinitionsStore(this)
|
|
91
|
+
}
|
|
92
|
+
if (this.version) {
|
|
93
|
+
const globalMc = (globalThis as { loadedData?: IndexedData, mcData?: IndexedData }).loadedData
|
|
94
|
+
?? (globalThis as { mcData?: IndexedData }).mcData
|
|
95
|
+
if (isWebWorker && globalMc?.entitiesByName) {
|
|
96
|
+
this.mcData = globalMc
|
|
97
|
+
} else {
|
|
98
|
+
this.mcData = MinecraftData(this.version)
|
|
99
|
+
}
|
|
59
100
|
// this.itemsRenderer = new ItemsRenderer(
|
|
60
101
|
// this.version,
|
|
61
102
|
// this.blockstatesModels,
|
|
@@ -70,14 +111,18 @@ export class LoadedResourcesTransferrable {
|
|
|
70
111
|
for (const key in this) {
|
|
71
112
|
if (!Object.prototype.hasOwnProperty.call(this, key)) continue
|
|
72
113
|
if (typeof this[key] === 'function') continue
|
|
73
|
-
if (
|
|
74
|
-
|
|
114
|
+
if (
|
|
115
|
+
key === 'itemsRenderer' || key === 'worldBlockProvider' || key === 'mcData'
|
|
116
|
+
|| key === 'itemsDefinitionsStore' || key === 'sourceItemDefinitionsJson'
|
|
117
|
+
) {
|
|
75
118
|
continue
|
|
76
119
|
}
|
|
77
120
|
cloned[key] = this[key as keyof this]
|
|
78
121
|
}
|
|
79
122
|
|
|
80
123
|
cloned.customTextures = {}
|
|
124
|
+
delete cloned.itemsDefinitionsStore
|
|
125
|
+
delete cloned.sourceItemDefinitionsJson
|
|
81
126
|
return cloned as LoadedResourcesTransferrable
|
|
82
127
|
}
|
|
83
128
|
|
|
@@ -106,10 +151,36 @@ const STABLE_MODELS_VERSION = '1.21.4'
|
|
|
106
151
|
export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<ResourceManagerEvents>) {
|
|
107
152
|
static restorerName = 'ResourcesManager'
|
|
108
153
|
|
|
154
|
+
rebuildWorkerRenderers (resources: LoadedResourcesTransferrable): void {
|
|
155
|
+
if (!isWebWorker) return
|
|
156
|
+
if (!resources.version || !resources.blockstatesModels || !resources.blocksAtlasJson) return
|
|
157
|
+
|
|
158
|
+
this.blocksAtlasParser = new AtlasParser({ latest: resources.blocksAtlasJson }, '')
|
|
159
|
+
if (resources.itemsAtlasJson) {
|
|
160
|
+
this.itemsAtlasParser = new AtlasParser({ latest: resources.itemsAtlasJson }, '')
|
|
161
|
+
} else if (!this.itemsAtlasParser?.atlas?.latest) {
|
|
162
|
+
if (!this.sourceItemsAtlases || Object.keys(this.sourceItemsAtlases).length === 0) return
|
|
163
|
+
this.itemsAtlasParser = new AtlasParser(this.sourceItemsAtlases, '')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
resources.itemsRenderer = new ItemsRenderer(
|
|
167
|
+
resources.version,
|
|
168
|
+
resources.blockstatesModels,
|
|
169
|
+
this.itemsAtlasParser,
|
|
170
|
+
this.blocksAtlasParser
|
|
171
|
+
)
|
|
172
|
+
resources.worldBlockProvider = worldBlockProvider(
|
|
173
|
+
resources.blockstatesModels,
|
|
174
|
+
this.blocksAtlasParser.atlas,
|
|
175
|
+
STABLE_MODELS_VERSION
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
109
179
|
static restoreTransferred(data: any, worker?: Worker) {
|
|
110
180
|
const resourcesManager = new ResourcesManager()
|
|
111
|
-
const upResources = (
|
|
112
|
-
resourcesManager.currentResources = new LoadedResourcesTransferrable(
|
|
181
|
+
const upResources = (transferData: LoadedResourcesTransferrable) => {
|
|
182
|
+
resourcesManager.currentResources = new LoadedResourcesTransferrable(transferData)
|
|
183
|
+
resourcesManager.rebuildWorkerRenderers(resourcesManager.currentResources)
|
|
113
184
|
}
|
|
114
185
|
upResources(data.currentResources)
|
|
115
186
|
if (worker) {
|
|
@@ -128,6 +199,17 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
|
|
|
128
199
|
return resourcesManager
|
|
129
200
|
}
|
|
130
201
|
|
|
202
|
+
enrichTransferSnapshot (transfer?: LoadedResourcesTransferrable): LoadedResourcesTransferrable | undefined {
|
|
203
|
+
if (!transfer) return transfer
|
|
204
|
+
if (this.itemsAtlasParser?.atlas?.latest) {
|
|
205
|
+
transfer.itemsAtlasJson = this.itemsAtlasParser.atlas.latest
|
|
206
|
+
}
|
|
207
|
+
if (this.blocksAtlasParser?.atlas?.latest) {
|
|
208
|
+
transfer.blocksAtlasJson = this.blocksAtlasParser.atlas.latest
|
|
209
|
+
}
|
|
210
|
+
return transfer
|
|
211
|
+
}
|
|
212
|
+
|
|
131
213
|
prepareForTransfer(worker?: Worker) {
|
|
132
214
|
if (worker) {
|
|
133
215
|
// todo do it automatically
|
|
@@ -138,21 +220,24 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
|
|
|
138
220
|
class: ResourcesManager.restorerName,
|
|
139
221
|
type: 'event',
|
|
140
222
|
eventName,
|
|
141
|
-
args,
|
|
223
|
+
args: sanitizeWorkerEventArgs(args),
|
|
142
224
|
})
|
|
143
225
|
// todo handle assetsInventoryReady
|
|
144
226
|
if (eventName === 'assetsTexturesUpdated' || eventName === 'assetsInventoryReady') {
|
|
227
|
+
const currentResources = this.enrichTransferSnapshot(
|
|
228
|
+
this.currentResources?.prepareForTransfer(),
|
|
229
|
+
)
|
|
145
230
|
worker.postMessage({
|
|
146
231
|
class: ResourcesManager.restorerName,
|
|
147
232
|
type: 'newResources',
|
|
148
|
-
currentResources
|
|
233
|
+
currentResources,
|
|
149
234
|
})
|
|
150
235
|
}
|
|
151
236
|
}) as any
|
|
152
237
|
}
|
|
153
238
|
return {
|
|
154
239
|
__restorer: ResourcesManager.restorerName,
|
|
155
|
-
currentResources: this.currentResources?.prepareForTransfer(),
|
|
240
|
+
currentResources: this.enrichTransferSnapshot(this.currentResources?.prepareForTransfer()),
|
|
156
241
|
}
|
|
157
242
|
}
|
|
158
243
|
|
|
@@ -305,6 +390,7 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
|
|
|
305
390
|
|
|
306
391
|
this.itemsAtlasParser = new AtlasParser({ latest: itemsAtlas }, itemsCanvas.toDataURL())
|
|
307
392
|
resources.itemsAtlasImage = await createImageBitmap(itemsCanvas)
|
|
393
|
+
resources.itemsAtlasJson = this.itemsAtlasParser.atlas.latest
|
|
308
394
|
}
|
|
309
395
|
|
|
310
396
|
async generateGuiTextures() {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import blocksAtlases from 'mc-assets/dist/blocksAtlases.json'
|
|
4
|
+
import itemsAtlases from 'mc-assets/dist/itemsAtlases.json'
|
|
5
|
+
import blockstatesModels from 'mc-assets/dist/blockStatesModels.json'
|
|
6
|
+
import { AtlasParser } from 'mc-assets/dist/atlasParser'
|
|
7
|
+
import {
|
|
8
|
+
getItemsDefinitionsStoreForRender,
|
|
9
|
+
LoadedResourcesTransferrable,
|
|
10
|
+
ResourcesManager,
|
|
11
|
+
} from './resourcesManager'
|
|
12
|
+
|
|
13
|
+
vi.mock('../three/documentRenderer', () => ({
|
|
14
|
+
isWebWorker: true,
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
describe('ResourcesManager.rebuildWorkerRenderers', () => {
|
|
18
|
+
it('creates ItemsRenderer with working modelsStore.get in worker context', () => {
|
|
19
|
+
const blocksAtlasParser = new AtlasParser(blocksAtlases as any, '')
|
|
20
|
+
const itemsAtlasParser = new AtlasParser(itemsAtlases as any, '')
|
|
21
|
+
const resources = new LoadedResourcesTransferrable({
|
|
22
|
+
version: '1.21.4',
|
|
23
|
+
texturesVersion: '1.21.4',
|
|
24
|
+
blockstatesModels,
|
|
25
|
+
blocksAtlasJson: blocksAtlasParser.atlas.latest,
|
|
26
|
+
itemsAtlasJson: itemsAtlasParser.atlas.latest,
|
|
27
|
+
allReady: true,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const manager = new ResourcesManager()
|
|
31
|
+
manager.rebuildWorkerRenderers(resources)
|
|
32
|
+
|
|
33
|
+
expect(resources.itemsRenderer).toBeDefined()
|
|
34
|
+
expect(resources.worldBlockProvider).toBeDefined()
|
|
35
|
+
const tex = resources.itemsRenderer!.getItemTexture('item/missing_texture')
|
|
36
|
+
expect(tex).toBeDefined()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('getItemsDefinitionsStoreForRender returns store with .get in worker', () => {
|
|
40
|
+
const resources = new LoadedResourcesTransferrable({
|
|
41
|
+
version: '1.21.4',
|
|
42
|
+
blockstatesModels,
|
|
43
|
+
blocksAtlasJson: (new AtlasParser(blocksAtlases as any, '')).atlas.latest,
|
|
44
|
+
itemsDefinitionsStore: { data: { latest: {} }, inclusive: false },
|
|
45
|
+
})
|
|
46
|
+
const store = getItemsDefinitionsStoreForRender(resources)
|
|
47
|
+
expect(typeof store.get).toBe('function')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('falls back to bundled items atlas when itemsAtlasJson is missing', () => {
|
|
51
|
+
const resources = new LoadedResourcesTransferrable({
|
|
52
|
+
version: '1.21.4',
|
|
53
|
+
blockstatesModels,
|
|
54
|
+
blocksAtlasJson: (new AtlasParser(blocksAtlases as any, '')).atlas.latest,
|
|
55
|
+
})
|
|
56
|
+
const manager = new ResourcesManager()
|
|
57
|
+
manager.rebuildWorkerRenderers(resources)
|
|
58
|
+
expect(resources.itemsRenderer).toBeDefined()
|
|
59
|
+
expect(resources.itemsRenderer!.getItemTexture('item/missing_texture')).toBeDefined()
|
|
60
|
+
})
|
|
61
|
+
})
|
package/src/three/appShared.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { BlockModel } from 'mc-assets/dist/types'
|
|
|
3
3
|
import { ItemSpecificContextProperties } from '../playerState/types'
|
|
4
4
|
import { PlayerStateRenderer } from '../playerState/playerState'
|
|
5
5
|
import { GeneralInputItem, getItemModelName } from '../lib/items'
|
|
6
|
-
import {
|
|
6
|
+
import { ResourcesManagerTransferred } from '../resourcesManager'
|
|
7
7
|
import { renderSlot } from './renderSlot'
|
|
8
8
|
|
|
9
9
|
export const getItemUv = (item: Record<string, any>, specificProps: ItemSpecificContextProperties, resourcesManager: ResourcesManagerTransferred, playerState: PlayerStateRenderer): {
|
|
@@ -544,6 +544,7 @@ export class ChunkMeshManager {
|
|
|
544
544
|
this.pendingNearReveal.set(chunkKey, Date.now())
|
|
545
545
|
this.armNearRevealTimer(chunkKey)
|
|
546
546
|
this.armExpectedGraceTimer(chunkKey)
|
|
547
|
+
this.tryRevealPending()
|
|
547
548
|
return
|
|
548
549
|
}
|
|
549
550
|
this.flushChunkDisplay(chunkKey)
|
|
@@ -598,9 +599,8 @@ export class ChunkMeshManager {
|
|
|
598
599
|
* `chunkKey` is not yet `finishedChunks=true`.
|
|
599
600
|
*
|
|
600
601
|
* Two regimes by `ageMs` (time spent in `pendingNearReveal`):
|
|
601
|
-
* - Within `EXPECTED_NEAR_GRACE_MS`:
|
|
602
|
-
*
|
|
603
|
-
* blocker (catches "far worker beats near worker").
|
|
602
|
+
* - Within `EXPECTED_NEAR_GRACE_MS`: nearer columns in the circle that are
|
|
603
|
+
* loaded but not finished block (far worker beats near worker).
|
|
604
604
|
* - After grace: only actually-loaded-but-not-finished columns block,
|
|
605
605
|
* so a never-arriving column does not freeze the view.
|
|
606
606
|
*/
|
|
@@ -633,6 +633,7 @@ export class ChunkMeshManager {
|
|
|
633
633
|
const oz = (playerCz + dCz) << 4
|
|
634
634
|
const otherKey = `${ox},${oz}`
|
|
635
635
|
if (otherKey === chunkKey) continue
|
|
636
|
+
if (!loadedChunks[otherKey]) continue
|
|
636
637
|
if (!finishedChunks[otherKey]) return true
|
|
637
638
|
}
|
|
638
639
|
}
|
package/src/three/entities.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { createItemMesh } from './itemMesh'
|
|
|
22
22
|
import * as Entity from './entity/EntityMesh'
|
|
23
23
|
import { getMesh } from './entity/EntityMesh'
|
|
24
24
|
import { WalkingGeneralSwing } from './entity/animations'
|
|
25
|
-
import { disposeObject, loadTexture, loadThreeJsTextureFromUrl } from './threeJsUtils'
|
|
25
|
+
import { disposeObject, loadNearestFilterTexture, loadTexture, loadThreeJsTextureFromUrl } from './threeJsUtils'
|
|
26
26
|
import { armorModel, armorTextures, elytraTexture } from './entity/armorModels'
|
|
27
27
|
import { WorldRendererThree } from './worldRendererThree'
|
|
28
28
|
import { IndexedData } from 'minecraft-data'
|
|
@@ -213,6 +213,19 @@ const nametags = {}
|
|
|
213
213
|
|
|
214
214
|
const isFirstUpperCase = (str) => str.charAt(0) === str.charAt(0).toUpperCase()
|
|
215
215
|
|
|
216
|
+
function metadataAsArray (metadata: unknown): unknown[] | undefined {
|
|
217
|
+
if (metadata == null) return undefined
|
|
218
|
+
if (Array.isArray(metadata)) return metadata
|
|
219
|
+
if (typeof metadata === 'object') {
|
|
220
|
+
const record = metadata as Record<string, unknown>
|
|
221
|
+
const keys = Object.keys(record).filter((k) => /^\d+$/.test(k)).map(Number).sort((a, b) => a - b)
|
|
222
|
+
if (keys.length && keys[0] === 0 && keys.every((k, i) => k === i)) {
|
|
223
|
+
return keys.map((k) => record[String(k)])
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return undefined
|
|
227
|
+
}
|
|
228
|
+
|
|
216
229
|
function getEntityMesh(mcData: IndexedData | undefined, entity: import('prismarine-entity').Entity & { delete?: any; pos?: any; name?: any }, world: WorldRendererThree, options: { fontFamily: string }, overrides) {
|
|
217
230
|
if (entity.name) {
|
|
218
231
|
try {
|
|
@@ -706,7 +719,7 @@ export class Entities {
|
|
|
706
719
|
uuidPerSkinUrlsCache = {} as Record<string, { skinUrl?: string, capeUrl?: string }>
|
|
707
720
|
currentSkinUrls = {} as Record<string, string>
|
|
708
721
|
|
|
709
|
-
private isCanvasBlank(canvas: HTMLCanvasElement): boolean {
|
|
722
|
+
private isCanvasBlank(canvas: HTMLCanvasElement | OffscreenCanvas): boolean {
|
|
710
723
|
return !canvas.getContext('2d')
|
|
711
724
|
?.getImageData(0, 0, canvas.width, canvas.height).data
|
|
712
725
|
.some(channel => channel !== 0)
|
|
@@ -802,12 +815,10 @@ export class Entities {
|
|
|
802
815
|
let skinTexture: THREE.Texture
|
|
803
816
|
let skinCanvas: OffscreenCanvas
|
|
804
817
|
if (skinUrl === stevePngUrl) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
ctx.drawImage(skinTexture.image, 0, 0)
|
|
810
|
-
skinCanvas = canvas
|
|
818
|
+
const steveSkin = await loadSkinImage(stevePngUrl)
|
|
819
|
+
playerCustomSkinImage = steveSkin.image
|
|
820
|
+
skinTexture = new THREE.CanvasTexture(steveSkin.canvas)
|
|
821
|
+
skinCanvas = steveSkin.canvas
|
|
811
822
|
} else {
|
|
812
823
|
const { canvas, image } = await loadSkinImage(skinUrl)
|
|
813
824
|
playerCustomSkinImage = image
|
|
@@ -822,12 +833,12 @@ export class Entities {
|
|
|
822
833
|
playerObject.skin.modelType = inferModelType(skinCanvas)
|
|
823
834
|
playerObject.skin['isCustom'] = skinUrl !== stevePngUrl
|
|
824
835
|
|
|
825
|
-
let earsCanvas:
|
|
836
|
+
let earsCanvas: OffscreenCanvas | undefined
|
|
826
837
|
if (!playerCustomSkinImage) {
|
|
827
838
|
renderEars = false
|
|
828
839
|
} else if (renderEars) {
|
|
829
|
-
earsCanvas =
|
|
830
|
-
loadEarsToCanvasFromSkin(earsCanvas, playerCustomSkinImage)
|
|
840
|
+
earsCanvas = createCanvas(64, 64)
|
|
841
|
+
loadEarsToCanvasFromSkin(earsCanvas as unknown as HTMLCanvasElement, playerCustomSkinImage)
|
|
831
842
|
renderEars = !this.isCanvasBlank(earsCanvas)
|
|
832
843
|
}
|
|
833
844
|
if (renderEars) {
|
|
@@ -1088,7 +1099,7 @@ export class Entities {
|
|
|
1088
1099
|
? { name: entity.name }
|
|
1089
1100
|
: entity.name === 'falling_block'
|
|
1090
1101
|
? { blockState: entity['objectData'] }
|
|
1091
|
-
: entity.metadata?.find((m: any) => typeof m === 'object' && m?.itemCount)
|
|
1102
|
+
: metadataAsArray(entity.metadata)?.find((m: any) => typeof m === 'object' && m?.itemCount)
|
|
1092
1103
|
if (item) {
|
|
1093
1104
|
const object = this.getItemMesh(item, {
|
|
1094
1105
|
'minecraft:display_context': 'ground',
|
|
@@ -1179,7 +1190,8 @@ export class Entities {
|
|
|
1179
1190
|
|
|
1180
1191
|
const meta = getGeneralEntitiesMetadata(entity, this.mcData)
|
|
1181
1192
|
|
|
1182
|
-
const
|
|
1193
|
+
const meta0 = metadataAsArray(entity.metadata)?.[0] ?? (entity.metadata as { 0?: unknown } | undefined)?.[0]
|
|
1194
|
+
const isInvisible = ((meta0 ?? 0) as unknown as number) & 0x20 || (this.worldRenderer.playerStateReactive.cameraSpectatingEntity === entity.id && this.worldRenderer.playerStateUtils.isSpectator())
|
|
1183
1195
|
for (const child of mesh!.children ?? []) {
|
|
1184
1196
|
if (child.name !== 'nametag') {
|
|
1185
1197
|
child.visible = !isInvisible
|
|
@@ -1514,12 +1526,8 @@ export class Entities {
|
|
|
1514
1526
|
}
|
|
1515
1527
|
|
|
1516
1528
|
loadMap(data: any) {
|
|
1517
|
-
const texture =
|
|
1518
|
-
|
|
1519
|
-
texture.magFilter = THREE.NearestFilter
|
|
1520
|
-
texture.minFilter = THREE.NearestFilter
|
|
1521
|
-
texture.needsUpdate = true
|
|
1522
|
-
}
|
|
1529
|
+
const texture = loadNearestFilterTexture(data)
|
|
1530
|
+
texture.needsUpdate = true
|
|
1523
1531
|
return texture
|
|
1524
1532
|
}
|
|
1525
1533
|
|
|
@@ -8,7 +8,7 @@ import ocelotPng from 'mc-assets/dist/other-textures/latest/entity/cat/ocelot.pn
|
|
|
8
8
|
import arrowTexture from 'mc-assets/dist/other-textures/1.21.2/entity/projectiles/arrow.png'
|
|
9
9
|
import spectralArrowTexture from 'mc-assets/dist/other-textures/1.21.2/entity/projectiles/spectral_arrow.png'
|
|
10
10
|
import tippedArrowTexture from 'mc-assets/dist/other-textures/1.21.2/entity/projectiles/tipped_arrow.png'
|
|
11
|
-
import { loadTexture } from '../threeJsUtils'
|
|
11
|
+
import { loadNearestFilterTexture, loadTexture } from '../threeJsUtils'
|
|
12
12
|
import { WorldRendererThree } from '../worldRendererThree'
|
|
13
13
|
import entities from './entities.json'
|
|
14
14
|
import { externalModels } from './objModels'
|
|
@@ -505,9 +505,7 @@ export class EntityMesh {
|
|
|
505
505
|
partRoot.position.set(x, y, z)
|
|
506
506
|
}
|
|
507
507
|
if (metadata?.texture) {
|
|
508
|
-
const texture =
|
|
509
|
-
texture.minFilter = THREE.NearestFilter
|
|
510
|
-
texture.magFilter = THREE.NearestFilter
|
|
508
|
+
const texture = loadNearestFilterTexture(metadata.texture)
|
|
511
509
|
partRoot.traverse((child) => {
|
|
512
510
|
if (child instanceof THREE.Mesh) {
|
|
513
511
|
child.material = new THREE.MeshBasicMaterial({
|
|
@@ -547,9 +545,7 @@ export class EntityMesh {
|
|
|
547
545
|
obj.position.set(x, y, z)
|
|
548
546
|
}
|
|
549
547
|
if (metadata?.texture) {
|
|
550
|
-
const texture =
|
|
551
|
-
texture.minFilter = THREE.NearestFilter
|
|
552
|
-
texture.magFilter = THREE.NearestFilter
|
|
548
|
+
const texture = loadNearestFilterTexture(metadata.texture)
|
|
553
549
|
const material = new THREE.MeshBasicMaterial({
|
|
554
550
|
map: texture,
|
|
555
551
|
transparent: true,
|
|
@@ -616,9 +612,7 @@ export class EntityMesh {
|
|
|
616
612
|
debugFlags.isHardcodedTexture = true
|
|
617
613
|
}
|
|
618
614
|
if (!texturePath) throw new Error(`No texture for ${type}`)
|
|
619
|
-
const texture =
|
|
620
|
-
texture.minFilter = THREE.NearestFilter
|
|
621
|
-
texture.magFilter = THREE.NearestFilter
|
|
615
|
+
const texture = loadNearestFilterTexture(texturePath)
|
|
622
616
|
const material = new THREE.MeshBasicMaterial({
|
|
623
617
|
map: texture,
|
|
624
618
|
transparent: true,
|
|
@@ -149,23 +149,33 @@ export const createGraphicsBackendBase = () => {
|
|
|
149
149
|
|
|
150
150
|
const startMenuBackground = async (menuBackgroundStartOptions?: MenuBackgroundOptions) => {
|
|
151
151
|
if (!documentRenderer) throw new Error('Document renderer not initialized')
|
|
152
|
-
if (worldRenderer) return
|
|
153
152
|
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
153
|
+
if (worldRenderer) {
|
|
154
|
+
worldRenderer.destroy()
|
|
155
|
+
worldRenderer = null
|
|
156
|
+
frameTimingCollector = null
|
|
157
|
+
;(globalThis as any).world = undefined
|
|
158
|
+
;(globalThis as any).frameTimingCollector = undefined
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (menuBackgroundRenderer) {
|
|
162
|
+
menuBackgroundRenderer.dispose()
|
|
163
|
+
menuBackgroundRenderer = null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const mergedOptions: MenuBackgroundOptions = {
|
|
167
|
+
...initOptions.config.menuBackground,
|
|
168
|
+
...menuBackgroundStartOptions
|
|
168
169
|
}
|
|
170
|
+
menuBackgroundRenderer = new MenuBackgroundRenderer(
|
|
171
|
+
documentRenderer,
|
|
172
|
+
{ ...initOptions },
|
|
173
|
+
mergedOptions,
|
|
174
|
+
!!process.env.SINGLE_FILE_BUILD_MODE
|
|
175
|
+
)
|
|
176
|
+
callModsMethod('menuBackgroundCreated', menuBackgroundRenderer)
|
|
177
|
+
await menuBackgroundRenderer.start(mergedOptions)
|
|
178
|
+
callModsMethod('menuBackgroundReady', menuBackgroundRenderer)
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
const startWorld = async (displayOptionsArg: DisplayWorldOptions) => {
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
import { GraphicsBackend, GraphicsBackendLoader } from '../graphicsBackend'
|
|
4
4
|
import { useWorkerProxy, deepPrepareForTransfer, findProblemTransfer } from '../lib/workerProxy'
|
|
5
|
-
import {
|
|
5
|
+
import { meshersSendMcDataAwait } from '../lib/worldrendererCommon'
|
|
6
6
|
import { dynamicMcDataFiles } from '../lib/buildSharedConfig.mjs'
|
|
7
7
|
import { addNewStat } from '../lib/ui/newStats'
|
|
8
|
+
import type { MenuBackgroundOptions } from './menuBackground/types'
|
|
9
|
+
import { MENU_BACKGROUND_MC_VERSION } from './menuBackground/shared'
|
|
8
10
|
import { createGraphicsBackendBase, type ThreeJsBackendMethods } from './graphicsBackendBase'
|
|
9
11
|
import { addCanvasForWorker } from './documentRenderer'
|
|
10
12
|
|
|
@@ -58,14 +60,36 @@ export const createGraphicsBackendOffThread: GraphicsBackendLoader = async (init
|
|
|
58
60
|
const backend: GraphicsBackend = {
|
|
59
61
|
id: 'threejs',
|
|
60
62
|
displayName: `three.js ${THREE.REVISION}`,
|
|
61
|
-
async startMenuBackground() {
|
|
63
|
+
async startMenuBackground(menuBackgroundStartOptions?: MenuBackgroundOptions) {
|
|
64
|
+
const mcData = menuBackgroundStartOptions?.resourcesManager?.currentResources?.mcData
|
|
65
|
+
if (mcData) {
|
|
66
|
+
const workerThreeSendData = {
|
|
67
|
+
...dynamicMcDataFiles,
|
|
68
|
+
items: 'itemsArray',
|
|
69
|
+
entities: 'entitiesArray',
|
|
70
|
+
}
|
|
71
|
+
await meshersSendMcDataAwait([worker], MENU_BACKGROUND_MC_VERSION, workerThreeSendData, mcData)
|
|
72
|
+
}
|
|
73
|
+
const prepared = deepPrepareForTransfer(menuBackgroundStartOptions ?? {}, worker)
|
|
74
|
+
try {
|
|
75
|
+
await proxy.startMenuBackground(structuredClone(prepared))
|
|
76
|
+
} catch (err) {
|
|
77
|
+
findProblemTransfer(prepared)
|
|
78
|
+
throw err
|
|
79
|
+
}
|
|
80
|
+
},
|
|
62
81
|
async startWorld(options) {
|
|
63
82
|
const workerThreeSendData = {
|
|
64
83
|
...dynamicMcDataFiles,
|
|
65
84
|
items: 'itemsArray',
|
|
66
85
|
entities: 'entitiesArray',
|
|
67
86
|
}
|
|
68
|
-
|
|
87
|
+
await meshersSendMcDataAwait(
|
|
88
|
+
[worker],
|
|
89
|
+
options.version,
|
|
90
|
+
workerThreeSendData,
|
|
91
|
+
options.resourcesManager.currentResources.mcData
|
|
92
|
+
)
|
|
69
93
|
console.log('mc data sent to three worker')
|
|
70
94
|
|
|
71
95
|
options.inWorldRenderingConfig['__syncToWorker'] = true
|
package/src/three/hand.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
-
import { loadSkinFromUsername, loadSkinImage } from '../lib/utils/skins'
|
|
4
|
-
import { steveTexture } from './entities'
|
|
3
|
+
import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins'
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
export const getMyHand = async (image?: string, userName?: string) => {
|
|
8
7
|
let newMap: THREE.Texture
|
|
9
8
|
if (!image && !userName) {
|
|
10
|
-
|
|
9
|
+
const { canvas } = await loadSkinImage(stevePngUrl)
|
|
10
|
+
newMap = new THREE.CanvasTexture(canvas)
|
|
11
11
|
} else {
|
|
12
12
|
if (!image) {
|
|
13
13
|
image = await loadSkinFromUsername(userName!, 'skin')
|
package/src/three/itemMesh.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
|
|
4
|
+
/** Canvas used for item texture extraction (main thread or worker). */
|
|
5
|
+
export type ItemTextureCanvas = HTMLCanvasElement | OffscreenCanvas
|
|
6
|
+
|
|
7
|
+
function createItemTextureCanvas (width: number, height: number): ItemTextureCanvas {
|
|
8
|
+
if (typeof document !== 'undefined') {
|
|
9
|
+
const canvas = document.createElement('canvas')
|
|
10
|
+
canvas.width = width
|
|
11
|
+
canvas.height = height
|
|
12
|
+
return canvas
|
|
13
|
+
}
|
|
14
|
+
return new OffscreenCanvas(width, height)
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
export interface Create3DItemMeshOptions {
|
|
5
18
|
depth: number
|
|
6
19
|
pixelSize?: number
|
|
@@ -17,7 +30,7 @@ export interface Create3DItemMeshResult {
|
|
|
17
30
|
* from a canvas containing the item texture
|
|
18
31
|
*/
|
|
19
32
|
export function create3DItemMesh (
|
|
20
|
-
canvas:
|
|
33
|
+
canvas: ItemTextureCanvas,
|
|
21
34
|
options: Create3DItemMeshOptions
|
|
22
35
|
): Create3DItemMeshResult {
|
|
23
36
|
const { depth, pixelSize } = options
|
|
@@ -246,18 +259,18 @@ export interface ItemMeshResult {
|
|
|
246
259
|
export function extractItemTextureToCanvas (
|
|
247
260
|
sourceTexture: THREE.Texture,
|
|
248
261
|
textureInfo: ItemTextureInfo
|
|
249
|
-
):
|
|
262
|
+
): ItemTextureCanvas {
|
|
250
263
|
const { u, v, sizeX, sizeY } = textureInfo
|
|
251
264
|
|
|
252
265
|
// Calculate canvas size - fix the calculation
|
|
253
266
|
const canvasWidth = Math.max(1, Math.floor(sizeX * sourceTexture.image.width))
|
|
254
267
|
const canvasHeight = Math.max(1, Math.floor(sizeY * sourceTexture.image.height))
|
|
255
268
|
|
|
256
|
-
const canvas =
|
|
257
|
-
canvas.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
269
|
+
const canvas = createItemTextureCanvas(canvasWidth, canvasHeight)
|
|
270
|
+
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null
|
|
271
|
+
if (!ctx) {
|
|
272
|
+
throw new Error('Failed to get 2d context for item texture canvas')
|
|
273
|
+
}
|
|
261
274
|
ctx.imageSmoothingEnabled = false
|
|
262
275
|
|
|
263
276
|
// Draw the item texture region to canvas
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
export function menuBackgroundAssetUrl (...segments: string[]): string {
|
|
3
|
+
const relative = segments.filter(s => s.length > 0).join('/')
|
|
4
|
+
const base =
|
|
5
|
+
typeof globalThis.location !== 'undefined' && globalThis.location.href
|
|
6
|
+
? globalThis.location.href
|
|
7
|
+
: typeof import.meta !== 'undefined' && import.meta.url
|
|
8
|
+
? import.meta.url
|
|
9
|
+
: `/${relative}`
|
|
10
|
+
try {
|
|
11
|
+
return new URL(relative, base).href
|
|
12
|
+
} catch {
|
|
13
|
+
return `/${relative}`
|
|
14
|
+
}
|
|
15
|
+
}
|