minecraft-renderer 0.1.43 → 0.1.45

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 (33) hide show
  1. package/dist/mesher.js +35 -35
  2. package/dist/mesher.js.map +4 -4
  3. package/dist/mesherWasm.js +61 -61
  4. package/dist/minecraft-renderer.js +59 -59
  5. package/dist/minecraft-renderer.js.meta.json +1 -1
  6. package/dist/threeWorker.js +458 -458
  7. package/package.json +1 -1
  8. package/src/graphicsBackend/appViewer.ts +19 -7
  9. package/src/graphicsBackend/types.ts +5 -1
  10. package/src/index.ts +33 -0
  11. package/src/lib/ui/newStats.ts +16 -2
  12. package/src/lib/worldrendererCommon.ts +2 -2
  13. package/src/mesher-shared/models.ts +28 -11
  14. package/src/mesher-shared/vertexShading.ts +35 -0
  15. package/src/three/entities.ts +22 -6
  16. package/src/three/graphicsBackendBase.ts +28 -20
  17. package/src/three/graphicsBackendOffThread.ts +1 -2
  18. package/src/three/menuBackground/activeView.ts +19 -0
  19. package/src/three/menuBackground/classic.ts +148 -0
  20. package/src/three/menuBackground/config.ts +23 -0
  21. package/src/three/menuBackground/defaultOptions.ts +141 -0
  22. package/src/three/menuBackground/futuristic.ts +859 -0
  23. package/src/three/menuBackground/index.ts +36 -0
  24. package/src/three/menuBackground/renderer.ts +97 -0
  25. package/src/three/menuBackground/shared.ts +3 -0
  26. package/src/three/menuBackground/types.ts +37 -0
  27. package/src/three/menuBackground/worldBlocks.ts +144 -0
  28. package/src/three/modules/rain.ts +21 -3
  29. package/src/three/waypointSprite.ts +108 -106
  30. package/src/three/worldRendererThree.ts +9 -12
  31. package/src/wasm-mesher/bridge/render-from-wasm.ts +57 -12
  32. package/src/three/panorama.ts +0 -312
  33. package/src/three/panoramaShared.ts +0 -2
@@ -2,6 +2,15 @@
2
2
  import * as THREE from 'three'
3
3
  import { createCanvas } from '../lib/utils'
4
4
 
5
+ /**
6
+ * Limits label texture resolution on high-DPR devices (sprite still sizes in screen px via Three.js;
7
+ * main win is fewer canvas pixels / less GPU memory — especially on iOS).
8
+ */
9
+ const LABEL_CANVAS_MAX_DEVICE_PIXEL_RATIO = 1
10
+
11
+ /** Distance label repaints when this bucket (meters) changes — fewer canvas uploads while moving. */
12
+ const DISTANCE_LABEL_STEP_M = 10
13
+
5
14
  // Centralized visual configuration (in screen pixels)
