minecraft-renderer 0.1.63 → 0.1.64

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.
@@ -8,31 +8,31 @@ import { resizeMenuBackgroundCamera } from './activeView'
8
8
  import { loadThreeJsTextureFromBitmap } from '../threeJsUtils'
9
9
  import { MENU_BACKGROUND_MOTION_DEFAULTS, MENU_BACKGROUND_OPTION_DEFAULTS } from './config'
10
10
  import {
11
- FUTURISTIC_CAMERA_IDS,
12
- FUTURISTIC_SCENE_IDS,
11
+ V2_CAMERA_IDS,
12
+ V2_SCENE_IDS,
13
13
  MINECRAFT_BLOCK_GROUP_IDS,
14
- type FuturisticCameraId,
15
- type FuturisticSceneId,
14
+ type V2CameraId,
15
+ type V2SceneId,
16
16
  type MinecraftBlockGroupId,
17
- } from './futuristicMeta'
17
+ } from './v2Meta'
18
18
 
19
19
  export {
20
- FUTURISTIC_SCENE_IDS,
21
- FUTURISTIC_SCENE_LABELS,
22
- FUTURISTIC_CAMERA_IDS,
23
- FUTURISTIC_CAMERA_LABELS,
20
+ V2_SCENE_IDS,
21
+ V2_SCENE_LABELS,
22
+ V2_CAMERA_IDS,
23
+ V2_CAMERA_LABELS,
24
24
  MINECRAFT_BLOCK_GROUP_IDS,
25
25
  MINECRAFT_BLOCK_GROUP_LABELS,
26
- } from './futuristicMeta'
27
- export type { FuturisticSceneId, FuturisticCameraId, MinecraftBlockGroupId } from './futuristicMeta'
26
+ } from './v2Meta'
27
+ export type { V2SceneId, V2CameraId, MinecraftBlockGroupId } from './v2Meta'
28
28
 
29
29
  /** Mouse parallax scale (HTML prototype uses 1). */
30
30
  const MOUSE_INFLUENCE = 0.1
31
31
 
