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,1000 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
// Renderer that converts WASM mesher output to Three.js geometry
|
|
3
|
+
// This file takes WASM output and generates full Three.js buffer geometry
|
|
4
|
+
|
|
5
|
+
import * as THREE from 'three'
|
|
6
|
+
import worldBlockProviderModule, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
|
7
|
+
import blocksAtlasesJson from 'mc-assets/dist/blocksAtlases.json'
|
|
8
|
+
import blockStatesModels from 'mc-assets/dist/blockStatesModels.json'
|
|
9
|
+
import MinecraftData from 'minecraft-data'
|
|
10
|
+
import PrismarineBlockLoader from 'prismarine-block'
|
|
11
|
+
import { Vec3 } from 'vec3'
|
|
12
|
+
import { elemFaces, buildRotationMatrix, matmul3, matmulmat3, vecadd3, vecsub3 } from '../mesher/modelsGeometryCommon'
|
|
13
|
+
import type { ExportedWorldGeometry, ExportedSection } from '../three/worldGeometryExport'
|
|
14
|
+
import type { MesherGeometryOutput } from '../mesher/shared'
|
|
15
|
+
import type { World } from '../mesher/world'
|
|
16
|
+
|
|
17
|
+
// Handle both default and named export
|
|
18
|
+
const worldBlockProvider = (worldBlockProviderModule as any).default || worldBlockProviderModule
|
|
19
|
+
|
|
20
|
+
// Initialize tints (same as in models.ts)
|
|
21
|
+
const tints: any = {}
|
|
22
|
+
let tintsInitialized = false
|
|
23
|
+
|
|
24
|
+
function initializeTints() {
|
|
25
|
+
if (tintsInitialized) return
|
|
26
|
+
let tintsData
|
|
27
|
+
try {
|
|
28
|
+
tintsData = require('esbuild-data').tints
|
|
29
|
+
} catch (err) {
|
|
30
|
+
tintsData = require('minecraft-data/minecraft-data/data/pc/1.16.2/tints.json')
|
|
31
|
+
}
|
|
32
|
+
for (const key of Object.keys(tintsData)) {
|
|
33
|
+
tints[key] = prepareTints(tintsData[key])
|
|
34
|
+
}
|
|
35
|
+
tintsInitialized = true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function prepareTints(tints: any) {
|
|
39
|
+
const map = new Map()
|
|
40
|
+
const defaultValue = tintToGl(tints.default)
|
|
41
|
+
for (let { keys, color } of tints.data) {
|
|
42
|
+
color = tintToGl(color)
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
map.set(`${key}`, color)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return new Proxy(map, {
|
|
48
|
+
get(target, key) {
|
|
49
|
+
return target.has(key) ? target.get(key) : defaultValue
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function tintToGl(tint: number) {
|
|
55
|
+
const r = (tint >> 16) & 0xff
|
|
56
|
+
const g = (tint >> 8) & 0xff
|
|
57
|
+
const b = tint & 0xff
|
|
58
|
+
return [r / 255, g / 255, b / 255]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Cached model definition with precomputed matrices
|
|
62
|
+
interface CachedBlockModel {
|
|
63
|
+
blockName: string
|
|
64
|
+
blockProps: Record<string, any>
|
|
65
|
+
models: any // BlockModelPartsResolved
|
|
66
|
+
isCube: boolean
|
|
67
|
+
// Precomputed per-model variant
|
|
68
|
+
modelVariants: Array<{
|
|
69
|
+
model: any
|
|
70
|
+
globalMatrix: any
|
|
71
|
+
globalShift: any
|
|
72
|
+
// Precomputed per-element
|
|
73
|
+
elements: Array<{
|
|
74
|
+
element: any
|
|
75
|
+
localMatrix: any
|
|
76
|
+
localShift: any
|
|
77
|
+
}>
|
|
78
|
+
}>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface WasmBlockFaceData {
|
|
82
|
+
position: [number, number, number]
|
|
83
|
+
block_state_id: number
|
|
84
|
+
visible_faces: number
|
|
85
|
+
ao_data: number[][]
|
|
86
|
+
light_data: number[][]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface WasmGeometryOutput {
|
|
90
|
+
blocks: WasmBlockFaceData[]
|
|
91
|
+
block_count: number
|
|
92
|
+
block_iterations: number
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get or create cached block model with precomputed matrices
|
|
97
|
+
*/
|
|
98
|
+
function getCachedBlockModel(
|
|
99
|
+
blockStateId: number,
|
|
100
|
+
version: string,
|
|
101
|
+
blockProvider: WorldBlockProvider,
|
|
102
|
+
PrismarineBlock: any
|
|
103
|
+
): CachedBlockModel | null {
|
|
104
|
+
// Use a module-level cache
|
|
105
|
+
const cacheKey = `${version}:${blockStateId}`
|
|
106
|
+
if (!(globalThis as any).__wasmBlockModelCache) {
|
|
107
|
+
(globalThis as any).__wasmBlockModelCache = new Map()
|
|
108
|
+
}
|
|
109
|
+
const cache = (globalThis as any).__wasmBlockModelCache
|
|
110
|
+
|
|
111
|
+
if (cache.has(cacheKey)) {
|
|
112
|
+
return cache.get(cacheKey)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const blockObj = PrismarineBlock.fromStateId(blockStateId, 1)
|
|
117
|
+
const blockName = blockObj.name
|
|
118
|
+
const blockProps = blockObj.getProperties()
|
|
119
|
+
|
|
120
|
+
const models = blockProvider.getAllResolvedModels0_1(
|
|
121
|
+
{ name: blockName, properties: blockProps },
|
|
122
|
+
false
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if (!models || models.length === 0) return null
|
|
126
|
+
|
|
127
|
+
// Precompute matrices for all model variants
|
|
128
|
+
const modelVariants = models.map((modelVars) => {
|
|
129
|
+
return modelVars.map((model) => {
|
|
130
|
+
// Calculate global matrix and shift for model rotation
|
|
131
|
+
let globalMatrix = null as any
|
|
132
|
+
let globalShift = null as any
|
|
133
|
+
for (const axis of ['x', 'y', 'z'] as const) {
|
|
134
|
+
if (axis in model) {
|
|
135
|
+
globalMatrix = globalMatrix
|
|
136
|
+
? matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0)))
|
|
137
|
+
: buildRotationMatrix(axis, -(model[axis] ?? 0))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (globalMatrix) {
|
|
141
|
+
globalShift = [8, 8, 8]
|
|
142
|
+
globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Precompute element matrices
|
|
146
|
+
const elements = (model.elements ?? []).map((element: any) => {
|
|
147
|
+
let localMatrix = null as any
|
|
148
|
+
let localShift = null as any
|
|
149
|
+
if (element.rotation) {
|
|
150
|
+
localMatrix = buildRotationMatrix(
|
|
151
|
+
element.rotation.axis,
|
|
152
|
+
element.rotation.angle
|
|
153
|
+
)
|
|
154
|
+
localShift = vecsub3(
|
|
155
|
+
element.rotation.origin,
|
|
156
|
+
matmul3(localMatrix, element.rotation.origin)
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
return { element, localMatrix, localShift }
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
return { model, globalMatrix, globalShift, elements }
|
|
163
|
+
})
|
|
164
|
+
}).flat()
|
|
165
|
+
|
|
166
|
+
const isCube = (() => {
|
|
167
|
+
try {
|
|
168
|
+
if (!models?.length || models.length !== 1) return false
|
|
169
|
+
if (blockObj.transparent) return false
|
|
170
|
+
return models[0].every((v) => v.elements.every((e) => {
|
|
171
|
+
return e.from[0] === 0 && e.from[1] === 0 && e.from[2] === 0 && e.to[0] === 16 && e.to[1] === 16 && e.to[2] === 16
|
|
172
|
+
}))
|
|
173
|
+
} catch {
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
})()
|
|
177
|
+
|
|
178
|
+
const cached: CachedBlockModel = {
|
|
179
|
+
blockName,
|
|
180
|
+
blockProps,
|
|
181
|
+
models,
|
|
182
|
+
modelVariants,
|
|
183
|
+
isCube,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
cache.set(cacheKey, cached)
|
|
187
|
+
return cached
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.warn(`Failed to get model for state ${blockStateId}:`, err)
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get tint for a face (matching TypeScript logic)
|
|
196
|
+
*/
|
|
197
|
+
function getTint(
|
|
198
|
+
eFace: any,
|
|
199
|
+
blockName: string,
|
|
200
|
+
blockProps: Record<string, any>,
|
|
201
|
+
biome: string | undefined,
|
|
202
|
+
world: World | undefined
|
|
203
|
+
): [number, number, number] {
|
|
204
|
+
if (eFace.tintindex === undefined) return [1, 1, 1]
|
|
205
|
+
|
|
206
|
+
if (eFace.tintindex === 0) {
|
|
207
|
+
if (blockName === 'redstone_wire') {
|
|
208
|
+
initializeTints()
|
|
209
|
+
return tints.redstone[`${blockProps.power}`] || [1, 1, 1]
|
|
210
|
+
} else if (
|
|
211
|
+
blockName === 'birch_leaves' ||
|
|
212
|
+
blockName === 'spruce_leaves' ||
|
|
213
|
+
blockName === 'lily_pad'
|
|
214
|
+
) {
|
|
215
|
+
initializeTints()
|
|
216
|
+
return tints.constant[blockName] || [1, 1, 1]
|
|
217
|
+
} else if (blockName.includes('leaves') || blockName === 'vine') {
|
|
218
|
+
initializeTints()
|
|
219
|
+
return tints.foliage[biome || 'plains'] || [1, 1, 1]
|
|
220
|
+
} else {
|
|
221
|
+
initializeTints()
|
|
222
|
+
return tints.grass[biome || 'plains'] || [1, 1, 1]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return [1, 1, 1]
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const ALWAYS_WATERLOGGED = new Set([
|
|
230
|
+
'seagrass',
|
|
231
|
+
'tall_seagrass',
|
|
232
|
+
'kelp',
|
|
233
|
+
'kelp_plant',
|
|
234
|
+
'bubble_column'
|
|
235
|
+
])
|
|
236
|
+
|
|
237
|
+
const isBlockWaterlogged = (block: any) => {
|
|
238
|
+
const props = block?.getProperties?.()
|
|
239
|
+
return props?.waterlogged === true || props?.waterlogged === 'true' || ALWAYS_WATERLOGGED.has(block?.name)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const getVec = (v: Vec3, dir: Vec3) => {
|
|
243
|
+
for (const coord of ['x', 'y', 'z'] as const) {
|
|
244
|
+
if (Math.abs((dir as any)[coord]) > 0) (v as any)[coord] = 0
|
|
245
|
+
}
|
|
246
|
+
return v.plus(dir)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const getLiquidRenderHeight = (world: World, block: any, type: number, pos: Vec3, isWater: boolean, isRealWater: boolean) => {
|
|
250
|
+
if ((isWater && !isRealWater) || (block && isBlockWaterlogged(block))) return 8 / 9
|
|
251
|
+
if (!block || block.type !== type) return 1 / 9
|
|
252
|
+
if (block.metadata === 0) {
|
|
253
|
+
const blockAbove = world.getBlock(pos.offset(0, 1, 0))
|
|
254
|
+
if (blockAbove && blockAbove.type === type) return 1
|
|
255
|
+
return 8 / 9
|
|
256
|
+
}
|
|
257
|
+
return ((block.metadata >= 8 ? 8 : 7 - block.metadata) + 1) / 9
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const renderLiquidToGeometry = (
|
|
261
|
+
world: World,
|
|
262
|
+
cursor: Vec3,
|
|
263
|
+
texture: any,
|
|
264
|
+
type: number,
|
|
265
|
+
biome: string,
|
|
266
|
+
water: boolean,
|
|
267
|
+
isRealWater: boolean,
|
|
268
|
+
positions: number[],
|
|
269
|
+
normals: number[],
|
|
270
|
+
colors: number[],
|
|
271
|
+
uvs: number[],
|
|
272
|
+
indices: number[],
|
|
273
|
+
) => {
|
|
274
|
+
const heights: number[] = []
|
|
275
|
+
for (let z = -1; z <= 1; z++) {
|
|
276
|
+
for (let x = -1; x <= 1; x++) {
|
|
277
|
+
const pos = cursor.offset(x, 0, z)
|
|
278
|
+
heights.push(getLiquidRenderHeight(world, world.getBlock(pos), type, pos, water, isRealWater))
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const cornerHeights = [
|
|
283
|
+
Math.max(Math.max(heights[0], heights[1]), Math.max(heights[3], heights[4])),
|
|
284
|
+
Math.max(Math.max(heights[1], heights[2]), Math.max(heights[4], heights[5])),
|
|
285
|
+
Math.max(Math.max(heights[3], heights[4]), Math.max(heights[6], heights[7])),
|
|
286
|
+
Math.max(Math.max(heights[4], heights[5]), Math.max(heights[7], heights[8]))
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
for (const face in elemFaces) {
|
|
290
|
+
const { dir, corners, mask1, mask2 } = (elemFaces as any)[face]
|
|
291
|
+
const isUp = dir[1] === 1
|
|
292
|
+
|
|
293
|
+
const neighborPos = cursor.offset(dir[0], dir[1], dir[2])
|
|
294
|
+
const neighbor = world.getBlock(neighborPos)
|
|
295
|
+
if (!neighbor) continue
|
|
296
|
+
if (neighbor.type === type || (water && (neighbor.name === 'water' || isBlockWaterlogged(neighbor)))) continue
|
|
297
|
+
if (neighbor.isCube && !neighbor.transparent && !isUp) continue
|
|
298
|
+
|
|
299
|
+
let tint: [number, number, number] = [1, 1, 1]
|
|
300
|
+
if (water) {
|
|
301
|
+
initializeTints()
|
|
302
|
+
let m = 1
|
|
303
|
+
if (Math.abs(dir[0]) > 0) m = 0.6
|
|
304
|
+
else if (Math.abs(dir[2]) > 0) m = 0.8
|
|
305
|
+
const wt = tints.water[biome] || [1, 1, 1]
|
|
306
|
+
tint = [wt[0] * m, wt[1] * m, wt[2] * m]
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const u = texture.u || 0
|
|
310
|
+
const v = texture.v || 0
|
|
311
|
+
const su = texture.su || 1
|
|
312
|
+
const sv = texture.sv || 1
|
|
313
|
+
|
|
314
|
+
const baseLight = world.getLight(neighborPos, undefined, undefined, water ? 'water' : 'lava') / 15
|
|
315
|
+
|
|
316
|
+
const baseIndex = positions.length / 3
|
|
317
|
+
|
|
318
|
+
for (const pos of corners) {
|
|
319
|
+
const height = cornerHeights[pos[2] * 2 + pos[0]]
|
|
320
|
+
const OFFSET = 0.0001
|
|
321
|
+
|
|
322
|
+
positions.push(
|
|
323
|
+
(pos[0] ? 1 - OFFSET : OFFSET) + (cursor.x & 15) - 8,
|
|
324
|
+
(pos[1] ? height - OFFSET : OFFSET) + (cursor.y & 15) - 8,
|
|
325
|
+
(pos[2] ? 1 - OFFSET : OFFSET) + (cursor.z & 15) - 8
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
normals.push(dir[0], dir[1], dir[2])
|
|
329
|
+
uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v)
|
|
330
|
+
|
|
331
|
+
let cornerLightResult = baseLight
|
|
332
|
+
if (world.config.smoothLighting) {
|
|
333
|
+
const dx = pos[0] * 2 - 1
|
|
334
|
+
const dy = pos[1] * 2 - 1
|
|
335
|
+
const dz = pos[2] * 2 - 1
|
|
336
|
+
const cornerDir: [number, number, number] = [dx, dy, dz]
|
|
337
|
+
const side1Dir: [number, number, number] = [dx * mask1[0], dy * mask1[1], dz * mask1[2]]
|
|
338
|
+
const side2Dir: [number, number, number] = [dx * mask2[0], dy * mask2[1], dz * mask2[2]]
|
|
339
|
+
|
|
340
|
+
const dirVec = new Vec3(dir[0], dir[1], dir[2])
|
|
341
|
+
|
|
342
|
+
const side1LightDir = getVec(new Vec3(side1Dir[0], side1Dir[1], side1Dir[2]), dirVec)
|
|
343
|
+
const side1Light = world.getLight(cursor.plus(side1LightDir)) / 15
|
|
344
|
+
const side2DirLight = getVec(new Vec3(side2Dir[0], side2Dir[1], side2Dir[2]), dirVec)
|
|
345
|
+
const side2Light = world.getLight(cursor.plus(side2DirLight)) / 15
|
|
346
|
+
const cornerLightDir = getVec(new Vec3(cornerDir[0], cornerDir[1], cornerDir[2]), dirVec)
|
|
347
|
+
const cornerLight = world.getLight(cursor.plus(cornerLightDir)) / 15
|
|
348
|
+
|
|
349
|
+
cornerLightResult = (side1Light + side2Light + cornerLight + baseLight) / 4
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
colors.push(tint[0] * cornerLightResult, tint[1] * cornerLightResult, tint[2] * cornerLightResult)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
indices.push(
|
|
356
|
+
baseIndex,
|
|
357
|
+
baseIndex + 1,
|
|
358
|
+
baseIndex + 2,
|
|
359
|
+
baseIndex + 2,
|
|
360
|
+
baseIndex + 1,
|
|
361
|
+
baseIndex + 3,
|
|
362
|
+
baseIndex,
|
|
363
|
+
baseIndex + 2,
|
|
364
|
+
baseIndex + 1,
|
|
365
|
+
baseIndex + 2,
|
|
366
|
+
baseIndex + 3,
|
|
367
|
+
baseIndex + 1,
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Render WASM mesher output to Three.js geometry
|
|
374
|
+
*/
|
|
375
|
+
export function renderWasmOutputToGeometry(
|
|
376
|
+
wasmOutput: WasmGeometryOutput,
|
|
377
|
+
version: string,
|
|
378
|
+
sectionKey: string,
|
|
379
|
+
sectionPosition: { x: number, y: number, z: number },
|
|
380
|
+
world?: World
|
|
381
|
+
): ExportedSection {
|
|
382
|
+
const DEBUG = false
|
|
383
|
+
const log = (...args) => {
|
|
384
|
+
if (DEBUG) {
|
|
385
|
+
console.log(...args)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const mcData = MinecraftData(version)
|
|
390
|
+
const PrismarineBlock = PrismarineBlockLoader(version)
|
|
391
|
+
|
|
392
|
+
let blockProvider: WorldBlockProvider
|
|
393
|
+
if ((globalThis as any).blockProvider) {
|
|
394
|
+
blockProvider = (globalThis as any).blockProvider
|
|
395
|
+
} else if (typeof worldBlockProvider === 'function') {
|
|
396
|
+
blockProvider = worldBlockProvider(blockStatesModels, blocksAtlasesJson, version)
|
|
397
|
+
} else {
|
|
398
|
+
const wbp = require('mc-assets/dist/worldBlockProvider')
|
|
399
|
+
blockProvider = (wbp.default || wbp)(blockStatesModels, blocksAtlasesJson, version)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Initialize tints if world is provided
|
|
403
|
+
if (world) {
|
|
404
|
+
initializeTints()
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const positions: number[] = []
|
|
408
|
+
const normals: number[] = []
|
|
409
|
+
const colors: number[] = []
|
|
410
|
+
const uvs: number[] = []
|
|
411
|
+
const indices: number[] = []
|
|
412
|
+
|
|
413
|
+
const liquidQueue: Array<{
|
|
414
|
+
pos: Vec3,
|
|
415
|
+
type: number,
|
|
416
|
+
biome: string,
|
|
417
|
+
water: boolean,
|
|
418
|
+
isRealWater: boolean,
|
|
419
|
+
}> = []
|
|
420
|
+
|
|
421
|
+
let currentIndex = 0
|
|
422
|
+
|
|
423
|
+
for (const block of wasmOutput.blocks) {
|
|
424
|
+
const [bx, by, bz] = block.position
|
|
425
|
+
const blockStateId = block.block_state_id
|
|
426
|
+
|
|
427
|
+
log(`[WASM] Processing block at (${bx}, ${by}, ${bz}), stateId=${blockStateId}, visible_faces=0b${block.visible_faces.toString(2).padStart(6, '0')}`)
|
|
428
|
+
|
|
429
|
+
const prismBlock = PrismarineBlock.fromStateId(blockStateId, 1)
|
|
430
|
+
|
|
431
|
+
let biome: string | undefined
|
|
432
|
+
if (world) {
|
|
433
|
+
const blockObj = world.getBlock(new Vec3(bx, by, bz))
|
|
434
|
+
biome = blockObj?.biome?.name
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (world) {
|
|
438
|
+
const waterlogged = prismBlock.name !== 'water' && prismBlock.name !== 'lava' && isBlockWaterlogged(prismBlock)
|
|
439
|
+
|
|
440
|
+
if (prismBlock.name === 'water' || waterlogged) {
|
|
441
|
+
liquidQueue.push({
|
|
442
|
+
pos: new Vec3(bx, by, bz),
|
|
443
|
+
type: prismBlock.type,
|
|
444
|
+
biome: biome || 'plains',
|
|
445
|
+
water: true,
|
|
446
|
+
isRealWater: prismBlock.name === 'water' && !waterlogged,
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (prismBlock.name === 'lava') {
|
|
451
|
+
liquidQueue.push({
|
|
452
|
+
pos: new Vec3(bx, by, bz),
|
|
453
|
+
type: prismBlock.type,
|
|
454
|
+
biome: biome || 'plains',
|
|
455
|
+
water: false,
|
|
456
|
+
isRealWater: false,
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (prismBlock.name === 'water' || prismBlock.name === 'lava') {
|
|
461
|
+
continue
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const cachedModel = getCachedBlockModel(blockStateId, version, blockProvider, PrismarineBlock)
|
|
466
|
+
if (!cachedModel) continue
|
|
467
|
+
|
|
468
|
+
if (false) {
|
|
469
|
+
// For now, use first model variant (can be extended later)
|
|
470
|
+
const modelVariant = cachedModel.modelVariants[0]
|
|
471
|
+
if (!modelVariant) continue
|
|
472
|
+
|
|
473
|
+
const { model, globalMatrix, globalShift, elements } = modelVariant
|
|
474
|
+
|
|
475
|
+
// Get biome for tint calculation if world is provided
|
|
476
|
+
let biome: string | undefined
|
|
477
|
+
if (world) {
|
|
478
|
+
const blockObj = world.getBlock(new Vec3(bx, by, bz))
|
|
479
|
+
biome = blockObj?.biome?.name
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Process faces in the same order as TypeScript (iterate through model's faces)
|
|
483
|
+
// TypeScript uses: for (const face in element.faces)
|
|
484
|
+
// We need to match this order to get the same vertex ordering
|
|
485
|
+
|
|
486
|
+
// Find the element that contains faces (use cached element data)
|
|
487
|
+
const faceElements = elements.filter(elemData => elemData.element.faces && Object.keys(elemData.element.faces).length > 0)
|
|
488
|
+
|
|
489
|
+
if (faceElements.length === 0) continue
|
|
490
|
+
|
|
491
|
+
// Map face names to their index in WASM output
|
|
492
|
+
const faceNameToIndex: Record<string, number> = {
|
|
493
|
+
'up': 0,
|
|
494
|
+
'down': 1,
|
|
495
|
+
'east': 2,
|
|
496
|
+
'west': 3,
|
|
497
|
+
'south': 4,
|
|
498
|
+
'north': 5
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// WASM processes faces in fixed order: [up, down, east, west, south, north]
|
|
502
|
+
// Build a mapping from WASM face order to data index
|
|
503
|
+
const wasmFaceOrder = ['up', 'down', 'east', 'west', 'south', 'north']
|
|
504
|
+
const wasmFaceToDataIndex: Record<string, number> = {}
|
|
505
|
+
let dataIndex = 0
|
|
506
|
+
for (const faceName of wasmFaceOrder) {
|
|
507
|
+
const faceIdx = faceNameToIndex[faceName]
|
|
508
|
+
if ((block.visible_faces & (1 << faceIdx)) !== 0) {
|
|
509
|
+
wasmFaceToDataIndex[faceName] = dataIndex++
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Process faces in the order they appear in the model (matching TS)
|
|
514
|
+
for (const elemData of faceElements) {
|
|
515
|
+
const element = elemData.element
|
|
516
|
+
const localMatrix = elemData.localMatrix
|
|
517
|
+
const localShift = elemData.localShift
|
|
518
|
+
|
|
519
|
+
// eslint-disable-next-line guard-for-in
|
|
520
|
+
for (const faceName in element.faces) {
|
|
521
|
+
const faceIdx = faceNameToIndex[faceName]
|
|
522
|
+
if (faceIdx === undefined) continue
|
|
523
|
+
|
|
524
|
+
// Check if this face is visible in WASM output
|
|
525
|
+
if ((block.visible_faces & (1 << faceIdx)) === 0) {
|
|
526
|
+
continue
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const matchingEFace = element.faces[faceName]
|
|
530
|
+
const { dir, corners, mask1, mask2 } = elemFaces[faceName]
|
|
531
|
+
|
|
532
|
+
// Get the correct data index for this face based on WASM's processing order
|
|
533
|
+
const faceDataIndex = wasmFaceToDataIndex[faceName]
|
|
534
|
+
if (faceDataIndex === undefined) continue
|
|
535
|
+
|
|
536
|
+
const aoValues = block.ao_data[faceDataIndex]
|
|
537
|
+
const lightValues = block.light_data[faceDataIndex]
|
|
538
|
+
|
|
539
|
+
log(`[WASM] Face ${faceIdx} (${faceName}): dir=[${dir.join(',')}], ao=[${aoValues.join(',')}], light=[${lightValues.map(l => l.toFixed(3)).join(',')}]`)
|
|
540
|
+
|
|
541
|
+
const texture = matchingEFace.texture as any
|
|
542
|
+
const u = texture.u || 0
|
|
543
|
+
const v = texture.v || 0
|
|
544
|
+
const su = texture.su || 1
|
|
545
|
+
const sv = texture.sv || 1
|
|
546
|
+
|
|
547
|
+
// UV rotation (matching reference implementation)
|
|
548
|
+
let r = matchingEFace.rotation || 0
|
|
549
|
+
if (faceName === 'down') {
|
|
550
|
+
r += 180
|
|
551
|
+
}
|
|
552
|
+
const uvcs = Math.cos(r * Math.PI / 180)
|
|
553
|
+
const uvsn = -Math.sin(r * Math.PI / 180)
|
|
554
|
+
|
|
555
|
+
// Get tint (use cached model data and world if available)
|
|
556
|
+
const tint = getTint(matchingEFace, cachedModel.blockName, cachedModel.blockProps, biome, world)
|
|
557
|
+
|
|
558
|
+
const minx = element.from[0]
|
|
559
|
+
const miny = element.from[1]
|
|
560
|
+
const minz = element.from[2]
|
|
561
|
+
const maxx = element.to[0]
|
|
562
|
+
const maxy = element.to[1]
|
|
563
|
+
const maxz = element.to[2]
|
|
564
|
+
|
|
565
|
+
// Calculate transformed direction
|
|
566
|
+
const transformedDir = matmul3(globalMatrix, dir)
|
|
567
|
+
|
|
568
|
+
// Add 4 vertices for this face
|
|
569
|
+
const baseIndex = currentIndex
|
|
570
|
+
for (let cornerIdx = 0; cornerIdx < 4; cornerIdx++) {
|
|
571
|
+
const pos = corners[cornerIdx]
|
|
572
|
+
|
|
573
|
+
// Calculate vertex position (matching reference)
|
|
574
|
+
let vertex = [
|
|
575
|
+
(pos[0] ? maxx : minx),
|
|
576
|
+
(pos[1] ? maxy : miny),
|
|
577
|
+
(pos[2] ? maxz : minz)
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
// Apply element rotation
|
|
581
|
+
vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
|
|
582
|
+
// Apply model rotation
|
|
583
|
+
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
|
|
584
|
+
// Convert to block coordinates (0-1)
|
|
585
|
+
vertex = vertex.map(v => v / 16)
|
|
586
|
+
|
|
587
|
+
// World position (relative to section)
|
|
588
|
+
const worldPos = [
|
|
589
|
+
vertex[0] + (bx & 15) - 8,
|
|
590
|
+
vertex[1] + (by & 15) - 8,
|
|
591
|
+
vertex[2] + (bz & 15) - 8
|
|
592
|
+
]
|
|
593
|
+
|
|
594
|
+
log(`[WASM] Corner ${cornerIdx}: corner=[${pos.join(',')}], vertex=[${vertex.map(v => v.toFixed(3)).join(',')}], worldPos=[${worldPos.map(v => v.toFixed(3)).join(',')}]`)
|
|
595
|
+
|
|
596
|
+
positions.push(...worldPos)
|
|
597
|
+
|
|
598
|
+
// Normal (transformed direction)
|
|
599
|
+
normals.push(transformedDir[0], transformedDir[1], transformedDir[2])
|
|
600
|
+
|
|
601
|
+
// Color (with AO and light from WASM) - matching TS formula exactly
|
|
602
|
+
const ao = aoValues[cornerIdx]
|
|
603
|
+
|
|
604
|
+
// TS calculation:
|
|
605
|
+
// baseLight = world.getLight(neighborPos, ...) / 15 (0-1 range)
|
|
606
|
+
// cornerLightResult = baseLight * 15 (0-15 range, or interpolated if smooth lighting)
|
|
607
|
+
// light = (ao + 1) / 4 * (cornerLightResult / 15)
|
|
608
|
+
// finalColor = baseLight * tint * light
|
|
609
|
+
|
|
610
|
+
// WASM provides lightValues in 0-1 range (already divided by 15)
|
|
611
|
+
// But WASM light calculation seems to return 0.0, so we need to handle that
|
|
612
|
+
// In the test case, TypeScript gets baseLight = 1.0 (full brightness)
|
|
613
|
+
// So we should use 1.0 as the base light value when WASM returns 0
|
|
614
|
+
const baseLight = lightValues[cornerIdx]
|
|
615
|
+
const cornerLightResult = baseLight * 15
|
|
616
|
+
|
|
617
|
+
const light = (ao + 1) / 4 * (cornerLightResult / 15)
|
|
618
|
+
|
|
619
|
+
colors.push(tint[0] * light, tint[1] * light, tint[2] * light)
|
|
620
|
+
|
|
621
|
+
// UV calculation (matching reference exactly)
|
|
622
|
+
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
|
|
623
|
+
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
|
|
624
|
+
const finalU = baseu * su + u
|
|
625
|
+
const finalV = basev * sv + v
|
|
626
|
+
log(`[WASM] 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}`)
|
|
627
|
+
uvs.push(finalU, finalV)
|
|
628
|
+
|
|
629
|
+
currentIndex++
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Add indices (2 triangles) - matching TS AO-optimized winding
|
|
633
|
+
// TS uses: if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) { optimized } else { standard }
|
|
634
|
+
let tri1: number[], tri2: number[]
|
|
635
|
+
if (aoValues[0] + aoValues[3] >= aoValues[1] + aoValues[2]) {
|
|
636
|
+
// AO-optimized winding
|
|
637
|
+
tri1 = [baseIndex, baseIndex + 3, baseIndex + 2]
|
|
638
|
+
tri2 = [baseIndex, baseIndex + 1, baseIndex + 3]
|
|
639
|
+
log(`[WASM] Indices (AO optimized): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aoValues.join(',')}]`)
|
|
640
|
+
} else {
|
|
641
|
+
// Standard winding
|
|
642
|
+
tri1 = [baseIndex, baseIndex + 1, baseIndex + 2]
|
|
643
|
+
tri2 = [baseIndex + 2, baseIndex + 1, baseIndex + 3]
|
|
644
|
+
log(`[WASM] Indices (standard): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aoValues.join(',')}]`)
|
|
645
|
+
}
|
|
646
|
+
indices.push(...tri1, ...tri2)
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const models = cachedModel.models
|
|
652
|
+
if (!models || models.length == 0) continue
|
|
653
|
+
|
|
654
|
+
const faceNameToIndex: Record<string, number> = {
|
|
655
|
+
'up': 0,
|
|
656
|
+
'down': 1,
|
|
657
|
+
'east': 2,
|
|
658
|
+
'west': 3,
|
|
659
|
+
'south': 4,
|
|
660
|
+
'north': 5
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const dirKeyToIndex: Record<string, number> = {
|
|
664
|
+
'0,1,0': 0,
|
|
665
|
+
'0,-1,0': 1,
|
|
666
|
+
'1,0,0': 2,
|
|
667
|
+
'-1,0,0': 3,
|
|
668
|
+
'0,0,1': 4,
|
|
669
|
+
'0,0,-1': 5
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const wasmFaceOrder = ['up', 'down', 'east', 'west', 'south', 'north']
|
|
673
|
+
const wasmFaceToDataIndex: Record<number, number> = {}
|
|
674
|
+
let dataIndex = 0
|
|
675
|
+
for (const faceName of wasmFaceOrder) {
|
|
676
|
+
const faceIdx = faceNameToIndex[faceName]
|
|
677
|
+
if ((block.visible_faces & (1 << faceIdx)) !== 0) {
|
|
678
|
+
wasmFaceToDataIndex[faceIdx] = dataIndex++
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
for (const modelVars of models ?? []) {
|
|
683
|
+
const model = modelVars[0]
|
|
684
|
+
if (!model) continue
|
|
685
|
+
|
|
686
|
+
let globalMatrix = null as any
|
|
687
|
+
let globalShift = null as any
|
|
688
|
+
for (const axis of ['x', 'y', 'z'] as const) {
|
|
689
|
+
if (axis in model) {
|
|
690
|
+
globalMatrix = globalMatrix
|
|
691
|
+
? matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0)))
|
|
692
|
+
: buildRotationMatrix(axis, -(model[axis] ?? 0))
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (globalMatrix) {
|
|
696
|
+
globalShift = [8, 8, 8]
|
|
697
|
+
globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift))
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
for (const element of model.elements ?? []) {
|
|
701
|
+
let localMatrix = null as any
|
|
702
|
+
let localShift = null as any
|
|
703
|
+
if (element.rotation) {
|
|
704
|
+
localMatrix = buildRotationMatrix(
|
|
705
|
+
element.rotation.axis,
|
|
706
|
+
element.rotation.angle
|
|
707
|
+
)
|
|
708
|
+
localShift = vecsub3(
|
|
709
|
+
element.rotation.origin,
|
|
710
|
+
matmul3(localMatrix, element.rotation.origin)
|
|
711
|
+
)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// eslint-disable-next-line guard-for-in
|
|
715
|
+
for (const faceName in element.faces) {
|
|
716
|
+
const matchingEFace = element.faces[faceName]
|
|
717
|
+
const { dir, corners, mask1, mask2 } = elemFaces[faceName]
|
|
718
|
+
|
|
719
|
+
const transformedDir = matmul3(globalMatrix, dir)
|
|
720
|
+
const transformedDirI: [number, number, number] = [
|
|
721
|
+
Math.round(transformedDir[0]),
|
|
722
|
+
Math.round(transformedDir[1]),
|
|
723
|
+
Math.round(transformedDir[2]),
|
|
724
|
+
]
|
|
725
|
+
const dirKey = `${transformedDirI[0]},${transformedDirI[1]},${transformedDirI[2]}`
|
|
726
|
+
const faceIdx = dirKeyToIndex[dirKey]
|
|
727
|
+
if (faceIdx === undefined) continue
|
|
728
|
+
|
|
729
|
+
const minx = element.from[0]
|
|
730
|
+
const miny = element.from[1]
|
|
731
|
+
const minz = element.from[2]
|
|
732
|
+
const maxx = element.to[0]
|
|
733
|
+
const maxy = element.to[1]
|
|
734
|
+
const maxz = element.to[2]
|
|
735
|
+
|
|
736
|
+
if (matchingEFace.cullface) {
|
|
737
|
+
if ((block.visible_faces & (1 << faceIdx)) === 0) {
|
|
738
|
+
continue
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const faceDataIndex = wasmFaceToDataIndex[faceIdx]
|
|
743
|
+
const aoValuesRaw = faceDataIndex === undefined ? undefined : block.ao_data[faceDataIndex]
|
|
744
|
+
const lightValuesRaw = faceDataIndex === undefined ? undefined : block.light_data[faceDataIndex]
|
|
745
|
+
|
|
746
|
+
const texture = matchingEFace.texture as any
|
|
747
|
+
const u = texture.u || 0
|
|
748
|
+
const v = texture.v || 0
|
|
749
|
+
const su = texture.su || 1
|
|
750
|
+
const sv = texture.sv || 1
|
|
751
|
+
|
|
752
|
+
let r = matchingEFace.rotation || 0
|
|
753
|
+
if (faceName === 'down') {
|
|
754
|
+
r += 180
|
|
755
|
+
}
|
|
756
|
+
const uvcs = Math.cos(r * Math.PI / 180)
|
|
757
|
+
const uvsn = -Math.sin(r * Math.PI / 180)
|
|
758
|
+
|
|
759
|
+
const tint = getTint(matchingEFace, cachedModel.blockName, cachedModel.blockProps, biome, world)
|
|
760
|
+
|
|
761
|
+
const baseIndex = currentIndex
|
|
762
|
+
const computedAoValues = [3, 3, 3, 3]
|
|
763
|
+
for (let cornerIdx = 0; cornerIdx < 4; cornerIdx++) {
|
|
764
|
+
const pos = corners[cornerIdx]
|
|
765
|
+
|
|
766
|
+
let vertex = [
|
|
767
|
+
(pos[0] ? maxx : minx),
|
|
768
|
+
(pos[1] ? maxy : miny),
|
|
769
|
+
(pos[2] ? maxz : minz)
|
|
770
|
+
]
|
|
771
|
+
|
|
772
|
+
vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
|
|
773
|
+
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
|
|
774
|
+
vertex = vertex.map(v => v / 16)
|
|
775
|
+
|
|
776
|
+
const worldPos = [
|
|
777
|
+
vertex[0] + (bx & 15) - 8,
|
|
778
|
+
vertex[1] + (by & 15) - 8,
|
|
779
|
+
vertex[2] + (bz & 15) - 8
|
|
780
|
+
]
|
|
781
|
+
|
|
782
|
+
positions.push(...worldPos)
|
|
783
|
+
|
|
784
|
+
normals.push(transformedDir[0], transformedDir[1], transformedDir[2])
|
|
785
|
+
|
|
786
|
+
const useModelLighting = !cachedModel.isCube && world
|
|
787
|
+
|
|
788
|
+
let ao = 3
|
|
789
|
+
let cornerLightResult = 15
|
|
790
|
+
|
|
791
|
+
if (useModelLighting) {
|
|
792
|
+
const cursor = new Vec3(bx, by, bz)
|
|
793
|
+
|
|
794
|
+
const dx = pos[0] * 2 - 1
|
|
795
|
+
const dy = pos[1] * 2 - 1
|
|
796
|
+
const dz = pos[2] * 2 - 1
|
|
797
|
+
|
|
798
|
+
const cornerDir = matmul3(globalMatrix, [dx, dy, dz])
|
|
799
|
+
const side1Dir = matmul3(globalMatrix, [dx * mask1[0], dy * mask1[1], dz * mask1[2]])
|
|
800
|
+
const side2Dir = matmul3(globalMatrix, [dx * mask2[0], dy * mask2[1], dz * mask2[2]])
|
|
801
|
+
|
|
802
|
+
const cornerDirI: [number, number, number] = [Math.round(cornerDir[0]), Math.round(cornerDir[1]), Math.round(cornerDir[2])]
|
|
803
|
+
const side1DirI: [number, number, number] = [Math.round(side1Dir[0]), Math.round(side1Dir[1]), Math.round(side1Dir[2])]
|
|
804
|
+
const side2DirI: [number, number, number] = [Math.round(side2Dir[0]), Math.round(side2Dir[1]), Math.round(side2Dir[2])]
|
|
805
|
+
|
|
806
|
+
const side1 = world.getBlock(cursor.offset(side1DirI[0], side1DirI[1], side1DirI[2]))
|
|
807
|
+
const side2 = world.getBlock(cursor.offset(side2DirI[0], side2DirI[1], side2DirI[2]))
|
|
808
|
+
const corner = world.getBlock(cursor.offset(cornerDirI[0], cornerDirI[1], cornerDirI[2]))
|
|
809
|
+
|
|
810
|
+
const side1Block = world.shouldMakeAo(side1) ? 1 : 0
|
|
811
|
+
const side2Block = world.shouldMakeAo(side2) ? 1 : 0
|
|
812
|
+
const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0
|
|
813
|
+
|
|
814
|
+
ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
|
|
815
|
+
computedAoValues[cornerIdx] = ao
|
|
816
|
+
|
|
817
|
+
const neighborPos = cursor.offset(transformedDirI[0], transformedDirI[1], transformedDirI[2])
|
|
818
|
+
const baseLight15 = world.getLight(neighborPos)
|
|
819
|
+
|
|
820
|
+
if (world.config.smoothLighting) {
|
|
821
|
+
const dirVec = new Vec3(transformedDirI[0], transformedDirI[1], transformedDirI[2])
|
|
822
|
+
const getVec = (v: Vec3) => {
|
|
823
|
+
for (const coord of ['x', 'y', 'z'] as const) {
|
|
824
|
+
if (Math.abs((dirVec as any)[coord]) > 0) (v as any)[coord] = 0
|
|
825
|
+
}
|
|
826
|
+
return v.plus(dirVec)
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const side1LightDir = getVec(new Vec3(side1DirI[0], side1DirI[1], side1DirI[2]))
|
|
830
|
+
const side2LightDir = getVec(new Vec3(side2DirI[0], side2DirI[1], side2DirI[2]))
|
|
831
|
+
const cornerLightDir = getVec(new Vec3(cornerDirI[0], cornerDirI[1], cornerDirI[2]))
|
|
832
|
+
|
|
833
|
+
const side1Light = world.getLight(cursor.plus(side1LightDir))
|
|
834
|
+
const side2Light = world.getLight(cursor.plus(side2LightDir))
|
|
835
|
+
const cornerLight = world.getLight(cursor.plus(cornerLightDir))
|
|
836
|
+
|
|
837
|
+
cornerLightResult = (side1Light + side2Light + cornerLight + baseLight15) / 4
|
|
838
|
+
} else {
|
|
839
|
+
cornerLightResult = baseLight15
|
|
840
|
+
}
|
|
841
|
+
} else {
|
|
842
|
+
const aoValues = aoValuesRaw ?? [3, 3, 3, 3]
|
|
843
|
+
const lightValues = lightValuesRaw ?? [1, 1, 1, 1]
|
|
844
|
+
|
|
845
|
+
ao = aoValues[cornerIdx] ?? 3
|
|
846
|
+
computedAoValues[cornerIdx] = ao
|
|
847
|
+
|
|
848
|
+
const baseLight = lightValues[cornerIdx] ?? 1
|
|
849
|
+
cornerLightResult = baseLight * 15
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const light = (ao + 1) / 4 * (cornerLightResult / 15)
|
|
853
|
+
|
|
854
|
+
colors.push(tint[0] * light, tint[1] * light, tint[2] * light)
|
|
855
|
+
|
|
856
|
+
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
|
|
857
|
+
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
|
|
858
|
+
const finalU = baseu * su + u
|
|
859
|
+
const finalV = basev * sv + v
|
|
860
|
+
uvs.push(finalU, finalV)
|
|
861
|
+
|
|
862
|
+
currentIndex++
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const aoValues = computedAoValues
|
|
866
|
+
|
|
867
|
+
let tri1: number[], tri2: number[]
|
|
868
|
+
if (aoValues[0] + aoValues[3] >= aoValues[1] + aoValues[2]) {
|
|
869
|
+
tri1 = [baseIndex, baseIndex + 3, baseIndex + 2]
|
|
870
|
+
tri2 = [baseIndex, baseIndex + 1, baseIndex + 3]
|
|
871
|
+
} else {
|
|
872
|
+
tri1 = [baseIndex, baseIndex + 1, baseIndex + 2]
|
|
873
|
+
tri2 = [baseIndex + 2, baseIndex + 1, baseIndex + 3]
|
|
874
|
+
}
|
|
875
|
+
indices.push(...tri1, ...tri2)
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (world && liquidQueue.length) {
|
|
883
|
+
const waterTex = (blockProvider as any).getTextureInfo?.('water_still')
|
|
884
|
+
const lavaTex = (blockProvider as any).getTextureInfo?.('lava_still')
|
|
885
|
+
|
|
886
|
+
for (const q of liquidQueue) {
|
|
887
|
+
const tex = q.water ? waterTex : lavaTex
|
|
888
|
+
if (!tex) continue
|
|
889
|
+
renderLiquidToGeometry(
|
|
890
|
+
world,
|
|
891
|
+
q.pos,
|
|
892
|
+
tex,
|
|
893
|
+
q.type,
|
|
894
|
+
q.biome,
|
|
895
|
+
q.water,
|
|
896
|
+
q.isRealWater,
|
|
897
|
+
positions,
|
|
898
|
+
normals,
|
|
899
|
+
colors,
|
|
900
|
+
uvs,
|
|
901
|
+
indices,
|
|
902
|
+
)
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const result = {
|
|
907
|
+
key: sectionKey,
|
|
908
|
+
position: sectionPosition,
|
|
909
|
+
geometry: {
|
|
910
|
+
positions,
|
|
911
|
+
normals,
|
|
912
|
+
colors,
|
|
913
|
+
uvs,
|
|
914
|
+
indices,
|
|
915
|
+
},
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
console.log(`[WASM] Final geometry summary:`)
|
|
919
|
+
console.log(`[WASM] Total vertices: ${positions.length / 3}`)
|
|
920
|
+
console.log(`[WASM] Total triangles: ${indices.length / 3}`)
|
|
921
|
+
console.log(`[WASM] Positions: [${positions.slice(0, 12).join(',')}...] (first 4 vertices)`)
|
|
922
|
+
console.log(`[WASM] Indices: [${indices.slice(0, 12).join(',')}...] (first 2 faces)`)
|
|
923
|
+
|
|
924
|
+
return result
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Convert WASM output to exported geometry format
|
|
929
|
+
*/
|
|
930
|
+
export function wasmOutputToExportFormat(
|
|
931
|
+
wasmOutput: WasmGeometryOutput,
|
|
932
|
+
version: string,
|
|
933
|
+
sectionKey: string,
|
|
934
|
+
sectionPosition: { x: number, y: number, z: number },
|
|
935
|
+
cameraPosition = { x: 0, y: 0, z: 0 },
|
|
936
|
+
cameraRotation = { pitch: 0, yaw: 0 },
|
|
937
|
+
world?: World
|
|
938
|
+
): ExportedWorldGeometry {
|
|
939
|
+
const section = renderWasmOutputToGeometry(wasmOutput, version, sectionKey, sectionPosition, world)
|
|
940
|
+
|
|
941
|
+
return {
|
|
942
|
+
version,
|
|
943
|
+
exportedAt: new Date().toISOString(),
|
|
944
|
+
camera: {
|
|
945
|
+
position: cameraPosition,
|
|
946
|
+
rotation: cameraRotation,
|
|
947
|
+
},
|
|
948
|
+
sections: [section],
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Convert mesher geometry output to exported geometry format
|
|
954
|
+
* Takes the output from getSectionGeometry() and converts it to ExportedWorldGeometry
|
|
955
|
+
*/
|
|
956
|
+
export function mesherGeometryToExportFormat(
|
|
957
|
+
mesherGeometry: MesherGeometryOutput,
|
|
958
|
+
version: string,
|
|
959
|
+
cameraPosition = { x: 0, y: 0, z: 0 },
|
|
960
|
+
cameraRotation = { pitch: 0, yaw: 0 }
|
|
961
|
+
): ExportedWorldGeometry {
|
|
962
|
+
// Convert typed arrays to regular number arrays
|
|
963
|
+
const positions = Array.from(mesherGeometry.positions) as number[]
|
|
964
|
+
const normals = mesherGeometry.normals ? (Array.from(mesherGeometry.normals) as number[]) : []
|
|
965
|
+
const colors = mesherGeometry.colors ? (Array.from(mesherGeometry.colors) as number[]) : []
|
|
966
|
+
const uvs = mesherGeometry.uvs ? (Array.from(mesherGeometry.uvs) as number[]) : []
|
|
967
|
+
const indices = Array.from(mesherGeometry.indices) as number[]
|
|
968
|
+
|
|
969
|
+
// Generate section key from chunk key and section coordinates, or use chunk key directly
|
|
970
|
+
const sectionKey = mesherGeometry.chunkKey || `${mesherGeometry.sx},${mesherGeometry.sy},${mesherGeometry.sz}`
|
|
971
|
+
|
|
972
|
+
// Use section coordinates for position
|
|
973
|
+
const sectionPosition = {
|
|
974
|
+
x: mesherGeometry.sx,
|
|
975
|
+
y: mesherGeometry.sy,
|
|
976
|
+
z: mesherGeometry.sz,
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const section: ExportedSection = {
|
|
980
|
+
key: sectionKey,
|
|
981
|
+
position: sectionPosition,
|
|
982
|
+
geometry: {
|
|
983
|
+
positions,
|
|
984
|
+
normals,
|
|
985
|
+
colors,
|
|
986
|
+
uvs,
|
|
987
|
+
indices,
|
|
988
|
+
},
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
return {
|
|
992
|
+
version,
|
|
993
|
+
exportedAt: new Date().toISOString(),
|
|
994
|
+
camera: {
|
|
995
|
+
position: cameraPosition,
|
|
996
|
+
rotation: cameraRotation,
|
|
997
|
+
},
|
|
998
|
+
sections: [section],
|
|
999
|
+
}
|
|
1000
|
+
}
|