minecraft-renderer 0.1.105 → 0.1.106

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.105",
3
+ "version": "0.1.106",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,61 +20,41 @@ const addPositions = [
20
20
  // [[0, 0, -1], 'stone'],
21
21
  ] as const
22
22
 
23
- test('Known blocks are not rendered', () => {
24
- const { mesherWorld, getGeometry, pos, mcData } = setup(lastVersion, addPositions as any)
23
+ /** Meshes the default state of every block of the version and returns the blocks that gave no geometry / errored */
24
+ function meshAllBlocks(version: string) {
25
+ const { mesherWorld, getGeometry, pos, mcData } = setup(version, addPositions as any)
25
26
  const ignoreAsExpected = new Set([...INVISIBLE_BLOCKS, 'water', 'lava'])
26
27
 
27
- let time = 0
28
- let times = 0
29
- const missingBlocks = {} /* as {[number, number]} */
30
- const erroredBlocks = {} /* as {[number, number]} */
28
+ const missingBlocks: string[] = []
29
+ const erroredBlocks: string[] = []
31
30
  for (const block of mcData.blocksArray) {
32
31
  if (ignoreAsExpected.has(block.name)) continue
33
- // if (block.maxStateId! - block.minStateId! > 100) continue
34
- // for (let i = block.minStateId!; i <= block.maxStateId!; i++) {
35
- for (let i = block.defaultState; i <= block.defaultState; i++) {
36
- // if (block.transparent) continue
37
- mesherWorld.setBlockStateId(pos, i)
38
- const start = performance.now()
39
- const { centerFaces, totalTiles, centerTileNeighbors, attr } = getGeometry()
40
- time += performance.now() - start
41
- times++
42
- if (centerFaces === 0) {
43
- const objAdd = attr.hadErrors ? erroredBlocks : missingBlocks
44
- if (objAdd[block.name]) continue
45
- objAdd[block.name] = true
46
- // invalidBlocks[block.name] = [i - block.defaultState!, centerTileNeighbors]
47
- // console.log('INVALID', block.name, centerTileNeighbors, i - block.minStateId)
48
- }
49
- }
32
+ mesherWorld.setBlockStateId(pos, block.defaultState)
33
+ const { centerFaces, attr } = getGeometry()
34
+ if (centerFaces === 0) (attr.hadErrors ? erroredBlocks : missingBlocks).push(block.name)
50
35
  }
51
- console.log('Checking blocks of version', lastVersion)
52
- console.log('Average time', time / times)
53
- // should be fixed, but to avoid regressions & for visibility
54
- // TODO resolve creaking_heart issue (1.21.3)
55
- // TODO 26.x: copper chests / copper golem statues (block entity models) and golden_dandelion have no block model yet
56
- expect(missingBlocks).toMatchInlineSnapshot(`
57
- {
58
- "copper_chest": true,
59
- "copper_golem_statue": true,
60
- "exposed_copper_chest": true,
61
- "exposed_copper_golem_statue": true,
62
- "golden_dandelion": true,
63
- "oxidized_copper_chest": true,
64
- "oxidized_copper_golem_statue": true,
65
- "potted_golden_dandelion": true,
66
- "structure_void": true,
67
- "waxed_copper_chest": true,
68
- "waxed_copper_golem_statue": true,
69
- "waxed_exposed_copper_chest": true,
70
- "waxed_exposed_copper_golem_statue": true,
71
- "waxed_oxidized_copper_chest": true,
72
- "waxed_oxidized_copper_golem_statue": true,
73
- "waxed_weathered_copper_chest": true,
74
- "waxed_weathered_copper_golem_statue": true,
75
- "weathered_copper_chest": true,
76
- "weathered_copper_golem_statue": true,
77
- }
78
- `)
79
- expect(erroredBlocks).toMatchInlineSnapshot('{}')
36
+ return { missingBlocks, erroredBlocks }
37
+ }
38
+
39
+ // These tests only guard against making the renderer worse. Blocks that a new minecraft-data / mc-assets version adds
40
+ // without a model must not fail them (versions are updated automatically), so the newest version is only checked for errors.
41
+
42
+ // TODO resolve creaking_heart issue (1.21.3)
43
+ const KNOWN_MISSING_BLOCKS = ['structure_void']
44
+ // pinned: the data of a released version doesn't grow new blocks, so what renders here must keep rendering
45
+ const pinnedVersion = '1.21.8'
46
+
47
+ test(`Blocks of ${pinnedVersion} that rendered keep rendering`, () => {
48
+ const { missingBlocks, erroredBlocks } = meshAllBlocks(pinnedVersion)
49
+ // fixing a block (it renders now) is fine, only blocks that stopped rendering fail
50
+ expect(missingBlocks.filter(name => !KNOWN_MISSING_BLOCKS.includes(name))).toEqual([])
51
+ expect(erroredBlocks).toEqual([])
52
+ })
53
+
54
+ test('Newest version: no block fails to mesh', () => {
55
+ const { missingBlocks, erroredBlocks } = meshAllBlocks(lastVersion)
56
+ console.log('Checked blocks of version', lastVersion)
57
+ // visibility only, e.g. new block entities that need a custom model in mc-assets
58
+ if (missingBlocks.length > 0) console.warn(`Blocks of ${lastVersion} that render nothing:`, missingBlocks.join(', '))
59
+ expect(erroredBlocks).toEqual([])
80
60
  })
@@ -0,0 +1,50 @@
1
+ //@ts-nocheck
2
+ import { describe, expect, it } from 'vitest'
3
+ import { buildElementRotation, buildRotationMatrix, buildXyzRotationMatrix, matmul3 } from './modelsGeometryCommon'
4
+
5
+ const origin: [number, number, number] = [8, 8, 8]
6
+ const close = (a: number[], b: number[]) => a.forEach((v, i) => expect(v).toBeCloseTo(b[i], 9))
7
+
8
+ describe('buildElementRotation', () => {
9
+ it('keeps the axis/angle form working', () => {
10
+ const { localMatrix } = buildElementRotation({ origin, axis: 'y', angle: 90 })
11
+ close(matmul3(localMatrix, [1, 0, 0]), [0, 0, -1])
12
+ close(matmul3(localMatrix, [0, 0, 1]), [1, 0, 0])
13
+ })
14
+
15
+ it.each(['x', 'y', 'z'] as const)('x/y/z form about a single axis (%s) equals the axis/angle form', axis => {
16
+ const viaAxis = buildElementRotation({ origin: [2, 5, 11], axis, angle: 37 })
17
+ const viaXyz = buildElementRotation({ origin: [2, 5, 11], [axis]: 37 })
18
+ viaAxis.localMatrix.forEach((row, i) => close(viaXyz.localMatrix[i], row))
19
+ close(viaXyz.localShift, viaAxis.localShift)
20
+ })
21
+
22
+ it('applies a combined x/y/z rotation as rotationZYX: z first, then y, then x', () => {
23
+ const rotation = { x: -40.06672, y: 3.06532, z: -2.57258 }
24
+ const vector = [0.3, -1.2, 0.8]
25
+ const stepwise = matmul3(
26
+ buildRotationMatrix('x', rotation.x),
27
+ matmul3(buildRotationMatrix('y', rotation.y), matmul3(buildRotationMatrix('z', rotation.z), vector))
28
+ )
29
+ close(matmul3(buildXyzRotationMatrix(rotation), vector), stepwise)
30
+ // order matters (guards against x, y, z being swapped)
31
+ const reversed = matmul3(
32
+ buildRotationMatrix('z', rotation.z),
33
+ matmul3(buildRotationMatrix('y', rotation.y), matmul3(buildRotationMatrix('x', rotation.x), vector))
34
+ )
35
+ expect(Math.abs(reversed[0] - stepwise[0]) + Math.abs(reversed[1] - stepwise[1])).toBeGreaterThan(1e-3)
36
+ })
37
+
38
+ it('rotates around the element origin (the origin stays in place)', () => {
39
+ const rotationOrigin: [number, number, number] = [2.53162, 8.56801, 5.10154]
40
+ const { localMatrix, localShift } = buildElementRotation({ origin: rotationOrigin, x: -40, y: 3, z: -2.5 })
41
+ const moved = matmul3(localMatrix, rotationOrigin).map((v, i) => v + localShift[i])
42
+ close(moved, rotationOrigin)
43
+ })
44
+
45
+ it('treats a rotation without any angle as no rotation', () => {
46
+ const { localMatrix, localShift } = buildElementRotation({ origin })
47
+ localMatrix.forEach((row, i) => close(row, [0, 1, 2].map(j => (i === j ? 1 : 0))))
48
+ close(localShift, [0, 0, 0])
49
+ })
50
+ })
@@ -88,11 +88,27 @@ export function matmulmat3(a, b) {
88
88
  return te
89
89
  }
90
90
 
91
+ /** Plain row-major product a * b (not the transposed convention of matmulmat3) */
92
+ function mulMat3(a: number[][], b: number[][]) {
93
+ return a.map((row, i) => row.map((_, j) => row[0] * b[0][j] + row[1] * b[1][j] + row[2] * b[2][j]))
94
+ }
95
+
96
+ /**
97
+ * 26.x element rotation: degrees about each axis, `{ origin, x, y, z }` instead of `{ origin, axis, angle }`.
98
+ * Applied like vanilla's `rotationZYX(z, y, x)`: a vector is rotated about z first, then y, then x.
99
+ */
100
+ export function buildXyzRotationMatrix({ x = 0, y = 0, z = 0 }: { x?: number; y?: number; z?: number }) {
101
+ return mulMat3(buildRotationMatrix('x', x), mulMat3(buildRotationMatrix('y', y), buildRotationMatrix('z', z)))
102
+ }
103
+
91
104
  export function buildElementRotation(rotation) {
92
- let localMatrix = buildRotationMatrix(rotation.axis, rotation.angle)
105
+ // elements without an axis use the 26.x x/y/z form (a missing x/y/z is 0, so no rotation at all is the identity)
106
+ const isAxisAngle = rotation.axis !== undefined
107
+ let localMatrix = isAxisAngle ? buildRotationMatrix(rotation.axis, rotation.angle) : buildXyzRotationMatrix(rotation)
93
108
  let localShift = vecsub3(rotation.origin, matmul3(localMatrix, rotation.origin))
94
109
 
95
- if (rotation.rescale) {
110
+ // rescale is only defined for a single axis rotation
111
+ if (rotation.rescale && isAxisAngle) {
96
112
  const FIT_TO_BLOCK_SCALE_MULTIPLIER = 2 - Math.sqrt(2)
97
113
  const angleRad = (rotation.angle * Math.PI) / 180
98
114
  const scale = Math.abs(Math.sin(angleRad)) * FIT_TO_BLOCK_SCALE_MULTIPLIER
@@ -19,6 +19,7 @@ import { GlobalBlockBuffer } from '../../three/globalBlockBuffer'
19
19
  import { buildVisibleCubeSpans } from '../../three/cubeDrawSpans'
20
20
  import { createCubeBlockMaterial, computeSectionOriginRel, setCubeShadingTheme } from '../../three/shaders/cubeBlockShader'
21
21
  import * as THREE from 'three'
22
+ import blocksAtlasesJson from 'mc-assets/dist/blocksAtlases.json'
22
23
  import { renderWasmOutputToGeometry } from '../bridge/render-from-wasm'
23
24
 
24
25
  const VERSION = '1.16.5'
@@ -29,8 +30,8 @@ function requireShaderCubeResources() {
29
30
  if (!resources) throw new Error('shader cube resources unavailable in test')
30
31
  return resources
31
32
  }
32
- /** mc-assets blocksAtlases.json → stone */
33
- const STONE_ATLAS_TILE_INDEX = 552
33
+ /** mc-assets blocksAtlases.json → stone (looked up, tile positions change whenever the atlas gets new textures) */
34
+ const STONE_ATLAS_TILE_INDEX = blocksAtlasesJson.latest.textures.stone.tileIndex
34
35
 
35
36
  beforeEach(() => {
36
37
  resetShaderCubeResources()