minecraft-renderer 0.1.79 → 0.1.81

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.
Files changed (205) hide show
  1. package/README.md +66 -26
  2. package/dist/mesher.js +36 -36
  3. package/dist/mesher.js.map +4 -4
  4. package/dist/mesherWasm.js +217 -217
  5. package/dist/minecraft-renderer.js +66 -66
  6. package/dist/minecraft-renderer.js.meta.json +1 -1
  7. package/dist/threeWorker.js +450 -450
  8. package/package.json +5 -2
  9. package/src/bundler/bundlePrepare.ts +3 -8
  10. package/src/graphicsBackend/appViewer.ts +10 -7
  11. package/src/graphicsBackend/config.ts +1 -5
  12. package/src/graphicsBackend/playerState.ts +35 -34
  13. package/src/graphicsBackend/preloadWorkers.ts +43 -41
  14. package/src/graphicsBackend/rendererDefaultOptions.ts +68 -70
  15. package/src/graphicsBackend/rendererOptionsSync.ts +24 -50
  16. package/src/graphicsBackend/types.ts +2 -2
  17. package/src/index.ts +6 -27
  18. package/src/lib/DebugGui.ts +34 -31
  19. package/src/lib/animationController.ts +9 -9
  20. package/src/lib/bakeLegacyLight.ts +1 -6
  21. package/src/lib/bindAbortableListener.test.ts +32 -12
  22. package/src/lib/bindAbortableListener.ts +14 -6
  23. package/src/lib/blockEntityLightRegistry.ts +11 -20
  24. package/src/lib/blockEntityLighting.test.ts +1 -6
  25. package/src/lib/blockEntityLighting.ts +5 -5
  26. package/src/lib/buildSharedConfig.mjs +4 -4
  27. package/src/lib/buildWorkerMcDataIndexes.test.ts +4 -4
  28. package/src/lib/buildWorkerMcDataIndexes.ts +11 -19
  29. package/src/lib/cameraBobbing.ts +1 -1
  30. package/src/lib/canvas2DOverlay.example.ts +13 -33
  31. package/src/lib/canvas2DOverlay.quickstart.ts +24 -54
  32. package/src/lib/canvas2DOverlay.ts +28 -47
  33. package/src/lib/cleanupDecorator.ts +2 -2
  34. package/src/lib/createPlayerObject.test.ts +2 -7
  35. package/src/lib/createPlayerObject.ts +18 -25
  36. package/src/lib/frameTimingCollector.ts +2 -5
  37. package/src/lib/guiRenderer.ts +57 -42
  38. package/src/lib/items.ts +15 -9
  39. package/src/lib/mesherlogReader.ts +5 -6
  40. package/src/lib/simpleUtils.ts +5 -5
  41. package/src/lib/skyLight.ts +3 -3
  42. package/src/lib/smoothSwitcher.ts +11 -18
  43. package/src/lib/ui/newStats.ts +10 -12
  44. package/src/lib/utils/proxy.ts +7 -1
  45. package/src/lib/utils/skins.ts +4 -4
  46. package/src/lib/utils.ts +1 -1
  47. package/src/lib/workerMessageSanitize.ts +5 -12
  48. package/src/lib/workerProxy.ts +52 -42
  49. package/src/lib/workerSyncOps.test.ts +58 -41
  50. package/src/lib/worldrendererCommon.reconfigure.test.ts +24 -21
  51. package/src/lib/worldrendererCommon.removeColumn.test.ts +11 -13
  52. package/src/lib/worldrendererCommon.ts +349 -211
  53. package/src/mesher-legacy/mesher.ts +9 -4
  54. package/src/mesher-legacy/test/a.ts +1 -1
  55. package/src/mesher-legacy/test/mesherTester.ts +2 -2
  56. package/src/mesher-legacy/test/playground.ts +1 -1
  57. package/src/mesher-legacy/test/run/test-js.ts +1 -1
  58. package/src/mesher-legacy/test/snapshotUtils.ts +3 -8
  59. package/src/mesher-legacy/test/test-perf.ts +1 -1
  60. package/src/mesher-legacy/test/tests.test.ts +2 -2
  61. package/src/mesher-shared/blockEntityMetadata.test.ts +7 -5
  62. package/src/mesher-shared/blockEntityMetadata.ts +28 -19
  63. package/src/mesher-shared/blockPropertiesForMeshing.ts +2 -2
  64. package/src/mesher-shared/computeHeightmap.ts +2 -4
  65. package/src/mesher-shared/exportedGeometryTypes.ts +3 -3
  66. package/src/mesher-shared/faceOcclusion.ts +324 -0
  67. package/src/mesher-shared/models.ts +80 -131
  68. package/src/mesher-shared/modelsGeometryCommon.ts +30 -15
  69. package/src/mesher-shared/shared.ts +46 -46
  70. package/src/mesher-shared/standaloneRenderer.ts +40 -44
  71. package/src/mesher-shared/tests/faceOcclusion.test.ts +179 -0
  72. package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +12 -8
  73. package/src/mesher-shared/vertexShading.ts +2 -11
  74. package/src/mesher-shared/world.ts +19 -19
  75. package/src/mesher-shared/worldLighting.test.ts +2 -2
  76. package/src/performanceMonitor/PerformanceMonitor.ts +4 -13
  77. package/src/performanceMonitor/formatPerformanceFactorsDebug.ts +2 -2
  78. package/src/performanceMonitor/index.ts +1 -5
  79. package/src/performanceMonitor/types.ts +1 -1
  80. package/src/playerState/playerState.ts +55 -50
  81. package/src/playerState/types.ts +2 -8
  82. package/src/playground/allEntitiesDebug.ts +19 -16
  83. package/src/playground/baseScene.ts +29 -32
  84. package/src/playground/mobileControls.tsx +133 -129
  85. package/src/playground/playground.html +76 -73
  86. package/src/playground/playground.ts +14 -1
  87. package/src/playground/playgroundUi.tsx +100 -74
  88. package/src/playground/reactUtils.ts +3 -7
  89. package/src/playground/scenes/entities.ts +10 -7
  90. package/src/playground/scenes/frequentUpdates.ts +5 -5
  91. package/src/playground/scenes/geometryExport.ts +6 -20
  92. package/src/playground/scenes/highPosEntityUpdates.ts +2 -10
  93. package/src/playground/scenes/index.ts +1 -1
  94. package/src/playground/scenes/lightingStarfield.ts +18 -12
  95. package/src/playground/scenes/main.ts +15 -11
  96. package/src/playground/scenes/partialBlockCulling.ts +31 -0
  97. package/src/playground/scenes/railsCobweb.ts +1 -1
  98. package/src/playground/scenes/rotationIssue.ts +1 -1
  99. package/src/playground/scenes/slabsOptimization.ts +1 -2
  100. package/src/playground/scenes/transparencyIssue.ts +1 -1
  101. package/src/playground/shared.ts +2 -2
  102. package/src/resourcesManager/resourcesManager.ts +29 -54
  103. package/src/resourcesManager/resourcesManager.worker.test.ts +6 -10
  104. package/src/shims/minecraftData.ts +70 -24
  105. package/src/sign-renderer/index.html +10 -12
  106. package/src/sign-renderer/index.ts +29 -29
  107. package/src/sign-renderer/playground.ts +22 -15
  108. package/src/sign-renderer/tests.test.ts +16 -13
  109. package/src/sign-renderer/vite.config.ts +2 -2
  110. package/src/three/appShared.ts +46 -25
  111. package/src/three/bannerRenderer.ts +33 -61
  112. package/src/three/cameraCollisionBlockCache.ts +156 -0
  113. package/src/three/cameraCollisionSolidity.ts +50 -0
  114. package/src/three/cameraShake.ts +15 -6
  115. package/src/three/chunkMeshManager.ts +223 -390
  116. package/src/three/cinimaticScript.ts +26 -15
  117. package/src/three/cubeDrawSpans.ts +27 -37
  118. package/src/three/cubeMultiDraw.ts +9 -22
  119. package/src/three/documentRenderer.ts +15 -18
  120. package/src/three/entities.ts +175 -150
  121. package/src/three/entity/EntityMesh.ts +114 -108
  122. package/src/three/entity/animations.js +3 -3
  123. package/src/three/entity/armorModels.ts +15 -15
  124. package/src/three/entity/gltfAnimationUtils.ts +2 -5
  125. package/src/three/fireworks.ts +34 -32
  126. package/src/three/fireworksRenderer.ts +32 -29
  127. package/src/three/globalBlockBuffer.ts +167 -100
  128. package/src/three/globalLegacyBuffer.ts +248 -264
  129. package/src/three/graphicsBackendBase.ts +19 -25
  130. package/src/three/graphicsBackendOffThread.ts +12 -15
  131. package/src/three/hand.ts +21 -21
  132. package/src/three/handLegacy.ts +3 -13
  133. package/src/three/holdingBlock.ts +116 -79
  134. package/src/three/holdingBlockFactory.ts +1 -4
  135. package/src/three/holdingBlockItemIdentity.test.ts +25 -17
  136. package/src/three/holdingBlockItemIdentity.ts +9 -6
  137. package/src/three/holdingBlockLegacy.ts +113 -132
  138. package/src/three/itemMesh.ts +20 -29
  139. package/src/three/legacyMultiDraw.ts +10 -16
  140. package/src/three/legacySectionCull.ts +8 -14
  141. package/src/three/menuBackground/activeView.ts +1 -4
  142. package/src/three/menuBackground/assetUrl.ts +1 -1
  143. package/src/three/menuBackground/classic.ts +4 -12
  144. package/src/three/menuBackground/gpuPreference.ts +1 -3
  145. package/src/three/menuBackground/index.ts +2 -11
  146. package/src/three/menuBackground/renderer.ts +1 -5
  147. package/src/three/menuBackground/types.ts +1 -4
  148. package/src/three/menuBackground/v2.ts +196 -96
  149. package/src/three/menuBackground/v2Meta.ts +3 -3
  150. package/src/three/menuBackground/worldBlocks.ts +37 -35
  151. package/src/three/modules/blockBreakParticles.ts +59 -28
  152. package/src/three/modules/cameraBobbing.ts +3 -6
  153. package/src/three/modules/index.ts +1 -1
  154. package/src/three/modules/rain.ts +10 -23
  155. package/src/three/modules/sciFiWorldReveal.ts +11 -25
  156. package/src/three/modules/starfield.ts +7 -13
  157. package/src/three/renderSlot.ts +28 -16
  158. package/src/three/rendererModuleSystem.ts +1 -3
  159. package/src/three/sceneOrigin.ts +96 -33
  160. package/src/three/sectionRaycastAabb.ts +24 -110
  161. package/src/three/shaderCubeMesh.ts +6 -17
  162. package/src/three/shaders/cubeBlockShader.ts +85 -96
  163. package/src/three/shaders/legacyBlockShader.ts +21 -43
  164. package/src/three/shaders/textureIndexMapping.ts +89 -95
  165. package/src/three/shaders/tintPalette.ts +156 -170
  166. package/src/three/signTextureCache.ts +9 -12
  167. package/src/three/skyboxRenderer.ts +62 -70
  168. package/src/three/tests/chunkMeshManagerLegacy.test.ts +34 -64
  169. package/src/three/tests/cubeDrawSpans.test.ts +55 -25
  170. package/src/three/tests/globalBlockBuffer.test.ts +217 -0
  171. package/src/three/tests/globalLegacyBuffer.test.ts +300 -41
  172. package/src/three/tests/legacyMultiDraw.test.ts +8 -29
  173. package/src/three/tests/pr3PerFrameCpuCuts.test.ts +106 -29
  174. package/src/three/tests/signTextureCache.test.ts +4 -4
  175. package/src/three/thirdPersonVoxelRaycast.ts +120 -0
  176. package/src/three/threeJsMedia.ts +55 -72
  177. package/src/three/threeJsParticles.ts +32 -41
  178. package/src/three/threeJsSound.ts +3 -3
  179. package/src/three/threeJsUtils.ts +3 -3
  180. package/src/three/threeWorker.ts +2 -2
  181. package/src/three/waypointSprite.ts +55 -82
  182. package/src/three/waypoints.ts +20 -23
  183. package/src/three/world/cursorBlock.ts +25 -12
  184. package/src/three/world/vr.ts +11 -17
  185. package/src/three/worldGeometryExport.ts +15 -34
  186. package/src/three/worldRendererThree.ts +144 -166
  187. package/src/wasm-mesher/bridge/convertChunk.ts +3 -9
  188. package/src/wasm-mesher/bridge/render-from-wasm.ts +204 -229
  189. package/src/wasm-mesher/bridge/shaderCubeBridge.ts +26 -67
  190. package/src/wasm-mesher/tests/cameraCollisionBlockCache.test.ts +82 -0
  191. package/src/wasm-mesher/tests/cullingRegression.test.ts +307 -0
  192. package/src/wasm-mesher/tests/heightmapParity.test.ts +8 -18
  193. package/src/wasm-mesher/tests/mesherWasmConversionCache.test.ts +2 -2
  194. package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +1 -93
  195. package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +103 -141
  196. package/src/wasm-mesher/tests/splitColumnWasmOutput.test.ts +14 -22
  197. package/src/wasm-mesher/tests/thirdPersonVoxelRaycast.test.ts +63 -0
  198. package/src/wasm-mesher/worker/mesherWasm.ts +150 -141
  199. package/src/wasm-mesher/worker/mesherWasmConversionCache.ts +2 -8
  200. package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +1 -3
  201. package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +1 -5
  202. package/src/wasm-mesher/worker/mesherWasmRequestTracker.ts +42 -42
  203. package/src/worldView/worldView.spiral.test.ts +1 -1
  204. package/src/worldView/worldView.ts +34 -39
  205. package/src/worldView/worldViewWorkerBridge.test.ts +4 -4
