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.
@@ -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)
@@ -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
- attr.positions.push(
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
- attr.uvs.push(baseu * su + u, basev * sv + v)
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
- attr.indices[attr.indicesCount++] = ndx
494
- attr.indices[attr.indicesCount++] = ndx + 3
495
- attr.indices[attr.indicesCount++] = ndx + 2
496
- attr.indices[attr.indicesCount++] = ndx
497
- attr.indices[attr.indicesCount++] = ndx + 1
498
- attr.indices[attr.indicesCount++] = ndx + 3
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
- attr.indices[attr.indicesCount++] = ndx
501
- attr.indices[attr.indicesCount++] = ndx + 1
502
- attr.indices[attr.indicesCount++] = ndx + 2
503
- attr.indices[attr.indicesCount++] = ndx + 2
504
- attr.indices[attr.indicesCount++] = ndx + 1
505
- attr.indices[attr.indicesCount++] = ndx + 3
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 + 16,
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 + 16; cursor.y++) {
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
@@ -1,6 +1,9 @@
1
1
  //@ts-nocheck
2
2
  import { BlockType } from '../playground/shared'
3
3
 
4
+ export const IS_FULL_WORLD_SECTION = false
5
+ export const SECTION_HEIGHT = 16
6
+
4
7
  // only here for easier testing
5
8
  export const defaultMesherConfig = {
6
9
  version: '',
@@ -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')