minecraft-renderer 0.1.67 → 0.1.69

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-renderer",
3
- "version": "0.1.67",
3
+ "version": "0.1.69",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,7 +14,7 @@ export interface ExportedSection {
14
14
  shaderCubes?: {
15
15
  words: Uint32Array
16
16
  count: number
17
- formatVersion: 2
17
+ formatVersion: 3
18
18
  }
19
19
  }
20
20
 
@@ -69,7 +69,7 @@ export type MesherGeometryOutput = {
69
69
  shaderCubes?: {
70
70
  words: Uint32Array
71
71
  count: number
72
- formatVersion: 2
72
+ formatVersion: 3
73
73
  }
74
74
  }
75
75
 
@@ -149,12 +149,18 @@ const PAL: Record<V2SceneId, ScenePalette> = {
149
149
  ambient: 0x06_00_10, dir: 0x99_33_ff, pt1: 0xaa_44_ff, pt2: 0x44_00_aa, name: 'THE END'
150
150
  },
151
151
  cyber: {
152
- bg: 0x00_0a_06, fog: 0x00_0a_06, fogD: 0.010,
153
- blocks: [0x00_ff_ff, 0x00_ff_88, 0xaa_ff_00, 0x00_cc_ff, 0x66_ff_00, 0x00_ff_ee],
154
- emit: [0x00_22_11, 0x00_1a_00, 0x00_1a_22],
155
- nebula: [0x00_1a_12, 0x00_14_00, 0x00_12_1a],
156
- galFn: b => new THREE.Color(0, b, b * 0.6),
157
- ambient: 0x00_1a_0d, dir: 0x00_ff_aa, pt1: 0x00_ff_cc, pt2: 0x44_ff_00, name: 'CYBER'
152
+ bg: 0x0c_18_22, fog: 0x0c_18_22, fogD: 0.008,
153
+ blocks: [0x00_ff_ff, 0x44_ff_cc, 0xaa_ff_44, 0x66_dd_ff, 0x88_ff_66, 0x00_ff_aa],
154
+ emit: [0x0a_2a_33, 0x0a_33_22, 0x0a_28_38],
155
+ nebula: [0x12_30_40, 0x18_38_28, 0x14_32_44],
156
+ galFn: b => new THREE.Color(b * 0.15, b * 0.85, b * 0.95),
157
+ ambient: 0x1a_30_40, dir: 0x66_ff_cc, pt1: 0x44_ff_ee, pt2: 0x99_ff_44, name: 'CYBER',
158
+ starColor: 0xcc_f0_ff,
159
+ starOpacity: 0.75,
160
+ galaxyOpacity: 0.62,
161
+ nebulaOpacity: 0.36,
162
+ edgeLineColor: 0x44_ff_dd,
163
+ blockOpacity: [0.38, 0.54]
158
164
  },
