minecraft-renderer 0.1.48 → 0.1.49
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/mesherWasm.js +3740 -183
- package/dist/minecraft-renderer.js +332 -60
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +705 -433
- package/package.json +1 -1
- package/src/graphicsBackend/config.ts +4 -0
- package/src/graphicsBackend/playerState.ts +1 -0
- package/src/lib/worldrendererCommon.ts +13 -0
- package/src/mesher-shared/exportedGeometryTypes.ts +5 -1
- package/src/mesher-shared/shared.ts +8 -0
- package/src/three/chunkMeshManager.ts +312 -39
- package/src/three/globalBlockBuffer.ts +292 -0
- package/src/three/menuBackground/config.ts +1 -1
- package/src/three/menuBackground/defaultOptions.ts +18 -18
- package/src/three/modules/sciFiWorldReveal.ts +162 -68
- package/src/three/modules/starfield.ts +9 -1
- package/src/three/sectionRaycastAabb.ts +167 -0
- package/src/three/shaderCubeMesh.ts +93 -0
- package/src/three/shaders/cubeBlockShader.ts +354 -0
- package/src/three/shaders/textureIndexMapping.ts +122 -0
- package/src/three/shaders/tintPalette.ts +198 -0
- package/src/three/worldGeometryExport.ts +53 -25
- package/src/three/worldRendererThree.ts +56 -23
- package/src/wasm-mesher/bridge/render-from-wasm.ts +62 -185
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +396 -0
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +58 -0
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +360 -0
- package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +11 -4
- package/src/wasm-mesher/worker/mesherWasm.ts +17 -2
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
import type { WorldRendererThree } from './worldRendererThree'
|
|
4
4
|
import type { ExportedSection, ExportedWorldGeometry } from '../mesher-shared/exportedGeometryTypes'
|
|
5
|
+
import { getShaderCubeResources } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
6
|
+
import { createCubeBlockMaterial } from './shaders/cubeBlockShader'
|
|
7
|
+
import { createShaderCubeMesh } from './shaderCubeMesh'
|
|
5
8
|
|
|
6
9
|
export type { ExportedSection, ExportedWorldGeometry } from '../mesher-shared/exportedGeometryTypes'
|
|
7
10
|
|
|
@@ -114,40 +117,62 @@ export async function loadWorldGeometryFromUrl(url: string): Promise<ExportedWor
|
|
|
114
117
|
* Recreate THREE.js meshes from exported geometry
|
|
115
118
|
* Returns an array of mesh groups that can be added to a scene
|
|
116
119
|
*/
|
|
120
|
+
function shaderMaterialForExport(legacyMaterial: THREE.Material): THREE.ShaderMaterial | null {
|
|
121
|
+
const atlas = (legacyMaterial as THREE.MeshBasicMaterial).map
|
|
122
|
+
?? (legacyMaterial as THREE.MeshLambertMaterial).map
|
|
123
|
+
if (!atlas) return null
|
|
124
|
+
const shaderMat = createCubeBlockMaterial()
|
|
125
|
+
shaderMat.uniforms.u_atlas.value = atlas
|
|
126
|
+
const { tintPalette } = getShaderCubeResources()
|
|
127
|
+
if (!tintPalette.isReady()) tintPalette.createTexture()
|
|
128
|
+
shaderMat.uniforms.u_tintPalette.value = tintPalette.getTexture()
|
|
129
|
+
return shaderMat
|
|
130
|
+
}
|
|
131
|
+
|
|
117
132
|
export function createMeshesFromExport(
|
|
118
133
|
exportData: ExportedWorldGeometry,
|
|
119
|
-
material: THREE.Material
|
|
134
|
+
material: THREE.Material,
|
|
135
|
+
shaderMaterial?: THREE.ShaderMaterial | null,
|
|
120
136
|
): THREE.Group[] {
|
|
121
137
|
const groups: THREE.Group[] = []
|
|
138
|
+
const resolvedShaderMat = shaderMaterial ?? shaderMaterialForExport(material)
|
|
122
139
|
|
|
123
140
|
for (const section of exportData.sections) {
|
|
124
|
-
const
|
|
141
|
+
const group = new THREE.Group()
|
|
142
|
+
group.name = 'chunk'
|
|
125
143
|
|
|
126
|
-
geometry.
|
|
127
|
-
if (
|
|
128
|
-
geometry
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
const hasLegacy = section.geometry.positions.length > 0 && section.geometry.indices.length > 0
|
|
145
|
+
if (hasLegacy) {
|
|
146
|
+
const geometry = new THREE.BufferGeometry()
|
|
147
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(section.geometry.positions, 3))
|
|
148
|
+
if (section.geometry.normals.length) {
|
|
149
|
+
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(section.geometry.normals, 3))
|
|
150
|
+
}
|
|
151
|
+
if (section.geometry.colors.length) {
|
|
152
|
+
geometry.setAttribute('color', new THREE.Float32BufferAttribute(section.geometry.colors, 3))
|
|
153
|
+
}
|
|
154
|
+
if (section.geometry.uvs.length) {
|
|
155
|
+
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(section.geometry.uvs, 2))
|
|
156
|
+
}
|
|
157
|
+
const maxIndex = Math.max(...section.geometry.indices)
|
|
158
|
+
const IndexArrayType = maxIndex > 65_535 ? Uint32Array : Uint16Array
|
|
159
|
+
geometry.setIndex(new THREE.BufferAttribute(new IndexArrayType(section.geometry.indices), 1))
|
|
160
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
161
|
+
mesh.position.set(section.position.x, section.position.y, section.position.z)
|
|
162
|
+
mesh.name = 'mesh'
|
|
163
|
+
group.add(mesh)
|
|
135
164
|
}
|
|
136
165
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
mesh.position.set(section.position.x, section.position.y, section.position.z)
|
|
144
|
-
mesh.name = 'mesh'
|
|
145
|
-
|
|
146
|
-
const group = new THREE.Group()
|
|
147
|
-
group.name = 'chunk'
|
|
148
|
-
group.add(mesh)
|
|
166
|
+
const shaderCubes = section.shaderCubes
|
|
167
|
+
if (shaderCubes && shaderCubes.count > 0 && resolvedShaderMat) {
|
|
168
|
+
const shaderMesh = createShaderCubeMesh(shaderCubes, resolvedShaderMat)
|
|
169
|
+
shaderMesh.position.set(section.position.x, section.position.y, section.position.z)
|
|
170
|
+
group.add(shaderMesh)
|
|
171
|
+
}
|
|
149
172
|
|
|
150
|
-
|
|
173
|
+
if (group.children.length > 0) {
|
|
174
|
+
groups.push(group)
|
|
175
|
+
}
|
|
151
176
|
}
|
|
152
177
|
|
|
153
178
|
return groups
|
|
@@ -222,7 +247,10 @@ export async function applyWorldGeometryExport(
|
|
|
222
247
|
material = rendererMaterial
|
|
223
248
|
}
|
|
224
249
|
|
|
225
|
-
const
|
|
250
|
+
const shaderMat = exportData.sections.some(s => (s.shaderCubes?.count ?? 0) > 0)
|
|
251
|
+
? shaderMaterialForExport(material)
|
|
252
|
+
: null
|
|
253
|
+
const groups = createMeshesFromExport(exportData, material, shaderMat)
|
|
226
254
|
const container = new THREE.Group()
|
|
227
255
|
container.name = GEOMETRY_EXPORT_GROUP_NAME
|
|
228
256
|
if (hasEmbeddedTexture) {
|
|
@@ -42,6 +42,11 @@ type SectionKey = string
|
|
|
42
42
|
|
|
43
43
|
export class WorldRendererThree extends WorldRendererCommon {
|
|
44
44
|
outputFormat = 'threeJs' as const
|
|
45
|
+
|
|
46
|
+
protected override isShaderCubeBlocksEnabled(): boolean {
|
|
47
|
+
return this.worldRendererConfig.shaderCubeBlocks === true
|
|
48
|
+
&& !!this.renderer?.capabilities?.isWebGL2
|
|
49
|
+
}
|
|
45
50
|
chunkMeshManager: ChunkMeshManager
|
|
46
51
|
get sectionObjects() {
|
|
47
52
|
return this.chunkMeshManager.sectionObjects
|
|
@@ -522,6 +527,12 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
522
527
|
this.onReactiveConfigUpdated('defaultSkybox', (value) => {
|
|
523
528
|
this.skyboxRenderer.updateDefaultSkybox(value)
|
|
524
529
|
})
|
|
530
|
+
this.onReactiveConfigUpdated('shaderCubeDebugMode', () => {
|
|
531
|
+
this.chunkMeshManager.syncCubeShaderUniforms()
|
|
532
|
+
})
|
|
533
|
+
this.onReactiveConfigUpdated('futuristicReveal', () => {
|
|
534
|
+
this.updateModulesFromConfig()
|
|
535
|
+
})
|
|
525
536
|
|
|
526
537
|
let currentHandRenderer = this.displayOptions.inWorldRenderingConfig.handRenderer
|
|
527
538
|
this.onReactiveConfigUpdated('handRenderer', (value) => {
|
|
@@ -618,6 +629,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
618
629
|
texture.needsUpdate = true
|
|
619
630
|
texture.flipY = false
|
|
620
631
|
this.material.map = texture
|
|
632
|
+
this.chunkMeshManager.syncCubeShaderUniforms()
|
|
621
633
|
|
|
622
634
|
const itemsTexture = loadThreeJsTextureFromBitmap(resources.itemsAtlasImage!)
|
|
623
635
|
itemsTexture.needsUpdate = true
|
|
@@ -734,12 +746,21 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
734
746
|
* Optionally update data that are depedendent on the viewer position
|
|
735
747
|
*/
|
|
736
748
|
updatePosDataChunk(key: string) {
|
|
749
|
+
const sectionObj = this.sectionObjects[key]
|
|
750
|
+
if (!sectionObj) return
|
|
751
|
+
|
|
737
752
|
const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16))
|
|
738
753
|
// sum of distances: x + y + z
|
|
739
754
|
const chunkDistance = Math.abs(x - this.cameraSectionPos.x) + Math.abs(y - this.cameraSectionPos.y) + Math.abs(z - this.cameraSectionPos.z)
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
755
|
+
const renderOrder = 500 - chunkDistance
|
|
756
|
+
|
|
757
|
+
// Cubes in globalBlockBuffer may leave sectionObj.mesh unset.
|
|
758
|
+
const drawable = sectionObj.mesh
|
|
759
|
+
?? sectionObj.shaderMesh
|
|
760
|
+
?? sectionObj.children.find(child => child.name === 'mesh' || child.name === 'shaderMesh')
|
|
761
|
+
if (drawable) {
|
|
762
|
+
drawable.renderOrder = renderOrder
|
|
763
|
+
}
|
|
743
764
|
}
|
|
744
765
|
|
|
745
766
|
override updateViewerPosition(pos: Vec3): void {
|
|
@@ -820,7 +841,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
820
841
|
continue
|
|
821
842
|
}
|
|
822
843
|
|
|
823
|
-
if (!update.geometry
|
|
844
|
+
if (!this.chunkMeshManager.sectionHasRenderableContent(update.geometry)) {
|
|
824
845
|
this.chunkMeshManager.releaseSection(update.key)
|
|
825
846
|
continue
|
|
826
847
|
}
|
|
@@ -859,7 +880,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
859
880
|
return
|
|
860
881
|
}
|
|
861
882
|
|
|
862
|
-
if (!data.geometry
|
|
883
|
+
if (!this.chunkMeshManager.sectionHasRenderableContent(data.geometry)) {
|
|
863
884
|
this.chunkMeshManager.releaseSection(data.key)
|
|
864
885
|
return
|
|
865
886
|
}
|
|
@@ -965,25 +986,22 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
965
986
|
raycaster.set(scenePos, direction)
|
|
966
987
|
raycaster.far = distance // Limit raycast distance
|
|
967
988
|
|
|
968
|
-
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const mesh = obj.children.find(child => child.name === 'mesh')
|
|
974
|
-
if (!mesh) return false
|
|
975
|
-
|
|
976
|
-
// Check distance from player position to chunk
|
|
977
|
-
const chunkWorldPos = this._tpChunkWorldPos
|
|
978
|
-
mesh.getWorldPosition(chunkWorldPos)
|
|
979
|
-
const distance = scenePos.distanceTo(chunkWorldPos)
|
|
980
|
-
return distance < 80 // Only check chunks within 80 blocks
|
|
981
|
-
})
|
|
989
|
+
const maxCenterDistance = 80
|
|
990
|
+
const maxCenterDistSq = maxCenterDistance * maxCenterDistance
|
|
991
|
+
const ox = pos.x
|
|
992
|
+
const oy = pos.y
|
|
993
|
+
const oz = pos.z
|
|
982
994
|
|
|
983
|
-
//
|
|
995
|
+
// Legacy / deferred-shader meshes (scene-relative raycast)
|
|
984
996
|
const meshes: THREE.Object3D[] = []
|
|
985
|
-
for (const
|
|
986
|
-
|
|
997
|
+
for (const obj of Object.values(this.sectionObjects)) {
|
|
998
|
+
if (obj.name !== 'chunk' || !obj.visible) continue
|
|
999
|
+
if (obj.worldX === undefined) continue
|
|
1000
|
+
const dcx = obj.worldX - ox
|
|
1001
|
+
const dcy = obj.worldY! - oy
|
|
1002
|
+
const dcz = obj.worldZ! - oz
|
|
1003
|
+
if (dcx * dcx + dcy * dcy + dcz * dcz > maxCenterDistSq) continue
|
|
1004
|
+
const mesh = obj.children.find(child => child.name === 'mesh' || child.name === 'shaderMesh')
|
|
987
1005
|
if (mesh) meshes.push(mesh)
|
|
988
1006
|
}
|
|
989
1007
|
|
|
@@ -991,10 +1009,20 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
991
1009
|
|
|
992
1010
|
let finalDistance = distance
|
|
993
1011
|
if (intersects.length > 0) {
|
|
994
|
-
// Use intersection distance minus a small offset to prevent clipping
|
|
995
1012
|
finalDistance = Math.max(0.5, intersects[0].distance - 0.2)
|
|
996
1013
|
}
|
|
997
1014
|
|
|
1015
|
+
// Shader cubes in global buffer: tight per-section AABBs (no mesh raycast).
|
|
1016
|
+
const boxHit = this.chunkMeshManager.raycastShaderSectionAABBs(
|
|
1017
|
+
pos,
|
|
1018
|
+
direction,
|
|
1019
|
+
finalDistance,
|
|
1020
|
+
maxCenterDistance,
|
|
1021
|
+
)
|
|
1022
|
+
if (boxHit !== undefined) {
|
|
1023
|
+
finalDistance = Math.max(0.5, boxHit - 0.2)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
998
1026
|
const finalPos = new Vec3(
|
|
999
1027
|
pos.x + direction.x * finalDistance,
|
|
1000
1028
|
pos.y + direction.y * finalDistance,
|
|
@@ -1252,6 +1280,11 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|
|
1252
1280
|
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
|
1253
1281
|
const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
|
|
1254
1282
|
this.applyPendingSectionUpdates()
|
|
1283
|
+
const globalBuffer = this.chunkMeshManager.globalBlockBuffer
|
|
1284
|
+
if (globalBuffer) {
|
|
1285
|
+
globalBuffer.setCameraOrigin(this.cameraWorldPos.x, this.cameraWorldPos.y, this.cameraWorldPos.z)
|
|
1286
|
+
globalBuffer.uploadDirtyRange()
|
|
1287
|
+
}
|
|
1255
1288
|
this.renderer.render(this.scene, cam)
|
|
1256
1289
|
|
|
1257
1290
|
if (
|
|
@@ -12,8 +12,14 @@ import { Vec3 } from 'vec3'
|
|
|
12
12
|
import { elemFaces, buildRotationMatrix, matmul3, matmulmat3, vecadd3, vecsub3 } from '../../mesher-shared/modelsGeometryCommon'
|
|
13
13
|
import type { ExportedWorldGeometry, ExportedSection } from '../../mesher-shared/exportedGeometryTypes'
|
|
14
14
|
import type { MesherGeometryOutput } from '../../mesher-shared/shared'
|
|
15
|
+
import { SECTION_HEIGHT } from '../../mesher-shared/shared'
|
|
15
16
|
import type { World } from '../../mesher-shared/world'
|
|
16
17
|
import { resolveBlockPropertiesForMeshing } from '../../mesher-shared/blockPropertiesForMeshing'
|
|
18
|
+
import {
|
|
19
|
+
buildShaderCubesFromWords,
|
|
20
|
+
getShaderCubeResources,
|
|
21
|
+
tryBuildShaderCubeInstances,
|
|
22
|
+
} from './shaderCubeBridge'
|
|
17
23
|
import { getSideShading, vertexLightFromAo } from '../../mesher-shared/vertexShading'
|
|
18
24
|
import tintsJson from 'minecraft-data/minecraft-data/data/pc/1.16.2/tints.json'
|
|
19
25
|
|
|
@@ -82,6 +88,7 @@ interface WasmBlockFaceData {
|
|
|
82
88
|
visible_faces: number
|
|
83
89
|
ao_data: number[][]
|
|
84
90
|
light_data: number[][]
|
|
91
|
+
light_combined?: number[][] // Packed combined light for shader path ([u8; 4] per visible face)
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
export interface WasmGeometryOutput {
|
|
@@ -445,6 +452,16 @@ const renderLiquidToGeometry = (
|
|
|
445
452
|
}
|
|
446
453
|
}
|
|
447
454
|
|
|
455
|
+
export type RenderWasmOptions = {
|
|
456
|
+
/** Section height in blocks. Shader-cube path requires 16; other values keep legacy. */
|
|
457
|
+
sectionHeight?: number
|
|
458
|
+
/**
|
|
459
|
+
* Pack full-cube blocks into instanced shader words.
|
|
460
|
+
* Set false in parity tests that expect legacy vertex buffers only.
|
|
461
|
+
*/
|
|
462
|
+
shaderCubes?: boolean
|
|
463
|
+
}
|
|
464
|
+
|
|
448
465
|
/**
|
|
449
466
|
* Render WASM mesher output to Three.js geometry
|
|
450
467
|
*/
|
|
@@ -453,7 +470,8 @@ export function renderWasmOutputToGeometry(
|
|
|
453
470
|
version: string,
|
|
454
471
|
sectionKey: string,
|
|
455
472
|
sectionPosition: { x: number, y: number, z: number },
|
|
456
|
-
world?: World
|
|
473
|
+
world?: World,
|
|
474
|
+
options?: RenderWasmOptions,
|
|
457
475
|
): ExportedSection {
|
|
458
476
|
const DEBUG = false
|
|
459
477
|
const log = (...args) => {
|
|
@@ -496,6 +514,12 @@ export function renderWasmOutputToGeometry(
|
|
|
496
514
|
|
|
497
515
|
let currentIndex = 0
|
|
498
516
|
|
|
517
|
+
const sectionHeight = options?.sectionHeight ?? SECTION_HEIGHT
|
|
518
|
+
const shaderCubesEnabled = options?.shaderCubes !== false
|
|
519
|
+
const [sectionOx, sectionOy, sectionOz] = sectionKey.split(',').map((v) => parseInt(v, 10))
|
|
520
|
+
const shaderWordBuffer: number[] = []
|
|
521
|
+
const shaderResources = shaderCubesEnabled ? getShaderCubeResources() : null
|
|
522
|
+
|
|
499
523
|
for (const block of wasmOutput.blocks) {
|
|
500
524
|
const [bx, by, bz] = block.position
|
|
501
525
|
const blockStateId = block.block_state_id
|
|
@@ -548,185 +572,32 @@ export function renderWasmOutputToGeometry(
|
|
|
548
572
|
)
|
|
549
573
|
if (!cachedModel) continue
|
|
550
574
|
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
'down': 1,
|
|
578
|
-
'east': 2,
|
|
579
|
-
'west': 3,
|
|
580
|
-
'south': 4,
|
|
581
|
-
'north': 5
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// WASM processes faces in fixed order: [up, down, east, west, south, north]
|
|
585
|
-
// Build a mapping from WASM face order to data index
|
|
586
|
-
const wasmFaceOrder = ['up', 'down', 'east', 'west', 'south', 'north']
|
|
587
|
-
const wasmFaceToDataIndex: Record<string, number> = {}
|
|
588
|
-
let dataIndex = 0
|
|
589
|
-
for (const faceName of wasmFaceOrder) {
|
|
590
|
-
const faceIdx = faceNameToIndex[faceName]
|
|
591
|
-
if ((block.visible_faces & (1 << faceIdx)) !== 0) {
|
|
592
|
-
wasmFaceToDataIndex[faceName] = dataIndex++
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Process faces in the order they appear in the model (matching TS)
|
|
597
|
-
for (const elemData of faceElements) {
|
|
598
|
-
const element = elemData.element
|
|
599
|
-
const localMatrix = elemData.localMatrix
|
|
600
|
-
const localShift = elemData.localShift
|
|
601
|
-
|
|
602
|
-
// eslint-disable-next-line guard-for-in
|
|
603
|
-
for (const faceName in element.faces) {
|
|
604
|
-
const faceIdx = faceNameToIndex[faceName]
|
|
605
|
-
if (faceIdx === undefined) continue
|
|
606
|
-
|
|
607
|
-
// Check if this face is visible in WASM output
|
|
608
|
-
if ((block.visible_faces & (1 << faceIdx)) === 0) {
|
|
609
|
-
continue
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const matchingEFace = element.faces[faceName]
|
|
613
|
-
const { dir, corners, mask1, mask2 } = elemFaces[faceName]
|
|
614
|
-
|
|
615
|
-
// Get the correct data index for this face based on WASM's processing order
|
|
616
|
-
const faceDataIndex = wasmFaceToDataIndex[faceName]
|
|
617
|
-
if (faceDataIndex === undefined) continue
|
|
618
|
-
|
|
619
|
-
const aoValues = block.ao_data[faceDataIndex]
|
|
620
|
-
const lightValues = block.light_data[faceDataIndex]
|
|
621
|
-
|
|
622
|
-
log(`[WASM] Face ${faceIdx} (${faceName}): dir=[${dir.join(',')}], ao=[${aoValues.join(',')}], light=[${lightValues.map(l => l.toFixed(3)).join(',')}]`)
|
|
623
|
-
|
|
624
|
-
const texture = matchingEFace.texture as any
|
|
625
|
-
const u = texture.u || 0
|
|
626
|
-
const v = texture.v || 0
|
|
627
|
-
const su = texture.su || 1
|
|
628
|
-
const sv = texture.sv || 1
|
|
629
|
-
|
|
630
|
-
// UV rotation (matching reference implementation)
|
|
631
|
-
let r = matchingEFace.rotation || 0
|
|
632
|
-
if (faceName === 'down') {
|
|
633
|
-
r += 180
|
|
634
|
-
}
|
|
635
|
-
const uvcs = Math.cos(r * Math.PI / 180)
|
|
636
|
-
const uvsn = -Math.sin(r * Math.PI / 180)
|
|
637
|
-
|
|
638
|
-
// Get tint (use cached model data and world if available)
|
|
639
|
-
const tint = getTint(matchingEFace, cachedModel!.blockName, cachedModel!.blockProps, biome, world)
|
|
640
|
-
|
|
641
|
-
const minx = element.from[0]
|
|
642
|
-
const miny = element.from[1]
|
|
643
|
-
const minz = element.from[2]
|
|
644
|
-
const maxx = element.to[0]
|
|
645
|
-
const maxy = element.to[1]
|
|
646
|
-
const maxz = element.to[2]
|
|
647
|
-
|
|
648
|
-
// Calculate transformed direction
|
|
649
|
-
const transformedDir = matmul3(globalMatrix, dir)
|
|
650
|
-
|
|
651
|
-
// Add 4 vertices for this face
|
|
652
|
-
const baseIndex = currentIndex
|
|
653
|
-
for (let cornerIdx = 0; cornerIdx < 4; cornerIdx++) {
|
|
654
|
-
const pos = corners[cornerIdx]
|
|
655
|
-
|
|
656
|
-
// Calculate vertex position (matching reference)
|
|
657
|
-
let vertex = [
|
|
658
|
-
(pos[0] ? maxx : minx),
|
|
659
|
-
(pos[1] ? maxy : miny),
|
|
660
|
-
(pos[2] ? maxz : minz)
|
|
661
|
-
]
|
|
662
|
-
|
|
663
|
-
// Apply element rotation
|
|
664
|
-
vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
|
|
665
|
-
// Apply model rotation
|
|
666
|
-
vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
|
|
667
|
-
// Convert to block coordinates (0-1)
|
|
668
|
-
vertex = vertex.map(v => v / 16)
|
|
669
|
-
|
|
670
|
-
// World position (relative to section)
|
|
671
|
-
const worldPos = [
|
|
672
|
-
vertex[0] + (bx & 15) - 8,
|
|
673
|
-
vertex[1] + (by & 15) - 8,
|
|
674
|
-
vertex[2] + (bz & 15) - 8
|
|
675
|
-
]
|
|
676
|
-
|
|
677
|
-
log(`[WASM] Corner ${cornerIdx}: corner=[${pos.join(',')}], vertex=[${vertex.map(v => v.toFixed(3)).join(',')}], worldPos=[${worldPos.map(v => v.toFixed(3)).join(',')}]`)
|
|
678
|
-
|
|
679
|
-
positions.push(...worldPos)
|
|
680
|
-
|
|
681
|
-
// Normal (transformed direction)
|
|
682
|
-
normals.push(transformedDir[0], transformedDir[1], transformedDir[2])
|
|
683
|
-
|
|
684
|
-
// Color (with AO and light from WASM) - matching TS formula exactly
|
|
685
|
-
const ao = aoValues[cornerIdx]
|
|
686
|
-
|
|
687
|
-
// TS calculation:
|
|
688
|
-
// baseLight = world.getLight(neighborPos, ...) / 15 (0-1 range)
|
|
689
|
-
// cornerLightResult = baseLight * 15 (0-15 range, or interpolated if smooth lighting)
|
|
690
|
-
// light = (ao + 1) / 4 * (cornerLightResult / 15)
|
|
691
|
-
// finalColor = baseLight * tint * light
|
|
692
|
-
|
|
693
|
-
// WASM provides lightValues in 0-1 range (already divided by 15)
|
|
694
|
-
// But WASM light calculation seems to return 0.0, so we need to handle that
|
|
695
|
-
// In the test case, TypeScript gets baseLight = 1.0 (full brightness)
|
|
696
|
-
// So we should use 1.0 as the base light value when WASM returns 0
|
|
697
|
-
const cornerLight15 = (lightValues[cornerIdx] ?? 1) * 15
|
|
698
|
-
const faceDir = transformedDir as [number, number, number]
|
|
699
|
-
const light = computeMesherVertexLight(world, ao, cornerLight15, faceDir)
|
|
700
|
-
|
|
701
|
-
colors.push(tint[0] * light, tint[1] * light, tint[2] * light)
|
|
702
|
-
|
|
703
|
-
// UV calculation (matching reference exactly)
|
|
704
|
-
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
|
|
705
|
-
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
|
|
706
|
-
const finalU = baseu * su + u
|
|
707
|
-
const finalV = basev * sv + v
|
|
708
|
-
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}`)
|
|
709
|
-
uvs.push(finalU, finalV)
|
|
710
|
-
|
|
711
|
-
currentIndex++
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Add indices (2 triangles) - matching TS AO-optimized winding
|
|
715
|
-
// TS uses: if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) { optimized } else { standard }
|
|
716
|
-
let tri1: number[], tri2: number[]
|
|
717
|
-
if (aoValues[0] + aoValues[3] >= aoValues[1] + aoValues[2]) {
|
|
718
|
-
// AO-optimized winding
|
|
719
|
-
tri1 = [baseIndex, baseIndex + 3, baseIndex + 2]
|
|
720
|
-
tri2 = [baseIndex, baseIndex + 1, baseIndex + 3]
|
|
721
|
-
log(`[WASM] Indices (AO optimized): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aoValues.join(',')}]`)
|
|
722
|
-
} else {
|
|
723
|
-
// Standard winding
|
|
724
|
-
tri1 = [baseIndex, baseIndex + 1, baseIndex + 2]
|
|
725
|
-
tri2 = [baseIndex + 2, baseIndex + 1, baseIndex + 3]
|
|
726
|
-
log(`[WASM] Indices (standard): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aoValues.join(',')}]`)
|
|
727
|
-
}
|
|
728
|
-
indices.push(...tri1, ...tri2)
|
|
729
|
-
}
|
|
575
|
+
if (shaderResources) {
|
|
576
|
+
const modelVars = cachedModel.models[0]
|
|
577
|
+
const model = modelVars?.[0]
|
|
578
|
+
const element = model?.elements?.[0]
|
|
579
|
+
if (model && element) {
|
|
580
|
+
const doAO = (model as { ao?: boolean }).ao ?? cachedModel.boundingBox !== 'empty'
|
|
581
|
+
const emitted = tryBuildShaderCubeInstances(
|
|
582
|
+
block,
|
|
583
|
+
{
|
|
584
|
+
blockName: cachedModel.blockName,
|
|
585
|
+
blockProps: cachedModel.blockProps,
|
|
586
|
+
isCube: cachedModel.isCube,
|
|
587
|
+
model,
|
|
588
|
+
},
|
|
589
|
+
model,
|
|
590
|
+
{
|
|
591
|
+
sectionOrigin: { x: sectionOx, y: sectionOy, z: sectionOz },
|
|
592
|
+
sectionHeight,
|
|
593
|
+
biome,
|
|
594
|
+
tintPalette: shaderResources.tintPalette,
|
|
595
|
+
textureIndexMapping: shaderResources.textureIndexMapping,
|
|
596
|
+
doAO,
|
|
597
|
+
},
|
|
598
|
+
shaderWordBuffer,
|
|
599
|
+
)
|
|
600
|
+
if (emitted) continue
|
|
730
601
|
}
|
|
731
602
|
}
|
|
732
603
|
|
|
@@ -1002,7 +873,9 @@ export function renderWasmOutputToGeometry(
|
|
|
1002
873
|
}
|
|
1003
874
|
}
|
|
1004
875
|
|
|
1005
|
-
const
|
|
876
|
+
const shaderCubes = buildShaderCubesFromWords(shaderWordBuffer)
|
|
877
|
+
|
|
878
|
+
const result: ExportedSection = {
|
|
1006
879
|
key: sectionKey,
|
|
1007
880
|
position: sectionPosition,
|
|
1008
881
|
geometry: {
|
|
@@ -1012,6 +885,7 @@ export function renderWasmOutputToGeometry(
|
|
|
1012
885
|
uvs,
|
|
1013
886
|
indices,
|
|
1014
887
|
},
|
|
888
|
+
...(shaderCubes ? { shaderCubes } : {}),
|
|
1015
889
|
}
|
|
1016
890
|
|
|
1017
891
|
log(`[WASM] Final geometry summary:`)
|
|
@@ -1059,10 +933,10 @@ export function renderWasmOutputToGeometry(
|
|
|
1059
933
|
export function splitColumnWasmOutputToSections(
|
|
1060
934
|
fullColumnOutput: WasmGeometryOutput,
|
|
1061
935
|
requestedSectionKeys: Array<{ x: number, y: number, z: number }>,
|
|
1062
|
-
ctx: { version: string, world?: World, sectionHeight?: number }
|
|
936
|
+
ctx: { version: string, world?: World, sectionHeight?: number, shaderCubes?: boolean }
|
|
1063
937
|
): Map<string, { exported: ExportedSection, blocksCount: number }> {
|
|
1064
938
|
const { version, world } = ctx
|
|
1065
|
-
const sectionHeight = ctx.sectionHeight ??
|
|
939
|
+
const sectionHeight = ctx.sectionHeight ?? SECTION_HEIGHT
|
|
1066
940
|
|
|
1067
941
|
// Bucket blocks by section Y once, so we don't re-scan the full column
|
|
1068
942
|
// for every requested section. Bucket key = section-relative chunk Y
|
|
@@ -1101,7 +975,8 @@ export function splitColumnWasmOutputToSections(
|
|
|
1101
975
|
version,
|
|
1102
976
|
sectionKey,
|
|
1103
977
|
sectionPosition,
|
|
1104
|
-
world
|
|
978
|
+
world,
|
|
979
|
+
{ sectionHeight, shaderCubes: ctx.shaderCubes },
|
|
1105
980
|
)
|
|
1106
981
|
out.set(sectionKey, { exported, blocksCount: sectionBlocks.length })
|
|
1107
982
|
}
|
|
@@ -1121,7 +996,9 @@ export function wasmOutputToExportFormat(
|
|
|
1121
996
|
cameraRotation = { pitch: 0, yaw: 0 },
|
|
1122
997
|
world?: World
|
|
1123
998
|
): ExportedWorldGeometry {
|
|
1124
|
-
const section = renderWasmOutputToGeometry(wasmOutput, version, sectionKey, sectionPosition, world
|
|
999
|
+
const section = renderWasmOutputToGeometry(wasmOutput, version, sectionKey, sectionPosition, world, {
|
|
1000
|
+
shaderCubes: true,
|
|
1001
|
+
})
|
|
1125
1002
|
|
|
1126
1003
|
return {
|
|
1127
1004
|
version,
|