minecraft-renderer 0.1.21 → 0.1.22
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 +1 -1
- package/dist/mesher.js.map +2 -2
- package/dist/minecraft-renderer.js +53 -53
- package/dist/threeWorker.js +397 -397
- package/package.json +6 -1
- package/src/graphicsBackend/config.ts +3 -3
- package/src/graphicsBackend/types.ts +2 -2
- package/src/lib/worldrendererCommon.ts +12 -7
- package/src/mesher/mesher.ts +2 -2
- package/src/mesher/shared.ts +1 -1
- package/src/playground/allEntitiesDebug.ts +6 -4
- package/src/playground/scenes/allEntities.ts +1 -1
- package/src/playground/scenes/floorRandom.ts +1 -1
- package/src/three/appShared.ts +2 -2
- package/src/three/documentRenderer.ts +2 -2
- package/src/three/graphicsBackendBase.ts +2 -0
- package/src/three/holdingBlock.ts +1 -1
- package/src/three/modules/index.ts +2 -0
- package/src/three/modules/rain.ts +181 -0
- package/src/three/modules/sciFiWorldReveal.ts +9 -0
- package/src/three/modules/starfield.ts +9 -0
- package/src/three/renderSlot.ts +9 -9
- package/src/three/rendererModuleSystem.ts +3 -0
- package/src/three/worldRendererThree.ts +54 -19
- package/src/wasm-lib/render-from-wasm.ts +161 -161
- package/src/worldView/worldView.ts +1 -1
|
@@ -33,7 +33,7 @@ import { DEFAULT_TEMPERATURE, SkyboxRenderer } from './skyboxRenderer'
|
|
|
33
33
|
import { FireworksManager } from './fireworks'
|
|
34
34
|
import { downloadWorldGeometry } from './worldGeometryExport'
|
|
35
35
|
import { WorldBlockGeometry } from './worldBlockGeometry'
|
|
36
|
-
import type { RendererModuleManifest, RegisteredModule } from './rendererModuleSystem'
|
|
36
|
+
import type { RendererModuleManifest, RegisteredModule, RendererModuleController } from './rendererModuleSystem'
|
|
37
37
|
import { BUILTIN_MODULES } from './modules/index'
|
|
38
38
|
|
|
39
39
|
type SectionKey = string
|
|
@@ -73,7 +73,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
73
73
|
return this.worldBlockGeometry.estimatedMemoryUsage
|
|
74
74
|
}
|
|
75
75
|
// Module system
|
|
76
|
-
private modules =
|
|
76
|
+
private modules = {} as Record<string, RegisteredModule>
|
|
77
77
|
sectionsOffsetsAnimations = {} as {
|
|
78
78
|
[chunkKey: string]: {
|
|
79
79
|
time: number,
|
|
@@ -170,7 +170,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
170
170
|
* Register a renderer module
|
|
171
171
|
*/
|
|
172
172
|
registerModule(manifest: RendererModuleManifest): void {
|
|
173
|
-
if (this.modules
|
|
173
|
+
if (manifest.id in this.modules) {
|
|
174
174
|
console.warn(`Module ${manifest.id} is already registered`)
|
|
175
175
|
return
|
|
176
176
|
}
|
|
@@ -181,12 +181,13 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
181
181
|
manifest,
|
|
182
182
|
controller,
|
|
183
183
|
enabled: false,
|
|
184
|
+
toggle: () => this.toggleModule(manifest.id),
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
this.modules
|
|
187
|
+
this.modules[manifest.id] = registered
|
|
187
188
|
|
|
188
189
|
if (manifest.enabledDefault) {
|
|
189
|
-
this.
|
|
190
|
+
this.toggleModule(manifest.id, true)
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
|
|
@@ -195,7 +196,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
195
196
|
* Enable a module
|
|
196
197
|
*/
|
|
197
198
|
enableModule(moduleId: string): void {
|
|
198
|
-
const module = this.modules
|
|
199
|
+
const module = this.modules[moduleId]
|
|
199
200
|
if (!module) {
|
|
200
201
|
console.warn(`Module ${moduleId} not found`)
|
|
201
202
|
return
|
|
@@ -216,7 +217,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
216
217
|
* Disable a module
|
|
217
218
|
*/
|
|
218
219
|
disableModule(moduleId: string): void {
|
|
219
|
-
const module = this.modules
|
|
220
|
+
const module = this.modules[moduleId]
|
|
220
221
|
if (!module) {
|
|
221
222
|
console.warn(`Module ${moduleId} not found`)
|
|
222
223
|
return
|
|
@@ -241,21 +242,62 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Toggle a module on/off, or force a specific state
|
|
247
|
+
*/
|
|
248
|
+
toggleModule(moduleId: string, forceState?: boolean): boolean {
|
|
249
|
+
const module = this.modules[moduleId]
|
|
250
|
+
if (!module) {
|
|
251
|
+
console.warn(`Module ${moduleId} not found`)
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const targetState = forceState !== undefined ? forceState : !module.enabled
|
|
256
|
+
|
|
257
|
+
if (targetState === module.enabled) return module.enabled
|
|
258
|
+
|
|
259
|
+
if (!targetState && module.manifest.cannotBeDisabled) {
|
|
260
|
+
console.warn(`Module ${moduleId} cannot be disabled`)
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.enabled = targetState
|
|
265
|
+
|
|
266
|
+
if (targetState) {
|
|
267
|
+
module.controller.enable()
|
|
268
|
+
// Register render callback if provided
|
|
269
|
+
if (module.controller.render && !this.onRender.includes(module.controller.render)) {
|
|
270
|
+
this.onRender.push(module.controller.render)
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
module.controller.disable()
|
|
274
|
+
// Unregister render callback if provided
|
|
275
|
+
if (module.controller.render) {
|
|
276
|
+
const index = this.onRender.indexOf(module.controller.render)
|
|
277
|
+
if (index > -1) {
|
|
278
|
+
this.onRender.splice(index, 1)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return targetState
|
|
284
|
+
}
|
|
285
|
+
|
|
244
286
|
/**
|
|
245
287
|
* Dispose all modules
|
|
246
288
|
*/
|
|
247
289
|
private disposeModules(): void {
|
|
248
|
-
for (const module of this.modules
|
|
290
|
+
for (const module of Object.values(this.modules)) {
|
|
249
291
|
module.controller.dispose()
|
|
250
292
|
}
|
|
251
|
-
this.modules
|
|
293
|
+
this.modules = {}
|
|
252
294
|
}
|
|
253
295
|
|
|
254
296
|
/**
|
|
255
297
|
* Initialize all registered modules
|
|
256
298
|
*/
|
|
257
299
|
private initializeModules(): void {
|
|
258
|
-
for (const [id, module] of this.modules
|
|
300
|
+
for (const [id, module] of Object.entries(this.modules)) {
|
|
259
301
|
if (module.manifest.enabledDefault) {
|
|
260
302
|
this.enableModule(id)
|
|
261
303
|
}
|
|
@@ -266,7 +308,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
266
308
|
* Get a module controller by ID
|
|
267
309
|
*/
|
|
268
310
|
getModule<T = any>(moduleId: string): T | undefined {
|
|
269
|
-
return this.modules
|
|
311
|
+
return this.modules[moduleId]?.controller as T | undefined
|
|
270
312
|
}
|
|
271
313
|
|
|
272
314
|
get cameraObject() {
|
|
@@ -486,7 +528,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
486
528
|
text += `TE: ${formatBigNumber(this.renderer.info.memory.textures)} `
|
|
487
529
|
text += `F: ${formatBigNumber(this.tilesRendered)} `
|
|
488
530
|
text += `B: ${formatBigNumber(this.blocksRendered)} `
|
|
489
|
-
text += `MEM: ${this.getEstimatedMemoryUsage().readable}`
|
|
531
|
+
text += `MEM: ${this.worldBlockGeometry.getEstimatedMemoryUsage().readable}`
|
|
490
532
|
pane.updateText(text)
|
|
491
533
|
this.backendInfoReport = text
|
|
492
534
|
}
|
|
@@ -1144,11 +1186,4 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1144
1186
|
reloadWorld() {
|
|
1145
1187
|
this.entities.reloadEntities()
|
|
1146
1188
|
}
|
|
1147
|
-
|
|
1148
|
-
/**
|
|
1149
|
-
* Get estimated memory usage in a human-readable format
|
|
1150
|
-
*/
|
|
1151
|
-
getEstimatedMemoryUsage(): { bytes: number; readable: string } {
|
|
1152
|
-
return this.worldBlockGeometry.getEstimatedMemoryUsage()
|
|
1153
|
-
}
|
|
1154
1189
|
}
|
|
@@ -466,187 +466,187 @@ export function renderWasmOutputToGeometry(
|
|
|
466
466
|
if (!cachedModel) continue
|
|
467
467
|
|
|
468
468
|
if (false) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
469
|
+
// For now, use first model variant (can be extended later)
|
|
470
|
+
const modelVariant = cachedModel!.modelVariants[0]
|
|
471
|
+
if (!modelVariant) continue
|
|
472
472
|
|
|
473
|
-
|
|
473
|
+
const { model, globalMatrix, globalShift, elements } = modelVariant
|
|
474
474
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
481
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
485
|
|
|
486
|
-
|
|
487
|
-
|
|
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
488
|
|
|
489
|
-
|
|
489
|
+
if (faceElements.length === 0) continue
|
|
490
490
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
500
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
+
}
|
|
510
511
|
}
|
|
511
|
-
}
|
|
512
512
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
518
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
523
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
524
|
+
// Check if this face is visible in WASM output
|
|
525
|
+
if ((block.visible_faces & (1 << faceIdx)) === 0) {
|
|
526
|
+
continue
|
|
527
|
+
}
|
|
528
528
|
|
|
529
|
-
|
|
530
|
-
|
|
529
|
+
const matchingEFace = element.faces[faceName]
|
|
530
|
+
const { dir, corners, mask1, mask2 } = elemFaces[faceName]
|
|
531
531
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
535
|
|
|
536
|
-
|
|
537
|
-
|
|
536
|
+
const aoValues = block.ao_data[faceDataIndex]
|
|
537
|
+
const lightValues = block.light_data[faceDataIndex]
|
|
538
538
|
|
|
539
|
-
|
|
539
|
+
log(`[WASM] Face ${faceIdx} (${faceName}): dir=[${dir.join(',')}], ao=[${aoValues.join(',')}], light=[${lightValues.map(l => l.toFixed(3)).join(',')}]`)
|
|
540
540
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
546
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
}
|
|
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)
|
|
631
554
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
+
}
|
|
647
648
|
}
|
|
648
649
|
}
|
|
649
|
-
}
|
|
650
650
|
|
|
651
651
|
const models = cachedModel.models
|
|
652
652
|
if (!models || models.length == 0) continue
|
|
@@ -363,7 +363,7 @@ export class WorldView extends (EventEmitter as new () => TypedEmitter<WorldView
|
|
|
363
363
|
|
|
364
364
|
// Unload chunks that are no longer in view
|
|
365
365
|
const chunksToUnload: Vec3[] = []
|
|
366
|
-
const viewDistanceWithBuffer = this.viewDistance + this.keepChunksDistance
|
|
366
|
+
const viewDistanceWithBuffer = force ? this.viewDistance : this.viewDistance + this.keepChunksDistance
|
|
367
367
|
|
|
368
368
|
for (const coords of Object.keys(this.loadedChunks)) {
|
|
369
369
|
const [x, z] = coords.split(',').map(Number)
|