minecraft-renderer 0.1.54 → 0.1.55
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/minecraft-renderer.js +59 -59
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +3989 -3989
- package/package.json +1 -1
- package/src/lib/buildWorkerMcDataIndexes.test.ts +30 -0
- package/src/lib/buildWorkerMcDataIndexes.ts +93 -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 +14 -3
- package/src/three/chunkMeshManager.ts +4 -3
- package/src/three/entities.ts +8 -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/menuBackground/assetUrl.ts +15 -0
- package/src/three/menuBackground/classic.ts +11 -5
- package/src/three/menuBackground/futuristic.ts +3 -0
- package/src/three/threeWorker.ts +12 -11
- package/src/three/worldRendererThree.ts +7 -0
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { augmentWorkerMcData } from './buildWorkerMcDataIndexes'
|
|
4
|
+
|
|
5
|
+
describe('augmentWorkerMcData', () => {
|
|
6
|
+
it('is idempotent when mcData is augmented twice (menu then world)', () => {
|
|
7
|
+
const mcData: Record<string, unknown> = {
|
|
8
|
+
entities: [{ name: 'bat', id: 1, type: 'mob' }],
|
|
9
|
+
blocks: [{ name: 'stone', id: 3, minStateId: 48, maxStateId: 63, defaultState: 48 }],
|
|
10
|
+
}
|
|
11
|
+
augmentWorkerMcData(mcData)
|
|
12
|
+
expect(() => augmentWorkerMcData(mcData)).not.toThrow()
|
|
13
|
+
expect(mcData.__workerIndexesBuilt).toBe(true)
|
|
14
|
+
expect(Array.isArray(mcData.blocksArray)).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('builds entitiesByName and items indexes from arrays', () => {
|
|
18
|
+
const mcData: Record<string, unknown> = {
|
|
19
|
+
entities: [{ name: 'bat', id: 1, type: 'mob' }],
|
|
20
|
+
items: [{ name: 'dirt', id: 2 }],
|
|
21
|
+
blocks: [{ name: 'stone', id: 3, minStateId: 48, maxStateId: 63, defaultState: 48 }],
|
|
22
|
+
}
|
|
23
|
+
augmentWorkerMcData(mcData)
|
|
24
|
+
expect((mcData.entitiesByName as Record<string, { name: string }>).bat?.name).toBe('bat')
|
|
25
|
+
expect((mcData.entities as Record<number, { id: number }>)[1]?.id).toBe(1)
|
|
26
|
+
expect((mcData.itemsByName as Record<string, unknown>).dirt).toBeDefined()
|
|
27
|
+
expect((mcData.items as Record<number, unknown>)[2]).toBeDefined()
|
|
28
|
+
expect((mcData.blocksByStateId as Record<number, unknown>)[48]).toBeDefined()
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
type McElement = Record<string, unknown>
|
|
3
|
+
|
|
4
|
+
function buildIndexFromArray<T extends McElement> (
|
|
5
|
+
array: T[],
|
|
6
|
+
field: keyof T
|
|
7
|
+
): Record<string | number, T> {
|
|
8
|
+
return array.reduce<Record<string | number, T>>((index, element) => {
|
|
9
|
+
index[element[field] as string | number] = element
|
|
10
|
+
return index
|
|
11
|
+
}, {})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildIndexFromArrayWithRanges<T extends McElement> (
|
|
15
|
+
array: T[],
|
|
16
|
+
minField: keyof T,
|
|
17
|
+
maxField: keyof T
|
|
18
|
+
): Record<number, T> {
|
|
19
|
+
return array.reduce<Record<number, T>>((index, element) => {
|
|
20
|
+
const min = element[minField] as number
|
|
21
|
+
const max = element[maxField] as number
|
|
22
|
+
for (let i = min; i <= max; i++) {
|
|
23
|
+
index[i] = element
|
|
24
|
+
}
|
|
25
|
+
return index
|
|
26
|
+
}, {})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ensureBlockStateIds (blocks: McElement[]) {
|
|
30
|
+
if (!blocks.length) return
|
|
31
|
+
if ('minStateId' in blocks[0] && 'defaultState' in blocks[0]) return
|
|
32
|
+
for (const block of blocks) {
|
|
33
|
+
const id = block.id as number
|
|
34
|
+
block.minStateId = id << 4
|
|
35
|
+
block.maxStateId = (block.minStateId as number) + 15
|
|
36
|
+
block.defaultState = block.minStateId
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getSourceArray (
|
|
41
|
+
mcData: Record<string, unknown>,
|
|
42
|
+
arrayKey: string,
|
|
43
|
+
rawKey: string
|
|
44
|
+
): McElement[] | undefined {
|
|
45
|
+
const fromArrayKey = mcData[arrayKey]
|
|
46
|
+
if (Array.isArray(fromArrayKey)) {
|
|
47
|
+
return fromArrayKey as McElement[]
|
|
48
|
+
}
|
|
49
|
+
const raw = mcData[rawKey]
|
|
50
|
+
if (Array.isArray(raw)) {
|
|
51
|
+
return raw as McElement[]
|
|
52
|
+
}
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function augmentWorkerMcData (mcData: Record<string, unknown>) {
|
|
57
|
+
if (mcData.__workerIndexesBuilt) {
|
|
58
|
+
return mcData
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const blocks = getSourceArray(mcData, 'blocksArray', 'blocks')
|
|
62
|
+
if (blocks?.length) {
|
|
63
|
+
ensureBlockStateIds(blocks)
|
|
64
|
+
mcData.blocksArray = blocks
|
|
65
|
+
mcData.blocks = buildIndexFromArray(blocks, 'id')
|
|
66
|
+
mcData.blocksByName = buildIndexFromArray(blocks, 'name')
|
|
67
|
+
mcData.blocksByStateId = buildIndexFromArrayWithRanges(blocks, 'minStateId', 'maxStateId')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const items = getSourceArray(mcData, 'itemsArray', 'items')
|
|
71
|
+
if (items?.length) {
|
|
72
|
+
mcData.itemsArray = items
|
|
73
|
+
mcData.itemsByName = buildIndexFromArray(items, 'name')
|
|
74
|
+
mcData.items = buildIndexFromArray(items, 'id')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const entities = getSourceArray(mcData, 'entitiesArray', 'entities')
|
|
78
|
+
if (entities?.length) {
|
|
79
|
+
mcData.entitiesArray = entities
|
|
80
|
+
mcData.entitiesByName = buildIndexFromArray(entities, 'name')
|
|
81
|
+
mcData.entities = buildIndexFromArray(entities, 'id')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const biomes = getSourceArray(mcData, 'biomesArray', 'biomes')
|
|
85
|
+
if (biomes?.length) {
|
|
86
|
+
mcData.biomesArray = biomes
|
|
87
|
+
mcData.biomes = buildIndexFromArray(biomes, 'id')
|
|
88
|
+
mcData.biomesByName = buildIndexFromArray(biomes, 'name')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
mcData.__workerIndexesBuilt = true
|
|
92
|
+
return mcData
|
|
93
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { restoreTransferred } from './workerProxy'
|
|
4
|
+
|
|
5
|
+
describe('restoreTransferred Set/Map', () => {
|
|
6
|
+
const worker = null as unknown as Worker
|
|
7
|
+
|
|
8
|
+
it('restores Set from __setValues', () => {
|
|
9
|
+
const out = restoreTransferred({ __restorer: 'Set', __setValues: ['a', 'b'] }, [], worker, false)
|
|
10
|
+
expect(out).toEqual(new Set(['a', 'b']))
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('does not throw when legacy values is a function (Map.values collision)', () => {
|
|
14
|
+
const map = new Map([['k', 1]])
|
|
15
|
+
const out = restoreTransferred({ __restorer: 'Set', values: map.values.bind(map) }, [], worker, false)
|
|
16
|
+
expect(out).toEqual(new Set())
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('restores Map from __mapEntries', () => {
|
|
20
|
+
const out = restoreTransferred({ __restorer: 'Map', __mapEntries: [['a', 1]] }, [], worker, false)
|
|
21
|
+
expect(out).toEqual(new Map([['a', 1]]))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('does not throw when legacy entries is a function', () => {
|
|
25
|
+
const m = new Map()
|
|
26
|
+
const out = restoreTransferred({ __restorer: 'Map', entries: m.entries.bind(m) }, [], worker, false)
|
|
27
|
+
expect(out).toEqual(new Map())
|
|
28
|
+
})
|
|
29
|
+
})
|
package/src/lib/workerProxy.ts
CHANGED
|
@@ -117,6 +117,10 @@ const getSyncId = () => {
|
|
|
117
117
|
return Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
const applySyncPatch = (target: any, patch: any, worker: Worker) => {
|
|
121
|
+
Object.assign(target, restoreTransferred(patch, [], worker, false))
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
const setupObjectSync = (obj: any, originalObj: any, worker: Worker, isValtio: boolean, debugKey: string) => {
|
|
121
125
|
if (!obj['__syncToWorker'] && !obj['__syncFromWorker'] && !isValtio) return
|
|
122
126
|
|
|
@@ -141,13 +145,45 @@ const setupObjectSync = (obj: any, originalObj: any, worker: Worker, isValtio: b
|
|
|
141
145
|
worker.addEventListener('message', (event: any) => {
|
|
142
146
|
if (event.data.type === 'sync' && event.data.syncId === syncId) {
|
|
143
147
|
currentWorkerSyncStats.fromWorker++
|
|
144
|
-
|
|
148
|
+
applySyncPatch(originalObj, event.data.value, worker)
|
|
145
149
|
}
|
|
146
150
|
})
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
154
|
+
const serializeMapForTransfer = (map: Map<unknown, unknown>) => ({
|
|
155
|
+
__restorer: 'Map',
|
|
156
|
+
__mapEntries: Array.from(map.entries()),
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const serializeSetForTransfer = (set: Set<unknown>) => ({
|
|
160
|
+
__restorer: 'Set',
|
|
161
|
+
__setValues: [...set],
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const isSetLike = (value: unknown): value is Set<unknown> => {
|
|
165
|
+
return value instanceof Set || Object.prototype.toString.call(value) === '[object Set]'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const isMapLike = (value: unknown): value is Map<unknown, unknown> => {
|
|
169
|
+
return value instanceof Map || Object.prototype.toString.call(value) === '[object Map]'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const iterableFromPlainObject = (obj: Record<string, unknown>) => {
|
|
173
|
+
return Object.keys(obj)
|
|
174
|
+
.filter(k => !k.startsWith('__'))
|
|
175
|
+
.sort((a, b) => Number(a) - Number(b))
|
|
176
|
+
.map(k => obj[k])
|
|
177
|
+
}
|
|
178
|
+
|
|
150
179
|
const cloneValtioObject = (obj: any) => {
|
|
180
|
+
if (isMapLike(obj)) {
|
|
181
|
+
return serializeMapForTransfer(obj)
|
|
182
|
+
}
|
|
183
|
+
if (isSetLike(obj)) {
|
|
184
|
+
return serializeSetForTransfer(obj)
|
|
185
|
+
}
|
|
186
|
+
|
|
151
187
|
if (getVersion(obj) === undefined) {
|
|
152
188
|
return obj
|
|
153
189
|
}
|
|
@@ -179,16 +215,19 @@ export const deepPrepareForTransfer = (obj: any, worker: Worker, autoRemoveMetho
|
|
|
179
215
|
continue
|
|
180
216
|
}
|
|
181
217
|
|
|
182
|
-
// print a warning for Date, RegExp,
|
|
183
|
-
if (obj[key] instanceof Date || obj[key] instanceof RegExp || obj[key] instanceof
|
|
218
|
+
// print a warning for Date, RegExp, WeakMap, WeakSet
|
|
219
|
+
if (obj[key] instanceof Date || obj[key] instanceof RegExp || obj[key] instanceof WeakMap || obj[key] instanceof WeakSet) {
|
|
184
220
|
console.warn(`Warning: ${key} is a ${typeof obj[key]}, which is not supported for transfer.`)
|
|
185
221
|
}
|
|
186
222
|
|
|
187
223
|
// default restorers main -> worker
|
|
224
|
+
if (isMapLike(obj[key])) {
|
|
225
|
+
newObj[key] = serializeMapForTransfer(obj[key])
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
188
228
|
// Set (only primitive values)
|
|
189
|
-
if (obj[key]
|
|
190
|
-
newObj[key] =
|
|
191
|
-
newObj[key]['__restorer'] = 'Set'
|
|
229
|
+
if (isSetLike(obj[key])) {
|
|
230
|
+
newObj[key] = serializeSetForTransfer(obj[key])
|
|
192
231
|
continue
|
|
193
232
|
}
|
|
194
233
|
if (obj[key] instanceof Vec3) {
|
|
@@ -253,7 +292,7 @@ const receiveSyncedObject = (obj: any, worker: Worker, debugKey: string) => {
|
|
|
253
292
|
if (obj['__syncToWorker']) {
|
|
254
293
|
worker.addEventListener('message', (event: any) => {
|
|
255
294
|
if (event.data.type === 'sync' && event.data.syncId === syncId) {
|
|
256
|
-
|
|
295
|
+
applySyncPatch(obj, event.data.value, worker)
|
|
257
296
|
}
|
|
258
297
|
})
|
|
259
298
|
}
|
|
@@ -275,10 +314,36 @@ const receiveSyncedObject = (obj: any, worker: Worker, debugKey: string) => {
|
|
|
275
314
|
}
|
|
276
315
|
|
|
277
316
|
const defaultRestorers = [
|
|
317
|
+
{
|
|
318
|
+
restorerName: 'Map',
|
|
319
|
+
restoreTransferred(obj, _worker: Worker) {
|
|
320
|
+
if (Array.isArray(obj)) {
|
|
321
|
+
return new Map(obj)
|
|
322
|
+
}
|
|
323
|
+
const raw = obj.__mapEntries ?? obj.entries
|
|
324
|
+
if (Array.isArray(raw)) {
|
|
325
|
+
return new Map(raw)
|
|
326
|
+
}
|
|
327
|
+
if (raw != null && typeof raw === 'object' && typeof raw !== 'function') {
|
|
328
|
+
return new Map(Object.entries(raw as Record<string, unknown>))
|
|
329
|
+
}
|
|
330
|
+
return new Map()
|
|
331
|
+
}
|
|
332
|
+
},
|
|
278
333
|
{
|
|
279
334
|
restorerName: 'Set',
|
|
280
|
-
restoreTransferred(obj,
|
|
281
|
-
|
|
335
|
+
restoreTransferred(obj, _worker: Worker) {
|
|
336
|
+
if (Array.isArray(obj)) {
|
|
337
|
+
return new Set(obj)
|
|
338
|
+
}
|
|
339
|
+
const raw = obj.__setValues ?? obj.values
|
|
340
|
+
if (Array.isArray(raw)) {
|
|
341
|
+
return new Set(raw)
|
|
342
|
+
}
|
|
343
|
+
if (raw != null && typeof raw === 'object' && typeof raw !== 'function') {
|
|
344
|
+
return new Set(iterableFromPlainObject(raw as Record<string, unknown>))
|
|
345
|
+
}
|
|
346
|
+
return new Set()
|
|
282
347
|
}
|
|
283
348
|
},
|
|
284
349
|
{
|
|
@@ -296,41 +361,50 @@ export const addDefaultRestorer = (restorer: { restorerName: string, restoreTran
|
|
|
296
361
|
export const restoreTransferred = (obj: any, restorersArg: any[], worker: Worker, errorHandler: ((error: Error) => void) | boolean = true) => {
|
|
297
362
|
const restorers = [...defaultRestorers, ...restorersArg]
|
|
298
363
|
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
364
|
+
const restoreValue = (value: any, debugKey: string): any => {
|
|
365
|
+
if (value == null || typeof value !== 'object') {
|
|
366
|
+
return value
|
|
367
|
+
}
|
|
302
368
|
|
|
303
|
-
|
|
304
|
-
|
|
369
|
+
if (value['__restorer']) {
|
|
370
|
+
const restorer = restorers.find(r => {
|
|
371
|
+
return r.restorerName ? r.restorerName === value['__restorer'] : r.name === value['__restorer']
|
|
372
|
+
})
|
|
373
|
+
if (restorer) {
|
|
374
|
+
return restorer.restoreTransferred(value, worker)
|
|
305
375
|
}
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
obj[key] = restorer.restoreTransferred(obj[key], worker)
|
|
314
|
-
} else {
|
|
315
|
-
const error = new Error(`Restorer ${obj[key]['__restorer']} not found`)
|
|
316
|
-
if (typeof errorHandler === 'function') {
|
|
317
|
-
errorHandler(error)
|
|
318
|
-
} else if (errorHandler) {
|
|
319
|
-
throw error
|
|
320
|
-
} else {
|
|
321
|
-
console.error(error)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
376
|
+
const error = new Error(`Restorer ${value['__restorer']} not found`)
|
|
377
|
+
if (typeof errorHandler === 'function') {
|
|
378
|
+
errorHandler(error)
|
|
379
|
+
} else if (errorHandler) {
|
|
380
|
+
throw error
|
|
381
|
+
} else {
|
|
382
|
+
console.error(error)
|
|
324
383
|
}
|
|
384
|
+
return value
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (Array.isArray(value)) {
|
|
388
|
+
return value.map((item, index) => restoreValue(item, `${debugKey}[${index}]`))
|
|
389
|
+
}
|
|
325
390
|
|
|
326
|
-
|
|
327
|
-
|
|
391
|
+
for (const key in value) {
|
|
392
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) continue
|
|
393
|
+
const child = value[key]
|
|
394
|
+
if (child != null && typeof child === 'object') {
|
|
395
|
+
value[key] = restoreValue(child, `${debugKey}.${key}`)
|
|
328
396
|
}
|
|
397
|
+
}
|
|
329
398
|
|
|
330
|
-
|
|
399
|
+
if (value['__valtio']) {
|
|
400
|
+
value = proxy(value)
|
|
331
401
|
}
|
|
402
|
+
|
|
403
|
+
receiveSyncedObject(value, worker, debugKey)
|
|
404
|
+
return value
|
|
332
405
|
}
|
|
333
|
-
|
|
406
|
+
|
|
407
|
+
return restoreValue(obj, 'root')
|
|
334
408
|
}
|
|
335
409
|
|
|
336
410
|
// const workerProxy = createWorkerProxy({
|
|
@@ -1295,3 +1295,28 @@ export const meshersSendMcData = (workers: Worker[], version: string, mcDataKeys
|
|
|
1295
1295
|
worker.postMessage({ type: 'mcData', mcData })
|
|
1296
1296
|
}
|
|
1297
1297
|
}
|
|
1298
|
+
|
|
1299
|
+
/** Wait for worker `mcDataApplied` after {@link meshersSendMcData}. */
|
|
1300
|
+
export const meshersSendMcDataAwait = (
|
|
1301
|
+
workers: Worker[],
|
|
1302
|
+
version: string,
|
|
1303
|
+
mcDataKeys = dynamicMcDataFiles,
|
|
1304
|
+
mcDataFull: IndexedData,
|
|
1305
|
+
timeoutMs = 10_000
|
|
1306
|
+
): Promise<void> => {
|
|
1307
|
+
return Promise.all(workers.map(worker => new Promise<void>((resolve, reject) => {
|
|
1308
|
+
const timeout = setTimeout(() => {
|
|
1309
|
+
worker.removeEventListener('message', handler as EventListener)
|
|
1310
|
+
reject(new Error(`mcData transfer timeout (${timeoutMs}ms)`))
|
|
1311
|
+
}, timeoutMs)
|
|
1312
|
+
const handler = ({ data }: MessageEvent) => {
|
|
1313
|
+
if (data?.type === 'mcDataApplied') {
|
|
1314
|
+
clearTimeout(timeout)
|
|
1315
|
+
worker.removeEventListener('message', handler as EventListener)
|
|
1316
|
+
resolve()
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
worker.addEventListener('message', handler as EventListener)
|
|
1320
|
+
meshersSendMcData([worker], version, mcDataKeys, mcDataFull)
|
|
1321
|
+
}))).then(() => undefined)
|
|
1322
|
+
}
|
|
@@ -12,6 +12,7 @@ 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
17
|
import { getLoadedItemDefinitionsStore } from 'mc-assets'
|
|
17
18
|
|
|
@@ -55,7 +56,15 @@ export class LoadedResourcesTransferrable {
|
|
|
55
56
|
constructor(data?: any) {
|
|
56
57
|
if (data) {
|
|
57
58
|
Object.assign(this, data)
|
|
58
|
-
|
|
59
|
+
}
|
|
60
|
+
if (this.version) {
|
|
61
|
+
const globalMc = (globalThis as { loadedData?: IndexedData, mcData?: IndexedData }).loadedData
|
|
62
|
+
?? (globalThis as { mcData?: IndexedData }).mcData
|
|
63
|
+
if (isWebWorker && globalMc?.entitiesByName) {
|
|
64
|
+
this.mcData = globalMc
|
|
65
|
+
} else {
|
|
66
|
+
this.mcData = MinecraftData(this.version)
|
|
67
|
+
}
|
|
59
68
|
// this.itemsRenderer = new ItemsRenderer(
|
|
60
69
|
// this.version,
|
|
61
70
|
// this.blockstatesModels,
|
|
@@ -70,8 +79,10 @@ export class LoadedResourcesTransferrable {
|
|
|
70
79
|
for (const key in this) {
|
|
71
80
|
if (!Object.prototype.hasOwnProperty.call(this, key)) continue
|
|
72
81
|
if (typeof this[key] === 'function') continue
|
|
73
|
-
if (
|
|
74
|
-
|
|
82
|
+
if (
|
|
83
|
+
key === 'itemsRenderer' || key === 'worldBlockProvider' || key === 'mcData'
|
|
84
|
+
|| key === 'itemsDefinitionsStore' || key === 'sourceItemDefinitionsJson'
|
|
85
|
+
) {
|
|
75
86
|
continue
|
|
76
87
|
}
|
|
77
88
|
cloned[key] = this[key as keyof this]
|
|
@@ -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
|
@@ -706,7 +706,7 @@ export class Entities {
|
|
|
706
706
|
uuidPerSkinUrlsCache = {} as Record<string, { skinUrl?: string, capeUrl?: string }>
|
|
707
707
|
currentSkinUrls = {} as Record<string, string>
|
|
708
708
|
|
|
709
|
-
private isCanvasBlank(canvas: HTMLCanvasElement): boolean {
|
|
709
|
+
private isCanvasBlank(canvas: HTMLCanvasElement | OffscreenCanvas): boolean {
|
|
710
710
|
return !canvas.getContext('2d')
|
|
711
711
|
?.getImageData(0, 0, canvas.width, canvas.height).data
|
|
712
712
|
.some(channel => channel !== 0)
|
|
@@ -802,12 +802,10 @@ export class Entities {
|
|
|
802
802
|
let skinTexture: THREE.Texture
|
|
803
803
|
let skinCanvas: OffscreenCanvas
|
|
804
804
|
if (skinUrl === stevePngUrl) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
ctx.drawImage(skinTexture.image, 0, 0)
|
|
810
|
-
skinCanvas = canvas
|
|
805
|
+
const steveSkin = await loadSkinImage(stevePngUrl)
|
|
806
|
+
playerCustomSkinImage = steveSkin.image
|
|
807
|
+
skinTexture = new THREE.CanvasTexture(steveSkin.canvas)
|
|
808
|
+
skinCanvas = steveSkin.canvas
|
|
811
809
|
} else {
|
|
812
810
|
const { canvas, image } = await loadSkinImage(skinUrl)
|
|
813
811
|
playerCustomSkinImage = image
|
|
@@ -822,12 +820,12 @@ export class Entities {
|
|
|
822
820
|
playerObject.skin.modelType = inferModelType(skinCanvas)
|
|
823
821
|
playerObject.skin['isCustom'] = skinUrl !== stevePngUrl
|
|
824
822
|
|
|
825
|
-
let earsCanvas:
|
|
823
|
+
let earsCanvas: OffscreenCanvas | undefined
|
|
826
824
|
if (!playerCustomSkinImage) {
|
|
827
825
|
renderEars = false
|
|
828
826
|
} else if (renderEars) {
|
|
829
|
-
earsCanvas =
|
|
830
|
-
loadEarsToCanvasFromSkin(earsCanvas, playerCustomSkinImage)
|
|
827
|
+
earsCanvas = createCanvas(64, 64)
|
|
828
|
+
loadEarsToCanvasFromSkin(earsCanvas as unknown as HTMLCanvasElement, playerCustomSkinImage)
|
|
831
829
|
renderEars = !this.isCanvasBlank(earsCanvas)
|
|
832
830
|
}
|
|
833
831
|
if (renderEars) {
|
|
@@ -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')
|
|
@@ -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
|
+
}
|