32
- export interface FuturisticMenuBackgroundOptions {
32
+ export interface V2MenuBackgroundOptions {
33
33
  useMinecraftTextures?: boolean
34
- initialScene?: FuturisticSceneId
35
- initialCamera?: FuturisticCameraId
34
+ initialScene?: V2SceneId
35
+ initialCamera?: V2CameraId
36
36
  initialBlockGroup?: MinecraftBlockGroupId
37
37
  /** Camera path speed multiplier (0 = frozen path; mouse parallax unchanged). */
38
38
  initialCameraSpeed?: number
@@ -41,7 +41,7 @@ export interface FuturisticMenuBackgroundOptions {
41
41
  resourcesManager?: ResourcesManager
42
42
  }
43
43
 
44
- /** Block pools for textured floating cubes (selected via {@link FuturisticMenuBackground.setBlockGroup}). */
44
+ /** Block pools for textured floating cubes (selected via {@link V2MenuBackground.setBlockGroup}). */
45
45
  export const MINECRAFT_BLOCK_GROUPS = {
46
46
  mixed: [
47
47
  'white_wool', 'cyan_wool', 'blue_wool', 'purple_wool',
@@ -123,7 +123,7 @@ interface FloatingBlock {
123
123
  minecraftBlockName?: string
124
124
  }
125
125
 
126
- const PAL: Record<FuturisticSceneId, ScenePalette> = {
126
+ const PAL: Record<V2SceneId, ScenePalette> = {
127
127
  galaxy: {
128
128
  bg: 0x02_04_12, fog: 0x02_04_12, fogD: 0.011,
129
129
  blocks: [0x00_f0_ff, 0x00_d4_ff, 0x00_b8_ff, 0x00_e8_ff, 0x22_cc_ff, 0x00_a8_ff],
@@ -192,7 +192,7 @@ const PAL: Record<FuturisticSceneId, ScenePalette> = {
192
192
  }
193
193
  }
194
194
 
195
- const CAMS: Record<FuturisticCameraId, CameraMode> = {
195
+ const CAMS: Record<V2CameraId, CameraMode> = {
196
196
  cruise: {
197
197
  pos: (t, mx, my) => ({ x: Math.sin(t * 0.28) * 18 + Math.cos(t * 0.11) * 7 + mx * 10, y: Math.sin(t * 0.19) * 6 + Math.cos(t * 0.31) * 3 + my * 6, z: 0 }),
198
198
  look: (t, mx, my) => ({ x: Math.sin((t + 0.18) * 0.28) * 18 + mx * 8, y: Math.sin((t + 0.18) * 0.19) * 6 + my * 4, z: -25 }),
@@ -232,7 +232,7 @@ const CAMS: Record<FuturisticCameraId, CameraMode> = {
232
232
  }
233
233
  }
234
234
 
235
- const CAM_SPD: Record<FuturisticCameraId, number> = {
235
+ const CAM_SPD: Record<V2CameraId, number> = {
236
236
  cruise: 1,
237
237
  barrel: 1.6,
238
238
  dive: 2.2,
@@ -270,7 +270,7 @@ const makeSkyGradientTexture = (gradient: NonNullable<ScenePalette['gradientBg']
270
270
  return tex
271
271
  }
272
272
 
273
- export class FuturisticMenuBackground implements MenuBackgroundView {
273
+ export class V2MenuBackground implements MenuBackgroundView {
274
274
  readonly scene: THREE.Scene
275
275
  readonly camera: THREE.PerspectiveCamera
276
276
 
@@ -288,8 +288,8 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
288
288
  private readonly bGeo = new THREE.BoxGeometry(1, 1, 1)
289
289
  private readonly eGeo = new THREE.EdgesGeometry(this.bGeo)
290
290
 
291
- private curScene: FuturisticSceneId
292
- private curCam: FuturisticCameraId
291
+ private curScene: V2SceneId
292
+ private curCam: V2CameraId
293
293
  private blockGroup: MinecraftBlockGroupId
294
294
  private cameraSpeed: number
295
295
  private blockSpeed: number
@@ -307,16 +307,19 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
307
307
  private gradientSkyTexture: THREE.CanvasTexture | null = null
308
308
  private disposed = false
309
309
  private animTime = 0
310
+ private texturesApplied = false
311
+ private textureLoadInProgress = false
312
+ private onAssetsTexturesUpdated?: () => void
310
313
 
311
314
  constructor(
312
315
  private readonly documentRenderer: DocumentRenderer,
313
- options: FuturisticMenuBackgroundOptions = {},
316
+ options: V2MenuBackgroundOptions = {},
314
317
  private readonly abortSignal?: AbortSignal
315
318
  ) {
316
319
  const d = MENU_BACKGROUND_OPTION_DEFAULTS
317
- this.curScene = options.initialScene ?? d.futuristicScene
318
- this.curCam = options.initialCamera ?? d.futuristicCamera
319
- this.blockGroup = options.initialBlockGroup ?? d.futuristicBlockGroup
320
+ this.curScene = options.initialScene ?? d.v2Scene
321
+ this.curCam = options.initialCamera ?? d.v2Camera
322
+ this.blockGroup = options.initialBlockGroup ?? d.v2BlockGroup
320
323
  this.cameraSpeed = options.initialCameraSpeed ?? MENU_BACKGROUND_MOTION_DEFAULTS.camera
321
324
  this.blockSpeed = options.initialBlockSpeed ?? MENU_BACKGROUND_MOTION_DEFAULTS.block
322
325
  this.useMinecraftTextures = options.useMinecraftTextures ?? d.minecraftTextures
@@ -420,13 +423,79 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
420
423
  }
421
424
 
422
425
  async init() {
423
- if (this.useMinecraftTextures) {
424
- try {
425
- await this.loadMinecraftTextures()
426
- } catch (err) {
427
- console.warn('[FuturisticMenuBackground] Failed to load Minecraft textures, using solid colors:', err)
428
- this.useMinecraftTextures = false
426
+ if (!this.useMinecraftTextures) return
427
+ void this.scheduleMinecraftTextureLoad()
428
+ }
429
+
430
+ private scheduleMinecraftTextureLoad() {
431
+ if (!this.useMinecraftTextures || this.disposed || this.texturesApplied || this.textureLoadInProgress) return
432
+ void this.tryApplyMinecraftTextures()
433
+ }
434
+
435
+ private attachAssetsListener() {
436
+ const rm = this.resourcesManager
437
+ if (!rm || this.onAssetsTexturesUpdated) return
438
+ this.onAssetsTexturesUpdated = () => this.scheduleMinecraftTextureLoad()
439
+ rm.on('assetsTexturesUpdated', this.onAssetsTexturesUpdated)
440
+ }
441
+
442
+ private detachAssetsListener() {
443
+ const rm = this.resourcesManager
444
+ if (!rm || !this.onAssetsTexturesUpdated) return
445
+ rm.off('assetsTexturesUpdated', this.onAssetsTexturesUpdated)
446
+ this.onAssetsTexturesUpdated = undefined
447
+ }
448
+
449
+ private hasBlockAtlas(resourcesManager: ResourcesManager): boolean {
450
+ const resources = resourcesManager.currentResources
451
+ return !!(resources?.blocksAtlasImage && resources.blocksAtlasJson)
452
+ }
453
+
454
+ private async ensureAtlasReady(resourcesManager: ResourcesManager): Promise<boolean> {
455
+ await this.ensureMcDataLoaded()
456
+ if (this.hasBlockAtlas(resourcesManager)) return true
457
+
458
+ if (typeof document === 'undefined' && resourcesManager !== this.resourcesManager) {
459
+ return false
460
+ }
461
+
462
+ resourcesManager.currentConfig = {
463
+ ...resourcesManager.currentConfig,
464
+ version: MENU_BACKGROUND_MC_VERSION,
465
+ noInventoryGui: true
466
+ }
467
+
468
+ try {
469
+ await resourcesManager.updateAssetsData?.({})
470
+ } catch {
471
+ return false
472
+ }
473
+
474
+ return this.hasBlockAtlas(resourcesManager)
475
+ }
476
+
477
+ private async tryApplyMinecraftTextures() {
478
+ if (this.disposed || !this.useMinecraftTextures || this.texturesApplied) return
479
+
480
+ this.textureLoadInProgress = true
481
+ try {
482
+ const resourcesManager = this.resourcesManager ?? new ResourcesManager()
483
+ const ready = await this.ensureAtlasReady(resourcesManager)
484
+ if (!ready) {
485
+ if (this.resourcesManager) this.attachAssetsListener()
486
+ return
429
487
  }
488
+ if (this.disposed) return
489
+
490
+ this.applyMinecraftTexturesFromAtlas(resourcesManager)
491
+ this.texturesApplied = true
492
+ this.detachAssetsListener()
493
+ } catch (err) {
494
+ console.warn('[V2MenuBackground] Failed to load Minecraft textures, using solid colors:', err)
495
+ this.useMinecraftTextures = false
496
+ this.detachAssetsListener()
497
+ } finally {
498
+ this.textureLoadInProgress = false
430
499
  }
431
500
  }
432
501
 
@@ -626,23 +695,7 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
626
695
  return null
627
696
  }
628
697
 
629
- private async loadMinecraftTextures() {
630
- await this.ensureMcDataLoaded()
631
-
632
- const resourcesManager = this.resourcesManager ?? new ResourcesManager()
633
- const needsAssetUpdate = !resourcesManager.currentResources?.blocksAtlasImage
634
- if (needsAssetUpdate) {
635
- if (typeof document === 'undefined') {
636
- throw new Error('Menu atlas missing in worker; pass resourcesManager from main thread')
637
- }
638
- resourcesManager.currentConfig = {
639
- ...resourcesManager.currentConfig,
640
- version: MENU_BACKGROUND_MC_VERSION,
641
- noInventoryGui: true
642
- }
643
- await resourcesManager.updateAssetsData?.({})
644
- }
645
-
698
+ private applyMinecraftTexturesFromAtlas(resourcesManager: ResourcesManager) {
646
699
  const resources = resourcesManager.currentResources
647
700
  if (!resources?.blocksAtlasImage || !resources.blocksAtlasJson) {
648
701
  throw new Error('Block atlas not available')
@@ -690,8 +743,8 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
690
743
  }
691
744
  }
692
745
 
693
- setScene(name: FuturisticSceneId) {
694
- if (!(FUTURISTIC_SCENE_IDS as readonly string[]).includes(name)) return
746
+ setScene(name: V2SceneId) {
747
+ if (!(V2_SCENE_IDS as readonly string[]).includes(name)) return
695
748
  if (name === this.curScene || this.transitioning) return
696
749
  this.transitioning = true
697
750
  this.curScene = name
@@ -726,8 +779,8 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
726
779
  }, 150)
727
780
  }
728
781
 
729
- setCamera(name: FuturisticCameraId) {
730
- if (!(FUTURISTIC_CAMERA_IDS as readonly string[]).includes(name)) return
782
+ setCamera(name: V2CameraId) {
783
+ if (!(V2_CAMERA_IDS as readonly string[]).includes(name)) return
731
784
  this.curCam = name
732
785
  }
733
786
 
@@ -749,18 +802,15 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
749
802
  mat.dispose()
750
803
  }
751
804
  this.blockMaterialPool.clear()
752
- try {
753
- await this.loadMinecraftTextures()
754
- } catch (err) {
755
- console.warn('[FuturisticMenuBackground] Failed to reload block group textures:', err)
756
- }
805
+ this.texturesApplied = false
806
+ this.scheduleMinecraftTextureLoad()
757
807
  }
758
808
 
759
- getSceneId(): FuturisticSceneId {
809
+ getSceneId(): V2SceneId {
760
810
  return this.curScene
761
811
  }
762
812
 
763
- getCameraId(): FuturisticCameraId {
813
+ getCameraId(): V2CameraId {
764
814
  return this.curCam
765
815
  }
766
816
 
@@ -830,6 +880,7 @@ export class FuturisticMenuBackground implements MenuBackgroundView {
830
880
 
831
881
  dispose() {
832
882
  this.disposed = true
883
+ this.detachAssetsListener()
833
884
  this.scene.clear()
834
885
  this.bGeo.dispose()
835
886
  this.eGeo.dispose()
@@ -1,13 +1,13 @@
1
1
  //@ts-nocheck
2
2
  /** Settings / labels only — no Three.js or DocumentRenderer (safe for defaultOptions imports). */
3
3
 
4
- export const FUTURISTIC_SCENE_IDS = ['galaxy', 'nether', 'end', 'cyber', 'light'] as const
5
- export type FuturisticSceneId = typeof FUTURISTIC_SCENE_IDS[number]
4
+ export const V2_SCENE_IDS = ['galaxy', 'nether', 'end', 'cyber', 'light'] as const
5
+ export type V2SceneId = typeof V2_SCENE_IDS[number]
6
6
 
7
- export const FUTURISTIC_CAMERA_IDS = ['cruise', 'barrel', 'dive', 'orbit', 'snake'] as const
8
- export type FuturisticCameraId = typeof FUTURISTIC_CAMERA_IDS[number]
7
+ export const V2_CAMERA_IDS = ['cruise', 'barrel', 'dive', 'orbit', 'snake'] as const
8
+ export type V2CameraId = typeof V2_CAMERA_IDS[number]
9
9
 
10
- export const FUTURISTIC_SCENE_LABELS: Record<FuturisticSceneId, string> = {
10
+ export const V2_SCENE_LABELS: Record<V2SceneId, string> = {
11
11
  galaxy: 'Galaxy',
12
12
  nether: 'Nether',
13
13
  end: 'The End',
@@ -15,7 +15,7 @@ export const FUTURISTIC_SCENE_LABELS: Record<FuturisticSceneId, string> = {
15
15
  light: 'Light Space'
16
16
  }
17
17
 
18
- export const FUTURISTIC_CAMERA_LABELS: Record<FuturisticCameraId, string> = {
18
+ export const V2_CAMERA_LABELS: Record<V2CameraId, string> = {
19
19
  cruise: 'Cruise',
20
20
  barrel: 'Barrel',
21
21
  dive: 'Dive',
@@ -0,0 +1,29 @@
1
+ //@ts-nocheck
2
+ import { describe, expect, test } from 'vitest'
3
+ import { SectionRequestTracker } from '../worker/mesherWasmRequestTracker'
4
+
5
+ describe('SectionRequestTracker.clearColumn', () => {
6
+ test('removes all pending keys for the column', () => {
7
+ const tracker = new SectionRequestTracker()
8
+ tracker.addRequest('160,64,0')
9
+ tracker.addRequest('160,80,0')
10
+ tracker.addRequest('0,64,0')
11
+
12
+ tracker.clearColumn(160, 0)
13
+
14
+ expect(tracker.hasPending('160,64,0')).toBe(false)
15
+ expect(tracker.hasPending('160,80,0')).toBe(false)
16
+ expect(tracker.hasPending('0,64,0')).toBe(true)
17
+ expect(tracker.size()).toBe(1)
18
+ })
19
+
20
+ test('is a no-op when the column has no pending keys', () => {
21
+ const tracker = new SectionRequestTracker()
22
+ tracker.addRequest('0,64,0')
23
+
24
+ tracker.clearColumn(160, 0)
25
+
26
+ expect(tracker.hasPending('0,64,0')).toBe(true)
27
+ expect(tracker.size()).toBe(1)
28
+ })
29
+ })
@@ -767,6 +767,13 @@ const handleMessage = async (data: any) => {
767
767
  if (!world) break
768
768
  world.removeColumn(data.x, data.z)
769
769
  world.customBlockModels.delete(`${data.x},${data.z}`)
770
+ requestTracker.clearColumn(data.x, data.z)
771
+ for (const key of [...dirtySections.keys()]) {
772
+ const [sx, , sz] = key.split(',').map(Number)
773
+ if (sx === data.x && sz === data.z) {
774
+ dirtySections.delete(key)
775
+ }
776
+ }
770
777
  if (Object.keys(world.columns).length === 0) softCleanup()
771
778
  break
772
779
  }
@@ -49,6 +49,16 @@ export class SectionRequestTracker {
49
49
  this.counts.clear()
50
50
  }
51
51
 
52
+ /** Drop all pending requests for one column (`cx`,`cz` = column origin in block coords). */
53
+ clearColumn (cx: number, cz: number): void {
54
+ for (const key of [...this.counts.keys()]) {
55
+ const [x, , z] = key.split(',').map(Number)
56
+ if (x === cx && z === cz) {
57
+ this.counts.delete(key)
58
+ }
59
+ }
60
+ }
61
+
52
62
  /** Number of distinct keys with pending requests. */
53
63
  size (): number {
54
64
  return this.counts.size
@@ -0,0 +1,38 @@
1
+ //@ts-nocheck
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import { Vec3 } from 'vec3'
4
+ import { WorldView } from './worldView'
5
+
6
+ describe('WorldView._loadChunks spiral guard', () => {
7
+ it('does not let a superseded spiral reset inLoading or panic state', async () => {
8
+ const world = {
9
+ getColumnAt: () => null,
10
+ setBlockStateId: vi.fn(),
11
+ }
12
+ const view = new WorldView(world, 8, new Vec3(0, 64, 0))
13
+ view.addWaitTime = 0
14
+ view.loadChunk = vi.fn(async () => {})
15
+
16
+ const positions = [new Vec3(0, 0, 0)]
17
+ const spiralA = view._loadChunks(positions, new Vec3(0, 64, 0))
18
+ await Promise.resolve()
19
+
20
+ expect(view.inLoading).toBe(true)
21
+ expect(view.spiralNumber).toBe(1)
22
+
23
+ const spiralB = view._loadChunks(positions, new Vec3(0, 64, 0))
24
+ await Promise.resolve()
25
+
26
+ expect(view.spiralNumber).toBe(2)
27
+ expect(view.inLoading).toBe(true)
28
+ expect(view.gotPanicLastTime).toBe(false)
29
+
30
+ await spiralA
31
+ expect(view.inLoading).toBe(true)
32
+ expect(view.gotPanicLastTime).toBe(false)
33
+
34
+ view.waitingSpiralChunksLoad['0,0']?.(true)
35
+ await spiralB
36
+ expect(view.inLoading).toBe(false)
37
+ })
38
+ })
@@ -265,6 +265,8 @@ export class WorldView extends (EventEmitter as new () => TypedEmitter<WorldView
265
265
  this.chunkProgress()
266
266
  })
267
267
 
268
+ if (spiralNumber !== this.spiralNumber) return
269
+
268
270
  if (this.panicTimeout) clearTimeout(this.panicTimeout)
269
271
  this.inLoading = false
270
272
  this.gotPanicLastTime = false