minecraft-renderer 0.1.39 → 0.1.41
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 +8 -8
- package/dist/mesher.js.map +4 -4
- package/dist/mesherWasm.js +94 -94
- package/dist/minecraft-renderer.js +57 -57
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +66 -66
- package/package.json +3 -4
- package/src/bundler/bundlePrepare.ts +56 -0
- package/src/graphicsBackend/appViewer.ts +10 -0
- package/src/graphicsBackend/config.ts +5 -1
- package/src/graphicsBackend/preloadWorkers.ts +187 -0
- package/src/lib/worldrendererCommon.ts +26 -2
- package/src/{mesher → mesher-legacy}/mesher.ts +14 -4
- package/src/{mesher → mesher-legacy}/test/mesherTester.ts +2 -2
- package/src/{mesher → mesher-legacy}/test/run/test-js.ts +1 -1
- package/src/{mesher → mesher-legacy}/test/test-perf.ts +1 -1
- package/src/{mesher → mesher-legacy}/test/tests.test.ts +1 -1
- package/src/{mesher → mesher-shared}/shared.ts +2 -0
- package/src/playground/allEntitiesDebug.ts +1 -1
- package/src/three/chunkMeshManager.ts +1 -1
- package/src/three/entities.ts +19 -6
- package/src/three/entity/EntityMesh.ts +123 -140
- package/src/three/graphicsBackendBase.ts +13 -0
- package/src/three/holdingBlock.ts +1 -1
- package/src/three/holdingBlockLegacy.ts +1 -1
- package/src/three/modules/sciFiWorldReveal.ts +1 -1
- package/src/three/worldRendererThree.ts +2 -2
- package/src/wasm-mesher/README.md +90 -0
- package/src/{wasm-lib → wasm-mesher/bridge}/convertChunk.ts +2 -2
- package/src/{wasm-lib → wasm-mesher/bridge}/render-from-wasm.ts +4 -4
- package/src/wasm-mesher/runtime-build/wasm_mesher.d.ts +210 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher.js +881 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm.d.ts +24 -0
- package/src/{mesher/test → wasm-mesher/tests}/heightmapParity.test.ts +4 -4
- package/src/{mesher/test → wasm-mesher/tests}/mesherWasmConversionCache.test.ts +2 -2
- package/src/{mesher/test → wasm-mesher/tests}/splitColumnWasmOutput.test.ts +1 -1
- package/src/wasm-mesher/worker/mesherWasm.ts +1247 -0
- package/src/{mesher → wasm-mesher/worker}/mesherWasmConversionCache.ts +1 -1
- package/src/worldView/types.ts +90 -0
- package/src/mesher/mesherWasm.ts +0 -696
- package/wasm/wasm_mesher.d.ts +0 -46
- package/wasm/wasm_mesher.js +0 -443
- package/wasm/wasm_mesher_bg.wasm +0 -0
- package/wasm/wasm_mesher_bg.wasm.d.ts +0 -9
- /package/src/{mesher → mesher-legacy}/test/a.ts +0 -0
- /package/src/{mesher → mesher-legacy}/test/playground.ts +0 -0
- /package/src/{mesher → mesher-legacy}/test/run/chunk.ts +0 -0
- /package/src/{mesher → mesher-legacy}/test/snapshotUtils.ts +0 -0
- /package/src/{mesher → mesher-shared}/blockEntityMetadata.ts +0 -0
- /package/src/{mesher → mesher-shared}/computeHeightmap.ts +0 -0
- /package/src/{mesher → mesher-shared}/models.ts +0 -0
- /package/src/{mesher → mesher-shared}/modelsGeometryCommon.ts +0 -0
- /package/src/{mesher → mesher-shared}/standaloneRenderer.ts +0 -0
- /package/src/{mesher → mesher-shared}/world.ts +0 -0
- /package/src/{mesher → mesher-shared}/worldConstants.ts +0 -0
- /package/src/{mesher → wasm-mesher/worker}/mesherWasmRequestTracker.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minecraft-renderer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"src",
|
|
12
|
-
"wasm",
|
|
13
12
|
"logo.webp"
|
|
14
13
|
],
|
|
15
14
|
"browserslist": {
|
|
@@ -121,8 +120,8 @@
|
|
|
121
120
|
"watch:lib": "node scripts/buildLib.mjs -w",
|
|
122
121
|
"build:mesher": "node scripts/buildMesherWorker.mjs",
|
|
123
122
|
"build:wasm": "cd wasm-mesher && ./build.sh web",
|
|
124
|
-
"test:wasm": "cd wasm-mesher && wasm-pack build --target nodejs --out-dir pkg --dev && node build.mjs && node test-chunk.cjs && node test-section-boundary.cjs",
|
|
125
|
-
"test:wasm:boundary": "cd wasm-mesher && wasm-pack build --target nodejs --out-dir pkg --dev && node build.mjs && node test-section-boundary.cjs",
|
|
123
|
+
"test:wasm": "cd wasm-mesher && wasm-pack build --target nodejs --out-dir pkg --dev && node build.mjs && node tests/test-chunk.cjs && node tests/test-section-boundary.cjs",
|
|
124
|
+
"test:wasm:boundary": "cd wasm-mesher && wasm-pack build --target nodejs --out-dir pkg --dev && node build.mjs && node tests/test-section-boundary.cjs",
|
|
126
125
|
"watch:mesher": "pnpm build:mesher -w",
|
|
127
126
|
"build:threeworker": "node scripts/buildThreeWorker.mjs",
|
|
128
127
|
"watch:threeworker": "pnpm build:threeworker -w",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { cp, mkdir, stat } from 'node:fs/promises'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
/** Worker-related basenames inside `minecraft-renderer`/`dist`; skipped if missing. */
|
|
7
|
+
export const MESHER_DIST_FILES = [
|
|
8
|
+
'mesher.js',
|
|
9
|
+
'mesher.js.map',
|
|
10
|
+
'mesherWasm.js',
|
|
11
|
+
'mesherWasm.js.map',
|
|
12
|
+
] as const
|
|
13
|
+
|
|
14
|
+
export type BundlePrepareMesherOptions = {
|
|
15
|
+
cwd?: string
|
|
16
|
+
packageName?: string
|
|
17
|
+
outDir?: string
|
|
18
|
+
mesherDistDir?: string
|
|
19
|
+
files?: readonly string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveSrcDist (opts: BundlePrepareMesherOptions | undefined, cwd: string): string {
|
|
23
|
+
if (opts?.mesherDistDir) return path.resolve(cwd, opts.mesherDistDir)
|
|
24
|
+
const pkg = opts?.packageName ?? 'minecraft-renderer'
|
|
25
|
+
const req = createRequire(path.join(cwd, 'package.json'))
|
|
26
|
+
return path.join(path.dirname(req.resolve(`${pkg}/package.json`)), 'dist')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function bundlePrepareMesherWorkers (opts?: BundlePrepareMesherOptions): Promise<string[]> {
|
|
30
|
+
const cwd = opts?.cwd ?? process.cwd()
|
|
31
|
+
const outDir = path.resolve(cwd, opts?.outDir ?? 'dist')
|
|
32
|
+
const srcDist = resolveSrcDist(opts, cwd)
|
|
33
|
+
const names = opts?.files ?? MESHER_DIST_FILES
|
|
34
|
+
|
|
35
|
+
await stat(srcDist).catch(() => {
|
|
36
|
+
throw new Error(`[bundlePrepareMesherWorkers] missing dist: ${srcDist}`)
|
|
37
|
+
})
|
|
38
|
+
await mkdir(outDir, { recursive: true })
|
|
39
|
+
|
|
40
|
+
const copied: string[] = []
|
|
41
|
+
for (const name of names) {
|
|
42
|
+
const from = path.join(srcDist, name)
|
|
43
|
+
let st
|
|
44
|
+
try {
|
|
45
|
+
st = await stat(from)
|
|
46
|
+
} catch {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
if (!st.isFile()) continue
|
|
50
|
+
const to = path.join(outDir, name)
|
|
51
|
+
await mkdir(path.dirname(to), { recursive: true })
|
|
52
|
+
await cp(from, to)
|
|
53
|
+
copied.push(path.relative(process.cwd(), to) || to)
|
|
54
|
+
}
|
|
55
|
+
return copied
|
|
56
|
+
}
|
|
@@ -26,6 +26,7 @@ import { getInitialPlayerState } from './playerState'
|
|
|
26
26
|
import { defaultWorldRendererConfig, defaultGraphicsBackendConfig, getDefaultRendererState, WorldRendererConfig } from './config'
|
|
27
27
|
import { PlayerStateReactive } from '../playerState/playerState'
|
|
28
28
|
import { ResourcesManager, ResourcesManagerTransferred } from '../resourcesManager'
|
|
29
|
+
import { preloadMesherWorkerScript } from './preloadWorkers'
|
|
29
30
|
|
|
30
31
|
export interface AppViewerOptions {
|
|
31
32
|
config?: Partial<GraphicsBackendConfig>
|
|
@@ -101,6 +102,15 @@ export class AppViewer {
|
|
|
101
102
|
this.resolveWorldReady = resolve
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Preload mesher worker script (HTTP validate + ephemeral Worker + `mc-web-ping` / `mc-web-pong`).
|
|
107
|
+
* Chooses `/mesherWasm.js` vs `/mesher.js` from `inWorldRenderingConfig.wasmMesher`.
|
|
108
|
+
*/
|
|
109
|
+
preloadWorkers (): Promise<void> {
|
|
110
|
+
const script = this.inWorldRenderingConfig.wasmMesher ? 'mesherWasm.js' : 'mesher.js'
|
|
111
|
+
return preloadMesherWorkerScript({ script })
|
|
112
|
+
}
|
|
113
|
+
|
|
104
114
|
/**
|
|
105
115
|
* Load a graphics backend.
|
|
106
116
|
*/
|
|
@@ -25,7 +25,7 @@ export const defaultWorldRendererConfig = {
|
|
|
25
25
|
futuristicReveal: false,
|
|
26
26
|
|
|
27
27
|
// Performance settings
|
|
28
|
-
wasmMesher:
|
|
28
|
+
wasmMesher: true,
|
|
29
29
|
mesherWorkers: 1,
|
|
30
30
|
addChunksBatchWaitTime: 200,
|
|
31
31
|
_experimentalSmoothChunkLoading: true,
|
|
@@ -35,6 +35,10 @@ export const defaultWorldRendererConfig = {
|
|
|
35
35
|
* iOS Safari and other low-RAM environments). Trades performance for
|
|
36
36
|
* lower per-worker RAM. */
|
|
37
37
|
disableMesherConversionCache: false,
|
|
38
|
+
/** Whether to dedicate the last worker exclusively to block-update
|
|
39
|
+
* remeshing (change worker). When true, initial chunk meshing is
|
|
40
|
+
* distributed only across workers[0 .. n-2]. */
|
|
41
|
+
dedicatedChangeWorker: false,
|
|
38
42
|
|
|
39
43
|
// Rendering engine settings
|
|
40
44
|
/** Face shading: vanilla Minecraft vs higher-contrast client look */
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/** Structured reason so logs / support can tell fetch vs worker vs ping failures apart. */
|
|
3
|
+
export type MesherWorkerPreloadFailure =
|
|
4
|
+
| { phase: 'fetch'; code: 'timeout' | 'network' | 'bad-status'; status?: number; detail?: string }
|
|
5
|
+
| { phase: 'fetch'; code: 'invalid-body'; hint: 'empty' | 'html' }
|
|
6
|
+
| { phase: 'worker'; code: 'construct-failed'; message: string }
|
|
7
|
+
| { phase: 'worker'; code: 'script-error'; message: string }
|
|
8
|
+
| { phase: 'ping'; code: 'timeout' | 'messageerror' | 'post-failed'; detail?: string }
|
|
9
|
+
|
|
10
|
+
export class MesherWorkerPreloadError extends Error {
|
|
11
|
+
readonly failure: MesherWorkerPreloadFailure
|
|
12
|
+
|
|
13
|
+
constructor(message: string, failure: MesherWorkerPreloadFailure) {
|
|
14
|
+
super(message)
|
|
15
|
+
this.name = 'MesherWorkerPreloadError'
|
|
16
|
+
this.failure = failure
|
|
17
|
+
console.error('[mesher preload]', failure, message)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isMcWebPong(data: unknown): boolean {
|
|
22
|
+
if (!data || typeof data !== 'object') return false
|
|
23
|
+
if ((data as { type?: string }).type === 'mc-web-pong') return true
|
|
24
|
+
if (Array.isArray(data)) {
|
|
25
|
+
return data.some(d => d && typeof d === 'object' && (d as { type?: string }).type === 'mc-web-pong')
|
|
26
|
+
}
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_FETCH_MS = 45_000
|
|
31
|
+
const DEFAULT_PING_MS = 10_000
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validates a mesher worker script over HTTP (not HTML/error page), instantiates a Worker, and waits for `mc-web-pong`.
|
|
35
|
+
* Use `mesher.js` for the legacy mesher bundle and `mesherWasm.js` for the WASM mesher bundle.
|
|
36
|
+
* Single-file builds skip (blob worker).
|
|
37
|
+
*/
|
|
38
|
+
export async function preloadMesherWorkerScript(opts?: {
|
|
39
|
+
fetchTimeoutMs?: number
|
|
40
|
+
pingTimeoutMs?: number
|
|
41
|
+
/** Worker script basename relative to `document.baseURI`. Defaults to `mesher.js`. */
|
|
42
|
+
script?: string
|
|
43
|
+
}): Promise<void> {
|
|
44
|
+
if (process.env.SINGLE_FILE_BUILD) return
|
|
45
|
+
|
|
46
|
+
const fetchTimeoutMs = opts?.fetchTimeoutMs ?? DEFAULT_FETCH_MS
|
|
47
|
+
const pingTimeoutMs = opts?.pingTimeoutMs ?? DEFAULT_PING_MS
|
|
48
|
+
const scriptBasename = opts?.script ?? 'mesher.js'
|
|
49
|
+
const scriptUrl = new URL(scriptBasename, document.baseURI).href
|
|
50
|
+
|
|
51
|
+
let res: Response
|
|
52
|
+
try {
|
|
53
|
+
const ctrl = new AbortController()
|
|
54
|
+
const t = window.setTimeout(() => ctrl.abort(), fetchTimeoutMs)
|
|
55
|
+
try {
|
|
56
|
+
res = await fetch(scriptUrl, {
|
|
57
|
+
credentials: 'same-origin',
|
|
58
|
+
cache: 'force-cache',
|
|
59
|
+
signal: ctrl.signal,
|
|
60
|
+
})
|
|
61
|
+
} finally {
|
|
62
|
+
clearTimeout(t)
|
|
63
|
+
}
|
|
64
|
+
} catch (e: unknown) {
|
|
65
|
+
const err = e as { name?: string; message?: string }
|
|
66
|
+
if (err?.name === 'AbortError') {
|
|
67
|
+
throw new MesherWorkerPreloadError(
|
|
68
|
+
`Mesher script fetch timed out after ${fetchTimeoutMs}ms (${scriptUrl}).`,
|
|
69
|
+
{ phase: 'fetch', code: 'timeout' }
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
throw new MesherWorkerPreloadError(
|
|
73
|
+
`Mesher script fetch failed (network): ${err?.message ?? e}. URL: ${scriptUrl}`,
|
|
74
|
+
{ phase: 'fetch', code: 'network', detail: String(err?.message ?? e) }
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
throw new MesherWorkerPreloadError(
|
|
80
|
+
`Mesher script HTTP ${res.status} ${res.statusText}: ${scriptUrl}`,
|
|
81
|
+
{ phase: 'fetch', code: 'bad-status', status: res.status }
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const contentType = res.headers.get('content-type') ?? ''
|
|
86
|
+
const buf = await res.arrayBuffer()
|
|
87
|
+
if (buf.byteLength === 0) {
|
|
88
|
+
throw new MesherWorkerPreloadError(
|
|
89
|
+
`Mesher script response was empty: ${scriptUrl}`,
|
|
90
|
+
{ phase: 'fetch', code: 'invalid-body', hint: 'empty' }
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const headSize = Math.min(1024, buf.byteLength)
|
|
95
|
+
const head = new TextDecoder().decode(buf.slice(0, headSize)).trimStart()
|
|
96
|
+
if (head.startsWith('<!DOCTYPE') || head.startsWith('<html') || head.startsWith('<HTML')) {
|
|
97
|
+
throw new MesherWorkerPreloadError(
|
|
98
|
+
`Mesher URL returned HTML (wrong path, redirect, or SPA fallback), not JavaScript: ${scriptUrl}`,
|
|
99
|
+
{ phase: 'fetch', code: 'invalid-body', hint: 'html' }
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (contentType.length > 0 && !/javascript|ecmascript/i.test(contentType)) {
|
|
104
|
+
console.warn('[mesher preload] Unexpected Content-Type for mesher worker script:', contentType, scriptUrl)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let worker: Worker | undefined
|
|
108
|
+
try {
|
|
109
|
+
worker = new Worker(scriptUrl)
|
|
110
|
+
} catch (e: unknown) {
|
|
111
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
112
|
+
throw new MesherWorkerPreloadError(
|
|
113
|
+
`Could not construct Worker for mesher (${scriptUrl}): ${msg}`,
|
|
114
|
+
{ phase: 'worker', code: 'construct-failed', message: msg }
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await new Promise<void>((resolve, reject) => {
|
|
119
|
+
let settled = false
|
|
120
|
+
const pingTimer = window.setTimeout(() => {
|
|
121
|
+
if (settled) return
|
|
122
|
+
settled = true
|
|
123
|
+
cleanup()
|
|
124
|
+
reject(new MesherWorkerPreloadError(
|
|
125
|
+
`Mesher worker did not reply with mc-web-pong within ${pingTimeoutMs}ms (wrong script, SW stale cache, worker blocked, or COEP/CORP). URL: ${scriptUrl}`,
|
|
126
|
+
{ phase: 'ping', code: 'timeout' }
|
|
127
|
+
))
|
|
128
|
+
}, pingTimeoutMs)
|
|
129
|
+
|
|
130
|
+
const cleanup = () => {
|
|
131
|
+
clearTimeout(pingTimer)
|
|
132
|
+
const w = worker
|
|
133
|
+
worker = undefined
|
|
134
|
+
if (!w) return
|
|
135
|
+
w.removeEventListener('message', onMessage)
|
|
136
|
+
w.removeEventListener('error', onError)
|
|
137
|
+
w.removeEventListener('messageerror', onMessageError)
|
|
138
|
+
w.terminate()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const done = () => {
|
|
142
|
+
if (settled) return
|
|
143
|
+
settled = true
|
|
144
|
+
cleanup()
|
|
145
|
+
resolve()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const fail = (err: Error) => {
|
|
149
|
+
if (settled) return
|
|
150
|
+
settled = true
|
|
151
|
+
cleanup()
|
|
152
|
+
reject(err)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function onMessage(ev: MessageEvent) {
|
|
156
|
+
if (isMcWebPong(ev.data)) done()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onError(ev: ErrorEvent) {
|
|
160
|
+
fail(new MesherWorkerPreloadError(
|
|
161
|
+
`Mesher worker script failed to load or threw during startup: ${ev.message || 'unknown'} @ ${scriptUrl}`,
|
|
162
|
+
{ phase: 'worker', code: 'script-error', message: ev.message }
|
|
163
|
+
))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function onMessageError() {
|
|
167
|
+
fail(new MesherWorkerPreloadError(
|
|
168
|
+
`Mesher worker message channel error (structured clone / deserialization). URL: ${scriptUrl}`,
|
|
169
|
+
{ phase: 'ping', code: 'messageerror' }
|
|
170
|
+
))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
worker!.addEventListener('message', onMessage)
|
|
174
|
+
worker!.addEventListener('error', onError)
|
|
175
|
+
worker!.addEventListener('messageerror', onMessageError)
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
worker!.postMessage({ type: 'mc-web-ping', t: performance.now(), workerIndex: 0 })
|
|
179
|
+
} catch (e: unknown) {
|
|
180
|
+
const detail = e instanceof Error ? e.message : String(e)
|
|
181
|
+
fail(new MesherWorkerPreloadError(
|
|
182
|
+
`Failed to post mc-web-ping to mesher worker: ${detail}`,
|
|
183
|
+
{ phase: 'ping', code: 'post-failed', detail }
|
|
184
|
+
))
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
}
|
|
@@ -9,7 +9,7 @@ import { proxy, subscribe } from 'valtio'
|
|
|
9
9
|
import type { ResourcesManagerTransferred } from '../resourcesManager/resourcesManager'
|
|
10
10
|
import { dynamicMcDataFiles } from './buildSharedConfig.mjs'
|
|
11
11
|
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState, SoundSystem } from '../graphicsBackend/types'
|
|
12
|
-
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent, SECTION_HEIGHT } from '../mesher/shared'
|
|
12
|
+
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent, SECTION_HEIGHT } from '../mesher-shared/shared'
|
|
13
13
|
import { chunkPos } from './simpleUtils'
|
|
14
14
|
import { addNewStat, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats'
|
|
15
15
|
import { getPlayerStateUtils } from '../graphicsBackend/playerState'
|
|
@@ -997,6 +997,30 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
997
997
|
getWorkerNumber(pos: Vec3, updateAction = false) {
|
|
998
998
|
const CHUNK_SIZE = 16
|
|
999
999
|
const sectionHeight = this.getSectionHeight()
|
|
1000
|
+
const dedicated = this.worldRendererConfig.dedicatedChangeWorker
|
|
1001
|
+
|
|
1002
|
+
if (dedicated && this.workers.length > 1) {
|
|
1003
|
+
// WASM column meshing must keep all vertical sections of a chunk
|
|
1004
|
+
// column on one worker — skip dedicated change worker to avoid
|
|
1005
|
+
// concurrent column meshing across different workers.
|
|
1006
|
+
if (this.worldRendererConfig.wasmMesher) {
|
|
1007
|
+
return mod(Math.floor(pos.x / CHUNK_SIZE) + Math.floor(pos.z / CHUNK_SIZE), this.workers.length)
|
|
1008
|
+
}
|
|
1009
|
+
if (updateAction) {
|
|
1010
|
+
const key = `${Math.floor(pos.x / CHUNK_SIZE) * CHUNK_SIZE},${Math.floor(pos.y / sectionHeight) * sectionHeight},${Math.floor(pos.z / CHUNK_SIZE) * CHUNK_SIZE}`
|
|
1011
|
+
const busy = this.sectionsWaiting.get(key) && !this.finishedSections[key]
|
|
1012
|
+
if (busy) {
|
|
1013
|
+
// Section is already being meshed by a general worker — route
|
|
1014
|
+
// the update to the same worker to avoid concurrent meshing.
|
|
1015
|
+
const generalWorkers = this.workers.length - 1
|
|
1016
|
+
return mod(Math.floor(pos.x / CHUNK_SIZE) + Math.floor(pos.y / sectionHeight) + Math.floor(pos.z / CHUNK_SIZE), generalWorkers)
|
|
1017
|
+
}
|
|
1018
|
+
return this.workers.length - 1
|
|
1019
|
+
}
|
|
1020
|
+
const generalWorkers = this.workers.length - 1
|
|
1021
|
+
return mod(Math.floor(pos.x / CHUNK_SIZE) + Math.floor(pos.y / sectionHeight) + Math.floor(pos.z / CHUNK_SIZE), generalWorkers)
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1000
1024
|
if (this.worldRendererConfig.wasmMesher) {
|
|
1001
1025
|
// WASM column meshing must keep all vertical sections of a chunk column
|
|
1002
1026
|
// on one worker. Hash by x/z only and bypass the change-worker shortcut
|
|
@@ -1097,7 +1121,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|
|
1097
1121
|
// Dispatch sections to workers based on position
|
|
1098
1122
|
// This guarantees uniformity accross workers and that a given section
|
|
1099
1123
|
// is always dispatched to the same worker
|
|
1100
|
-
const hash = this.getWorkerNumber(pos, useChangeWorker && this.mesherLogger.active)
|
|
1124
|
+
const hash = this.getWorkerNumber(pos, useChangeWorker && (this.mesherLogger.active || this.worldRendererConfig.dedicatedChangeWorker))
|
|
1101
1125
|
this.sectionsWaiting.set(key, (this.sectionsWaiting.get(key) ?? 0) + 1)
|
|
1102
1126
|
if (this.forceCallFromMesherReplayer) {
|
|
1103
1127
|
this.workers[hash].postMessage({
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import { Vec3 } from 'vec3'
|
|
3
|
-
import { World } from '
|
|
4
|
-
import { getSectionGeometry, setBlockStatesData as setMesherData } from '
|
|
5
|
-
import { BlockStateModelInfo } from '
|
|
6
|
-
import { handleGetHeightmap, EMPTY_COLUMN_HEIGHTMAP_SENTINEL } from '
|
|
3
|
+
import { World } from '../mesher-shared/world'
|
|
4
|
+
import { getSectionGeometry, setBlockStatesData as setMesherData } from '../mesher-shared/models'
|
|
5
|
+
import { BlockStateModelInfo } from '../mesher-shared/shared'
|
|
6
|
+
import { handleGetHeightmap, EMPTY_COLUMN_HEIGHTMAP_SENTINEL } from '../mesher-shared/computeHeightmap'
|
|
7
7
|
|
|
8
8
|
globalThis.structuredClone ??= (value) => JSON.parse(JSON.stringify(value))
|
|
9
9
|
|
|
@@ -169,6 +169,16 @@ const handleMessage = data => {
|
|
|
169
169
|
|
|
170
170
|
break
|
|
171
171
|
}
|
|
172
|
+
case 'mc-web-ping': {
|
|
173
|
+
const replyWorkerIndex = typeof data.workerIndex === 'number' ? data.workerIndex : workerIndex
|
|
174
|
+
global.postMessage({
|
|
175
|
+
type: 'mc-web-pong',
|
|
176
|
+
workerIndex: replyWorkerIndex,
|
|
177
|
+
t: data.t,
|
|
178
|
+
recvAt: typeof performance !== 'undefined' ? performance.now() : undefined,
|
|
179
|
+
})
|
|
180
|
+
break
|
|
181
|
+
}
|
|
172
182
|
// No default
|
|
173
183
|
}
|
|
174
184
|
}
|
|
@@ -3,8 +3,8 @@ import ChunkLoader, { PCChunk } from 'prismarine-chunk'
|
|
|
3
3
|
import { Vec3 } from 'vec3'
|
|
4
4
|
import MinecraftData from 'minecraft-data'
|
|
5
5
|
import blocksAtlasesJson from 'mc-assets/dist/blocksAtlases.json'
|
|
6
|
-
import { World as MesherWorld } from '
|
|
7
|
-
import { setBlockStatesData, getSectionGeometry } from '
|
|
6
|
+
import { World as MesherWorld } from '../../mesher-shared/world'
|
|
7
|
+
import { setBlockStatesData, getSectionGeometry } from '../../mesher-shared/models'
|
|
8
8
|
|
|
9
9
|
interface Options {
|
|
10
10
|
chunkOverride?: PCChunk
|
|
@@ -3,7 +3,7 @@ import ChunkLoader from 'prismarine-chunk'
|
|
|
3
3
|
import { setup } from '../mesherTester'
|
|
4
4
|
import { compareOrWriteSnapshot } from '../snapshotUtils'
|
|
5
5
|
import { getChunk, VERSION } from './chunk'
|
|
6
|
-
import { mesherGeometryToExportFormat } from '../../../wasm-
|
|
6
|
+
import { mesherGeometryToExportFormat } from '../../../wasm-mesher/bridge/render-from-wasm'
|
|
7
7
|
import fs from 'fs'
|
|
8
8
|
import { join } from 'path'
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ import PrismarineWorld from 'prismarine-world'
|
|
|
3
3
|
import PrismarineChunk from 'prismarine-chunk'
|
|
4
4
|
import { Vec3 } from 'vec3'
|
|
5
5
|
import MinecraftData from 'minecraft-data'
|
|
6
|
-
import { defaultMesherConfig } from '
|
|
6
|
+
import { defaultMesherConfig } from '../../mesher-shared/shared'
|
|
7
7
|
import { setup } from './mesherTester.js'
|
|
8
8
|
import { generateSpiralMatrix } from '../../lib/spiral'
|
|
9
9
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import { test, expect } from 'vitest'
|
|
3
3
|
import { versions } from 'minecraft-data'
|
|
4
|
-
import { INVISIBLE_BLOCKS } from '
|
|
4
|
+
import { INVISIBLE_BLOCKS } from '../../mesher-shared/worldConstants'
|
|
5
5
|
import { setup } from './mesherTester'
|
|
6
6
|
|
|
7
7
|
const lastVersion = versions.pc.map(x => x.minecraftVersion).filter(version => !version.includes('w'))[0]
|
|
@@ -87,6 +87,8 @@ export interface MesherMainEvents {
|
|
|
87
87
|
};
|
|
88
88
|
blockStateModelInfo: { type: 'blockStateModelInfo'; info: Record<string, BlockStateModelInfo> };
|
|
89
89
|
heightmap: { type: 'heightmap'; key: string; heightmap: Int16Array };
|
|
90
|
+
/** Reply to `{ type: 'mc-web-ping', t?, workerIndex? }` from the main thread (not batched in worker). */
|
|
91
|
+
mcWebPong: { type: 'mc-web-pong'; workerIndex: number; t?: number; recvAt?: number };
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
export type MesherMainEvent = MesherMainEvents[keyof MesherMainEvents]
|
|
@@ -34,7 +34,7 @@ export const displayEntitiesDebugList = (mcData: IndexedData) => {
|
|
|
34
34
|
const results: Array<{
|
|
35
35
|
entity: string;
|
|
36
36
|
supported: boolean;
|
|
37
|
-
type?: 'obj' | 'bedrock' | 'special';
|
|
37
|
+
type?: 'obj' | 'bedrock' | 'gltf' | 'special';
|
|
38
38
|
mappedFrom?: string;
|
|
39
39
|
textureMap?: boolean;
|
|
40
40
|
errors?: string[];
|
|
@@ -3,7 +3,7 @@ import PrismarineChatLoader from 'prismarine-chat'
|
|
|
3
3
|
import * as THREE from 'three'
|
|
4
4
|
import * as nbt from 'prismarine-nbt'
|
|
5
5
|
import { Vec3 } from 'vec3'
|
|
6
|
-
import { MesherGeometryOutput } from '../mesher/shared'
|
|
6
|
+
import { MesherGeometryOutput } from '../mesher-shared/shared'
|
|
7
7
|
import { chunkPos } from '../lib/simpleUtils'
|
|
8
8
|
import { renderSign } from '../sign-renderer'
|
|
9
9
|
import { getMesh } from './entity/EntityMesh'
|
package/src/three/entities.ts
CHANGED
|
@@ -28,6 +28,12 @@ import { WorldRendererThree } from './worldRendererThree'
|
|
|
28
28
|
import { IndexedData } from 'minecraft-data'
|
|
29
29
|
import { ItemSpecificContextProperties } from '../playerState/types'
|
|
30
30
|
|
|
31
|
+
export type EntityModelOverridePart = {
|
|
32
|
+
modelPath: string | ArrayBuffer
|
|
33
|
+
modelType: Entity.EntityModelType
|
|
34
|
+
metadata?: any
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
// Type for entity metadata - simplified version
|
|
32
38
|
type EntityMetadataVersions = {
|
|
33
39
|
[key: string]: any
|
|
@@ -250,7 +256,7 @@ export class Entities {
|
|
|
250
256
|
currentlyRendering = true
|
|
251
257
|
cachedMapsImages = {} as Record<number, string>
|
|
252
258
|
itemFrameMaps = {} as Record<number, Array<THREE.Mesh<THREE.PlaneGeometry, THREE.MeshLambertMaterial>>>
|
|
253
|
-
pendingModelOverrides = new Map<string, {
|
|
259
|
+
pendingModelOverrides = new Map<string, { parts: EntityModelOverridePart[] }>()
|
|
254
260
|
|
|
255
261
|
private motionCache = new Map<string, { pos: THREE.Vector3, speed: number }>()
|
|
256
262
|
private _wasThirdPerson = false
|
|
@@ -1380,8 +1386,8 @@ export class Entities {
|
|
|
1380
1386
|
beforeEntityAdded(entity: import('prismarine-entity').Entity) {
|
|
1381
1387
|
const override = this.pendingModelOverrides.get(entity.id.toString())
|
|
1382
1388
|
if (override) {
|
|
1383
|
-
const {
|
|
1384
|
-
entity['customModel'] =
|
|
1389
|
+
const { parts } = override
|
|
1390
|
+
entity['customModel'] = parts.length === 1 ? parts[0]! : { parts }
|
|
1385
1391
|
this.pendingModelOverrides.delete(entity.id.toString())
|
|
1386
1392
|
}
|
|
1387
1393
|
}
|
|
@@ -1580,9 +1586,16 @@ export class Entities {
|
|
|
1580
1586
|
return intersects[0]?.object
|
|
1581
1587
|
}
|
|
1582
1588
|
|
|
1583
|
-
updateEntityModel(
|
|
1584
|
-
|
|
1585
|
-
|
|
1589
|
+
updateEntityModel(
|
|
1590
|
+
entityId: string,
|
|
1591
|
+
modelPathOrParts: string | EntityModelOverridePart[],
|
|
1592
|
+
modelType?: Entity.EntityModelType,
|
|
1593
|
+
metadata?: any
|
|
1594
|
+
) {
|
|
1595
|
+
const parts: EntityModelOverridePart[] = Array.isArray(modelPathOrParts)
|
|
1596
|
+
? modelPathOrParts
|
|
1597
|
+
: [{ modelPath: modelPathOrParts, modelType: modelType!, metadata }]
|
|
1598
|
+
this.pendingModelOverrides.set(entityId, { parts })
|
|
1586
1599
|
|
|
1587
1600
|
// Force entity recreation if it exists
|
|
1588
1601
|
const entity = this.entities[entityId]
|