6
15
  export const WAYPOINT_CONFIG = {
7
16
  // Target size in screen pixels (this controls the final sprite size)
@@ -60,9 +69,8 @@ export function createWaypointSprite (options: {
60
69
  visualScale?: number,
61
70
  opacity?: number,
62
71
  }): WaypointSprite {
63
- const color = options.color ?? 0xFF_00_00
72
+ let displayColor = options.color ?? 0xFF_00_00
64
73
  const depthTest = options.depthTest ?? false
65
- const labelYOffset = options.labelYOffset ?? 1.5
66
74
 
67
75
  // Get visual scale from options, metadata, server metadata, or default
68
76
  // Priority: options.visualScale > metadata.visualScale > window.serverMetadata?.waypointVisualScale > DEFAULT
@@ -78,61 +86,87 @@ export function createWaypointSprite (options: {
78
86
  ?? (typeof window === 'undefined' ? undefined : (window as any).serverMetadata?.waypointOpacity)
79
87
  ?? WAYPOINT_CONFIG.DEFAULT_OPACITY
80
88
 
81
- // Build combined sprite
82
- const sprite = createCombinedSprite(color, options.label ?? '', '0m', depthTest, visualScale)
89
+ const labelCanvas = createCanvas(getLabelCanvasSize(), getLabelCanvasSize())
90
+ drawCombinedOntoCanvas(labelCanvas, displayColor, options.label ?? '', '0m', visualScale)
91
+
92
+ const labelTexture = new THREE.CanvasTexture(labelCanvas)
93
+ labelTexture.anisotropy = 1
94
+ labelTexture.magFilter = THREE.LinearFilter
95
+ labelTexture.minFilter = THREE.LinearFilter
96
+ const material = new THREE.SpriteMaterial({
97
+ map: labelTexture,
98
+ transparent: true,
99
+ opacity: 1,
100
+ depthTest,
101
+ depthWrite: false,
102
+ })
103
+ const sprite = new THREE.Sprite(material)
104
+ sprite.position.set(0, 0, 0)
83
105
  sprite.renderOrder = 10
84
106
  sprite.material.opacity = opacity
85
107
  let currentLabel = options.label ?? ''
86
108
 
87
- // Performance optimization: cache distance text to avoid unnecessary updates
88
109
  let lastDistanceText = '0m'
89
- let lastDistance = 0
110
+ let lastDistanceBucket = Number.NaN
90
111
 
91
- // Offscreen arrow (detached by default)
92
112
  let arrowSprite: THREE.Sprite | undefined
113
+ let arrowCanvas: OffscreenCanvas | undefined
114
+ let arrowCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | undefined
115
+ let arrowTexture: THREE.CanvasTexture | undefined
93
116
  let arrowParent: THREE.Object3D | null = null
94
117
  let arrowEnabled = WAYPOINT_CONFIG.ARROW.enabledDefault
95
118
 
96
- // Group for easy add/remove
97
119
  const group = new THREE.Group()
98
120
  group.add(sprite)
99
121
 
100
- // Initial position
101
122
  const { x, y, z } = options.position
102
123
  group.position.set(x, y, z)
103
124
 
125
+ function refreshLabelTexture () {
126
+ labelTexture.needsUpdate = true
127
+ }
128
+
129
+ function paintArrowOnCanvas () {
130
+ if (!arrowCanvas || !arrowCtx) return
131
+ const size = arrowCanvas.width
132
+ arrowCtx.clearRect(0, 0, size, size)
133
+ arrowCtx.beginPath()
134
+ arrowCtx.moveTo(size * 0.15, size * 0.5)
135
+ arrowCtx.lineTo(size * 0.85, size * 0.5)
136
+ arrowCtx.lineTo(size * 0.5, size * 0.15)
137
+ arrowCtx.closePath()
138
+ const colorHex = `#${displayColor.toString(16).padStart(6, '0')}`
139
+ arrowCtx.lineWidth = 6
140
+ arrowCtx.strokeStyle = 'black'
141
+ arrowCtx.stroke()
142
+ arrowCtx.fillStyle = colorHex
143
+ arrowCtx.fill()
144
+ if (arrowTexture) arrowTexture.needsUpdate = true
145
+ }
146
+
104
147
  function setColor (newColor: number) {
105
- const canvas = drawCombinedCanvas(newColor, currentLabel, '0m', visualScale)
106
- const texture = new THREE.CanvasTexture(canvas)
107
- const mat = sprite.material
108
- mat.map?.dispose()
109
- mat.map = texture
110
- mat.needsUpdate = true
148
+ displayColor = newColor
149
+ lastDistanceText = '0m'
150
+ lastDistanceBucket = 0
151
+ drawCombinedOntoCanvas(labelCanvas, displayColor, currentLabel, '0m', visualScale)
152
+ refreshLabelTexture()
153
+ if (arrowSprite) paintArrowOnCanvas()
111
154
  }
112
155
 
113
156
  function setLabel (newLabel?: string) {
114
157
  currentLabel = newLabel ?? ''
115
- const canvas = drawCombinedCanvas(color, currentLabel, '0m', visualScale)
116
- const texture = new THREE.CanvasTexture(canvas)
117
- const mat = sprite.material
118
- mat.map?.dispose()
119
- mat.map = texture
120
- mat.needsUpdate = true
158
+ drawCombinedOntoCanvas(labelCanvas, displayColor, currentLabel, lastDistanceText, visualScale)
159
+ refreshLabelTexture()
121
160
  }
122
161
 
123
162
  function updateDistanceText (label: string, distanceText: string) {
124
- // Performance optimization: only update if distance text actually changed
125
163
  if (distanceText === lastDistanceText) {
126
164
  return
127
165
  }
128
166
  lastDistanceText = distanceText
129
167
 
130
- const canvas = drawCombinedCanvas(color, label, distanceText, visualScale)
131
- const texture = new THREE.CanvasTexture(canvas)
132
- const mat = sprite.material
133
- mat.map?.dispose()
134
- mat.map = texture
135
- mat.needsUpdate = true
168
+ drawCombinedOntoCanvas(labelCanvas, displayColor, label, distanceText, visualScale)
169
+ refreshLabelTexture()
136
170
  }
137
171
 
138
172
  function setVisible (visible: boolean) {
@@ -143,7 +177,6 @@ export function createWaypointSprite (options: {
143
177
  group.position.set(nx, ny, nz)
144
178
  }
145
179
 
146
- // Keep constant pixel size on screen using global config
147
180
  function updateScaleScreenPixels (
148
181
  cameraPosition: THREE.Vector3,
149
182
  cameraFov: number,
@@ -152,7 +185,6 @@ export function createWaypointSprite (options: {
152
185
  ) {
153
186
  const vFovRad = cameraFov * Math.PI / 180
154
187
  const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance
155
- // Use configured target screen size with visual scale multiplier
156
188
  const scale = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.TARGET_SCREEN_PX * visualScale / viewportHeightPx)
157
189
  sprite.scale.set(scale, scale, 1)
158
190
  }
@@ -160,28 +192,15 @@ export function createWaypointSprite (options: {
160
192
  function ensureArrow () {
161
193
  if (arrowSprite) return
162
194
  const size = 128
163
- const canvas = createCanvas(size, size)
164
- const ctx = canvas.getContext('2d')!
165
- ctx.clearRect(0, 0, size, size)
166
-
167
- // Draw arrow shape
168
- ctx.beginPath()
169
- ctx.moveTo(size * 0.15, size * 0.5)
170
- ctx.lineTo(size * 0.85, size * 0.5)
171
- ctx.lineTo(size * 0.5, size * 0.15)
172
- ctx.closePath()
173
-
174
- // Use waypoint color for arrow
175
- const colorHex = `#${color.toString(16).padStart(6, '0')}`
176
- ctx.lineWidth = 6
177
- ctx.strokeStyle = 'black'
178
- ctx.stroke()
179
- ctx.fillStyle = colorHex
180
- ctx.fill()
181
-
182
- const texture = new THREE.CanvasTexture(canvas)
183
- const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false, opacity })
184
- arrowSprite = new THREE.Sprite(material)
195
+ arrowCanvas = createCanvas(size, size)
196
+ arrowCtx = arrowCanvas.getContext('2d')!
197
+ paintArrowOnCanvas()
198
+ arrowTexture = new THREE.CanvasTexture(arrowCanvas)
199
+ arrowTexture.anisotropy = 1
200
+ arrowTexture.magFilter = THREE.LinearFilter
201
+ arrowTexture.minFilter = THREE.LinearFilter
202
+ const matTex = new THREE.SpriteMaterial({ map: arrowTexture, transparent: true, depthTest: false, depthWrite: false, opacity })
203
+ arrowSprite = new THREE.Sprite(matTex)
185
204
  arrowSprite.renderOrder = 12
186
205
  arrowSprite.visible = false
187
206
  if (arrowParent) arrowParent.add(arrowSprite)
@@ -306,9 +325,8 @@ export function createWaypointSprite (options: {
306
325
  return false
307
326
  }
308
327
 
309
- function computeDistance (_cameraPosition: THREE.Vector3): number {
310
- // group.position is in scene space; camera is at scene origin (0,0,0)
311
- return group.position.length()
328
+ function computeDistance (cameraPosition: THREE.Vector3): number {
329
+ return cameraPosition.distanceTo(group.position)
312
330
  }
313
331
 
314
332
  function updateForCamera (
@@ -318,17 +336,14 @@ export function createWaypointSprite (options: {
318
336
  viewportHeightPx: number
319
337
  ): boolean {
320
338
  const distance = computeDistance(cameraPosition)
321
- // Keep constant pixel size
322
339
  updateScaleScreenPixels(cameraPosition, camera.fov, distance, viewportHeightPx)
323
340
 
324
- // Performance optimization: only update distance text if distance changed significantly
325
- const roundedDistance = Math.round(distance)
326
- if (Math.abs(roundedDistance - lastDistance) >= 1) {
327
- lastDistance = roundedDistance
328
- updateDistanceText(currentLabel, `${roundedDistance}m`)
341
+ const bucket = Math.round(distance / DISTANCE_LABEL_STEP_M) * DISTANCE_LABEL_STEP_M
342
+ if (bucket !== lastDistanceBucket) {
343
+ lastDistanceBucket = bucket
344
+ updateDistanceText(currentLabel, `${Math.max(0, bucket)}m`)
329
345
  }
330
346
 
331
- // Update arrow and visibility
332
347
  const onScreen = updateOffscreenArrow(camera, viewportWidthPx, viewportHeightPx)
333
348
  setVisible(onScreen)
334
349
  return onScreen
@@ -339,7 +354,6 @@ export function createWaypointSprite (options: {
339
354
  mat.map?.dispose()
340
355
  mat.dispose()
341
356
  if (arrowSprite) {
342
- // Remove arrow from parent before disposing
343
357
  if (arrowSprite.parent) {
344
358
  arrowSprite.parent.remove(arrowSprite)
345
359
  }
@@ -347,6 +361,10 @@ export function createWaypointSprite (options: {
347
361
  am.map?.dispose()
348
362
  am.dispose()
349
363
  }
364
+ arrowSprite = undefined
365
+ arrowCanvas = undefined
366
+ arrowCtx = undefined
367
+ arrowTexture = undefined
350
368
  }
351
369
 
352
370
  return {
@@ -365,41 +383,46 @@ export function createWaypointSprite (options: {
365
383
  }
366
384
 
367
385
  // Internal helpers
368
- function drawCombinedCanvas (color: number, id: string, distance: string, visualScale = 1): OffscreenCanvas {
369
- const scale = WAYPOINT_CONFIG.CANVAS_SCALE * (globalThis.devicePixelRatio || 1)
370
- const size = WAYPOINT_CONFIG.CANVAS_SIZE * scale
371
- const canvas = createCanvas(size, size)
386
+ function computeLabelCanvasLineScale (): number {
387
+ const dpr = globalThis.devicePixelRatio || 1
388
+ const effectiveDpr = Math.min(dpr, LABEL_CANVAS_MAX_DEVICE_PIXEL_RATIO)
389
+ return WAYPOINT_CONFIG.CANVAS_SCALE * effectiveDpr
390
+ }
391
+
392
+ function getLabelCanvasSize (): number {
393
+ return Math.round(WAYPOINT_CONFIG.CANVAS_SIZE * computeLabelCanvasLineScale())
394
+ }
395
+
396
+ function drawCombinedOntoCanvas (
397
+ canvas: OffscreenCanvas,
398
+ color: number,
399
+ id: string,
400
+ distance: string,
401
+ visualScale: number
402
+ ): void {
403
+ const size = canvas.width
404
+ const scale = computeLabelCanvasLineScale()
372
405
  const ctx = canvas.getContext('2d')!
373
406
 
374
- // Clear canvas
375
407
  ctx.clearRect(0, 0, size, size)
376
408
 
377
- // Draw dot with visual scale applied
378
409
  const centerX = size / 2
379
410
  const dotY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DOT_Y)
380
- const radius = Math.round(size * 0.05 * visualScale) // Dot takes up ~5% of canvas height, scaled
381
- const borderWidth = Math.max(2, Math.round(4 * scale * visualScale))
411
+ const innerRadius = Math.round(size * 0.05 * visualScale)
412
+ const outlinePad = Math.max(2, Math.round(4 * scale * visualScale))
413
+ const dotRadius = innerRadius + outlinePad
382
414
 
383
- // Outer border (black)
384
415
  ctx.beginPath()
385
- ctx.arc(centerX, dotY, radius + borderWidth, 0, Math.PI * 2)
386
- ctx.fillStyle = 'black'
387
- ctx.fill()
388
-
389
- // Inner circle (colored)
390
- ctx.beginPath()
391
- ctx.arc(centerX, dotY, radius, 0, Math.PI * 2)
416
+ ctx.arc(centerX, dotY, dotRadius, 0, Math.PI * 2)
392
417
  ctx.fillStyle = `#${color.toString(16).padStart(6, '0')}`
393
418
  ctx.fill()
394
419
 
395
- // Text properties
396
420
  ctx.textAlign = 'center'
397
421
  ctx.textBaseline = 'middle'
398
422
 
399
- // Title with visual scale applied
400
- const nameFontPx = Math.round(size * 0.08 * visualScale) // ~8% of canvas height, scaled
401
- const distanceFontPx = Math.round(size * 0.06 * visualScale) // ~6% of canvas height, scaled
402
- ctx.font = `bold ${nameFontPx}px mojangles`
423
+ const nameFontPx = Math.round(size * 0.08 * visualScale)
424
+ const distanceFontPx = Math.round(size * 0.06 * visualScale)
425
+ ctx.font = `800 ${nameFontPx}px mojangles`
403
426
  ctx.lineWidth = Math.max(2, Math.round(3 * scale * visualScale))
404
427
  const nameY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.NAME_Y)
405
428
 
@@ -408,8 +431,7 @@ function drawCombinedCanvas (color: number, id: string, distance: string, visual
408
431
  ctx.fillStyle = 'white'
409
432
  ctx.fillText(id, centerX, nameY)
410
433
 
411
- // Distance with visual scale applied
412
- ctx.font = `bold ${distanceFontPx}px mojangles`
434
+ ctx.font = `800 ${distanceFontPx}px mojangles`
413
435
  ctx.lineWidth = Math.max(2, Math.round(2 * scale * visualScale))
414
436
  const distanceY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DISTANCE_Y)
415
437
 
@@ -417,26 +439,6 @@ function drawCombinedCanvas (color: number, id: string, distance: string, visual
417
439
  ctx.strokeText(distance, centerX, distanceY)
418
440
  ctx.fillStyle = '#CCCCCC'
419
441
  ctx.fillText(distance, centerX, distanceY)
420
-
421
- return canvas
422
- }
423
-
424
- function createCombinedSprite (color: number, id: string, distance: string, depthTest: boolean, visualScale = 1): THREE.Sprite {
425
- const canvas = drawCombinedCanvas(color, id, distance, visualScale)
426
- const texture = new THREE.CanvasTexture(canvas)
427
- texture.anisotropy = 1
428
- texture.magFilter = THREE.LinearFilter
429
- texture.minFilter = THREE.LinearFilter
430
- const material = new THREE.SpriteMaterial({
431
- map: texture,
432
- transparent: true,
433
- opacity: 1,
434
- depthTest,
435
- depthWrite: false,
436
- })
437
- const sprite = new THREE.Sprite(material)
438
- sprite.position.set(0, 0, 0)
439
- return sprite
440
442
  }
441
443
 
442
444
  export const WaypointHelpers = {
@@ -9,7 +9,7 @@ import { renderSign } from '../sign-renderer'
9
9
  import { DisplayWorldOptions, GraphicsInitOptions } from '../graphicsBackend/types'
10
10
  import { chunkPos, sectionPos } from '../lib/simpleUtils'
11
11
  import { WorldRendererCommon } from '../lib/worldrendererCommon'
12
- import { addNewStat } from '../lib/ui/newStats'
12
+ import { addNewStat, MC_RENDERER_DEBUG_OVERLAY_CLASS } from '../lib/ui/newStats'
13
13
  import { MesherGeometryOutput } from '../mesher-shared/shared'
14
14
  import { ItemSpecificContextProperties } from '../playerState/types'
15
15
  import { setBlockPosition } from '../mesher-shared/standaloneRenderer'
@@ -387,7 +387,7 @@ export class WorldRendererThree extends WorldRendererCommon {
387
387
  if ((prop === 'x' || prop === 'y' || prop === 'z') && typeof value === 'number' && Math.abs(value) > WORLD_COORD_THRESHOLD) {
388
388
  warnOnce()
389
389
  }
390
- ;(target as any)[prop] = value
390
+ ; (target as any)[prop] = value
391
391
  return true
392
392
  },
393
393
  get(target, prop, receiver) {
@@ -701,22 +701,19 @@ export class WorldRendererThree extends WorldRendererCommon {
701
701
  addDebugOverlay() {
702
702
  if (this.debugOverlayAdded) return
703
703
  this.debugOverlayAdded = true
704
- const pane = addNewStat('debug-overlay')
704
+ const pane = addNewStat('debug-overlay', 80, 0, undefined, { className: MC_RENDERER_DEBUG_OVERLAY_CLASS })
705
705
  setInterval(() => {
706
706
  pane.setVisibility(this.displayAdvancedStats)
707
707
  if (this.displayAdvancedStats) {
708
- const formatBigNumber = (num: number) => {
709
- return new Intl.NumberFormat('en-US', {}).format(num)
710
- }
708
+ const formatFull = (num: number) => new Intl.NumberFormat('en-US', {}).format(num)
709
+ const formatCompact = (num: number) => new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1 }).format(num)
711
710
  let text = ''
712
- text += `C: ${formatBigNumber(this.renderer.info.render.calls)} `
713
- text += `TR: ${formatBigNumber(this.renderer.info.render.triangles)} `
714
- text += `TE: ${formatBigNumber(this.renderer.info.memory.textures)} `
715
- text += `F: ${formatBigNumber(this.tilesRendered)} `
716
- text += `B: ${formatBigNumber(this.blocksRendered)} `
711
+ text += `TE: ${formatFull(this.renderer.info.memory.textures)} `
712
+ text += `F: ${formatCompact(this.tilesRendered)} `
713
+ text += `B: ${formatCompact(this.blocksRendered)} `
717
714
  text += `MEM: ${this.chunkMeshManager.getEstimatedMemoryUsage().total} `
718
715
  const poolStats = this.chunkMeshManager.getStats()
719
- text += `POOL: ${poolStats.activeCount}/${poolStats.poolSize} HR: ${poolStats.hitRate}`
716
+ text += `POOL: ${poolStats.activeCount}/${poolStats.poolSize}`
720
717
  pane.updateText(text)
721
718
  this.backendInfoReport = text
722
719
  }
@@ -13,6 +13,8 @@ import { elemFaces, buildRotationMatrix, matmul3, matmulmat3, vecadd3, vecsub3 }
13
13
  import type { ExportedWorldGeometry, ExportedSection } from '../../three/worldGeometryExport'
14
14
  import type { MesherGeometryOutput } from '../../mesher-shared/shared'
15
15
  import type { World } from '../../mesher-shared/world'
16
+ import { resolveBlockPropertiesForMeshing } from '../../mesher-shared/models'
17
+ import { getSideShading, vertexLightFromAo } from '../../mesher-shared/vertexShading'
16
18
 
17
19
  // Handle both default and named export
18
20
  const worldBlockProvider = (worldBlockProviderModule as any).default || worldBlockProviderModule
@@ -134,6 +136,18 @@ export function extractColumnHeightmap(
134
136
  return out
135
137
  }
136
138
 
139
+ function computeMesherVertexLight(
140
+ world: World | undefined,
141
+ ao: number,
142
+ cornerLight15: number,
143
+ faceDir: [number, number, number]
144
+ ): number {
145
+ const shadingTheme = world?.config.shadingTheme ?? 'high-contrast'
146
+ const cardinalLight = world?.config.cardinalLight ?? 'default'
147
+ const sideShading = getSideShading(faceDir, shadingTheme, cardinalLight)
148
+ return vertexLightFromAo(ao, cornerLight15, sideShading, shadingTheme)
149
+ }
150
+
137
151
  /**
138
152
  * Get or create cached block model with precomputed matrices
139
153
  */
@@ -141,10 +155,32 @@ function getCachedBlockModel(
141
155
  blockStateId: number,
142
156
  version: string,
143
157
  blockProvider: WorldBlockProvider,
144
- PrismarineBlock: any
158
+ PrismarineBlock: any,
159
+ world?: World,
160
+ blockPos?: { x: number, y: number, z: number }
145
161
  ): CachedBlockModel | null {
146
- // Use a module-level cache
147
- const cacheKey = `${version}:${blockStateId}`
162
+ const usePreflat = !!(world?.preflat && blockPos)
163
+ let blockName: string
164
+ let blockProps: Record<string, unknown>
165
+ if (usePreflat) {
166
+ const resolved = resolveBlockPropertiesForMeshing(
167
+ world,
168
+ new Vec3(blockPos!.x, blockPos!.y, blockPos!.z),
169
+ blockProvider,
170
+ blockStateId,
171
+ PrismarineBlock
172
+ )
173
+ blockName = resolved.name
174
+ blockProps = resolved.properties
175
+ } else {
176
+ const blockObj = PrismarineBlock.fromStateId(blockStateId, 1)
177
+ blockName = blockObj.name
178
+ blockProps = blockObj.getProperties()
179
+ }
180
+
181
+ const cacheKey = usePreflat
182
+ ? `${version}:${blockStateId}:${blockName}:${JSON.stringify(blockProps)}`
183
+ : `${version}:${blockStateId}`
148
184
  if (!(globalThis as any).__wasmBlockModelCache) {
149
185
  (globalThis as any).__wasmBlockModelCache = new Map()
150
186
  }
@@ -156,11 +192,13 @@ function getCachedBlockModel(
156
192
 
157
193
  try {
158
194
  const blockObj = PrismarineBlock.fromStateId(blockStateId, 1)
159
- const blockName = blockObj.name
160
- const blockProps = blockObj.getProperties()
195
+ if (!usePreflat) {
196
+ blockName = blockObj.name
197
+ blockProps = blockObj.getProperties()
198
+ }
161
199
 
162
200
  const models = blockProvider.getAllResolvedModels0_1(
163
- { name: blockName, properties: blockProps },
201
+ { name: blockName, properties: blockProps as Record<string, string | number | boolean> },
164
202
  false
165
203
  )
166
204
 
@@ -505,7 +543,14 @@ export function renderWasmOutputToGeometry(
505
543
  }
506
544
  }
507
545
 
508
- const cachedModel = getCachedBlockModel(blockStateId, version, blockProvider, PrismarineBlock)
546
+ const cachedModel = getCachedBlockModel(
547
+ blockStateId,
548
+ version,
549
+ blockProvider,
550
+ PrismarineBlock,
551
+ world,
552
+ { x: bx, y: by, z: bz }
553
+ )
509
554
  if (!cachedModel) continue
510
555
 
511
556
  if (false) {
@@ -654,10 +699,9 @@ export function renderWasmOutputToGeometry(
654
699
  // But WASM light calculation seems to return 0.0, so we need to handle that
655
700
  // In the test case, TypeScript gets baseLight = 1.0 (full brightness)
656
701
  // So we should use 1.0 as the base light value when WASM returns 0
657
- const baseLight = lightValues[cornerIdx]
658
- const cornerLightResult = baseLight * 15
659
-
660
- const light = (ao + 1) / 4 * (cornerLightResult / 15)
702
+ const cornerLight15 = (lightValues[cornerIdx] ?? 1) * 15
703
+ const faceDir = transformedDir as [number, number, number]
704
+ const light = computeMesherVertexLight(world, ao, cornerLight15, faceDir)
661
705
 
662
706
  colors.push(tint[0] * light, tint[1] * light, tint[2] * light)
663
707
 
@@ -907,7 +951,8 @@ export function renderWasmOutputToGeometry(
907
951
  }
908
952
 
909
953
  if (doAO) {
910
- light = (ao + 1) / 4 * (cornerLightResult / 15)
954
+ const faceDir = transformedDirI as [number, number, number]
955
+ light = computeMesherVertexLight(world, ao, cornerLightResult, faceDir)
911
956
  }
912
957
 
913
958
  colors.push(tint[0] * light!, tint[1] * light!, tint[2] * light!)