minecraft-renderer 0.1.18 → 0.1.20
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 +87 -87
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +246 -0
- package/dist/minecraft-renderer.js +55 -55
- package/dist/threeWorker.js +395 -395
- package/package.json +3 -2
- package/public/wasm_mesher_bg.wasm +0 -0
- package/src/graphicsBackend/config.ts +4 -3
- package/src/lib/worldrendererCommon.ts +98 -48
- package/src/mesher/mesherWasm.ts +404 -0
- package/src/mesher/models.ts +53 -18
- package/src/mesher/shared.ts +3 -0
- package/src/mesher/test/run/chunk.ts +18 -0
- package/src/mesher/test/run/test-js.ts +26 -0
- package/src/mesher/test/snapshotUtils.ts +93 -0
- package/src/playground/baseScene.ts +4 -3
- package/src/resourcesManager/resourcesManager.ts +1 -2
- package/src/three/modules/sciFiWorldReveal.ts +5 -3
- package/src/three/worldBlockGeometry.ts +29 -4
- package/src/three/worldRendererThree.ts +7 -4
- package/src/wasm-lib/convertChunk.ts +192 -0
- package/src/wasm-lib/render-from-wasm.ts +1000 -0
- package/dist/metafile.json +0 -1
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { Vec3 } from 'vec3'
|
|
3
|
+
import { convertChunkToWasm } from '../wasm-lib/convertChunk'
|
|
4
|
+
import { renderWasmOutputToGeometry } from '../wasm-lib/render-from-wasm'
|
|
5
|
+
import { setBlockStatesData as setMesherData } from './models'
|
|
6
|
+
import { defaultMesherConfig, type MesherGeometryOutput, IS_FULL_WORLD_SECTION, SECTION_HEIGHT } from './shared'
|
|
7
|
+
import { worldColumnKey, World } from './world'
|
|
8
|
+
|
|
9
|
+
let wasm: typeof import('../../wasm-mesher/pkg/wasm_mesher.js') | null = null
|
|
10
|
+
let wasmInitialized = false
|
|
11
|
+
|
|
12
|
+
async function initWasm() {
|
|
13
|
+
if (wasmInitialized) return
|
|
14
|
+
try {
|
|
15
|
+
wasmInitialized = true
|
|
16
|
+
wasm = await import('../../wasm-mesher/pkg/wasm_mesher.js')
|
|
17
|
+
await wasm.default('/wasm_mesher_bg.wasm') as any
|
|
18
|
+
|
|
19
|
+
// const result = await testChunkShared(wasm)
|
|
20
|
+
// console.log('result', result)
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error('Failed to initialize WASM mesher:', err)
|
|
23
|
+
wasmInitialized = true // Don't try to initialize again
|
|
24
|
+
// Don't throw - allow worker to continue without WASM (will fail on first use)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
globalThis.structuredClone ??= (value) => JSON.parse(JSON.stringify(value))
|
|
29
|
+
|
|
30
|
+
if (globalThis.module && module.require) {
|
|
31
|
+
// If we are in a node environment, we need to fake some env variables
|
|
32
|
+
const r = module.require
|
|
33
|
+
const { parentPort } = r('worker_threads')
|
|
34
|
+
global.self = parentPort
|
|
35
|
+
global.postMessage = (value, transferList) => { parentPort.postMessage(value, transferList) }
|
|
36
|
+
global.performance = r('perf_hooks').performance
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let workerIndex = 0
|
|
40
|
+
let config = defaultMesherConfig
|
|
41
|
+
let version = '1.16.5'
|
|
42
|
+
let world: World // chunkKey -> chunk data
|
|
43
|
+
let dirtySections = new Map<string, number>()
|
|
44
|
+
let allDataReady = false
|
|
45
|
+
|
|
46
|
+
function sectionKey(x: number, y: number, z: number) {
|
|
47
|
+
return `${x},${y},${z}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const batchMessagesLimit = 100
|
|
51
|
+
|
|
52
|
+
let queuedMessages: any[] = []
|
|
53
|
+
let queueWaiting = false
|
|
54
|
+
const postMessage = (data: any, transferList: any[] = []) => {
|
|
55
|
+
queuedMessages.push({ data, transferList })
|
|
56
|
+
if (queuedMessages.length > batchMessagesLimit) {
|
|
57
|
+
drainQueue(0, batchMessagesLimit)
|
|
58
|
+
}
|
|
59
|
+
if (queueWaiting) return
|
|
60
|
+
queueWaiting = true
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
queueWaiting = false
|
|
63
|
+
drainQueue(0, queuedMessages.length)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function drainQueue(from: number, to: number) {
|
|
68
|
+
const messages = queuedMessages.slice(from, to)
|
|
69
|
+
global.postMessage(messages.map(m => m.data), messages.flatMap(m => m.transferList) as unknown as string)
|
|
70
|
+
queuedMessages = queuedMessages.slice(to)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let hadDirty = false
|
|
74
|
+
function setSectionDirty(pos: Vec3, value = true) {
|
|
75
|
+
if (hadDirty) return
|
|
76
|
+
|
|
77
|
+
// hadDirty = true
|
|
78
|
+
const x = Math.floor(pos.x / 16) * 16
|
|
79
|
+
const sectionHeight = getSectionHeight()
|
|
80
|
+
const y = Math.floor(pos.y / sectionHeight) * sectionHeight
|
|
81
|
+
const z = Math.floor(pos.z / 16) * 16
|
|
82
|
+
const key = sectionKey(x, y, z)
|
|
83
|
+
if (!value) {
|
|
84
|
+
dirtySections.delete(key)
|
|
85
|
+
postMessage({ type: 'sectionFinished', key, workerIndex })
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check if we have the chunk for this section
|
|
90
|
+
const chunk = world?.getColumn(x, z)
|
|
91
|
+
if (chunk?.getSection(pos)) {
|
|
92
|
+
dirtySections.set(key, (dirtySections.get(key) || 0) + 1)
|
|
93
|
+
} else {
|
|
94
|
+
postMessage({ type: 'sectionFinished', key, workerIndex })
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const softCleanup = () => {
|
|
99
|
+
world = new World(world.config.version)
|
|
100
|
+
globalThis.world = world
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handleMessage = async (data: any) => {
|
|
104
|
+
const globalVar: any = globalThis
|
|
105
|
+
|
|
106
|
+
if (data.type === 'mcData') {
|
|
107
|
+
globalVar.mcData = data.mcData
|
|
108
|
+
globalVar.loadedData = data.mcData
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (data.config) {
|
|
112
|
+
config = { ...config, ...data.config }
|
|
113
|
+
version = config.version || version
|
|
114
|
+
world ??= new World(version)
|
|
115
|
+
world.config = { ...world.config, ...data.config }
|
|
116
|
+
globalThis.world = world
|
|
117
|
+
globalThis.Vec3 = Vec3
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
switch (data.type) {
|
|
121
|
+
case 'mesherData': {
|
|
122
|
+
setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu')
|
|
123
|
+
;(globalThis as any).__wasmBlockModelCache = new Map()
|
|
124
|
+
|
|
125
|
+
await initWasm()
|
|
126
|
+
allDataReady = true
|
|
127
|
+
workerIndex = data.workerIndex
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
case 'dirty': {
|
|
131
|
+
const loc = new Vec3(data.x, data.y, data.z)
|
|
132
|
+
setSectionDirty(loc, data.value)
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
case 'chunk': {
|
|
136
|
+
world.addColumn(data.x, data.z, data.chunk)
|
|
137
|
+
if (data.customBlockModels) {
|
|
138
|
+
const chunkKey = `${data.x},${data.z}`
|
|
139
|
+
world.customBlockModels.set(chunkKey, data.customBlockModels)
|
|
140
|
+
}
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
case 'unloadChunk': {
|
|
144
|
+
world.removeColumn(data.x, data.z)
|
|
145
|
+
world.customBlockModels.delete(`${data.x},${data.z}`)
|
|
146
|
+
if (Object.keys(world.columns).length === 0) softCleanup()
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
case 'blockUpdate': {
|
|
150
|
+
const loc = new Vec3(data.pos.x, data.pos.y, data.pos.z).floored()
|
|
151
|
+
if (data.stateId !== undefined && data.stateId !== null) {
|
|
152
|
+
world?.setBlockStateId(loc, data.stateId)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const chunkKey = `${Math.floor(loc.x / 16) * 16},${Math.floor(loc.z / 16) * 16}`
|
|
156
|
+
if (data.customBlockModels) {
|
|
157
|
+
world?.customBlockModels.set(chunkKey, data.customBlockModels)
|
|
158
|
+
}
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
case 'reset': {
|
|
162
|
+
world = undefined as any
|
|
163
|
+
dirtySections.clear()
|
|
164
|
+
globalVar.mcData = null
|
|
165
|
+
globalVar.loadedData = null
|
|
166
|
+
allDataReady = false
|
|
167
|
+
break
|
|
168
|
+
}
|
|
169
|
+
// Note: getCustomBlockModel and getHeightmap not implemented in WASM version
|
|
170
|
+
// as they require World class functionality
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// eslint-disable-next-line no-restricted-globals -- TODO
|
|
175
|
+
self.onmessage = ({ data }) => {
|
|
176
|
+
if (Array.isArray(data)) {
|
|
177
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
178
|
+
data.forEach(handleMessage)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
handleMessage(data)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Calculate section height based on IS_FULL_WORLD_SECTION
|
|
186
|
+
const getSectionHeight = () => {
|
|
187
|
+
if (IS_FULL_WORLD_SECTION && config) {
|
|
188
|
+
return (config.worldMaxY || 256) - (config.worldMinY || 0)
|
|
189
|
+
}
|
|
190
|
+
return SECTION_HEIGHT
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
function collectChunksForSection(x: number, y: number, z: number) {
|
|
195
|
+
const result = [] as Array<{ x: number, z: number, chunk: any }>
|
|
196
|
+
result.push({ x, z, chunk: world.getColumn(x, z) })
|
|
197
|
+
const offsets = [-16, 0, 16]
|
|
198
|
+
for (const dx of offsets) {
|
|
199
|
+
for (const dz of offsets) {
|
|
200
|
+
if (dx === 0 && dz === 0) continue
|
|
201
|
+
const nx = x + dx
|
|
202
|
+
const nz = z + dz
|
|
203
|
+
const c = world.getColumn(nx, nz)
|
|
204
|
+
if (c) result.push({ x: nx, z: nz, chunk: c })
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return result.filter(r => r.chunk)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
setInterval(async () => {
|
|
211
|
+
if (!allDataReady) return
|
|
212
|
+
|
|
213
|
+
// Ensure WASM is initialized
|
|
214
|
+
if (!wasmInitialized) {
|
|
215
|
+
await initWasm()
|
|
216
|
+
if (!wasmInitialized) return // Still not initialized, skip this cycle
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (dirtySections.size === 0) return
|
|
220
|
+
|
|
221
|
+
const sectionHeight = getSectionHeight()
|
|
222
|
+
|
|
223
|
+
for (const key of dirtySections.keys()) {
|
|
224
|
+
// for (const key of [] as string[]) {
|
|
225
|
+
const [x, y, z] = key.split(',').map(v => parseInt(v, 10))
|
|
226
|
+
const chunk = world.getColumn(x, z)
|
|
227
|
+
|
|
228
|
+
let processTime = 0
|
|
229
|
+
if (chunk?.getSection(new Vec3(x, y, z)) && wasm) {
|
|
230
|
+
const start = performance.now()
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Convert chunk to WASM format (always recompute since section is dirty)
|
|
234
|
+
// If IS_FULL_WORLD_SECTION is false, only convert the specific section
|
|
235
|
+
const worldMinY = config?.worldMinY || 0
|
|
236
|
+
const worldMaxY = config?.worldMaxY || 256
|
|
237
|
+
const sectionY = IS_FULL_WORLD_SECTION ? undefined : y
|
|
238
|
+
const convertSectionHeight = IS_FULL_WORLD_SECTION ? undefined : sectionHeight
|
|
239
|
+
|
|
240
|
+
// Run WASM mesher for this section
|
|
241
|
+
const chunksToUse = collectChunksForSection(x, y, z)
|
|
242
|
+
const chunkCount = chunksToUse.length
|
|
243
|
+
|
|
244
|
+
const conversions = chunksToUse.map(({ x: cx, z: cz, chunk }) => convertChunkToWasm(
|
|
245
|
+
chunk,
|
|
246
|
+
version,
|
|
247
|
+
cx,
|
|
248
|
+
cz,
|
|
249
|
+
worldMinY,
|
|
250
|
+
worldMaxY,
|
|
251
|
+
sectionY,
|
|
252
|
+
convertSectionHeight
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
const {
|
|
256
|
+
invisibleBlocks,
|
|
257
|
+
transparentBlocks,
|
|
258
|
+
noAoBlocks,
|
|
259
|
+
cullIdenticalBlocks,
|
|
260
|
+
occludingBlocks,
|
|
261
|
+
} = conversions[0]
|
|
262
|
+
|
|
263
|
+
let wasmResult
|
|
264
|
+
if (chunkCount === 1 || !(wasm as any).generate_geometry_multi) {
|
|
265
|
+
const { blockStates, blockLight, skyLight, biomesArray } = conversions[0]
|
|
266
|
+
wasmResult = wasm.generate_geometry(
|
|
267
|
+
x, y, z, sectionHeight,
|
|
268
|
+
worldMinY, worldMaxY,
|
|
269
|
+
blockStates, blockLight, skyLight, biomesArray,
|
|
270
|
+
invisibleBlocks, transparentBlocks, noAoBlocks, cullIdenticalBlocks, occludingBlocks,
|
|
271
|
+
config?.enableLighting !== false,
|
|
272
|
+
config?.smoothLighting !== false,
|
|
273
|
+
config?.skyLight || 15
|
|
274
|
+
)
|
|
275
|
+
} else {
|
|
276
|
+
const perChunkLen = conversions[0].blockStates.length
|
|
277
|
+
const xs = new Int32Array(chunkCount)
|
|
278
|
+
const zs = new Int32Array(chunkCount)
|
|
279
|
+
const blockStatesAll = new Uint16Array(perChunkLen * chunkCount)
|
|
280
|
+
const blockLightAll = new Uint8Array(perChunkLen * chunkCount)
|
|
281
|
+
const skyLightAll = new Uint8Array(perChunkLen * chunkCount)
|
|
282
|
+
const biomesAll = new Uint8Array(perChunkLen * chunkCount)
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
285
|
+
const c = conversions[i]
|
|
286
|
+
xs[i] = chunksToUse[i].x
|
|
287
|
+
zs[i] = chunksToUse[i].z
|
|
288
|
+
blockStatesAll.set(c.blockStates, perChunkLen * i)
|
|
289
|
+
blockLightAll.set(c.blockLight, perChunkLen * i)
|
|
290
|
+
skyLightAll.set(c.skyLight, perChunkLen * i)
|
|
291
|
+
biomesAll.set(c.biomesArray, perChunkLen * i)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
wasmResult = (wasm as any).generate_geometry_multi(
|
|
295
|
+
x, y, z, sectionHeight,
|
|
296
|
+
worldMinY, worldMaxY,
|
|
297
|
+
xs, zs,
|
|
298
|
+
blockStatesAll, blockLightAll, skyLightAll, biomesAll,
|
|
299
|
+
invisibleBlocks, transparentBlocks, noAoBlocks, cullIdenticalBlocks, occludingBlocks,
|
|
300
|
+
config?.enableLighting !== false,
|
|
301
|
+
config?.smoothLighting !== false,
|
|
302
|
+
config?.skyLight || 15
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
// Convert WASM output to MesherGeometryOutput format
|
|
308
|
+
const sectionKeyStr = worldColumnKey(x, z)
|
|
309
|
+
const exportedSection = renderWasmOutputToGeometry(
|
|
310
|
+
wasmResult,
|
|
311
|
+
version,
|
|
312
|
+
sectionKeyStr,
|
|
313
|
+
{ x: x + 8, y: y + 8, z: z + 8 },
|
|
314
|
+
world
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
// Convert to MesherGeometryOutput format
|
|
318
|
+
// Determine if we need Uint32Array based on max index
|
|
319
|
+
const maxIndex = Math.max(...exportedSection.geometry.indices)
|
|
320
|
+
const using32Array = maxIndex > 65535
|
|
321
|
+
|
|
322
|
+
// console.log('exportedSection.geometry', exportedSection.geometry)
|
|
323
|
+
const geometry: MesherGeometryOutput = {
|
|
324
|
+
sectionYNumber: (y - (config?.worldMinY || 0)) >> 4,
|
|
325
|
+
chunkKey: sectionKeyStr,
|
|
326
|
+
sectionStartY: y,
|
|
327
|
+
sectionEndY: y + sectionHeight,
|
|
328
|
+
sectionStartX: x,
|
|
329
|
+
sectionEndX: x + 16,
|
|
330
|
+
sectionStartZ: z,
|
|
331
|
+
sectionEndZ: z + 16,
|
|
332
|
+
sx: x + 8,
|
|
333
|
+
sy: y + 8,
|
|
334
|
+
sz: z + 8,
|
|
335
|
+
positions: new Float32Array(exportedSection.geometry.positions),
|
|
336
|
+
normals: new Float32Array(exportedSection.geometry.normals),
|
|
337
|
+
colors: new Float32Array(exportedSection.geometry.colors),
|
|
338
|
+
uvs: new Float32Array(exportedSection.geometry.uvs),
|
|
339
|
+
indices: using32Array
|
|
340
|
+
? new Uint32Array(exportedSection.geometry.indices)
|
|
341
|
+
: new Uint16Array(exportedSection.geometry.indices),
|
|
342
|
+
indicesCount: exportedSection.geometry.indices.length,
|
|
343
|
+
using32Array,
|
|
344
|
+
tiles: {},
|
|
345
|
+
heads: {},
|
|
346
|
+
signs: {},
|
|
347
|
+
banners: {},
|
|
348
|
+
hadErrors: false,
|
|
349
|
+
blocksCount: wasmResult.block_count,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const transferable = [
|
|
353
|
+
geometry.positions?.buffer,
|
|
354
|
+
geometry.normals?.buffer,
|
|
355
|
+
geometry.colors?.buffer,
|
|
356
|
+
geometry.uvs?.buffer,
|
|
357
|
+
//@ts-ignore
|
|
358
|
+
geometry.indices?.buffer,
|
|
359
|
+
].filter(Boolean)
|
|
360
|
+
|
|
361
|
+
postMessage({ type: 'geometry', key, geometry, workerIndex }, transferable)
|
|
362
|
+
processTime = performance.now() - start
|
|
363
|
+
} catch (err) {
|
|
364
|
+
console.error(`[WASM Mesher] Error processing section ${key}:`, err)
|
|
365
|
+
// Send error geometry
|
|
366
|
+
const errorGeometry: MesherGeometryOutput = {
|
|
367
|
+
sectionYNumber: (y - (config?.worldMinY || 0)) >> 4,
|
|
368
|
+
chunkKey: worldColumnKey(x, z),
|
|
369
|
+
sectionStartY: y,
|
|
370
|
+
sectionEndY: y + sectionHeight,
|
|
371
|
+
sectionStartX: x,
|
|
372
|
+
sectionEndX: x + 16,
|
|
373
|
+
sectionStartZ: z,
|
|
374
|
+
sectionEndZ: z + 16,
|
|
375
|
+
sx: x + 8,
|
|
376
|
+
sy: y + 8,
|
|
377
|
+
sz: z + 8,
|
|
378
|
+
positions: new Float32Array(0),
|
|
379
|
+
normals: new Float32Array(0),
|
|
380
|
+
colors: new Float32Array(0),
|
|
381
|
+
uvs: new Float32Array(0),
|
|
382
|
+
indices: new Uint32Array(0),
|
|
383
|
+
indicesCount: 0,
|
|
384
|
+
using32Array: false,
|
|
385
|
+
tiles: {},
|
|
386
|
+
heads: {},
|
|
387
|
+
signs: {},
|
|
388
|
+
banners: {},
|
|
389
|
+
hadErrors: true,
|
|
390
|
+
blocksCount: 0,
|
|
391
|
+
}
|
|
392
|
+
postMessage({ type: 'geometry', key, geometry: errorGeometry, workerIndex })
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const dirtyTimes = dirtySections.get(key)
|
|
397
|
+
if (!dirtyTimes) throw new Error('dirtySections.get(key) is falsy')
|
|
398
|
+
for (let i = 0; i < dirtyTimes; i++) {
|
|
399
|
+
postMessage({ type: 'sectionFinished', key, workerIndex, processTime })
|
|
400
|
+
processTime = 0
|
|
401
|
+
}
|
|
402
|
+
dirtySections.delete(key)
|
|
403
|
+
}
|
|
404
|
+
}, 50)
|
package/src/mesher/models.ts
CHANGED
|
@@ -8,6 +8,9 @@ import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, veca
|
|
|
8
8
|
import { INVISIBLE_BLOCKS } from './worldConstants'
|
|
9
9
|
import { MesherGeometryOutput, HighestBlockInfo } from './shared'
|
|
10
10
|
|
|
11
|
+
// Log function disabled by default for zero overhead in production hot loops
|
|
12
|
+
const ENABLE_TS_LOGS = false
|
|
13
|
+
const tsLog = ENABLE_TS_LOGS ? console.log : () => { }
|
|
11
14
|
|
|
12
15
|
let blockProvider: WorldBlockProvider
|
|
13
16
|
|
|
@@ -282,6 +285,8 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
282
285
|
const { corners, mask1, mask2, side } = elemFaces[face]
|
|
283
286
|
const dir = matmul3(globalMatrix, elemFaces[face].dir)
|
|
284
287
|
|
|
288
|
+
tsLog(`[TS] Processing face ${face} at (${cursor.x}, ${cursor.y}, ${cursor.z}), dir=[${dir.join(',')}]`)
|
|
289
|
+
|
|
285
290
|
if (eFace.cullface) {
|
|
286
291
|
const neighbor = world.getBlock(cursor.plus(new Vec3(...dir)), blockProvider, {})
|
|
287
292
|
if (neighbor) {
|
|
@@ -308,6 +313,8 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
308
313
|
|
|
309
314
|
const ndx = Math.floor(attr.positions.length / 3)
|
|
310
315
|
|
|
316
|
+
tsLog(`[TS] Base index: ${ndx}`)
|
|
317
|
+
|
|
311
318
|
let tint = [1, 1, 1]
|
|
312
319
|
if (eFace.tintindex !== undefined) {
|
|
313
320
|
if (eFace.tintindex === 0) {
|
|
@@ -399,17 +406,24 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
399
406
|
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
|
|
400
407
|
vertex = vertex.map(v => v / 16)
|
|
401
408
|
|
|
402
|
-
|
|
409
|
+
const worldPos = [
|
|
403
410
|
vertex[0] + (cursor.x & 15) - 8,
|
|
404
411
|
vertex[1] + (cursor.y & 15) - 8,
|
|
405
412
|
vertex[2] + (cursor.z & 15) - 8
|
|
406
|
-
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
tsLog(`[TS] Corner ${pos.join(',')}: vertex=[${vertex.map(v => v.toFixed(3)).join(',')}], worldPos=[${worldPos.map(v => v.toFixed(3)).join(',')}]`)
|
|
416
|
+
|
|
417
|
+
attr.positions.push(...worldPos)
|
|
407
418
|
|
|
408
419
|
attr.normals.push(...dir)
|
|
409
420
|
|
|
410
421
|
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
|
|
411
422
|
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
|
|
412
|
-
|
|
423
|
+
const finalU = baseu * su + u
|
|
424
|
+
const finalV = basev * sv + v
|
|
425
|
+
tsLog(`[TS] UV: cornerUV=[${pos[3]},${pos[4]}], baseUV=[${baseu.toFixed(6)},${basev.toFixed(6)}], finalUV=[${finalU.toFixed(6)},${finalV.toFixed(6)}], texture=[u=${u},v=${v},su=${su},sv=${sv}], rotation=${r}`)
|
|
426
|
+
attr.uvs.push(finalU, finalV)
|
|
413
427
|
}
|
|
414
428
|
|
|
415
429
|
let light = 1
|
|
@@ -457,6 +471,10 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
457
471
|
// todo light should go upper on lower blocks
|
|
458
472
|
light = (ao + 1) / 4 * (cornerLightResult / 15)
|
|
459
473
|
aos.push(ao)
|
|
474
|
+
|
|
475
|
+
// Log AO and light for this corner (corner index is aos.length - 1)
|
|
476
|
+
const cornerIdx = aos.length - 1
|
|
477
|
+
tsLog(`[TS] Corner ${cornerIdx} AO=${ao}, light=${light.toFixed(3)}`)
|
|
460
478
|
}
|
|
461
479
|
|
|
462
480
|
if (!needTiles) {
|
|
@@ -489,20 +507,27 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
489
507
|
}
|
|
490
508
|
|
|
491
509
|
if (!needTiles) {
|
|
510
|
+
let tri1: number[], tri2: number[]
|
|
492
511
|
if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
attr.indices[attr.indicesCount++] =
|
|
497
|
-
attr.indices[attr.indicesCount++] =
|
|
498
|
-
attr.indices[attr.indicesCount++] =
|
|
512
|
+
tri1 = [ndx, ndx + 3, ndx + 2]
|
|
513
|
+
tri2 = [ndx, ndx + 1, ndx + 3]
|
|
514
|
+
tsLog(`[TS] Indices (AO optimized): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aos.join(',')}]`)
|
|
515
|
+
attr.indices[attr.indicesCount++] = tri1[0]
|
|
516
|
+
attr.indices[attr.indicesCount++] = tri1[1]
|
|
517
|
+
attr.indices[attr.indicesCount++] = tri1[2]
|
|
518
|
+
attr.indices[attr.indicesCount++] = tri2[0]
|
|
519
|
+
attr.indices[attr.indicesCount++] = tri2[1]
|
|
520
|
+
attr.indices[attr.indicesCount++] = tri2[2]
|
|
499
521
|
} else {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
attr.indices[attr.indicesCount++] =
|
|
504
|
-
attr.indices[attr.indicesCount++] =
|
|
505
|
-
attr.indices[attr.indicesCount++] =
|
|
522
|
+
tri1 = [ndx, ndx + 1, ndx + 2]
|
|
523
|
+
tri2 = [ndx + 2, ndx + 1, ndx + 3]
|
|
524
|
+
tsLog(`[TS] Indices (standard): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aos.join(',')}]`)
|
|
525
|
+
attr.indices[attr.indicesCount++] = tri1[0]
|
|
526
|
+
attr.indices[attr.indicesCount++] = tri1[1]
|
|
527
|
+
attr.indices[attr.indicesCount++] = tri1[2]
|
|
528
|
+
attr.indices[attr.indicesCount++] = tri2[0]
|
|
529
|
+
attr.indices[attr.indicesCount++] = tri2[1]
|
|
530
|
+
attr.indices[attr.indicesCount++] = tri2[2]
|
|
506
531
|
}
|
|
507
532
|
}
|
|
508
533
|
}
|
|
@@ -520,14 +545,14 @@ const isBlockWaterlogged = (block: Block) => {
|
|
|
520
545
|
}
|
|
521
546
|
|
|
522
547
|
let unknownBlockModel: BlockModelPartsResolved
|
|
523
|
-
export function getSectionGeometry(sx: number, sy: number, sz: number, world: World) {
|
|
548
|
+
export function getSectionGeometry(sx: number, sy: number, sz: number, world: World, readHeight = 16) {
|
|
524
549
|
let delayedRender = [] as Array<() => void>
|
|
525
550
|
|
|
526
551
|
const attr: MesherGeometryOutput = {
|
|
527
552
|
sectionYNumber: (sy - world.config.worldMinY) >> 4,
|
|
528
553
|
chunkKey: worldColumnKey(sx, sz),
|
|
529
554
|
sectionStartY: sy,
|
|
530
|
-
sectionEndY: sy +
|
|
555
|
+
sectionEndY: sy + readHeight,
|
|
531
556
|
sectionStartX: sx,
|
|
532
557
|
sectionEndX: sx + 16,
|
|
533
558
|
sectionStartZ: sz,
|
|
@@ -557,7 +582,7 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
557
582
|
}
|
|
558
583
|
|
|
559
584
|
const cursor = new Vec3(0, 0, 0)
|
|
560
|
-
for (cursor.y = sy; cursor.y < sy +
|
|
585
|
+
for (cursor.y = sy; cursor.y < sy + readHeight; cursor.y++) {
|
|
561
586
|
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
|
|
562
587
|
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
|
|
563
588
|
let block = world.getBlock(cursor, blockProvider, attr)!
|
|
@@ -642,6 +667,8 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
642
667
|
// cache
|
|
643
668
|
let { models } = block
|
|
644
669
|
|
|
670
|
+
tsLog(`[TS] Processing block at (${cursor.x}, ${cursor.y}, ${cursor.z}), name=${block.name}, stateId=${block.stateId}`)
|
|
671
|
+
|
|
645
672
|
models ??= unknownBlockModel
|
|
646
673
|
|
|
647
674
|
const firstForceVar = world.config.debugModelVariant?.[0]
|
|
@@ -734,6 +761,14 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
734
761
|
attr.indices = new Uint16Array(attr.indices)
|
|
735
762
|
}
|
|
736
763
|
|
|
764
|
+
tsLog(`[TS] Final geometry summary:`)
|
|
765
|
+
tsLog(`[TS] Total vertices: ${attr.positions.length / 3}`)
|
|
766
|
+
tsLog(`[TS] Total triangles: ${attr.indices.length / 3}`)
|
|
767
|
+
const posArray = Array.from(attr.positions)
|
|
768
|
+
const idxArray = Array.from(attr.indices)
|
|
769
|
+
tsLog(`[TS] Positions: [${posArray.slice(0, 12).join(',')}...] (first 4 vertices)`)
|
|
770
|
+
tsLog(`[TS] Indices: [${idxArray.slice(0, 12).join(',')}...] (first 2 faces)`)
|
|
771
|
+
|
|
737
772
|
if (needTiles) {
|
|
738
773
|
delete attr.positions
|
|
739
774
|
delete attr.normals
|
package/src/mesher/shared.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import ChunkLoader, { PCChunk } from 'prismarine-chunk'
|
|
3
|
+
import { biomes, bitMap, chunkData, groundUp } from '../../../../test-snapshots/1.16.5/chunk.json'
|
|
4
|
+
import { Vec3 } from 'vec3'
|
|
5
|
+
|
|
6
|
+
export const SNAPSHOT_FILE = 'test-snapshots/1.16.5/wasm-chunk.snapshot.json'
|
|
7
|
+
export const VERSION = '1.16.5'
|
|
8
|
+
export const getChunk = () => {
|
|
9
|
+
const Chunk = ChunkLoader(VERSION)
|
|
10
|
+
const chunkDataBuffer = Buffer.from(chunkData.data)
|
|
11
|
+
|
|
12
|
+
const chunk = new Chunk({ minY: 0, worldHeight: 256, x: 0, z: 0 }) as PCChunk
|
|
13
|
+
// chunk.load(chunkDataBuffer, bitMap, true, groundUp)
|
|
14
|
+
chunk.setBlockStateId(new Vec3(0, 1, 0), 1)
|
|
15
|
+
// chunk.setBlockStateId(new Vec3(0, 0, 1), 1)
|
|
16
|
+
|
|
17
|
+
return chunk
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import ChunkLoader from 'prismarine-chunk'
|
|
3
|
+
import { setup } from '../mesherTester'
|
|
4
|
+
import { compareOrWriteSnapshot } from '../snapshotUtils'
|
|
5
|
+
import { getChunk, VERSION } from './chunk'
|
|
6
|
+
import { mesherGeometryToExportFormat } from '../../../wasm-lib/render-from-wasm'
|
|
7
|
+
import fs from 'fs'
|
|
8
|
+
import { join } from 'path'
|
|
9
|
+
|
|
10
|
+
const chunk = getChunk()
|
|
11
|
+
const { getGeometry } = setup(VERSION, [], { chunkOverride: chunk, noDebugTiles: true, })
|
|
12
|
+
|
|
13
|
+
getGeometry()
|
|
14
|
+
console.time('getGeometry')
|
|
15
|
+
globalThis.a = 0
|
|
16
|
+
const { attr } = getGeometry()
|
|
17
|
+
console.log('attr.positions', attr.positions)
|
|
18
|
+
debugger
|
|
19
|
+
console.timeEnd('getGeometry')
|
|
20
|
+
console.log(globalThis.a)
|
|
21
|
+
|
|
22
|
+
fs.writeFileSync(join(__dirname, '../../../../public/world-geometry.json'), JSON.stringify(mesherGeometryToExportFormat(attr, VERSION), null, 2))
|
|
23
|
+
fs.writeFileSync(join(__dirname, '../../../../public/world-geometry-js.json'), JSON.stringify(mesherGeometryToExportFormat(attr, VERSION), null, 2))
|
|
24
|
+
|
|
25
|
+
// Compare or write snapshot
|
|
26
|
+
compareOrWriteSnapshot(attr, 'b.snapshot.json')
|