@@ -9,7 +9,7 @@ import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, veca
9
9
 
10
10
  type NeighborSide = 'up' | 'down' | 'east' | 'west' | 'north' | 'south'
11
11
 
12
- function tintToGl (tint) {
12
+ function tintToGl(tint) {
13
13
  const r = (tint >> 16) & 0xff
14
14
  const g = (tint >> 8) & 0xff
15
15
  const b = tint & 0xff
@@ -17,7 +17,7 @@ function tintToGl (tint) {
17
17
  }
18
18
 
19
19
  type Neighbors = Partial<Record<NeighborSide, boolean>>
20
- function renderElement (element: BlockElement, doAO: boolean, attr, globalMatrix, globalShift, block: Block | undefined, biome: string, neighbors: Neighbors) {
20
+ function renderElement(element: BlockElement, doAO: boolean, attr, globalMatrix, globalShift, block: Block | undefined, biome: string, neighbors: Neighbors) {
21
21
  const cursor = new Vec3(0, 0, 0)
22
22
 
23
23
  // const key = `${position.x},${position.y},${position.z}`
@@ -71,26 +71,17 @@ function renderElement (element: BlockElement, doAO: boolean, attr, globalMatrix
71
71
 
72
72
  // UV rotation
73
73
  const r = eFace.rotation || 0
74
- const uvcs = Math.cos(r * Math.PI / 180)
75
- const uvsn = -Math.sin(r * Math.PI / 180)
74
+ const uvcs = Math.cos((r * Math.PI) / 180)
75
+ const uvsn = -Math.sin((r * Math.PI) / 180)
76
76
 
77
77
  let localMatrix = null as any
78
78
  let localShift = null as any
79
79
 
80
80
  if (element.rotation) {
81
81
  // todo do we support rescale?
82
- localMatrix = buildRotationMatrix(
83
- element.rotation.axis,
84
- element.rotation.angle
85
- )
86
-
87
- localShift = vecsub3(
88
- element.rotation.origin,
89
- matmul3(
90
- localMatrix,
91
- element.rotation.origin
92
- )
93
- )
82
+ localMatrix = buildRotationMatrix(element.rotation.axis, element.rotation.angle)
83
+
84
+ localShift = vecsub3(element.rotation.origin, matmul3(localMatrix, element.rotation.origin))
94
85
  }
95
86
 
96
87
  const aos: number[] = []
@@ -98,21 +89,13 @@ function renderElement (element: BlockElement, doAO: boolean, attr, globalMatrix
98
89
  // const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15
99
90
  const baseLight = 1
100
91
  for (const pos of corners) {
101
- let vertex = [
102
- (pos[0] ? maxx : minx),
103
- (pos[1] ? maxy : miny),
104
- (pos[2] ? maxz : minz)
105
- ]
92
+ let vertex = [pos[0] ? maxx : minx, pos[1] ? maxy : miny, pos[2] ? maxz : minz]
106
93
 
107
94
  vertex = vecadd3(matmul3(localMatrix, vertex), localShift)
108
95
  vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift)
109
96
  vertex = vertex.map(v => v / 16)
110
97
 
111
- attr.positions.push(
112
- vertex[0]/* + (cursor.x & 15) - 8 */,
113
- vertex[1]/* + (cursor.y & 15) x */,
114
- vertex[2]/* + (cursor.z & 15) - 8 */
115
- )
98
+ attr.positions.push(vertex[0] /* + (cursor.x & 15) - 8 */, vertex[1] /* + (cursor.y & 15) x */, vertex[2] /* + (cursor.z & 15) - 8 */)
116
99
 
117
100
  attr.normals.push(...dir)
118
101
 
@@ -128,9 +111,9 @@ function renderElement (element: BlockElement, doAO: boolean, attr, globalMatrix
128
111
  const side2Block = 0
129
112
  const cornerBlock = 0
130
113
 
131
- const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock))
114
+ const ao = side1Block && side2Block ? 0 : 3 - (side1Block + side2Block + cornerBlock)
132
115
  // todo light should go upper on lower blocks
133
- light = (ao + 1) / 4 * (cornerLightResult / 15)
116
+ light = ((ao + 1) / 4) * (cornerLightResult / 15)
134
117
  aos.push(ao)
135
118
  }
136
119
 
@@ -151,22 +134,21 @@ function renderElement (element: BlockElement, doAO: boolean, attr, globalMatrix
151
134
  // }
152
135
 
153
136
  if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) {
154
- attr.indices.push(
155
-
156
- ndx, ndx + 3, ndx + 2,
157
- ndx, ndx + 1, ndx + 3
158
- )
137
+ attr.indices.push(ndx, ndx + 3, ndx + 2, ndx, ndx + 1, ndx + 3)
159
138
  } else {
160
- attr.indices.push(
161
-
162
- ndx, ndx + 1, ndx + 2,
163
- ndx + 2, ndx + 1, ndx + 3
164
- )
139
+ attr.indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3)
165
140
  }
166
141
  }
167
142
  }
168
143
 
169
- export const renderBlockThreeAttr = (models: BlockModelPartsResolved, block: Block | undefined, biome: string, mcData: IndexedData, variants = [], neighbors: Neighbors = {}) => {
144
+ export const renderBlockThreeAttr = (
145
+ models: BlockModelPartsResolved,
146
+ block: Block | undefined,
147
+ biome: string,
148
+ mcData: IndexedData,
149
+ variants = [],
150
+ neighbors: Neighbors = {}
151
+ ) => {
170
152
  const sx = 0
171
153
  const sy = 0
172
154
  const sz = 0
@@ -184,7 +166,7 @@ export const renderBlockThreeAttr = (models: BlockModelPartsResolved, block: Blo
184
166
  t_colors: [],
185
167
  t_uvs: [],
186
168
  indices: [],
187
- tiles: {},
169
+ tiles: {}
188
170
  } as Record<string, any>
189
171
 
190
172
  for (const [i, modelVars] of models.entries()) {
@@ -194,7 +176,11 @@ export const renderBlockThreeAttr = (models: BlockModelPartsResolved, block: Blo
194
176
  let globalShift = null as any
195
177
  for (const axis of ['x', 'y', 'z'] as const) {
196
178
  if (axis in model) {
197
- if (globalMatrix) { globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0))) } else { globalMatrix = buildRotationMatrix(axis, -(model[axis] ?? 0)) }
179
+ if (globalMatrix) {
180
+ globalMatrix = matmulmat3(globalMatrix, buildRotationMatrix(axis, -(model[axis] ?? 0)))
181
+ } else {
182
+ globalMatrix = buildRotationMatrix(axis, -(model[axis] ?? 0))
183
+ }
198
184
  }
199
185
  }
200
186
  if (globalMatrix) {
@@ -212,9 +198,19 @@ export const renderBlockThreeAttr = (models: BlockModelPartsResolved, block: Blo
212
198
  let ndx = attr.positions.length / 3
213
199
  for (let i = 0; i < attr.t_positions.length / 12; i++) {
214
200
  attr.indices.push(
215
- ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3,
201
+ ndx,
202
+ ndx + 1,
203
+ ndx + 2,
204
+ ndx + 2,
205
+ ndx + 1,
206
+ ndx + 3,
216
207
  // back face
217
- ndx, ndx + 2, ndx + 1, ndx + 2, ndx + 3, ndx + 1
208
+ ndx,
209
+ ndx + 2,
210
+ ndx + 1,
211
+ ndx + 2,
212
+ ndx + 3,
213
+ ndx + 1
218
214
  )
219
215
  ndx += 4
220
216
  }
@@ -266,6 +262,6 @@ export const getThreeBlockModelGroup = (material: THREE.Material, ...args: Param
266
262
  // return new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 }))
267
263
  }
268
264
 
269
- export const setBlockPosition = (object: THREE.Object3D, position: { x: number, y: number, z: number }) => {
265
+ export const setBlockPosition = (object: THREE.Object3D, position: { x: number; y: number; z: number }) => {
270
266
  object.position.set(position.x + 0.5, position.y + 0.5, position.z + 0.5)
271
267
  }
@@ -0,0 +1,179 @@
1
+ //@ts-nocheck
2
+ import { test, expect, beforeEach } from 'vitest'
3
+ import MinecraftData from 'minecraft-data'
4
+ import blocksAtlasesJson from 'mc-assets/dist/blocksAtlases.json'
5
+ import blockStatesModels from 'mc-assets/dist/blockStatesModels.json'
6
+ import worldBlockProvider from 'mc-assets/dist/worldBlockProvider'
7
+ import PrismarineBlockLoader from 'prismarine-block'
8
+ import Chunks from 'prismarine-chunk'
9
+ import { Vec3 } from 'vec3'
10
+ import { blockRendersSolid, getOcclusionShape, faceIsCulled, resetFaceOcclusionCache, oppositeDir, buildModelGlobalMatrix } from '../faceOcclusion'
11
+ import { elemFaces } from '../modelsGeometryCommon'
12
+ import { setBlockStatesData, getSectionGeometry } from '../models'
13
+ import { World } from '../world'
14
+
15
+ const VERSION = '1.18.2'
16
+
17
+ beforeEach(() => {
18
+ resetFaceOcclusionCache()
19
+ const mcData = MinecraftData(VERSION)
20
+ setBlockStatesData(blockStatesModels, blocksAtlasesJson, false, true, VERSION, { blocks: mcData.blocksArray })
21
+ })
22
+
23
+ function blockProvider() {
24
+ return (globalThis as any).blockProvider
25
+ }
26
+
27
+ function farmlandElement(provider: ReturnType<typeof worldBlockProvider>) {
28
+ const mcData = MinecraftData(VERSION)
29
+ const farmlandId = mcData.blocksByName.farmland!.defaultState
30
+ const blockObj = PrismarineBlockLoader(VERSION).fromStateId(farmlandId, 1)
31
+ const models = provider.getAllResolvedModels0_1({ name: blockObj.name, properties: blockObj.getProperties() }, false)
32
+ return { farmlandId, element: models[0]![0]!.elements![0]! }
33
+ }
34
+
35
+ function stairStateId(props: Record<string, string | boolean>) {
36
+ const mcData = MinecraftData(VERSION)
37
+ const Block = PrismarineBlockLoader(VERSION)
38
+ for (let sid = mcData.blocksByName.cut_copper_stairs!.minStateId; sid <= mcData.blocksByName.cut_copper_stairs!.maxStateId; sid++) {
39
+ const p = Block.fromStateId(sid, 1).getProperties() as Record<string, string>
40
+ let match = true
41
+ for (const [k, v] of Object.entries(props)) {
42
+ if (p[k] !== v) {
43
+ match = false
44
+ break
45
+ }
46
+ }
47
+ if (match) return sid
48
+ }
49
+ throw new Error(`no stair state for ${JSON.stringify(props)}`)
50
+ }
51
+
52
+ function stairModel(stateId: number, provider: ReturnType<typeof worldBlockProvider>) {
53
+ const blockObj = PrismarineBlockLoader(VERSION).fromStateId(stateId, 1)
54
+ return provider.getAllResolvedModels0_1({ name: blockObj.name, properties: blockObj.getProperties() }, false)[0]![0]!
55
+ }
56
+
57
+ test('blockRendersSolid: farmland opaque, glass and leaves not', () => {
58
+ const Block = PrismarineBlockLoader(VERSION)
59
+ const farmland = Block.fromStateId(MinecraftData(VERSION).blocksByName.farmland!.defaultState, 1)
60
+ const glass = Block.fromStateId(MinecraftData(VERSION).blocksByName.glass!.defaultState, 1)
61
+ const leaves = Block.fromStateId(MinecraftData(VERSION).blocksByName.oak_leaves!.defaultState, 1)
62
+ expect(blockRendersSolid(farmland)).toBe(true)
63
+ expect(blockRendersSolid(glass)).toBe(false)
64
+ expect(blockRendersSolid(leaves)).toBe(false)
65
+ })
66
+
67
+ test('getOcclusionShape: full cube covers entire plane', () => {
68
+ const mcData = MinecraftData(VERSION)
69
+ const stoneId = mcData.blocksByName.stone!.defaultState
70
+ const shape = getOcclusionShape(VERSION, stoneId, [1, 0, 0], blockProvider())
71
+ for (let row = 0; row < 16; row++) {
72
+ expect(shape[row]).toBe(0xffff)
73
+ }
74
+ })
75
+
76
+ test('getOcclusionShape: farmland side is shorter than full height', () => {
77
+ const mcData = MinecraftData(VERSION)
78
+ const farmlandId = mcData.blocksByName.farmland!.defaultState
79
+ const eastShape = getOcclusionShape(VERSION, farmlandId, [1, 0, 0], blockProvider())
80
+ let covered = 0
81
+ for (let row = 0; row < 16; row++) {
82
+ for (let col = 0; col < 16; col++) {
83
+ if (eastShape[row]! & (1 << col)) covered++
84
+ }
85
+ }
86
+ expect(covered).toBeLessThan(16 * 16)
87
+ expect(covered).toBeGreaterThan(0)
88
+ })
89
+
90
+ test('getOcclusionShape: west-facing stair fully covers west plane (step on west side)', () => {
91
+ const westId = stairStateId({ facing: 'west', half: 'bottom', shape: 'straight', waterlogged: false })
92
+ const westShape = getOcclusionShape(VERSION, westId, [-1, 0, 0], blockProvider())
93
+ for (let row = 0; row < 16; row++) {
94
+ expect(westShape[row]).toBe(0xffff)
95
+ }
96
+ })
97
+
98
+ test('faceIsCulled: farmland east face culled by adjacent farmland', () => {
99
+ const provider = blockProvider()
100
+ const { farmlandId, element } = farmlandElement(provider)
101
+ expect(faceIsCulled(VERSION, element, 'east', farmlandId, { stateId: farmlandId, name: 'farmland' }, provider, [1, 0, 0], null)).toBe(true)
102
+ })
103
+
104
+ test('faceIsCulled: farmland east face not culled by air', () => {
105
+ const provider = blockProvider()
106
+ const { farmlandId, element } = farmlandElement(provider)
107
+ expect(faceIsCulled(VERSION, element, 'east', 0, { stateId: farmlandId, name: 'farmland' }, provider, [1, 0, 0], null)).toBe(false)
108
+ })
109
+
110
+ test('faceIsCulled: glass identical neighbor still culls', () => {
111
+ const mcData = MinecraftData(VERSION)
112
+ const glassId = mcData.blocksByName.glass!.defaultState
113
+ const provider = blockProvider()
114
+ const models = provider.getAllResolvedModels0_1({ name: 'glass', properties: {} }, false)
115
+ const element = models[0]![0]!.elements![0]!
116
+ expect(faceIsCulled(VERSION, element, 'north', glassId, { stateId: glassId, name: 'glass' }, provider, [0, 0, -1], null)).toBe(true)
117
+ })
118
+
119
+ test('legacy mesher: adjacent farmland culled internal side', () => {
120
+ const mcData = MinecraftData(VERSION)
121
+ const Chunk = Chunks(VERSION) as any
122
+ const chunk = new Chunk(undefined as any)
123
+ const farmlandId = mcData.blocksByName.farmland!.defaultState
124
+ chunk.setBlockStateId(new Vec3(0, 0, 0), farmlandId)
125
+ chunk.setBlockStateId(new Vec3(1, 0, 0), farmlandId)
126
+ const world = new World(VERSION)
127
+ world.addColumn(0, 0, chunk.toJson())
128
+ const geo = getSectionGeometry(0, 0, 0, world, 16)
129
+ expect(geo.indicesCount / 6).toBe(10)
130
+ })
131
+
132
+ test('faceIsCulled: east stair interior faces culled against identical stair', () => {
133
+ const stairId = stairStateId({ facing: 'east', half: 'bottom', shape: 'straight', waterlogged: false })
134
+ const provider = blockProvider()
135
+ const model = stairModel(stairId, provider)
136
+ const globalMatrix = buildModelGlobalMatrix(model)
137
+ let culled = 0
138
+ for (const element of model.elements ?? []) {
139
+ for (const face of Object.keys(element.faces)) {
140
+ const localDir = elemFaces[face as keyof typeof elemFaces].dir as [number, number, number]
141
+ const worldDir = [Math.round(localDir[0]), Math.round(localDir[1]), Math.round(localDir[2])] as [number, number, number]
142
+ if (faceIsCulled(VERSION, element, face, stairId, { stateId: stairId, name: 'cut_copper_stairs' }, provider, worldDir, globalMatrix)) {
143
+ culled++
144
+ }
145
+ }
146
+ }
147
+ expect(culled).toBeGreaterThan(0)
148
+ })
149
+
150
+ test('faceIsCulled: west stair interior faces culled against identical west stair', () => {
151
+ const stairId = stairStateId({ facing: 'west', half: 'bottom', shape: 'straight', waterlogged: false })
152
+ const provider = blockProvider()
153
+ const model = stairModel(stairId, provider)
154
+ const globalMatrix = buildModelGlobalMatrix(model)
155
+ expect(globalMatrix).not.toBeNull()
156
+ let culled = 0
157
+ for (const element of model.elements ?? []) {
158
+ for (const face of Object.keys(element.faces)) {
159
+ const localDir = elemFaces[face as keyof typeof elemFaces].dir
160
+ const rotated = globalMatrix
161
+ ? [
162
+ globalMatrix[0][0] * localDir[0] + globalMatrix[0][1] * localDir[1] + globalMatrix[0][2] * localDir[2],
163
+ globalMatrix[1][0] * localDir[0] + globalMatrix[1][1] * localDir[1] + globalMatrix[1][2] * localDir[2],
164
+ globalMatrix[2][0] * localDir[0] + globalMatrix[2][1] * localDir[1] + globalMatrix[2][2] * localDir[2]
165
+ ]
166
+ : localDir
167
+ const worldDir = [Math.round(rotated[0]), Math.round(rotated[1]), Math.round(rotated[2])] as [number, number, number]
168
+ if (faceIsCulled(VERSION, element, face, stairId, { stateId: stairId, name: 'cut_copper_stairs' }, provider, worldDir, globalMatrix)) {
169
+ culled++
170
+ }
171
+ }
172
+ }
173
+ expect(culled).toBeGreaterThan(0)
174
+ })
175
+
176
+ test('oppositeDir inverts cardinal directions', () => {
177
+ expect(oppositeDir([1, 0, 0])[0]).toBe(-1)
178
+ expect(oppositeDir([0, 1, 0])[1]).toBe(-1)
179
+ })
@@ -3,14 +3,18 @@ import { test, expect } from 'vitest'
3
3
  import { setup } from '../../mesher-legacy/test/mesherTester'
4
4
 
5
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 })
6
+ const { getGeometry } = setup(
7
+ '1.16.5',
8
+ [
9
+ [[0, 0, 0], 'water'],
10
+ [[0, -1, 0], 'stone'],
11
+ [[1, 0, 0], 'stone'],
12
+ [[-1, 0, 0], 'stone'],
13
+ [[0, 0, 1], 'stone'],
14
+ [[0, 0, -1], 'stone']
15
+ ],
16
+ { noDebugTiles: true }
17
+ )
14
18
 
15
19
  const { attr } = getGeometry()
16
20
  const blend = attr.blend
@@ -4,11 +4,7 @@ import type { MesherConfig } from './shared'
4
4
  export type FaceDirection = readonly [number, number, number]
5
5
 
6
6
  /** Directional face darkening (matches legacy `renderElement` in models.ts). */
7
- export function getSideShading(
8
- dir: FaceDirection,
9
- shadingTheme: MesherConfig['shadingTheme'],
10
- cardinalLight: MesherConfig['cardinalLight']
11
- ): number {
7
+ export function getSideShading(dir: FaceDirection, shadingTheme: MesherConfig['shadingTheme'], cardinalLight: MesherConfig['cardinalLight']): number {
12
8
  if (shadingTheme === 'high-contrast') {
13
9
  return 0.8 + 0.5 * Math.max(0, 0.66 * dir[0] + 0.66 * dir[1] + 0.33 * dir[2])
14
10
  }
@@ -19,12 +15,7 @@ export function getSideShading(
19
15
  }
20
16
 
21
17
  /** Per-vertex brightness from AO (0–3) and corner light (0–15). */
22
- export function vertexLightFromAo(
23
- ao: number,
24
- cornerLight15: number,
25
- sideShading: number,
26
- shadingTheme: MesherConfig['shadingTheme']
27
- ): number {
18
+ export function vertexLightFromAo(ao: number, cornerLight15: number, sideShading: number, shadingTheme: MesherConfig['shadingTheme']): number {
28
19
  const lightNorm = cornerLight15 / 15
29
20
  if (shadingTheme === 'high-contrast') {
30
21
  return sideShading * ((ao + 1) / 4) * lightNorm
@@ -76,36 +76,31 @@ 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])
79
+ getChannelLightNorm(pos: Vec3): { block: number; sky: number } {
80
+ if (!(pos instanceof Vec3)) pos = new Vec3(...(pos as [number, number, number]))
81
81
  if (!this.config.enableLighting) return { block: 0, sky: 1 }
82
82
  const column = this.getColumnByPos(pos)
83
83
  if (!column || !hasChunkSection(column, pos)) return { block: 0, sky: 1 }
84
84
  const loc = posInChunk(pos)
85
85
  return {
86
86
  block: Math.min(15, column.getBlockLight(loc) + 2) / 15,
87
- sky: Math.min(15, column.getSkyLight(loc) + 2) / 15,
87
+ sky: Math.min(15, column.getSkyLight(loc) + 2) / 15
88
88
  }
89
89
  }
90
90
 
91
91
  getLight(pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
92
92
  // for easier testing
93
- if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
93
+ if (!(pos instanceof Vec3)) pos = new Vec3(...(pos as [number, number, number]))
94
94
  const { enableLighting, skyLight } = this.config
95
95
  if (!enableLighting) return 15
96
96
  // const key = `${pos.x},${pos.y},${pos.z}`
97
97
  // if (lightsCache.has(key)) return lightsCache.get(key)
98
98
  const column = this.getColumnByPos(pos)
99
99
  if (!column || !hasChunkSection(column, pos)) return 15
100
- let result = Math.min(
101
- 15,
102
- Math.max(
103
- column.getBlockLight(posInChunk(pos)),
104
- Math.min(skyLight, column.getSkyLight(posInChunk(pos)))
105
- ) + 2
106
- )
100
+ let result = Math.min(15, Math.max(column.getBlockLight(posInChunk(pos)), Math.min(skyLight, column.getSkyLight(posInChunk(pos)))) + 2)
107
101
  // lightsCache.set(key, result)
108
- if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
102
+ if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) {
103
+ // todo this is obviously wrong
109
104
  const lights = [
110
105
  this.getLight(pos.offset(0, 1, 0), undefined, true),
111
106
  this.getLight(pos.offset(0, -1, 0), undefined, true),
@@ -156,7 +151,7 @@ export class World {
156
151
 
157
152
  getBlock(pos: Vec3, blockProvider?: WorldBlockProvider, attr?: { hadErrors?: boolean }): WorldBlock | null {
158
153
  // for easier testing
159
- if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
154
+ if (!(pos instanceof Vec3)) pos = new Vec3(...(pos as [number, number, number]))
160
155
  const chunkKey = worldColumnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
161
156
  const modelOverride = this.customBlockModels.get(chunkKey)?.[getBlockPosKey(pos)]
162
157
 
@@ -190,11 +185,16 @@ export class World {
190
185
  b.name = namePropsStr.split('[')[0]
191
186
  const propsStr = namePropsStr.split('[')?.[1]?.split(']')
192
187
  if (propsStr) {
193
- const newProperties = Object.fromEntries(propsStr.join('').split(',').map(x => {
194
- let [key, val] = x.split('=')
195
- if (!isNaN(val)) val = parseInt(val, 10)
196
- return [key, val]
197
- }))
188
+ const newProperties = Object.fromEntries(
189
+ propsStr
190
+ .join('')
191
+ .split(',')
192
+ .map(x => {
193
+ let [key, val] = x.split('=')
194
+ if (!isNaN(val)) val = parseInt(val, 10)
195
+ return [key, val]
196
+ })
197
+ )
198
198
  b._properties = newProperties
199
199
  }
200
200
  }
@@ -228,7 +228,7 @@ export class World {
228
228
  block.models = blockProvider.getAllResolvedModels0_1(
229
229
  {
230
230
  name: block.name,
231
- properties: props,
231
+ properties: props
232
232
  },
233
233
  useFallbackModel,
234
234
  issues,
@@ -3,10 +3,10 @@ import { describe, expect, it } from 'vitest'
3
3
  import { Vec3 } from 'vec3'
4
4
  import { World } from './world'
5
5
 
6
- function mockColumn (blockLight: number, skyLight: number, withSection = true) {
6
+ function mockColumn(blockLight: number, skyLight: number, withSection = true) {
7
7
  return {
8
8
  getBlockLight: () => blockLight,
9
- getSkyLight: () => skyLight,
9
+ getSkyLight: () => skyLight
10
10
  }
11
11
  }
12
12
 
@@ -7,7 +7,7 @@ import {
7
7
  LONG_RENDER_TIME_MS,
8
8
  LOW_FPS_THRESHOLD,
9
9
  RENDER_TIME_HISTORY_SIZE,
10
- SLOW_ENTITIES_RENDER_MS,
10
+ SLOW_ENTITIES_RENDER_MS
11
11
  } from './constants'
12
12
  import type { FramePerformanceSample, PerformanceInstabilityFactors } from './types'
13
13
 
@@ -40,22 +40,13 @@ export class PerformanceMonitor {
40
40
 
41
41
  const historyLen = this.renderTimeHistory.length
42
42
  const longFrames = this.renderTimeHistory.filter(t => t >= LONG_RENDER_TIME_MS).length
43
- const constantLongRenderTime =
44
- historyLen >= CONSTANT_LONG_RENDER_MIN_SAMPLES &&
45
- longFrames / historyLen >= CONSTANT_LONG_RENDER_FRACTION
43
+ const constantLongRenderTime = historyLen >= CONSTANT_LONG_RENDER_MIN_SAMPLES && longFrames / historyLen >= CONSTANT_LONG_RENDER_FRACTION
46
44
 
47
45
  const tooManyTextures = sample.loadedTextureCount >= HIGH_TEXTURE_COUNT
48
46
 
49
- const tooManyEntities =
50
- lowFps &&
51
- sample.entitiesMs >= SLOW_ENTITIES_RENDER_MS &&
52
- sceneWithoutEntitiesMs <= FAST_SCENE_WITHOUT_ENTITIES_MS
47
+ const tooManyEntities = lowFps && sample.entitiesMs >= SLOW_ENTITIES_RENDER_MS && sceneWithoutEntitiesMs <= FAST_SCENE_WITHOUT_ENTITIES_MS
53
48
 
54
- const hasKnownCause =
55
- longRenderTime ||
56
- constantLongRenderTime ||
57
- tooManyEntities ||
58
- tooManyTextures
49
+ const hasKnownCause = longRenderTime || constantLongRenderTime || tooManyEntities || tooManyTextures
59
50
 
60
51
  const unknownReason = lowFps && !hasKnownCause
61
52
 
@@ -1,12 +1,12 @@
1
1
  //@ts-nocheck
2
2
  import type { PerformanceInstabilityFactors } from './types'
3
3
 
4
- const FACTOR_CODES: Array<{ key: keyof PerformanceInstabilityFactors, code: string }> = [
4
+ const FACTOR_CODES: Array<{ key: keyof PerformanceInstabilityFactors; code: string }> = [
5
5
  { key: 'longRenderTime', code: 'LR' },
6
6
  { key: 'constantLongRenderTime', code: 'CLR' },
7
7
  { key: 'tooManyEntities', code: 'ENT' },
8
8
  { key: 'tooManyTextures', code: 'TEX' },
9
- { key: 'unknownReason', code: 'UNK' },
9
+ { key: 'unknownReason', code: 'UNK' }
10
10
  ]
11
11
 
12
12
  /** Compact debug overlay fragment, e.g. `LR+ENT` or empty string. */
@@ -1,10 +1,6 @@
1
1
  //@ts-nocheck
2
2
  export type { FramePerformanceSample, PerformanceInstabilityFactors } from './types'
3
3
  export { defaultPerformanceInstabilityFactors } from './types'
4
- export {
5
- LONG_RENDER_TIME_MS,
6
- LOW_FPS_THRESHOLD,
7
- HIGH_TEXTURE_COUNT,
8
- } from './constants'
4
+ export { LONG_RENDER_TIME_MS, LOW_FPS_THRESHOLD, HIGH_TEXTURE_COUNT } from './constants'
9
5
  export { PerformanceMonitor } from './PerformanceMonitor'
10
6
  export { formatPerformanceFactorsDebug } from './formatPerformanceFactorsDebug'
@@ -13,7 +13,7 @@ export const defaultPerformanceInstabilityFactors = (): PerformanceInstabilityFa
13
13
  constantLongRenderTime: false,
14
14
  tooManyEntities: false,
15
15
  tooManyTextures: false,
16
- unknownReason: false,
16
+ unknownReason: false
17
17
  })
18
18
 
19
19
  export interface FramePerformanceSample {