minecraft-renderer 0.1.53 → 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/mesher.js +46 -46
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +167 -167
- 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/buildSharedConfig.mjs +2 -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 +31 -0
- package/src/resourcesManager/resourcesManager.ts +14 -3
- package/src/three/chunkMeshManager.ts +7 -4
- 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/worldGeometryExport.ts +3 -1
- package/src/three/worldRendererThree.ts +7 -0
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +14 -4
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +9 -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({
|
|
@@ -1277,6 +1277,8 @@ export const initMesherWorker = (onGotMessage: (data: any) => void, workerName =
|
|
|
1277
1277
|
return worker
|
|
1278
1278
|
}
|
|
1279
1279
|
|
|
1280
|
+
let mesherMcDataTintsMissingWarned = false
|
|
1281
|
+
|
|
1280
1282
|
export const meshersSendMcData = (workers: Worker[], version: string, mcDataKeys = dynamicMcDataFiles, mcDataFull: IndexedData) => {
|
|
1281
1283
|
const mcData = {
|
|
1282
1284
|
version: JSON.parse(JSON.stringify(mcDataFull.version))
|
|
@@ -1284,8 +1286,37 @@ export const meshersSendMcData = (workers: Worker[], version: string, mcDataKeys
|
|
|
1284
1286
|
for (const [finalKey, sourceKey] of Object.entries(mcDataKeys)) {
|
|
1285
1287
|
mcData[finalKey] = mcDataFull[sourceKey]
|
|
1286
1288
|
}
|
|
1289
|
+
if ('tints' in mcDataKeys && !mcData.tints && !mesherMcDataTintsMissingWarned) {
|
|
1290
|
+
mesherMcDataTintsMissingWarned = true
|
|
1291
|
+
console.warn(`[meshersSendMcData] mcData.tints missing for version ${version}; shader cubes will use legacy path in worker`)
|
|
1292
|
+
}
|
|
1287
1293
|
|
|
1288
1294
|
for (const worker of workers) {
|
|
1289
1295
|
worker.postMessage({ type: 'mcData', mcData })
|
|
1290
1296
|
}
|
|
1291
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]
|
|
@@ -174,7 +174,9 @@ export class ChunkMeshManager {
|
|
|
174
174
|
if (!mat) return
|
|
175
175
|
const atlas = (this.material as THREE.MeshBasicMaterial).map ?? null
|
|
176
176
|
mat.uniforms.u_atlas.value = atlas
|
|
177
|
-
const
|
|
177
|
+
const resources = getShaderCubeResources()
|
|
178
|
+
if (!resources) return
|
|
179
|
+
const { tintPalette } = resources
|
|
178
180
|
if (!tintPalette.isReady()) {
|
|
179
181
|
tintPalette.createTexture()
|
|
180
182
|
}
|
|
@@ -542,6 +544,7 @@ export class ChunkMeshManager {
|
|
|
542
544
|
this.pendingNearReveal.set(chunkKey, Date.now())
|
|
543
545
|
this.armNearRevealTimer(chunkKey)
|
|
544
546
|
this.armExpectedGraceTimer(chunkKey)
|
|
547
|
+
this.tryRevealPending()
|
|
545
548
|
return
|
|
546
549
|
}
|
|
547
550
|
this.flushChunkDisplay(chunkKey)
|
|
@@ -596,9 +599,8 @@ export class ChunkMeshManager {
|
|
|
596
599
|
* `chunkKey` is not yet `finishedChunks=true`.
|
|
597
600
|
*
|
|
598
601
|
* Two regimes by `ageMs` (time spent in `pendingNearReveal`):
|
|
599
|
-
* - Within `EXPECTED_NEAR_GRACE_MS`:
|
|
600
|
-
*
|
|
601
|
-
* 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).
|
|
602
604
|
* - After grace: only actually-loaded-but-not-finished columns block,
|
|
603
605
|
* so a never-arriving column does not freeze the view.
|
|
604
606
|
*/
|
|
@@ -631,6 +633,7 @@ export class ChunkMeshManager {
|
|
|
631
633
|
const oz = (playerCz + dCz) << 4
|
|
632
634
|
const otherKey = `${ox},${oz}`
|
|
633
635
|
if (otherKey === chunkKey) continue
|
|
636
|
+
if (!loadedChunks[otherKey]) continue
|
|
634
637
|
if (!finishedChunks[otherKey]) return true
|
|
635
638
|
}
|
|
636
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')
|