minecraft-renderer 0.1.72 → 0.1.73
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/README.md +1 -1
- package/dist/mesher.js +81 -81
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +1183 -943
- package/dist/minecraft-renderer.js +249 -78
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +1732 -1001
- package/package.json +3 -3
- package/src/graphicsBackend/rendererDefaultOptions.ts +2 -7
- package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
- package/src/lib/bakeLegacyLight.ts +17 -0
- package/src/lib/blockEntityLightRegistry.test.ts +18 -0
- package/src/lib/blockEntityLightRegistry.ts +75 -0
- package/src/lib/blockEntityLighting.test.ts +30 -0
- package/src/lib/blockEntityLighting.ts +53 -0
- package/src/lib/worldrendererCommon.ts +14 -6
- package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
- package/src/mesher-shared/blockEntityMetadata.ts +19 -3
- package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
- package/src/mesher-shared/models.ts +161 -92
- package/src/mesher-shared/shared.ts +15 -4
- package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
- package/src/mesher-shared/world.ts +12 -0
- package/src/mesher-shared/worldLighting.test.ts +54 -0
- package/src/playground/baseScene.ts +1 -1
- package/src/three/bannerRenderer.ts +10 -3
- package/src/three/chunkMeshManager.ts +663 -69
- package/src/three/cubeDrawSpans.ts +74 -0
- package/src/three/cubeMultiDraw.ts +119 -0
- package/src/three/documentRenderer.ts +0 -2
- package/src/three/entities.ts +5 -6
- package/src/three/entity/EntityMesh.ts +7 -5
- package/src/three/entity/gltfAnimationUtils.ts +5 -3
- package/src/three/globalBlockBuffer.ts +208 -12
- package/src/three/globalLegacyBuffer.ts +701 -0
- package/src/three/itemMesh.ts +5 -2
- package/src/three/legacySectionCull.ts +85 -0
- package/src/three/modules/sciFiWorldReveal.ts +347 -703
- package/src/three/modules/starfield.ts +3 -2
- package/src/three/sectionRaycastAabb.ts +25 -0
- package/src/three/shaders/cubeBlockShader.ts +80 -17
- package/src/three/shaders/legacyBlockShader.ts +292 -0
- package/src/three/skyboxRenderer.ts +1 -1
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
- package/src/three/tests/cubeDrawSpans.test.ts +73 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
- package/src/three/tests/legacySectionCull.test.ts +80 -0
- package/src/three/tests/signTextureCache.test.ts +83 -0
- package/src/three/threeJsMedia.ts +2 -2
- package/src/three/waypointSprite.ts +2 -2
- package/src/three/world/cursorBlock.ts +1 -0
- package/src/three/world/vr.ts +2 -2
- package/src/three/worldGeometryExport.ts +83 -26
- package/src/three/worldRendererThree.ts +94 -25
- package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +67 -5
- package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
|
@@ -12,9 +12,11 @@ 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 { bakeLegacyVertexColors } from '../../lib/bakeLegacyLight'
|
|
15
16
|
import { SECTION_HEIGHT } from '../../mesher-shared/shared'
|
|
16
17
|
import type { World } from '../../mesher-shared/world'
|
|
17
18
|
import { resolveBlockPropertiesForMeshing } from '../../mesher-shared/blockPropertiesForMeshing'
|
|
19
|
+
import { isSemiTransparentBlockName } from '../../mesher-shared/models'
|
|
18
20
|
import {
|
|
19
21
|
buildShaderCubesFromWords,
|
|
20
22
|
getShaderCubeResources,
|
|
@@ -87,8 +89,10 @@ interface WasmBlockFaceData {
|
|
|
87
89
|
block_state_id: number
|
|
88
90
|
visible_faces: number
|
|
89
91
|
ao_data: number[][]
|
|
90
|
-
light_data
|
|
91
|
-
|
|
92
|
+
light_data?: number[][]
|
|
93
|
+
sky_light_data?: number[][]
|
|
94
|
+
block_light_data?: number[][]
|
|
95
|
+
light_combined?: number[][]
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
export interface WasmGeometryOutput {
|
|
@@ -150,6 +154,78 @@ function computeMesherVertexLight(
|
|
|
150
154
|
return vertexLightFromAo(ao, cornerLight15, sideShading, shadingTheme)
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
function vertexTintAoColor (
|
|
158
|
+
world: World | undefined,
|
|
159
|
+
tint: [number, number, number],
|
|
160
|
+
ao: number,
|
|
161
|
+
faceDir: [number, number, number],
|
|
162
|
+
): [number, number, number] {
|
|
163
|
+
const shadingTheme = world?.config.shadingTheme ?? 'high-contrast'
|
|
164
|
+
const cardinalLight = world?.config.cardinalLight ?? 'default'
|
|
165
|
+
const sideShading = getSideShading(faceDir, shadingTheme, cardinalLight)
|
|
166
|
+
if (shadingTheme === 'high-contrast') {
|
|
167
|
+
const f = sideShading * ((ao + 1) / 4)
|
|
168
|
+
return [tint[0] * f, tint[1] * f, tint[2] * f]
|
|
169
|
+
}
|
|
170
|
+
const f = sideShading * (ao * 0.2 + 0.4)
|
|
171
|
+
return [tint[0] * f, tint[1] * f, tint[2] * f]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function sampleChannelLightAt (world: World, pos: Vec3): { block: number, sky: number } {
|
|
175
|
+
return world.getChannelLightNorm(pos)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function smoothChannelLightAt (
|
|
179
|
+
world: World,
|
|
180
|
+
cursor: Vec3,
|
|
181
|
+
faceDir: [number, number, number],
|
|
182
|
+
cornerOffset: [number, number, number],
|
|
183
|
+
faceIdx: number,
|
|
184
|
+
): { block: number, sky: number } {
|
|
185
|
+
const neighbor = cursor.offset(faceDir[0], faceDir[1], faceDir[2])
|
|
186
|
+
const base = sampleChannelLightAt(world, neighbor)
|
|
187
|
+
|
|
188
|
+
if (!world.config.smoothLighting) {
|
|
189
|
+
return base
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const mask1 = [
|
|
193
|
+
[1, 1, 0], [1, 1, 0], [1, 1, 0], [1, 1, 0], [1, 0, 1], [1, 0, 1],
|
|
194
|
+
][faceIdx]!
|
|
195
|
+
const mask2 = [
|
|
196
|
+
[0, 1, 1], [0, 1, 1], [1, 0, 1], [1, 0, 1], [0, 1, 1], [0, 1, 1],
|
|
197
|
+
][faceIdx]!
|
|
198
|
+
const [cx, cy, cz] = cornerOffset
|
|
199
|
+
const [fx, fy, fz] = faceDir
|
|
200
|
+
|
|
201
|
+
const shrink = (v: [number, number, number], mask: number[]) => {
|
|
202
|
+
const out: [number, number, number] = [cx * mask[0]!, cy * mask[1]!, cz * mask[2]!]
|
|
203
|
+
if (fx !== 0) out[0] = 0
|
|
204
|
+
if (fy !== 0) out[1] = 0
|
|
205
|
+
if (fz !== 0) out[2] = 0
|
|
206
|
+
return out
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const s1 = shrink([cx, cy, cz], mask1)
|
|
210
|
+
const s2 = shrink([cx, cy, cz], mask2)
|
|
211
|
+
const c = shrink([cx, cy, cz], [1, 1, 1])
|
|
212
|
+
|
|
213
|
+
const samples = [
|
|
214
|
+
base,
|
|
215
|
+
sampleChannelLightAt(world, neighbor.offset(s1[0], s1[1], s1[2])),
|
|
216
|
+
sampleChannelLightAt(world, neighbor.offset(s2[0], s2[1], s2[2])),
|
|
217
|
+
sampleChannelLightAt(world, neighbor.offset(c[0], c[1], c[2])),
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
let blockSum = 0
|
|
221
|
+
let skySum = 0
|
|
222
|
+
for (const s of samples) {
|
|
223
|
+
blockSum += s.block
|
|
224
|
+
skySum += s.sky
|
|
225
|
+
}
|
|
226
|
+
return { block: blockSum / 4, sky: skySum / 4 }
|
|
227
|
+
}
|
|
228
|
+
|
|
153
229
|
/**
|
|
154
230
|
* Get or create cached block model with precomputed matrices
|
|
155
231
|
*/
|
|
@@ -351,6 +427,8 @@ const renderLiquidToGeometry = (
|
|
|
351
427
|
positions: number[],
|
|
352
428
|
normals: number[],
|
|
353
429
|
colors: number[],
|
|
430
|
+
skyLights: number[],
|
|
431
|
+
blockLights: number[],
|
|
354
432
|
uvs: number[],
|
|
355
433
|
indices: number[],
|
|
356
434
|
) => {
|
|
@@ -394,7 +472,7 @@ const renderLiquidToGeometry = (
|
|
|
394
472
|
const su = texture.su || 1
|
|
395
473
|
const sv = texture.sv || 1
|
|
396
474
|
|
|
397
|
-
const
|
|
475
|
+
const baseChannels = sampleChannelLightAt(world, neighborPos)
|
|
398
476
|
|
|
399
477
|
const baseIndex = positions.length / 3
|
|
400
478
|
|
|
@@ -411,7 +489,8 @@ const renderLiquidToGeometry = (
|
|
|
411
489
|
normals.push(dir[0], dir[1], dir[2])
|
|
412
490
|
uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v)
|
|
413
491
|
|
|
414
|
-
let
|
|
492
|
+
let skyNorm = baseChannels.sky
|
|
493
|
+
let blockNorm = baseChannels.block
|
|
415
494
|
if (world.config.smoothLighting) {
|
|
416
495
|
const dx = pos[0] * 2 - 1
|
|
417
496
|
const dy = pos[1] * 2 - 1
|
|
@@ -423,16 +502,19 @@ const renderLiquidToGeometry = (
|
|
|
423
502
|
const dirVec = new Vec3(dir[0], dir[1], dir[2])
|
|
424
503
|
|
|
425
504
|
const side1LightDir = getVec(new Vec3(side1Dir[0], side1Dir[1], side1Dir[2]), dirVec)
|
|
426
|
-
const
|
|
427
|
-
const side2DirLight = getVec(new Vec3(side2Dir[0], side2Dir[1], side2Dir[2]), dirVec)
|
|
428
|
-
const side2Light = world.getLight(cursor.plus(side2DirLight)) / 15
|
|
505
|
+
const side2LightDir = getVec(new Vec3(side2Dir[0], side2Dir[1], side2Dir[2]), dirVec)
|
|
429
506
|
const cornerLightDir = getVec(new Vec3(cornerDir[0], cornerDir[1], cornerDir[2]), dirVec)
|
|
430
|
-
const cornerLight = world.getLight(cursor.plus(cornerLightDir)) / 15
|
|
431
507
|
|
|
432
|
-
|
|
508
|
+
const s1 = sampleChannelLightAt(world, cursor.plus(side1LightDir))
|
|
509
|
+
const s2 = sampleChannelLightAt(world, cursor.plus(side2LightDir))
|
|
510
|
+
const sc = sampleChannelLightAt(world, cursor.plus(cornerLightDir))
|
|
511
|
+
blockNorm = (s1.block + s2.block + sc.block + baseChannels.block) / 4
|
|
512
|
+
skyNorm = (s1.sky + s2.sky + sc.sky + baseChannels.sky) / 4
|
|
433
513
|
}
|
|
434
514
|
|
|
435
|
-
colors.push(tint[0]
|
|
515
|
+
colors.push(tint[0], tint[1], tint[2])
|
|
516
|
+
skyLights.push(skyNorm)
|
|
517
|
+
blockLights.push(blockNorm)
|
|
436
518
|
}
|
|
437
519
|
|
|
438
520
|
indices.push(
|
|
@@ -442,12 +524,26 @@ const renderLiquidToGeometry = (
|
|
|
442
524
|
baseIndex + 2,
|
|
443
525
|
baseIndex + 1,
|
|
444
526
|
baseIndex + 3,
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
baseIndex + 3
|
|
450
|
-
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
const dupBase = positions.length / 3
|
|
530
|
+
for (let v = 0; v < 4; v++) {
|
|
531
|
+
const src = (baseIndex + v) * 3
|
|
532
|
+
positions.push(positions[src]!, positions[src + 1]!, positions[src + 2]!)
|
|
533
|
+
normals.push(-dir[0], -dir[1], -dir[2])
|
|
534
|
+
const uvSrc = (baseIndex + v) * 2
|
|
535
|
+
uvs.push(uvs[uvSrc]!, uvs[uvSrc + 1]!)
|
|
536
|
+
colors.push(colors[src]!, colors[src + 1]!, colors[src + 2]!)
|
|
537
|
+
skyLights.push(skyLights[src / 3]!)
|
|
538
|
+
blockLights.push(blockLights[src / 3]!)
|
|
539
|
+
}
|
|
540
|
+
indices.push(
|
|
541
|
+
dupBase,
|
|
542
|
+
dupBase + 2,
|
|
543
|
+
dupBase + 1,
|
|
544
|
+
dupBase + 1,
|
|
545
|
+
dupBase + 2,
|
|
546
|
+
dupBase + 3,
|
|
451
547
|
)
|
|
452
548
|
}
|
|
453
549
|
}
|
|
@@ -501,9 +597,19 @@ export function renderWasmOutputToGeometry(
|
|
|
501
597
|
const positions: number[] = []
|
|
502
598
|
const normals: number[] = []
|
|
503
599
|
const colors: number[] = []
|
|
600
|
+
const skyLights: number[] = []
|
|
601
|
+
const blockLights: number[] = []
|
|
504
602
|
const uvs: number[] = []
|
|
505
603
|
const indices: number[] = []
|
|
506
604
|
|
|
605
|
+
const blendPositions: number[] = []
|
|
606
|
+
const blendNormals: number[] = []
|
|
607
|
+
const blendColors: number[] = []
|
|
608
|
+
const blendSkyLights: number[] = []
|
|
609
|
+
const blendBlockLights: number[] = []
|
|
610
|
+
const blendUvs: number[] = []
|
|
611
|
+
const blendIndices: number[] = []
|
|
612
|
+
|
|
507
613
|
const liquidQueue: Array<{
|
|
508
614
|
pos: Vec3,
|
|
509
615
|
type: number,
|
|
@@ -512,8 +618,6 @@ export function renderWasmOutputToGeometry(
|
|
|
512
618
|
isRealWater: boolean,
|
|
513
619
|
}> = []
|
|
514
620
|
|
|
515
|
-
let currentIndex = 0
|
|
516
|
-
|
|
517
621
|
const sectionHeight = options?.sectionHeight ?? SECTION_HEIGHT
|
|
518
622
|
const shaderCubesEnabled = options?.shaderCubes !== false
|
|
519
623
|
const [sectionOx, sectionOy, sectionOz] = sectionKey.split(',').map((v) => parseInt(v, 10))
|
|
@@ -602,6 +706,15 @@ export function renderWasmOutputToGeometry(
|
|
|
602
706
|
const models = cachedModel.models
|
|
603
707
|
if (!models || models.length == 0) continue
|
|
604
708
|
|
|
709
|
+
const routeToBlend = prismBlock.transparent && isSemiTransparentBlockName(cachedModel.blockName)
|
|
710
|
+
const tgtPos = routeToBlend ? blendPositions : positions
|
|
711
|
+
const tgtNorm = routeToBlend ? blendNormals : normals
|
|
712
|
+
const tgtCol = routeToBlend ? blendColors : colors
|
|
713
|
+
const tgtSky = routeToBlend ? blendSkyLights : skyLights
|
|
714
|
+
const tgtBlock = routeToBlend ? blendBlockLights : blockLights
|
|
715
|
+
const tgtUv = routeToBlend ? blendUvs : uvs
|
|
716
|
+
const tgtIdx = routeToBlend ? blendIndices : indices
|
|
717
|
+
|
|
605
718
|
const faceNameToIndex: Record<string, number> = {
|
|
606
719
|
'up': 0,
|
|
607
720
|
'down': 1,
|
|
@@ -701,7 +814,9 @@ export function renderWasmOutputToGeometry(
|
|
|
701
814
|
|
|
702
815
|
const faceDataIndex = faceIdx === undefined ? undefined : wasmFaceToDataIndex[faceIdx]
|
|
703
816
|
const aoValuesRaw = faceDataIndex === undefined ? undefined : block.ao_data[faceDataIndex]
|
|
704
|
-
const
|
|
817
|
+
const skyValuesRaw = faceDataIndex === undefined ? undefined : block.sky_light_data?.[faceDataIndex]
|
|
818
|
+
const blockValuesRaw = faceDataIndex === undefined ? undefined : block.block_light_data?.[faceDataIndex]
|
|
819
|
+
const lightValuesRaw = faceDataIndex === undefined ? undefined : block.light_data?.[faceDataIndex]
|
|
705
820
|
|
|
706
821
|
const texture = matchingEFace.texture as any
|
|
707
822
|
const u = texture.u || 0
|
|
@@ -718,7 +833,7 @@ export function renderWasmOutputToGeometry(
|
|
|
718
833
|
|
|
719
834
|
const tint = getTint(matchingEFace, cachedModel.blockName, cachedModel.blockProps, biome, world)
|
|
720
835
|
|
|
721
|
-
const baseIndex =
|
|
836
|
+
const baseIndex = tgtPos.length / 3
|
|
722
837
|
const computedAoValues = [3, 3, 3, 3]
|
|
723
838
|
for (let cornerIdx = 0; cornerIdx < 4; cornerIdx++) {
|
|
724
839
|
const pos = corners[cornerIdx]
|
|
@@ -739,20 +854,19 @@ export function renderWasmOutputToGeometry(
|
|
|
739
854
|
vertex[2] + (bz & 15) - 8
|
|
740
855
|
]
|
|
741
856
|
|
|
742
|
-
|
|
857
|
+
tgtPos.push(...worldPos)
|
|
743
858
|
|
|
744
|
-
|
|
859
|
+
tgtNorm.push(transformedDir[0], transformedDir[1], transformedDir[2])
|
|
745
860
|
|
|
746
|
-
const useModelLighting = !cachedModel.isCube && world
|
|
861
|
+
const useModelLighting = (!cachedModel.isCube || globalMatrix != null) && world
|
|
747
862
|
|
|
748
863
|
let ao = 3
|
|
749
|
-
let
|
|
750
|
-
let
|
|
864
|
+
let skyLightNorm = 1
|
|
865
|
+
let blockLightNorm = 0
|
|
866
|
+
const faceDir = transformedDirI as [number, number, number]
|
|
751
867
|
|
|
752
868
|
if (!doAO) {
|
|
753
|
-
// JS parity: skip AO/light sampling, emit full-bright vertex.
|
|
754
869
|
computedAoValues[cornerIdx] = 3
|
|
755
|
-
light = 1
|
|
756
870
|
} else if (useModelLighting) {
|
|
757
871
|
const cursor = new Vec3(bx, by, bz)
|
|
758
872
|
|
|
@@ -779,55 +893,45 @@ export function renderWasmOutputToGeometry(
|
|
|
779
893
|
ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
|
|
780
894
|
computedAoValues[cornerIdx] = ao
|
|
781
895
|
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const side2LightDir = getVec(new Vec3(side2DirI[0], side2DirI[1], side2DirI[2]))
|
|
796
|
-
const cornerLightDir = getVec(new Vec3(cornerDirI[0], cornerDirI[1], cornerDirI[2]))
|
|
797
|
-
|
|
798
|
-
const side1Light = world.getLight(cursor.plus(side1LightDir))
|
|
799
|
-
const side2Light = world.getLight(cursor.plus(side2LightDir))
|
|
800
|
-
const cornerLight = world.getLight(cursor.plus(cornerLightDir))
|
|
801
|
-
|
|
802
|
-
cornerLightResult = (side1Light + side2Light + cornerLight + baseLight15) / 4
|
|
803
|
-
} else {
|
|
804
|
-
cornerLightResult = baseLight15
|
|
805
|
-
}
|
|
896
|
+
const cornerDirL = matmul3(globalMatrix, [dx, dy, dz])
|
|
897
|
+
const cornerOffsetI: [number, number, number] = [
|
|
898
|
+
Math.round(cornerDirL[0]), Math.round(cornerDirL[1]), Math.round(cornerDirL[2]),
|
|
899
|
+
]
|
|
900
|
+
const channels = smoothChannelLightAt(
|
|
901
|
+
world,
|
|
902
|
+
cursor,
|
|
903
|
+
faceDir,
|
|
904
|
+
cornerOffsetI,
|
|
905
|
+
faceIdx ?? 0,
|
|
906
|
+
)
|
|
907
|
+
skyLightNorm = channels.sky
|
|
908
|
+
blockLightNorm = channels.block
|
|
806
909
|
} else {
|
|
807
910
|
const aoValues = aoValuesRaw ?? [3, 3, 3, 3]
|
|
808
|
-
const lightValues = lightValuesRaw ?? [1, 1, 1, 1]
|
|
809
911
|
|
|
810
912
|
ao = aoValues[cornerIdx] ?? 3
|
|
811
913
|
computedAoValues[cornerIdx] = ao
|
|
812
914
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
915
|
+
if (skyValuesRaw && blockValuesRaw) {
|
|
916
|
+
skyLightNorm = skyValuesRaw[cornerIdx] ?? 1
|
|
917
|
+
blockLightNorm = blockValuesRaw[cornerIdx] ?? 0
|
|
918
|
+
} else {
|
|
919
|
+
const combined = lightValuesRaw?.[cornerIdx] ?? 1
|
|
920
|
+
skyLightNorm = combined
|
|
921
|
+
blockLightNorm = 0
|
|
922
|
+
}
|
|
820
923
|
}
|
|
821
924
|
|
|
822
|
-
|
|
925
|
+
const tintAo = vertexTintAoColor(world, tint, ao, faceDir)
|
|
926
|
+
tgtCol.push(tintAo[0], tintAo[1], tintAo[2])
|
|
927
|
+
tgtSky.push(skyLightNorm)
|
|
928
|
+
tgtBlock.push(blockLightNorm)
|
|
823
929
|
|
|
824
930
|
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
|
|
825
931
|
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
|
|
826
932
|
const finalU = baseu * su + u
|
|
827
933
|
const finalV = basev * sv + v
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
currentIndex++
|
|
934
|
+
tgtUv.push(finalU, finalV)
|
|
831
935
|
}
|
|
832
936
|
|
|
833
937
|
const aoValues = computedAoValues
|
|
@@ -840,7 +944,7 @@ export function renderWasmOutputToGeometry(
|
|
|
840
944
|
tri1 = [baseIndex, baseIndex + 1, baseIndex + 2]
|
|
841
945
|
tri2 = [baseIndex + 2, baseIndex + 1, baseIndex + 3]
|
|
842
946
|
}
|
|
843
|
-
|
|
947
|
+
tgtIdx.push(...tri1, ...tri2)
|
|
844
948
|
}
|
|
845
949
|
}
|
|
846
950
|
}
|
|
@@ -862,11 +966,13 @@ export function renderWasmOutputToGeometry(
|
|
|
862
966
|
q.biome,
|
|
863
967
|
q.water,
|
|
864
968
|
q.isRealWater,
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
969
|
+
blendPositions,
|
|
970
|
+
blendNormals,
|
|
971
|
+
blendColors,
|
|
972
|
+
blendSkyLights,
|
|
973
|
+
blendBlockLights,
|
|
974
|
+
blendUvs,
|
|
975
|
+
blendIndices,
|
|
870
976
|
)
|
|
871
977
|
}
|
|
872
978
|
}
|
|
@@ -880,9 +986,22 @@ export function renderWasmOutputToGeometry(
|
|
|
880
986
|
positions,
|
|
881
987
|
normals,
|
|
882
988
|
colors,
|
|
989
|
+
skyLights,
|
|
990
|
+
blockLights,
|
|
883
991
|
uvs,
|
|
884
992
|
indices,
|
|
885
993
|
},
|
|
994
|
+
...(blendPositions.length > 0 ? {
|
|
995
|
+
blendGeometry: {
|
|
996
|
+
positions: blendPositions,
|
|
997
|
+
normals: blendNormals,
|
|
998
|
+
colors: blendColors,
|
|
999
|
+
skyLights: blendSkyLights,
|
|
1000
|
+
blockLights: blendBlockLights,
|
|
1001
|
+
uvs: blendUvs,
|
|
1002
|
+
indices: blendIndices,
|
|
1003
|
+
},
|
|
1004
|
+
} : {}),
|
|
886
1005
|
...(shaderCubes ? { shaderCubes } : {}),
|
|
887
1006
|
}
|
|
888
1007
|
|
|
@@ -1017,12 +1136,17 @@ export function mesherGeometryToExportFormat(
|
|
|
1017
1136
|
mesherGeometry: MesherGeometryOutput,
|
|
1018
1137
|
version: string,
|
|
1019
1138
|
cameraPosition = { x: 0, y: 0, z: 0 },
|
|
1020
|
-
cameraRotation = { pitch: 0, yaw: 0 }
|
|
1139
|
+
cameraRotation = { pitch: 0, yaw: 0 },
|
|
1140
|
+
skyLevel = 1,
|
|
1021
1141
|
): ExportedWorldGeometry {
|
|
1022
|
-
// Convert typed arrays to regular number arrays
|
|
1023
1142
|
const positions = Array.from(mesherGeometry.positions) as number[]
|
|
1024
1143
|
const normals = mesherGeometry.normals ? (Array.from(mesherGeometry.normals) as number[]) : []
|
|
1025
|
-
const
|
|
1144
|
+
const tintColors = mesherGeometry.colors ? (Array.from(mesherGeometry.colors) as number[]) : []
|
|
1145
|
+
const skyLights = mesherGeometry.skyLights ? (Array.from(mesherGeometry.skyLights) as number[]) : []
|
|
1146
|
+
const blockLights = mesherGeometry.blockLights ? (Array.from(mesherGeometry.blockLights) as number[]) : []
|
|
1147
|
+
const colors = skyLights.length
|
|
1148
|
+
? bakeLegacyVertexColors(tintColors, skyLights, blockLights, skyLevel)
|
|
1149
|
+
: tintColors
|
|
1026
1150
|
const uvs = mesherGeometry.uvs ? (Array.from(mesherGeometry.uvs) as number[]) : []
|
|
1027
1151
|
const indices = Array.from(mesherGeometry.indices) as number[]
|
|
1028
1152
|
|
|
@@ -1043,9 +1167,27 @@ export function mesherGeometryToExportFormat(
|
|
|
1043
1167
|
positions,
|
|
1044
1168
|
normals,
|
|
1045
1169
|
colors,
|
|
1170
|
+
skyLights,
|
|
1171
|
+
blockLights,
|
|
1046
1172
|
uvs,
|
|
1047
1173
|
indices,
|
|
1048
1174
|
},
|
|
1175
|
+
...(mesherGeometry.blend ? {
|
|
1176
|
+
blendGeometry: {
|
|
1177
|
+
positions: Array.from(mesherGeometry.blend.positions),
|
|
1178
|
+
normals: Array.from(mesherGeometry.blend.normals),
|
|
1179
|
+
colors: bakeLegacyVertexColors(
|
|
1180
|
+
Array.from(mesherGeometry.blend.colors),
|
|
1181
|
+
Array.from(mesherGeometry.blend.skyLights),
|
|
1182
|
+
Array.from(mesherGeometry.blend.blockLights),
|
|
1183
|
+
skyLevel,
|
|
1184
|
+
),
|
|
1185
|
+
skyLights: Array.from(mesherGeometry.blend.skyLights),
|
|
1186
|
+
blockLights: Array.from(mesherGeometry.blend.blockLights),
|
|
1187
|
+
uvs: Array.from(mesherGeometry.blend.uvs),
|
|
1188
|
+
indices: Array.from(mesherGeometry.blend.indices),
|
|
1189
|
+
},
|
|
1190
|
+
} : {}),
|
|
1049
1191
|
}
|
|
1050
1192
|
|
|
1051
1193
|
return {
|
|
@@ -59,7 +59,11 @@ export interface ShaderCubeBlockInput {
|
|
|
59
59
|
position: [number, number, number]
|
|
60
60
|
visible_faces: number
|
|
61
61
|
ao_data: number[][]
|
|
62
|
-
|
|
62
|
+
/** @deprecated combined f32; prefer sky_light_data + block_light_data or light_combined */
|
|
63
|
+
light_data?: number[][]
|
|
64
|
+
sky_light_data?: number[][]
|
|
65
|
+
block_light_data?: number[][]
|
|
66
|
+
/** Per-corner nibble-packed byte: high=sky4, low=block4 */
|
|
63
67
|
light_combined?: number[][]
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -286,6 +290,12 @@ export function unpackTexIndexFromWord2(word2: number): number {
|
|
|
286
290
|
return word2 & ((1 << WORD2.TEX_INDEX_BITS) - 1)
|
|
287
291
|
}
|
|
288
292
|
|
|
293
|
+
function packCornerLightByte (skyNorm: number, blockNorm: number): number {
|
|
294
|
+
const sky4 = Math.min(15, Math.round(Math.max(0, skyNorm) * 15))
|
|
295
|
+
const block4 = Math.min(15, Math.round(Math.max(0, blockNorm) * 15))
|
|
296
|
+
return ((sky4 << 4) | block4) & 0xff
|
|
297
|
+
}
|
|
298
|
+
|
|
289
299
|
function lightCombinedForFace(
|
|
290
300
|
block: ShaderCubeBlockInput,
|
|
291
301
|
faceDataIndex: number,
|
|
@@ -294,11 +304,13 @@ function lightCombinedForFace(
|
|
|
294
304
|
if (packed && packed.length === 4) {
|
|
295
305
|
return packed
|
|
296
306
|
}
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return
|
|
301
|
-
}
|
|
307
|
+
const sky = block.sky_light_data?.[faceDataIndex]
|
|
308
|
+
const blockL = block.block_light_data?.[faceDataIndex]
|
|
309
|
+
if (sky && blockL && sky.length === 4 && blockL.length === 4) {
|
|
310
|
+
return sky.map((s, i) => packCornerLightByte(s ?? 1, blockL[i] ?? 0))
|
|
311
|
+
}
|
|
312
|
+
const floats = block.light_data?.[faceDataIndex] ?? [1, 1, 1, 1]
|
|
313
|
+
return floats.map((f) => packCornerLightByte(f, 0))
|
|
302
314
|
}
|
|
303
315
|
|
|
304
316
|
function buildWasmFaceToDataIndex(visibleFaces: number): Record<number, number> {
|
|
Binary file
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
raycastAabb,
|
|
6
6
|
raycastShaderBlocksAabb,
|
|
7
7
|
raycastSectionAabb,
|
|
8
|
+
sectionAabbIntersectsRay,
|
|
8
9
|
} from '../../three/sectionRaycastAabb'
|
|
10
|
+
import { LEGACY_SECTION_HALF_EXTENT } from '../../three/legacySectionCull'
|
|
9
11
|
import { SHADER_CUBES_WORDS_PER_FACE } from '../bridge/shaderCubeBridge'
|
|
10
12
|
import { WORD0 } from '../../three/shaders/cubeBlockShader'
|
|
11
13
|
|
|
@@ -103,6 +105,24 @@ test('raycastShaderBlocksAabb: SoA stride-1 layout (GlobalBlockBuffer style)', (
|
|
|
103
105
|
expect(t).toBeLessThan(4)
|
|
104
106
|
})
|
|
105
107
|
|
|
108
|
+
const SECTION_HALF = LEGACY_SECTION_HALF_EXTENT + 0.01
|
|
109
|
+
|
|
110
|
+
test('sectionAabbIntersectsRay: ray toward section center within far', () => {
|
|
111
|
+
expect(sectionAabbIntersectsRay(8, 8, 8, 4, 8, 8, 1, 0, 0, 4, SECTION_HALF)).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('sectionAabbIntersectsRay: far shorter than gap to section', () => {
|
|
115
|
+
expect(sectionAabbIntersectsRay(8, 8, 8, -20, 8, 8, 1, 0, 0, 3, SECTION_HALF)).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('sectionAabbIntersectsRay: parallel offset ray misses box', () => {
|
|
119
|
+
expect(sectionAabbIntersectsRay(8, 8, 8, -20, 8, -20, 1, 0, 0, 100, SECTION_HALF)).toBe(false)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('sectionAabbIntersectsRay: origin inside box', () => {
|
|
123
|
+
expect(sectionAabbIntersectsRay(8, 8, 8, 8, 8, 8, 0, 1, 0, 4, SECTION_HALF)).toBe(true)
|
|
124
|
+
})
|
|
125
|
+
|
|
106
126
|
test('raycastAabb: narrow floor slab blocks downward ray', () => {
|
|
107
127
|
const words = new Uint32Array(SHADER_CUBES_WORDS_PER_FACE * 2)
|
|
108
128
|
words[0] = 4 | (0 << WORD0.LY_SHIFT) | (4 << WORD0.LZ_SHIFT)
|
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
SHADER_CUBES_WORDS_PER_FACE,
|
|
17
17
|
} from '../bridge/shaderCubeBridge'
|
|
18
18
|
import { GlobalBlockBuffer } from '../../three/globalBlockBuffer'
|
|
19
|
-
import {
|
|
19
|
+
import { buildVisibleCubeSpans } from '../../three/cubeDrawSpans'
|
|
20
|
+
import { createCubeBlockMaterial, computeSectionOriginRel } from '../../three/shaders/cubeBlockShader'
|
|
20
21
|
import * as THREE from 'three'
|
|
21
22
|
import { renderWasmOutputToGeometry } from '../bridge/render-from-wasm'
|
|
22
23
|
|
|
@@ -349,6 +350,38 @@ test('packWord2Empty: bit 18 set regardless of high X/Z bits in word2', () => {
|
|
|
349
350
|
expect(withHighBits & (1 << WORD2.EMPTY_SHIFT)).not.toBe(0)
|
|
350
351
|
})
|
|
351
352
|
|
|
353
|
+
test('section index relative decode past 2^20: exact integer subtract', () => {
|
|
354
|
+
const sectionBlockX = 21_050_000
|
|
355
|
+
const renderOrigin = { x: 21_000_000, y: 0, z: 0 }
|
|
356
|
+
const words: number[] = []
|
|
357
|
+
const block = {
|
|
358
|
+
position: [0, 0, 0] as [number, number, number],
|
|
359
|
+
visible_faces: 1 << 2,
|
|
360
|
+
ao_data: [[3, 3, 3, 3]],
|
|
361
|
+
light_data: [[1, 1, 1, 1]],
|
|
362
|
+
light_combined: [[255, 255, 255, 255]],
|
|
363
|
+
}
|
|
364
|
+
const { textureIndexMapping, tintPalette } = getShaderCubeResources()
|
|
365
|
+
const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
|
|
366
|
+
tryBuildShaderCubeInstances(
|
|
367
|
+
block,
|
|
368
|
+
{ blockName: 'stone', blockProps: {}, isCube: true, model },
|
|
369
|
+
model,
|
|
370
|
+
{
|
|
371
|
+
sectionOrigin: { x: sectionBlockX, y: 0, z: 0 },
|
|
372
|
+
sectionHeight: 16,
|
|
373
|
+
tintPalette,
|
|
374
|
+
textureIndexMapping,
|
|
375
|
+
},
|
|
376
|
+
words,
|
|
377
|
+
)
|
|
378
|
+
const base = decodeSectionBaseFromWords(words[2]!, words[3]!)
|
|
379
|
+
const sX = base.x / 16
|
|
380
|
+
const sectionOriginRel = computeSectionOriginRel(renderOrigin)
|
|
381
|
+
const sXr = sX - sectionOriginRel.x
|
|
382
|
+
expect(sXr * 16).toBe(sectionBlockX - renderOrigin.x)
|
|
383
|
+
})
|
|
384
|
+
|
|
352
385
|
test('GlobalBlockBuffer: free-list reuses slot with EMPTY sentinel', () => {
|
|
353
386
|
const scene = new THREE.Scene()
|
|
354
387
|
const mat = createCubeBlockMaterial()
|
|
@@ -661,6 +694,35 @@ test('GlobalBlockBuffer: takeSectionData reads relocated section slot', () => {
|
|
|
661
694
|
mat.dispose()
|
|
662
695
|
})
|
|
663
696
|
|
|
697
|
+
test('GlobalBlockBuffer: pendingMove draw start uses oldStart for visible spans', () => {
|
|
698
|
+
const scene = new THREE.Scene()
|
|
699
|
+
const mat = createCubeBlockMaterial()
|
|
700
|
+
const buffer = new GlobalBlockBuffer(mat, scene)
|
|
701
|
+
|
|
702
|
+
buffer.addSection('a', makeSectionWords([10]), 1)
|
|
703
|
+
buffer.addSection('b', makeSectionWords([20]), 1)
|
|
704
|
+
buffer.addSection('c', makeSectionWords([30]), 1)
|
|
705
|
+
buffer.removeSection('b')
|
|
706
|
+
drainAllUploads(buffer)
|
|
707
|
+
|
|
708
|
+
buffer.compactStep()
|
|
709
|
+
const move = buffer.getPendingMove()
|
|
710
|
+
expect(move?.key).toBe('c')
|
|
711
|
+
|
|
712
|
+
const slotStart = buffer.getSectionSlot('c')!.start
|
|
713
|
+
expect(buffer.getSectionDrawStart('c')).toBe(move!.oldStart)
|
|
714
|
+
expect(buffer.getSectionDrawStart('c')).not.toBe(slotStart)
|
|
715
|
+
|
|
716
|
+
const spans = buildVisibleCubeSpans(
|
|
717
|
+
[{ start: buffer.getSectionDrawStart('c')!, count: 1 }],
|
|
718
|
+
buffer.getHighWatermark(),
|
|
719
|
+
)
|
|
720
|
+
expect(spans[0]?.start).toBe(move!.oldStart)
|
|
721
|
+
|
|
722
|
+
buffer.dispose()
|
|
723
|
+
mat.dispose()
|
|
724
|
+
})
|
|
725
|
+
|
|
664
726
|
test('GlobalBlockBuffer: uploadDirtyRange budgets large dirty span across frames', () => {
|
|
665
727
|
const scene = new THREE.Scene()
|
|
666
728
|
const mat = createCubeBlockMaterial()
|
|
@@ -679,12 +741,12 @@ test('GlobalBlockBuffer: uploadDirtyRange budgets large dirty span across frames
|
|
|
679
741
|
|
|
680
742
|
const w0Attr = buffer.mesh.geometry.getAttribute('a_w0') as THREE.InstancedBufferAttribute
|
|
681
743
|
buffer.uploadDirtyRange()
|
|
682
|
-
expect(w0Attr.
|
|
683
|
-
expect(w0Attr.
|
|
744
|
+
expect(w0Attr.updateRanges[0].start).toBe(0)
|
|
745
|
+
expect(w0Attr.updateRanges[0].count).toBe(15_000)
|
|
684
746
|
|
|
685
747
|
buffer.uploadDirtyRange()
|
|
686
|
-
expect(w0Attr.
|
|
687
|
-
expect(w0Attr.
|
|
748
|
+
expect(w0Attr.updateRanges[0].start).toBe(15_000)
|
|
749
|
+
expect(w0Attr.updateRanges[0].count).toBe(5_000)
|
|
688
750
|
|
|
689
751
|
buffer.uploadDirtyRange()
|
|
690
752
|
expect((buffer as unknown as { pendingRanges: unknown[] }).pendingRanges).toHaveLength(0)
|