minecraft-renderer 0.1.71 → 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 +3 -3
- package/dist/mesher.js +81 -81
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +1183 -943
- package/dist/minecraft-renderer.js +250 -79
- 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 +5 -10
- 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.reconfigure.test.ts +202 -0
- package/src/lib/worldrendererCommon.ts +152 -22
- 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/graphicsBackendOffThread.ts +16 -1
- 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
- package/src/worldView/worldView.ts +11 -0
|
@@ -5,7 +5,7 @@ import moreBlockDataGeneratedJson from '../lib/moreBlockDataGenerated.json'
|
|
|
5
5
|
import { BlockType } from '../playground/shared'
|
|
6
6
|
import { World, BlockModelPartsResolved, WorldBlock as Block, WorldBlock, worldColumnKey } from './world'
|
|
7
7
|
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
|
|
8
|
-
import { getSideShading
|
|
8
|
+
import { getSideShading } from './vertexShading'
|
|
9
9
|
import { INVISIBLE_BLOCKS } from './worldConstants'
|
|
10
10
|
import { MesherGeometryOutput, HighestBlockInfo } from './shared'
|
|
11
11
|
import { collectBlockEntityMetadata } from './blockEntityMetadata'
|
|
@@ -23,6 +23,38 @@ const tints: any = {}
|
|
|
23
23
|
let needTiles = false
|
|
24
24
|
let semiTransparentBlocks: string[] = []
|
|
25
25
|
|
|
26
|
+
/** Mutable geometry bucket used while meshing (opaque or blend). */
|
|
27
|
+
export type MesherGeometryBucket = {
|
|
28
|
+
positions: number[]
|
|
29
|
+
normals: number[]
|
|
30
|
+
colors: number[]
|
|
31
|
+
skyLights: number[]
|
|
32
|
+
blockLights: number[]
|
|
33
|
+
uvs: number[]
|
|
34
|
+
indices: number[]
|
|
35
|
+
indicesCount: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function vertexTintAoColor (
|
|
39
|
+
tint: [number, number, number],
|
|
40
|
+
ao: number,
|
|
41
|
+
faceDir: [number, number, number],
|
|
42
|
+
shadingTheme: 'vanilla' | 'high-contrast',
|
|
43
|
+
cardinalLight: string,
|
|
44
|
+
): [number, number, number] {
|
|
45
|
+
const sideShading = getSideShading(faceDir, shadingTheme, cardinalLight)
|
|
46
|
+
if (shadingTheme === 'high-contrast') {
|
|
47
|
+
const f = sideShading * ((ao + 1) / 4)
|
|
48
|
+
return [tint[0] * f, tint[1] * f, tint[2] * f]
|
|
49
|
+
}
|
|
50
|
+
const f = sideShading * (ao * 0.2 + 0.4)
|
|
51
|
+
return [tint[0] * f, tint[1] * f, tint[2] * f]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isSemiTransparentBlockName (name: string): boolean {
|
|
55
|
+
return semiTransparentBlocks.includes(name)
|
|
56
|
+
}
|
|
57
|
+
|
|
26
58
|
let tintsData
|
|
27
59
|
try {
|
|
28
60
|
tintsData = require('esbuild-data').tints
|
|
@@ -89,7 +121,7 @@ const getVec = (v: Vec3, dir: Vec3) => {
|
|
|
89
121
|
return v.plus(dir)
|
|
90
122
|
}
|
|
91
123
|
|
|
92
|
-
function renderLiquid(world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: MesherGeometryOutput, isRealWater: boolean) {
|
|
124
|
+
function renderLiquid(world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, bucket: MesherGeometryBucket, attr: MesherGeometryOutput, isRealWater: boolean) {
|
|
93
125
|
const heights: number[] = []
|
|
94
126
|
for (let z = -1; z <= 1; z++) {
|
|
95
127
|
for (let x = -1; x <= 1; x++) {
|
|
@@ -144,21 +176,23 @@ function renderLiquid(world: World, cursor: Vec3, texture: any | undefined, type
|
|
|
144
176
|
const { su } = texture
|
|
145
177
|
const { sv } = texture
|
|
146
178
|
|
|
147
|
-
|
|
148
|
-
|
|
179
|
+
const baseChannels = world.getChannelLightNorm(neighborPos)
|
|
180
|
+
|
|
181
|
+
const baseIndex = bucket.positions.length / 3
|
|
149
182
|
|
|
150
183
|
for (const pos of corners) {
|
|
151
184
|
const height = cornerHeights[pos[2] * 2 + pos[0]]
|
|
152
185
|
const OFFSET = 0.0001
|
|
153
|
-
|
|
186
|
+
bucket.positions.push(
|
|
154
187
|
(pos[0] ? 1 - OFFSET : OFFSET) + (cursor.x & 15) - 8,
|
|
155
188
|
(pos[1] ? height - OFFSET : OFFSET) + (cursor.y & 15) - 8,
|
|
156
189
|
(pos[2] ? 1 - OFFSET : OFFSET) + (cursor.z & 15) - 8
|
|
157
190
|
)
|
|
158
|
-
|
|
159
|
-
|
|
191
|
+
bucket.normals.push(...dir)
|
|
192
|
+
bucket.uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v)
|
|
160
193
|
|
|
161
|
-
let
|
|
194
|
+
let skyNorm = baseChannels.sky
|
|
195
|
+
let blockNorm = baseChannels.block
|
|
162
196
|
if (world.config.smoothLighting) {
|
|
163
197
|
const dx = pos[0] * 2 - 1
|
|
164
198
|
const dy = pos[1] * 2 - 1
|
|
@@ -170,18 +204,47 @@ function renderLiquid(world: World, cursor: Vec3, texture: any | undefined, type
|
|
|
170
204
|
const dirVec = new Vec3(...dir as [number, number, number])
|
|
171
205
|
|
|
172
206
|
const side1LightDir = getVec(new Vec3(...side1Dir), dirVec)
|
|
173
|
-
const side1Light = world.getLight(cursor.plus(side1LightDir)) / 15
|
|
174
207
|
const side2DirLight = getVec(new Vec3(...side2Dir), dirVec)
|
|
175
|
-
const side2Light = world.getLight(cursor.plus(side2DirLight)) / 15
|
|
176
208
|
const cornerLightDir = getVec(new Vec3(...cornerDir), dirVec)
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
209
|
+
const s1 = world.getChannelLightNorm(cursor.plus(side1LightDir))
|
|
210
|
+
const s2 = world.getChannelLightNorm(cursor.plus(side2DirLight))
|
|
211
|
+
const sc = world.getChannelLightNorm(cursor.plus(cornerLightDir))
|
|
212
|
+
blockNorm = (s1.block + s2.block + sc.block + baseChannels.block) / 4
|
|
213
|
+
skyNorm = (s1.sky + s2.sky + sc.sky + baseChannels.sky) / 4
|
|
181
214
|
}
|
|
182
215
|
|
|
183
|
-
|
|
184
|
-
|
|
216
|
+
bucket.colors.push(tint[0], tint[1], tint[2])
|
|
217
|
+
bucket.skyLights.push(skyNorm)
|
|
218
|
+
bucket.blockLights.push(blockNorm)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!needTiles) {
|
|
222
|
+
// Quad A (front face)
|
|
223
|
+
bucket.indices[bucket.indicesCount++] = baseIndex
|
|
224
|
+
bucket.indices[bucket.indicesCount++] = baseIndex + 1
|
|
225
|
+
bucket.indices[bucket.indicesCount++] = baseIndex + 2
|
|
226
|
+
bucket.indices[bucket.indicesCount++] = baseIndex + 2
|
|
227
|
+
bucket.indices[bucket.indicesCount++] = baseIndex + 1
|
|
228
|
+
bucket.indices[bucket.indicesCount++] = baseIndex + 3
|
|
229
|
+
|
|
230
|
+
// Quad B (back face) — duplicate verts so global buffer keeps 6/4 invariant
|
|
231
|
+
const dupBase = bucket.positions.length / 3
|
|
232
|
+
for (let v = 0; v < 4; v++) {
|
|
233
|
+
const src = (baseIndex + v) * 3
|
|
234
|
+
bucket.positions.push(bucket.positions[src]!, bucket.positions[src + 1]!, bucket.positions[src + 2]!)
|
|
235
|
+
bucket.normals.push(-dir[0], -dir[1], -dir[2])
|
|
236
|
+
const uvSrc = (baseIndex + v) * 2
|
|
237
|
+
bucket.uvs.push(bucket.uvs[uvSrc]!, bucket.uvs[uvSrc + 1]!)
|
|
238
|
+
bucket.colors.push(bucket.colors[src]!, bucket.colors[src + 1]!, bucket.colors[src + 2]!)
|
|
239
|
+
bucket.skyLights.push(bucket.skyLights[baseIndex + v]!)
|
|
240
|
+
bucket.blockLights.push(bucket.blockLights[baseIndex + v]!)
|
|
241
|
+
}
|
|
242
|
+
bucket.indices[bucket.indicesCount++] = dupBase
|
|
243
|
+
bucket.indices[bucket.indicesCount++] = dupBase + 2
|
|
244
|
+
bucket.indices[bucket.indicesCount++] = dupBase + 1
|
|
245
|
+
bucket.indices[bucket.indicesCount++] = dupBase + 1
|
|
246
|
+
bucket.indices[bucket.indicesCount++] = dupBase + 2
|
|
247
|
+
bucket.indices[bucket.indicesCount++] = dupBase + 3
|
|
185
248
|
}
|
|
186
249
|
}
|
|
187
250
|
}
|
|
@@ -226,7 +289,7 @@ const identicalCull = (currentElement: BlockElement, neighbor: Block, direction:
|
|
|
226
289
|
|
|
227
290
|
let needSectionRecomputeOnChange = false
|
|
228
291
|
|
|
229
|
-
function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO: boolean, attr: MesherGeometryOutput, globalMatrix: any, globalShift: any, block: Block, biome: string) {
|
|
292
|
+
function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO: boolean, bucket: MesherGeometryBucket, attr: MesherGeometryOutput, globalMatrix: any, globalShift: any, block: Block, biome: string) {
|
|
230
293
|
const position = cursor
|
|
231
294
|
// const key = `${position.x},${position.y},${position.z}`
|
|
232
295
|
// if (!globalThis.allowedBlocks.includes(key)) return
|
|
@@ -264,7 +327,7 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
264
327
|
const { su } = texture
|
|
265
328
|
const { sv } = texture
|
|
266
329
|
|
|
267
|
-
const ndx = Math.floor(
|
|
330
|
+
const ndx = Math.floor(bucket.positions.length / 3)
|
|
268
331
|
|
|
269
332
|
tsLog(`[TS] Base index: ${ndx}`)
|
|
270
333
|
|
|
@@ -345,11 +408,9 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
345
408
|
|
|
346
409
|
const aos: number[] = []
|
|
347
410
|
const neighborPos = position.plus(new Vec3(...dir))
|
|
348
|
-
// 10%
|
|
349
411
|
const { smoothLighting, shadingTheme, cardinalLight } = world.config
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
const baseLight = sideShading * faceLight / 15
|
|
412
|
+
const baseChannels = world.getChannelLightNorm(neighborPos)
|
|
413
|
+
const faceDir = dir as [number, number, number]
|
|
353
414
|
for (const pos of corners) {
|
|
354
415
|
let vertex = [
|
|
355
416
|
(pos[0] ? maxx : minx),
|
|
@@ -370,19 +431,21 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
370
431
|
|
|
371
432
|
tsLog(`[TS] Corner ${pos.join(',')}: vertex=[${vertex.map(v => v.toFixed(3)).join(',')}], worldPos=[${worldPos.map(v => v.toFixed(3)).join(',')}]`)
|
|
372
433
|
|
|
373
|
-
|
|
434
|
+
bucket.positions.push(...worldPos)
|
|
374
435
|
|
|
375
|
-
|
|
436
|
+
bucket.normals.push(...dir)
|
|
376
437
|
|
|
377
438
|
const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5
|
|
378
439
|
const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5
|
|
379
440
|
const finalU = baseu * su + u
|
|
380
441
|
const finalV = basev * sv + v
|
|
381
442
|
tsLog(`[TS] UV: cornerUV=[${pos[3]},${pos[4]}], baseUV=[${baseu.toFixed(6)},${basev.toFixed(6)}], finalUV=[${finalU.toFixed(6)},${finalV.toFixed(6)}], texture=[u=${u},v=${v},su=${su},sv=${sv}], rotation=${r}`)
|
|
382
|
-
|
|
443
|
+
bucket.uvs.push(finalU, finalV)
|
|
383
444
|
}
|
|
384
445
|
|
|
385
|
-
let
|
|
446
|
+
let skyLightNorm = baseChannels.sky
|
|
447
|
+
let blockLightNorm = baseChannels.block
|
|
448
|
+
let ao = 3
|
|
386
449
|
if (doAO) {
|
|
387
450
|
const dx = pos[0] * 2 - 1
|
|
388
451
|
const dy = pos[1] * 2 - 1
|
|
@@ -394,8 +457,6 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
394
457
|
const side2 = world.getBlock(cursor.offset(...side2Dir))
|
|
395
458
|
const corner = world.getBlock(cursor.offset(...cornerDir))
|
|
396
459
|
|
|
397
|
-
let cornerLightResult = faceLight
|
|
398
|
-
|
|
399
460
|
if (smoothLighting) {
|
|
400
461
|
const dirVec = new Vec3(...dir)
|
|
401
462
|
const getVec = (v: Vec3) => {
|
|
@@ -405,37 +466,35 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
405
466
|
return v.plus(dirVec)
|
|
406
467
|
}
|
|
407
468
|
const side1LightDir = getVec(new Vec3(...side1Dir))
|
|
408
|
-
const side1Light = world.getLight(cursor.plus(side1LightDir))
|
|
409
469
|
const side2DirLight = getVec(new Vec3(...side2Dir))
|
|
410
|
-
const side2Light = world.getLight(cursor.plus(side2DirLight))
|
|
411
470
|
const cornerLightDir = getVec(new Vec3(...cornerDir))
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
|
|
471
|
+
const s1 = world.getChannelLightNorm(cursor.plus(side1LightDir))
|
|
472
|
+
const s2 = world.getChannelLightNorm(cursor.plus(side2DirLight))
|
|
473
|
+
const sc = world.getChannelLightNorm(cursor.plus(cornerLightDir))
|
|
474
|
+
blockLightNorm = (s1.block + s2.block + sc.block + baseChannels.block) / 4
|
|
475
|
+
skyLightNorm = (s1.sky + s2.sky + sc.sky + baseChannels.sky) / 4
|
|
416
476
|
}
|
|
417
477
|
|
|
418
478
|
const side1Block = world.shouldMakeAo(side1) ? 1 : 0
|
|
419
479
|
const side2Block = world.shouldMakeAo(side2) ? 1 : 0
|
|
420
480
|
const cornerBlock = world.shouldMakeAo(corner) ? 1 : 0
|
|
421
481
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
|
|
425
|
-
light = vertexLightFromAo(ao, cornerLightResult, sideShading, shadingTheme)
|
|
482
|
+
ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
|
|
426
483
|
aos.push(ao)
|
|
427
484
|
|
|
428
|
-
// Log AO and light for this corner (corner index is aos.length - 1)
|
|
429
485
|
const cornerIdx = aos.length - 1
|
|
430
|
-
tsLog(`[TS] Corner ${cornerIdx} AO=${ao},
|
|
486
|
+
tsLog(`[TS] Corner ${cornerIdx} AO=${ao}, sky=${skyLightNorm.toFixed(3)}, block=${blockLightNorm.toFixed(3)}`)
|
|
431
487
|
}
|
|
432
488
|
|
|
433
489
|
if (!needTiles) {
|
|
434
|
-
|
|
490
|
+
const tintAo = vertexTintAoColor(tint as [number, number, number], ao, faceDir, shadingTheme, cardinalLight)
|
|
491
|
+
bucket.colors.push(tintAo[0], tintAo[1], tintAo[2])
|
|
492
|
+
bucket.skyLights.push(skyLightNorm)
|
|
493
|
+
bucket.blockLights.push(blockLightNorm)
|
|
435
494
|
}
|
|
436
495
|
}
|
|
437
496
|
|
|
438
|
-
const lightWithColor = [
|
|
497
|
+
const lightWithColor = [baseChannels.sky * tint[0], baseChannels.sky * tint[1], baseChannels.sky * tint[2]] as [number, number, number]
|
|
439
498
|
|
|
440
499
|
if (needTiles) {
|
|
441
500
|
const tiles = attr.tiles as Tiles
|
|
@@ -451,7 +510,7 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
451
510
|
side,
|
|
452
511
|
textureIndex: eFace.texture.tileIndex,
|
|
453
512
|
neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`,
|
|
454
|
-
light:
|
|
513
|
+
light: Math.max(baseChannels.block, baseChannels.sky),
|
|
455
514
|
tint: lightWithColor,
|
|
456
515
|
//@ts-expect-error debug prop
|
|
457
516
|
texture: eFace.texture.debugName || block.name,
|
|
@@ -465,22 +524,22 @@ function renderElement(world: World, cursor: Vec3, element: BlockElement, doAO:
|
|
|
465
524
|
tri1 = [ndx, ndx + 3, ndx + 2]
|
|
466
525
|
tri2 = [ndx, ndx + 1, ndx + 3]
|
|
467
526
|
tsLog(`[TS] Indices (AO optimized): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aos.join(',')}]`)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
527
|
+
bucket.indices[bucket.indicesCount++] = tri1[0]
|
|
528
|
+
bucket.indices[bucket.indicesCount++] = tri1[1]
|
|
529
|
+
bucket.indices[bucket.indicesCount++] = tri1[2]
|
|
530
|
+
bucket.indices[bucket.indicesCount++] = tri2[0]
|
|
531
|
+
bucket.indices[bucket.indicesCount++] = tri2[1]
|
|
532
|
+
bucket.indices[bucket.indicesCount++] = tri2[2]
|
|
474
533
|
} else {
|
|
475
534
|
tri1 = [ndx, ndx + 1, ndx + 2]
|
|
476
535
|
tri2 = [ndx + 2, ndx + 1, ndx + 3]
|
|
477
536
|
tsLog(`[TS] Indices (standard): tri1=[${tri1.join(',')}], tri2=[${tri2.join(',')}], aos=[${aos.join(',')}]`)
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
537
|
+
bucket.indices[bucket.indicesCount++] = tri1[0]
|
|
538
|
+
bucket.indices[bucket.indicesCount++] = tri1[1]
|
|
539
|
+
bucket.indices[bucket.indicesCount++] = tri1[2]
|
|
540
|
+
bucket.indices[bucket.indicesCount++] = tri2[0]
|
|
541
|
+
bucket.indices[bucket.indicesCount++] = tri2[1]
|
|
542
|
+
bucket.indices[bucket.indicesCount++] = tri2[2]
|
|
484
543
|
}
|
|
485
544
|
}
|
|
486
545
|
}
|
|
@@ -516,11 +575,9 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
516
575
|
positions: [],
|
|
517
576
|
normals: [],
|
|
518
577
|
colors: [],
|
|
578
|
+
skyLights: [],
|
|
579
|
+
blockLights: [],
|
|
519
580
|
uvs: [],
|
|
520
|
-
t_positions: [],
|
|
521
|
-
t_normals: [],
|
|
522
|
-
t_colors: [],
|
|
523
|
-
t_uvs: [],
|
|
524
581
|
indices: [],
|
|
525
582
|
indicesCount: 0, // Track current index position
|
|
526
583
|
using32Array: true,
|
|
@@ -534,13 +591,34 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
534
591
|
blocksCount: 0
|
|
535
592
|
}
|
|
536
593
|
|
|
594
|
+
const opaqueBucket: MesherGeometryBucket = {
|
|
595
|
+
positions: attr.positions as number[],
|
|
596
|
+
normals: attr.normals as number[],
|
|
597
|
+
colors: attr.colors as number[],
|
|
598
|
+
skyLights: attr.skyLights as number[],
|
|
599
|
+
blockLights: attr.blockLights as number[],
|
|
600
|
+
uvs: attr.uvs as number[],
|
|
601
|
+
indices: attr.indices as number[],
|
|
602
|
+
indicesCount: attr.indicesCount,
|
|
603
|
+
}
|
|
604
|
+
const blendBucket: MesherGeometryBucket = {
|
|
605
|
+
positions: [],
|
|
606
|
+
normals: [],
|
|
607
|
+
colors: [],
|
|
608
|
+
skyLights: [],
|
|
609
|
+
blockLights: [],
|
|
610
|
+
uvs: [],
|
|
611
|
+
indices: [],
|
|
612
|
+
indicesCount: 0,
|
|
613
|
+
}
|
|
614
|
+
|
|
537
615
|
const cursor = new Vec3(0, 0, 0)
|
|
538
616
|
for (cursor.y = sy; cursor.y < sy + readHeight; cursor.y++) {
|
|
539
617
|
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
|
|
540
618
|
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
|
|
541
619
|
let block = world.getBlock(cursor, blockProvider, attr)!
|
|
542
620
|
if (INVISIBLE_BLOCKS.has(block.name)) continue
|
|
543
|
-
collectBlockEntityMetadata(block, cursor.x, cursor.y, cursor.z, attr, { disableBlockEntityTextures: world.config.disableBlockEntityTextures })
|
|
621
|
+
collectBlockEntityMetadata(block, cursor.x, cursor.y, cursor.z, attr, { disableBlockEntityTextures: world.config.disableBlockEntityTextures }, world)
|
|
544
622
|
const biome = block.biome.name
|
|
545
623
|
|
|
546
624
|
if (world.preflat) { // 10% perf
|
|
@@ -564,11 +642,11 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
564
642
|
const pos = cursor.clone()
|
|
565
643
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
566
644
|
delayedRender.push(() => {
|
|
567
|
-
renderLiquid(world, pos, blockProvider.getTextureInfo('water_still'), block.type, biome, true, attr, !isWaterlogged)
|
|
645
|
+
renderLiquid(world, pos, blockProvider.getTextureInfo('water_still'), block.type, biome, true, blendBucket, attr, !isWaterlogged)
|
|
568
646
|
})
|
|
569
647
|
attr.blocksCount++
|
|
570
648
|
} else if (block.name === 'lava') {
|
|
571
|
-
renderLiquid(world, cursor, blockProvider.getTextureInfo('lava_still'), block.type, biome, false, attr, false)
|
|
649
|
+
renderLiquid(world, cursor, blockProvider.getTextureInfo('lava_still'), block.type, biome, false, blendBucket, attr, false)
|
|
572
650
|
attr.blocksCount++
|
|
573
651
|
}
|
|
574
652
|
if (block.name !== 'water' && block.name !== 'lava' && !INVISIBLE_BLOCKS.has(block.name)) {
|
|
@@ -608,14 +686,14 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
608
686
|
|
|
609
687
|
for (const element of model.elements ?? []) {
|
|
610
688
|
const ao = model.ao ?? block.boundingBox !== 'empty'
|
|
611
|
-
if (block.transparent &&
|
|
689
|
+
if (block.transparent && isSemiTransparentBlockName(block.name)) {
|
|
612
690
|
const pos = cursor.clone()
|
|
613
691
|
delayedRender.push(() => {
|
|
614
|
-
renderElement(world, pos, element, ao, attr, globalMatrix, globalShift, block, biome)
|
|
692
|
+
renderElement(world, pos, element, ao, blendBucket, attr, globalMatrix, globalShift, block, biome)
|
|
615
693
|
})
|
|
616
694
|
} else {
|
|
617
695
|
// 60%
|
|
618
|
-
renderElement(world, cursor, element, ao, attr, globalMatrix, globalShift, block, biome)
|
|
696
|
+
renderElement(world, cursor, element, ao, opaqueBucket, attr, globalMatrix, globalShift, block, biome)
|
|
619
697
|
}
|
|
620
698
|
}
|
|
621
699
|
}
|
|
@@ -630,37 +708,13 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
630
708
|
}
|
|
631
709
|
delayedRender = []
|
|
632
710
|
|
|
633
|
-
|
|
634
|
-
for (let i = 0; i < attr.t_positions!.length / 12; i++) {
|
|
635
|
-
attr.indices[attr.indicesCount++] = ndx
|
|
636
|
-
attr.indices[attr.indicesCount++] = ndx + 1
|
|
637
|
-
attr.indices[attr.indicesCount++] = ndx + 2
|
|
638
|
-
attr.indices[attr.indicesCount++] = ndx + 2
|
|
639
|
-
attr.indices[attr.indicesCount++] = ndx + 1
|
|
640
|
-
attr.indices[attr.indicesCount++] = ndx + 3
|
|
641
|
-
// back face
|
|
642
|
-
attr.indices[attr.indicesCount++] = ndx
|
|
643
|
-
attr.indices[attr.indicesCount++] = ndx + 2
|
|
644
|
-
attr.indices[attr.indicesCount++] = ndx + 1
|
|
645
|
-
attr.indices[attr.indicesCount++] = ndx + 2
|
|
646
|
-
attr.indices[attr.indicesCount++] = ndx + 3
|
|
647
|
-
attr.indices[attr.indicesCount++] = ndx + 1
|
|
648
|
-
ndx += 4
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
attr.positions.push(...attr.t_positions!)
|
|
652
|
-
attr.normals.push(...attr.t_normals!)
|
|
653
|
-
attr.colors.push(...attr.t_colors!)
|
|
654
|
-
attr.uvs.push(...attr.t_uvs!)
|
|
655
|
-
|
|
656
|
-
delete attr.t_positions
|
|
657
|
-
delete attr.t_normals
|
|
658
|
-
delete attr.t_colors
|
|
659
|
-
delete attr.t_uvs
|
|
711
|
+
attr.indicesCount = opaqueBucket.indicesCount
|
|
660
712
|
|
|
661
713
|
attr.positions = new Float32Array(attr.positions) as any
|
|
662
714
|
attr.normals = new Float32Array(attr.normals) as any
|
|
663
715
|
attr.colors = new Float32Array(attr.colors) as any
|
|
716
|
+
attr.skyLights = new Float32Array(attr.skyLights) as any
|
|
717
|
+
attr.blockLights = new Float32Array(attr.blockLights) as any
|
|
664
718
|
attr.uvs = new Float32Array(attr.uvs) as any
|
|
665
719
|
attr.using32Array = arrayNeedsUint32(attr.indices)
|
|
666
720
|
if (attr.using32Array) {
|
|
@@ -669,6 +723,21 @@ export function getSectionGeometry(sx: number, sy: number, sz: number, world: Wo
|
|
|
669
723
|
attr.indices = new Uint16Array(attr.indices)
|
|
670
724
|
}
|
|
671
725
|
|
|
726
|
+
if (blendBucket.positions.length > 0) {
|
|
727
|
+
const blendUsing32 = arrayNeedsUint32(blendBucket.indices)
|
|
728
|
+
attr.blend = {
|
|
729
|
+
positions: new Float32Array(blendBucket.positions),
|
|
730
|
+
normals: new Float32Array(blendBucket.normals),
|
|
731
|
+
colors: new Float32Array(blendBucket.colors),
|
|
732
|
+
skyLights: new Float32Array(blendBucket.skyLights),
|
|
733
|
+
blockLights: new Float32Array(blendBucket.blockLights),
|
|
734
|
+
uvs: new Float32Array(blendBucket.uvs),
|
|
735
|
+
indices: blendUsing32
|
|
736
|
+
? new Uint32Array(blendBucket.indices)
|
|
737
|
+
: new Uint16Array(blendBucket.indices),
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
672
741
|
tsLog(`[TS] Final geometry summary:`)
|
|
673
742
|
tsLog(`[TS] Total vertices: ${attr.positions.length / 3}`)
|
|
674
743
|
tsLog(`[TS] Total triangles: ${attr.indices.length / 3}`)
|
|
@@ -30,6 +30,17 @@ export type CustomBlockModels = {
|
|
|
30
30
|
|
|
31
31
|
export type MesherConfig = typeof defaultMesherConfig
|
|
32
32
|
|
|
33
|
+
/** Vertex/index arrays for one opaque or blend geometry bucket. */
|
|
34
|
+
export type MesherGeometryBucketData = {
|
|
35
|
+
positions: Float32Array
|
|
36
|
+
normals: Float32Array
|
|
37
|
+
colors: Float32Array
|
|
38
|
+
skyLights: Float32Array
|
|
39
|
+
blockLights: Float32Array
|
|
40
|
+
uvs: Float32Array
|
|
41
|
+
indices: Uint32Array | Uint16Array
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
export type MesherGeometryOutput = {
|
|
34
45
|
sectionYNumber: number,
|
|
35
46
|
chunkKey: string,
|
|
@@ -47,11 +58,11 @@ export type MesherGeometryOutput = {
|
|
|
47
58
|
positions: any,
|
|
48
59
|
normals: any,
|
|
49
60
|
colors: any,
|
|
61
|
+
skyLights: any,
|
|
62
|
+
blockLights: any,
|
|
50
63
|
uvs: any,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
t_colors?: number[],
|
|
54
|
-
t_uvs?: number[],
|
|
64
|
+
/** Per-section blend geometry (water, lava, stained glass, ice, etc.). */
|
|
65
|
+
blend?: MesherGeometryBucketData,
|
|
55
66
|
|
|
56
67
|
indices: Uint32Array | Uint16Array | number[],
|
|
57
68
|
indicesCount: number,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { test, expect } from 'vitest'
|
|
3
|
+
import { setup } from '../../mesher-legacy/test/mesherTester'
|
|
4
|
+
|
|
5
|
+
test('renderLiquid blend output satisfies 6/4 quad invariant with both windings', () => {
|
|
6
|
+
const { getGeometry } = setup('1.16.5', [
|
|
7
|
+
[[0, 0, 0], 'water'],
|
|
8
|
+
[[0, -1, 0], 'stone'],
|
|
9
|
+
[[1, 0, 0], 'stone'],
|
|
10
|
+
[[-1, 0, 0], 'stone'],
|
|
11
|
+
[[0, 0, 1], 'stone'],
|
|
12
|
+
[[0, 0, -1], 'stone'],
|
|
13
|
+
], { noDebugTiles: true })
|
|
14
|
+
|
|
15
|
+
const { attr } = getGeometry()
|
|
16
|
+
const blend = attr.blend
|
|
17
|
+
expect(blend).toBeDefined()
|
|
18
|
+
expect(blend!.positions.length).toBeGreaterThan(0)
|
|
19
|
+
|
|
20
|
+
const quadCount = blend!.positions.length / 3 / 4
|
|
21
|
+
expect(blend!.indices.length / 6).toBe(quadCount)
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < blend!.indices.length; i += 12) {
|
|
24
|
+
const b = blend!.indices[i]!
|
|
25
|
+
const d = blend!.indices[i + 6]!
|
|
26
|
+
expect(blend!.indices[i]).toBe(b)
|
|
27
|
+
expect(blend!.indices[i + 1]).toBe(b + 1)
|
|
28
|
+
expect(blend!.indices[i + 2]).toBe(b + 2)
|
|
29
|
+
expect(blend!.indices[i + 3]).toBe(b + 2)
|
|
30
|
+
expect(blend!.indices[i + 4]).toBe(b + 1)
|
|
31
|
+
expect(blend!.indices[i + 5]).toBe(b + 3)
|
|
32
|
+
expect(blend!.indices[i + 6]).toBe(d)
|
|
33
|
+
expect(blend!.indices[i + 7]).toBe(d + 2)
|
|
34
|
+
expect(blend!.indices[i + 8]).toBe(d + 1)
|
|
35
|
+
expect(blend!.indices[i + 9]).toBe(d + 1)
|
|
36
|
+
expect(blend!.indices[i + 10]).toBe(d + 2)
|
|
37
|
+
expect(blend!.indices[i + 11]).toBe(d + 3)
|
|
38
|
+
expect(d).toBe(b + 4)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
@@ -76,6 +76,18 @@ export class World {
|
|
|
76
76
|
this.config.version = version
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
getChannelLightNorm (pos: Vec3): { block: number, sky: number } {
|
|
80
|
+
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
|
|
81
|
+
if (!this.config.enableLighting) return { block: 0, sky: 1 }
|
|
82
|
+
const column = this.getColumnByPos(pos)
|
|
83
|
+
if (!column || !hasChunkSection(column, pos)) return { block: 0, sky: 1 }
|
|
84
|
+
const loc = posInChunk(pos)
|
|
85
|
+
return {
|
|
86
|
+
block: Math.min(15, column.getBlockLight(loc) + 2) / 15,
|
|
87
|
+
sky: Math.min(15, column.getSkyLight(loc) + 2) / 15,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
getLight(pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
|
|
80
92
|
// for easier testing
|
|
81
93
|
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { Vec3 } from 'vec3'
|
|
4
|
+
import { World } from './world'
|
|
5
|
+
|
|
6
|
+
function mockColumn (blockLight: number, skyLight: number, withSection = true) {
|
|
7
|
+
return {
|
|
8
|
+
getBlockLight: () => blockLight,
|
|
9
|
+
getSkyLight: () => skyLight,
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('World.getChannelLightNorm', () => {
|
|
14
|
+
it('returns full-bright when enableLighting is false', () => {
|
|
15
|
+
const world = new World('1.16.5')
|
|
16
|
+
world.config.enableLighting = false
|
|
17
|
+
expect(world.getChannelLightNorm(new Vec3(0, 64, 0))).toEqual({ block: 0, sky: 1 })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('returns full-bright when column is missing', () => {
|
|
21
|
+
const world = new World('1.16.5')
|
|
22
|
+
world.config.enableLighting = true
|
|
23
|
+
expect(world.getChannelLightNorm(new Vec3(0, 64, 0))).toEqual({ block: 0, sky: 1 })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('returns full-bright when chunk section is missing', () => {
|
|
27
|
+
const world = new World('1.16.5')
|
|
28
|
+
world.config.enableLighting = true
|
|
29
|
+
world.columns['0,0'] = mockColumn(0, 0, false) as any
|
|
30
|
+
expect(world.getChannelLightNorm(new Vec3(8, 64, 8))).toEqual({ block: 0, sky: 1 })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('applies +2 brightness floor per channel in 0-15 space', () => {
|
|
34
|
+
const world = new World('1.16.5')
|
|
35
|
+
world.config.enableLighting = true
|
|
36
|
+
const column = mockColumn(0, 0)
|
|
37
|
+
;(column as any).sections = { 4: {} }
|
|
38
|
+
world.columns['0,0'] = column as any
|
|
39
|
+
const result = world.getChannelLightNorm(new Vec3(8, 64, 8))
|
|
40
|
+
expect(result.block).toBeCloseTo(2 / 15, 5)
|
|
41
|
+
expect(result.sky).toBeCloseTo(2 / 15, 5)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('clamps brightened channels at 15', () => {
|
|
45
|
+
const world = new World('1.16.5')
|
|
46
|
+
world.config.enableLighting = true
|
|
47
|
+
const column = mockColumn(14, 14)
|
|
48
|
+
;(column as any).sections = { 4: {} }
|
|
49
|
+
world.columns['0,0'] = column as any
|
|
50
|
+
const result = world.getChannelLightNorm(new Vec3(8, 64, 8))
|
|
51
|
+
expect(result.block).toBe(1)
|
|
52
|
+
expect(result.sky).toBe(1)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -21,7 +21,7 @@ import { createGraphicsBackendOffThread } from '../three/graphicsBackendOffThrea
|
|
|
21
21
|
import { WorldRendererThree } from '../three/worldRendererThree'
|
|
22
22
|
import createGraphicsBackendSingleThread from '../three/graphicsBackendSingleThread'
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
globalThis.THREE = THREE
|
|
25
25
|
|
|
26
26
|
// Scene configuration interface
|
|
27
27
|
export interface PlaygroundSceneConfig {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as THREE from 'three'
|
|
3
3
|
import { Vec3 } from 'vec3'
|
|
4
4
|
import { createCanvas } from '../lib/utils'
|
|
5
|
+
import { tintBannerMaterial } from '../lib/blockEntityLightRegistry'
|
|
5
6
|
import type { WorldRendererThree } from './worldRendererThree'
|
|
6
7
|
|
|
7
8
|
type BannerBlockEntity = {
|
|
@@ -228,8 +229,11 @@ export function createBannerMesh(
|
|
|
228
229
|
position: Vec3,
|
|
229
230
|
rotation: number,
|
|
230
231
|
isWall: boolean,
|
|
231
|
-
texture: THREE.Texture
|
|
232
|
-
|
|
232
|
+
texture: THREE.Texture,
|
|
233
|
+
blockLightNorm = 0,
|
|
234
|
+
skyLightNorm = 1,
|
|
235
|
+
skyLevel = 1,
|
|
236
|
+
): THREE.Group & { bannerTexture?: THREE.Texture, bannerMaterial?: THREE.MeshBasicMaterial } {
|
|
233
237
|
const bannerWidth = 13.6 / 16
|
|
234
238
|
const bannerHeight = 28 / 16
|
|
235
239
|
const clothXOffset = 0
|
|
@@ -250,9 +254,11 @@ export function createBannerMesh(
|
|
|
250
254
|
heightOffset = 0
|
|
251
255
|
}
|
|
252
256
|
|
|
257
|
+
const material = new THREE.MeshBasicMaterial({ map: texture, transparent: true })
|
|
258
|
+
tintBannerMaterial(material, blockLightNorm, skyLightNorm, skyLevel)
|
|
253
259
|
const mesh = new THREE.Mesh(
|
|
254
260
|
new THREE.PlaneGeometry(bannerWidth, bannerHeight),
|
|
255
|
-
|
|
261
|
+
material,
|
|
256
262
|
)
|
|
257
263
|
mesh.renderOrder = 999
|
|
258
264
|
|
|
@@ -272,6 +278,7 @@ export function createBannerMesh(
|
|
|
272
278
|
)
|
|
273
279
|
group.add(mesh)
|
|
274
280
|
group.bannerTexture = texture
|
|
281
|
+
group.bannerMaterial = material
|
|
275
282
|
group.position.set(position.x + 0.5, position.y + heightOffset, position.z + 0.5)
|
|
276
283
|
return group
|
|
277
284
|
}
|