159
165
  light: {
160
166
  bg: 0x88_98_b0,
@@ -148,8 +148,9 @@ void main() {
148
148
  }
149
149
 
150
150
  // --- Position: section base (multiples of 16) + face quad + block-local 0..15 ---
151
- int sX = int(a_w3 & 0xFFFFu) - 32768;
152
- int sZ = int((a_w3 >> 16u) & 0xFFFFu) - 32768;
151
+ // Must mirror WORD2/WORD3 constants in TS (GLSL cannot import them).
152
+ int sX = int((a_w3 & 0xFFFFu) | (((a_w2 >> 19u) & 0x3Fu) << 16u)) - 2097152;
153
+ int sZ = int(((a_w3 >> 16u) & 0xFFFFu) | (((a_w2 >> 25u) & 0x3Fu) << 16u)) - 2097152;
153
154
  int sY = int((a_w2 >> 13u) & 0x1Fu) - 4;
154
155
  vec3 sectionBase = vec3(float(sX * 16), float(sY * 16), float(sZ * 16));
155
156
  vec3 facePos = BASE[faceId] + u * DU[faceId] + v * DV[faceId];
@@ -343,12 +344,17 @@ export const WORD2 = {
343
344
  SECTION_Y_SHIFT: 13,
344
345
  SECTION_Y_BITS: 5,
345
346
  EMPTY_SHIFT: 18,
346
- SPARE_BITS: 13,
347
+ SECTION_X_HI_SHIFT: 19,
348
+ SECTION_Z_HI_SHIFT: 25,
349
+ SECTION_HI_BITS: 6,
350
+ SPARE_BITS: 1,
347
351
  } as const
348
352
 
349
- /** Section base X/Z packed into a_w3 (16-block units, biased). */
353
+ /** Section base X/Z: low 16 bits in a_w3, high 6 in a_w2 (22-bit biased section index). */
350
354
  export const WORD3 = {
351
- SECTION_X_BITS: 16,
352
- SECTION_Z_BITS: 16,
353
- SECTION_BIAS: 32768,
355
+ SECTION_BITS: 22,
356
+ SECTION_MASK: (1 << 22) - 1,
357
+ LO_BITS: 16,
358
+ HI_BITS: 6,
359
+ SECTION_BIAS: 2097152,
354
360
  } as const
@@ -524,8 +524,6 @@ export function renderWasmOutputToGeometry(
524
524
  const [bx, by, bz] = block.position
525
525
  const blockStateId = block.block_state_id
526
526
 
527
- log(`[WASM] Processing block at (${bx}, ${by}, ${bz}), stateId=${blockStateId}, visible_faces=0b${block.visible_faces.toString(2).padStart(6, '0')}`)
528
-
529
527
  const prismBlock = PrismarineBlock.fromStateId(blockStateId, 1)
530
528
 
531
529
  let biome: string | undefined
@@ -10,7 +10,7 @@ import { WORD0, WORD1, WORD2, WORD3 } from '../../three/shaders/cubeBlockShader'
10
10
  import { TextureIndexMapping, type TextureEntry } from '../../three/shaders/textureIndexMapping'
11
11
  import { TintPalette } from '../../three/shaders/tintPalette'
12
12
 
13
- export const SHADER_CUBES_FORMAT_VERSION = 2 as const
13
+ export const SHADER_CUBES_FORMAT_VERSION = 3 as const
14
14
  export const SHADER_CUBES_WORDS_PER_FACE = 4 as const
15
15
 
16
16
  export type ShaderCubesOutput = {
@@ -238,26 +238,40 @@ function packWord1(lightCombined: number[]): number {
238
238
  return w >>> 0
239
239
  }
240
240
 
241
- export function packWord2(texIndex: number, aoDiagonalFlip: boolean, sectionBaseY: number): number {
241
+ function biasedSectionIndex(sectionBaseCoord: number): number {
242
+ return (Math.floor(sectionBaseCoord / 16) + WORD3.SECTION_BIAS) & WORD3.SECTION_MASK
243
+ }
244
+
245
+ export function packWord2(
246
+ texIndex: number,
247
+ aoDiagonalFlip: boolean,
248
+ sectionBaseX: number,
249
+ sectionBaseY: number,
250
+ sectionBaseZ: number,
251
+ ): number {
242
252
  let w = texIndex & ((1 << WORD2.TEX_INDEX_BITS) - 1)
243
253
  if (aoDiagonalFlip) {
244
254
  w |= 1 << WORD2.DIAGONAL_FLAG_SHIFT
245
255
  }
246
256
  const sectionY = ((Math.floor(sectionBaseY / 16) + 4) & 0x1f) << WORD2.SECTION_Y_SHIFT
247
257
  w |= sectionY
258
+ const sx = biasedSectionIndex(sectionBaseX)
259
+ const sz = biasedSectionIndex(sectionBaseZ)
260
+ w |= ((sx >>> 16) & 0x3f) << WORD2.SECTION_X_HI_SHIFT
261
+ w |= ((sz >>> 16) & 0x3f) << WORD2.SECTION_Z_HI_SHIFT
248
262
  return w >>> 0
249
263
  }
250
264
 
251
265
  export function packWord3(sectionBaseX: number, sectionBaseZ: number): number {
252
- const sx = (Math.floor(sectionBaseX / 16) + WORD3.SECTION_BIAS) & 0xffff
253
- const sz = (Math.floor(sectionBaseZ / 16) + WORD3.SECTION_BIAS) & 0xffff
254
- return (sx | (sz << 16)) >>> 0
266
+ const sx = biasedSectionIndex(sectionBaseX)
267
+ const sz = biasedSectionIndex(sectionBaseZ)
268
+ return ((sx & 0xffff) | ((sz & 0xffff) << 16)) >>> 0
255
269
  }
256
270
 
257
271
  /** Decode section base block coords from packed words (round-trip helper for tests). */
258
272
  export function decodeSectionBaseFromWords(word2: number, word3: number): { x: number, y: number, z: number } {
259
- const sX = (word3 & 0xffff) - WORD3.SECTION_BIAS
260
- const sZ = ((word3 >>> 16) & 0xffff) - WORD3.SECTION_BIAS
273
+ const sX = ((word3 & 0xffff) | (((word2 >>> WORD2.SECTION_X_HI_SHIFT) & 0x3f) << 16)) - WORD3.SECTION_BIAS
274
+ const sZ = (((word3 >>> 16) & 0xffff) | (((word2 >>> WORD2.SECTION_Z_HI_SHIFT) & 0x3f) << 16)) - WORD3.SECTION_BIAS
261
275
  const sY = ((word2 >>> WORD2.SECTION_Y_SHIFT) & ((1 << WORD2.SECTION_Y_BITS) - 1)) - 4
262
276
  return { x: sX * 16, y: sY * 16, z: sZ * 16 }
263
277
  }
@@ -323,7 +337,14 @@ export function tryBuildShaderCubeInstances(
323
337
  opts: BuildShaderCubeInstancesOpts,
324
338
  words: number[],
325
339
  ): boolean {
326
- const { sectionOrigin, sectionHeight, biome, tintPalette, textureIndexMapping, doAO = true } = opts
340
+ const {
341
+ sectionOrigin,
342
+ sectionHeight,
343
+ biome,
344
+ tintPalette,
345
+ textureIndexMapping,
346
+ doAO = true,
347
+ } = opts
327
348
 
328
349
  if (!isShaderCubeBlock(cached, model, sectionHeight, textureIndexMapping)) {
329
350
  return false
@@ -381,9 +402,10 @@ export function tryBuildShaderCubeInstances(
381
402
  words.push(
382
403
  packWord0(lx, ly, lz, faceIdx, tintIndex, ao),
383
404
  packWord1(lightCombined),
384
- packWord2(texIndex, aoDiagonalFlip, sectionOrigin.y),
405
+ packWord2(texIndex, aoDiagonalFlip, sectionOrigin.x, sectionOrigin.y, sectionOrigin.z),
385
406
  packWord3(sectionOrigin.x, sectionOrigin.z),
386
407
  )
408
+
387
409
  }
388
410
 
389
411
  return true
@@ -1,6 +1,6 @@
1
1
  //@ts-nocheck
2
2
  import { test, expect, beforeEach } from 'vitest'
3
- import { WORD0, WORD2, WORD3 } from '../../three/shaders/cubeBlockShader'
3
+ import { WORD0, WORD2 } from '../../three/shaders/cubeBlockShader'
4
4
  import {
5
5
  resetShaderCubeResources,
6
6
  getShaderCubeResources,
@@ -10,6 +10,7 @@ import {
10
10
  countVisibleFaces,
11
11
  unpackTexIndexFromWord2,
12
12
  decodeSectionBaseFromWords,
13
+ packWord2Empty,
13
14
  packWord3,
14
15
  SHADER_CUBES_FORMAT_VERSION,
15
16
  SHADER_CUBES_WORDS_PER_FACE,
@@ -305,33 +306,47 @@ test('doAO false: full bright AO/light and no diagonal flip', () => {
305
306
  expect(words[2]! & (1 << WORD2.DIAGONAL_FLAG_SHIFT)).toBe(0)
306
307
  })
307
308
 
308
- test('section base coords round-trip in word2/word3', () => {
309
- const words: number[] = []
310
- const block = {
311
- position: [10, 17, 4] as [number, number, number],
312
- visible_faces: 1 << 2,
313
- ao_data: [[3, 3, 3, 3]],
314
- light_data: [[1, 1, 1, 1]],
315
- light_combined: [[255, 255, 255, 255]],
316
- }
317
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
318
- const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
319
- const sectionOrigin = { x: 0, y: 16, z: 32 }
320
- tryBuildShaderCubeInstances(
321
- block,
322
- { blockName: 'stone', blockProps: {}, isCube: true, model },
323
- model,
324
- { sectionOrigin, sectionHeight: 16, tintPalette, textureIndexMapping },
325
- words,
326
- )
327
- const base = decodeSectionBaseFromWords(words[2]!, words[3]!)
328
- expect(base).toEqual(sectionOrigin)
329
- const sX = (words[3]! & 0xffff) - WORD3.SECTION_BIAS
330
- const sZ = ((words[3]! >>> 16) & 0xffff) - WORD3.SECTION_BIAS
331
- const sY = ((words[2]! >>> WORD2.SECTION_Y_SHIFT) & 0x1f) - 4
332
- expect(sX * 16).toBe(sectionOrigin.x)
333
- expect(sY * 16).toBe(sectionOrigin.y)
334
- expect(sZ * 16).toBe(sectionOrigin.z)
309
+ const SECTION_ORIGIN_ROUND_TRIP_CASES: Array<{ x: number, y: number, z: number }> = [
310
+ { x: 0, y: 16, z: 32 },
311
+ { x: 0, y: 0, z: 0 },
312
+ { x: 524288, y: 0, z: 524288 },
313
+ { x: 1000000, y: 64, z: 1000000 },
314
+ { x: 33000000, y: 0, z: 33000000 },
315
+ { x: -524288, y: 0, z: -524288 },
316
+ { x: -1000000, y: 0, z: -1000000 },
317
+ { x: 1000000, y: 0, z: -1000000 },
318
+ ]
319
+
320
+ test.each(SECTION_ORIGIN_ROUND_TRIP_CASES)(
321
+ 'section base coords round-trip in word2/word3 at origin (%#)',
322
+ (sectionOrigin) => {
323
+ const words: number[] = []
324
+ const block = {
325
+ position: [10, 17, 4] as [number, number, number],
326
+ visible_faces: 1 << 2,
327
+ ao_data: [[3, 3, 3, 3]],
328
+ light_data: [[1, 1, 1, 1]],
329
+ light_combined: [[255, 255, 255, 255]],
330
+ }
331
+ const { textureIndexMapping, tintPalette } = getShaderCubeResources()
332
+ const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
333
+ tryBuildShaderCubeInstances(
334
+ block,
335
+ { blockName: 'stone', blockProps: {}, isCube: true, model },
336
+ model,
337
+ { sectionOrigin, sectionHeight: 16, tintPalette, textureIndexMapping },
338
+ words,
339
+ )
340
+ const base = decodeSectionBaseFromWords(words[2]!, words[3]!)
341
+ expect(base).toEqual(sectionOrigin)
342
+ },
343
+ )
344
+
345
+ test('packWord2Empty: bit 18 set regardless of high X/Z bits in word2', () => {
346
+ const empty = packWord2Empty()
347
+ expect(empty & (1 << WORD2.EMPTY_SHIFT)).not.toBe(0)
348
+ const withHighBits = empty | (0x3f << WORD2.SECTION_X_HI_SHIFT) | (0x3f << WORD2.SECTION_Z_HI_SHIFT)
349
+ expect(withHighBits & (1 << WORD2.EMPTY_SHIFT)).not.toBe(0)
335
350
  })
336
351
 
337
352
  test('GlobalBlockBuffer: free-list reuses slot with EMPTY sentinel', () => {
@@ -1357,7 +1357,7 @@ function processColumnTick() {
1357
1357
  geometry.shaderCubes = {
1358
1358
  words: new Uint32Array(exported.shaderCubes.words),
1359
1359
  count: exported.shaderCubes.count,
1360
- formatVersion: 2,
1360
+ formatVersion: 3,
1361
1361
  }
1362
1362
  }
1363
1363
  transferable = [