minecraft-renderer 0.1.73 → 0.1.75

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.
@@ -1,11 +1,6 @@
1
1
  //@ts-nocheck
2
2
  import { test, expect, vi, beforeEach } from 'vitest'
3
3
  import * as THREE from 'three'
4
- import { Vec3 } from 'vec3'
5
-
6
- vi.mock('../entity/EntityMesh', () => ({
7
- getMesh: vi.fn(),
8
- }))
9
4
 
10
5
  const renderSignMock = vi.fn()
11
6
  vi.mock('../../sign-renderer', () => ({
@@ -16,25 +11,11 @@ vi.mock('prismarine-chat', () => ({
16
11
  default: () => () => ({}),
17
12
  }))
18
13
 
19
- import { ChunkMeshManager } from '../chunkMeshManager'
14
+ import { getSignTexture, releaseSignTexture, disposeAllSignTextures } from '../signTextureCache'
20
15
  import type { WorldRendererThree } from '../worldRendererThree'
21
16
 
22
- function createManager (): ChunkMeshManager {
23
- const scene = new THREE.Scene()
24
- const material = new THREE.MeshBasicMaterial()
25
- const worldRenderer = {
26
- version: '1.20',
27
- shaderCubeBlocksEnabled: () => false,
28
- getModule: () => undefined,
29
- sceneOrigin: {
30
- track: () => {},
31
- removeAndUntrack: () => {},
32
- removeAndUntrackAll: () => {},
33
- },
34
- blockEntities: {},
35
- worldRendererConfig: {},
36
- } as unknown as WorldRendererThree
37
- return new ChunkMeshManager(worldRenderer, scene, material, 256, 1)
17
+ function createWorldRenderer (): WorldRendererThree {
18
+ return { version: '1.20' } as WorldRendererThree
38
19
  }
39
20
 
40
21
  function stubCanvas () {
@@ -44,40 +25,80 @@ function stubCanvas () {
44
25
  beforeEach(() => {
45
26
  renderSignMock.mockReset()
46
27
  renderSignMock.mockImplementation(() => stubCanvas())
28
+ disposeAllSignTextures()
47
29
  })
48
30
 
49
- test('getSignTexture: same blockEntity returns cached texture without re-render', () => {
50
- const manager = createManager()
51
- const signHeadsRenderer = (manager as unknown as { signHeadsRenderer: { getSignTexture: Function } }).signHeadsRenderer
52
- const pos = new Vec3(10, 64, 10)
31
+ test('getSignTexture: same content at different positions shares one texture', () => {
32
+ const wr = createWorldRenderer()
53
33
  const blockEntity = { Text1: '{"text":"Hello"}' }
54
34
 
55
- const tex1 = signHeadsRenderer.getSignTexture(pos, blockEntity, false)
56
- const tex2 = signHeadsRenderer.getSignTexture(pos, blockEntity, false)
35
+ const tex1 = getSignTexture(wr, blockEntity, false)
36
+ const tex2 = getSignTexture(wr, { ...blockEntity }, false)
57
37
 
58
38
  expect(tex1).toBeDefined()
59
39
  expect(tex2).toBe(tex1)
60
40
  expect(renderSignMock).toHaveBeenCalledTimes(1)
41
+ })
42
+
43
+ test('getSignTexture: different text yields different textures', () => {
44
+ const wr = createWorldRenderer()
61
45
 
62
- manager.dispose()
46
+ const tex1 = getSignTexture(wr, { Text1: '{"text":"Hello"}' }, false)!
47
+ const tex2 = getSignTexture(wr, { Text1: '{"text":"World"}' }, false)!
48
+
49
+ expect(tex2).not.toBe(tex1)
50
+ expect(renderSignMock).toHaveBeenCalledTimes(2)
63
51
  })
64
52
 
65
- test('getSignTexture: changed blockEntity disposes old texture and renders anew', () => {
66
- const manager = createManager()
67
- const signHeadsRenderer = (manager as unknown as { signHeadsRenderer: { getSignTexture: Function } }).signHeadsRenderer
68
- const pos = new Vec3(10, 64, 10)
53
+ test('releaseSignTexture: partial release keeps texture in cache', () => {
54
+ const wr = createWorldRenderer()
69
55
  const blockEntity = { Text1: '{"text":"Hello"}' }
70
56
 
71
- const tex1 = signHeadsRenderer.getSignTexture(pos, blockEntity, false)!
57
+ const tex1 = getSignTexture(wr, blockEntity, false)!
58
+ const tex2 = getSignTexture(wr, blockEntity, false)!
59
+ expect(tex1).toBe(tex2)
60
+
72
61
  const disposeSpy = vi.spyOn(tex1, 'dispose')
62
+ releaseSignTexture(tex1)
63
+
64
+ expect(disposeSpy).not.toHaveBeenCalled()
65
+ expect(getSignTexture(wr, blockEntity, false)).toBe(tex1)
66
+ expect(renderSignMock).toHaveBeenCalledTimes(1)
67
+ })
73
68
 
74
- const changed = { Text1: '{"text":"World"}' }
75
- const tex2 = signHeadsRenderer.getSignTexture(pos, changed, false)
69
+ test('releaseSignTexture: dispose at zero refcount removes cache entry', () => {
70
+ const wr = createWorldRenderer()
71
+ const blockEntity = { Text1: '{"text":"Hello"}' }
76
72
 
77
- expect(tex2).toBeDefined()
78
- expect(tex2).not.toBe(tex1)
73
+ const tex = getSignTexture(wr, blockEntity, false)!
74
+ const disposeSpy = vi.spyOn(tex, 'dispose')
75
+
76
+ releaseSignTexture(tex)
79
77
  expect(disposeSpy).toHaveBeenCalledTimes(1)
78
+
79
+ const tex2 = getSignTexture(wr, blockEntity, false)!
80
+ expect(tex2).not.toBe(tex)
80
81
  expect(renderSignMock).toHaveBeenCalledTimes(2)
82
+ })
83
+
84
+ test('getSignTexture: key ignores irrelevant NBT fields', () => {
85
+ const wr = createWorldRenderer()
86
+ const base = { Text1: '{"text":"Hello"}', Color: 'black' }
81
87
 
82
- manager.dispose()
88
+ const tex1 = getSignTexture(wr, base, false)!
89
+ const tex2 = getSignTexture(wr, { ...base, GlowingText: 1, is_waxed: 1 }, false)!
90
+
91
+ expect(tex2).toBe(tex1)
92
+ expect(renderSignMock).toHaveBeenCalledTimes(1)
93
+ })
94
+
95
+ test('getSignTexture: isHanging is part of cache key', () => {
96
+ const wr = createWorldRenderer()
97
+ const blockEntity = { Text1: '{"text":"Hello"}' }
98
+
99
+ const standing = getSignTexture(wr, blockEntity, false)!
100
+ const hanging = getSignTexture(wr, blockEntity, true)!
101
+
102
+ expect(hanging).not.toBe(standing)
103
+ expect(renderSignMock).toHaveBeenCalledTimes(2)
83
104
  })
@@ -2,12 +2,10 @@
2
2
  import * as THREE from 'three'
3
3
  import { Vec3 } from 'vec3'
4
4
  import nbt from 'prismarine-nbt'
5
- import PrismarineChatLoader from 'prismarine-chat'
6
5
  import * as tweenJs from '@tweenjs/tween.js'
7
6
  import { Biome } from 'minecraft-data'
8
- import { renderSign } from '../sign-renderer'
9
7
  import { DisplayWorldOptions, GraphicsInitOptions } from '../graphicsBackend/types'
10
- import { chunkPos, sectionPos } from '../lib/simpleUtils'
8
+ import { sectionPos } from '../lib/simpleUtils'
11
9
  import { WorldRendererCommon } from '../lib/worldrendererCommon'
12
10
  import { calculateSkyLightSimple } from '../lib/skyLight'
13
11
  import { addNewStat, MC_RENDERER_DEBUG_OVERLAY_CLASS } from '../lib/ui/newStats'
@@ -55,8 +53,6 @@ export class WorldRendererThree extends WorldRendererCommon {
55
53
  get sectionObjects() {
56
54
  return this.chunkMeshManager.sectionObjects
57
55
  }
58
- chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
59
- signsCache = new Map<string, any>()
60
56
  cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
61
57
  holdingBlock: IHoldingBlock
62
58
  holdingBlockLeft: IHoldingBlock
@@ -355,6 +351,11 @@ export class WorldRendererThree extends WorldRendererCommon {
355
351
  return targetState
356
352
  }
357
353
 
354
+ setRain(enabled: boolean): void {
355
+ this.worldRendererConfig.isRaining = enabled
356
+ this.toggleModule('rain', enabled)
357
+ }
358
+
358
359
  /**
359
360
  * Dispose all modules
360
361
  */
@@ -696,7 +697,7 @@ export class WorldRendererThree extends WorldRendererCommon {
696
697
  this.syncSkyLevelFromTime(newTime)
697
698
  }
698
699
 
699
- private syncSkyLevelFromTime (timeOfDay: number): void {
700
+ private syncSkyLevelFromTime(timeOfDay: number): void {
700
701
  const skyLevel = calculateSkyLightSimple(timeOfDay) / 15
701
702
  this.chunkMeshManager.setSkyLevel(skyLevel)
702
703
  }
@@ -792,10 +793,6 @@ export class WorldRendererThree extends WorldRendererCommon {
792
793
  }
793
794
  }
794
795
 
795
- override updateViewerPosition(pos: Vec3): void {
796
- this.viewerChunkPosition = pos
797
- }
798
-
799
796
  cameraSectionPositionUpdate() {
800
797
  // eslint-disable-next-line guard-for-in
801
798
  for (const key in this.sectionObjects) {
@@ -928,28 +925,6 @@ export class WorldRendererThree extends WorldRendererCommon {
928
925
  }
929
926
 
930
927
 
931
- getSignTexture(position: Vec3, blockEntity, isHanging, backSide = false) {
932
- const chunk = chunkPos(position)
933
- let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`)
934
- if (!textures) {
935
- textures = {}
936
- this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
937
- }
938
- const texturekey = `${position.x},${position.y},${position.z}`
939
- // todo investigate bug and remove this so don't need to clean in section dirty
940
- if (textures[texturekey]) return textures[texturekey]
941
-
942
- const PrismarineChat = PrismarineChatLoader(this.version)
943
- const canvas = renderSign(blockEntity, isHanging, PrismarineChat)
944
- if (!canvas) return
945
- const tex = new THREE.Texture(canvas)
946
- tex.magFilter = THREE.NearestFilter
947
- tex.minFilter = THREE.NearestFilter
948
- tex.needsUpdate = true
949
- textures[texturekey] = tex
950
- return tex
951
- }
952
-
953
928
  getCameraPosition(target?: THREE.Vector3): THREE.Vector3 {
954
929
  return (target ?? this._tmpCameraPos).set(this.cameraWorldPos.x, this.cameraWorldPos.y, this.cameraWorldPos.z)
955
930
  }
@@ -1442,49 +1417,6 @@ export class WorldRendererThree extends WorldRendererCommon {
1442
1417
  }
1443
1418
  }
1444
1419
 
1445
- renderSign(position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
1446
- const tex = this.getSignTexture(position, blockEntity, isHanging)
1447
-
1448
- if (!tex) return
1449
-
1450
- // todo implement
1451
- // const key = JSON.stringify({ position, rotation, isWall })
1452
- // if (this.signsCache.has(key)) {
1453
- // console.log('cached', key)
1454
- // } else {
1455
- // this.signsCache.set(key, tex)
1456
- // }
1457
-
1458
- const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true }))
1459
- mesh.renderOrder = 999
1460
-
1461
- const lineHeight = 7 / 16
1462
- const scaleFactor = isHanging ? 1.3 : 1
1463
- mesh.scale.set(1 * scaleFactor, lineHeight * scaleFactor, 1 * scaleFactor)
1464
-
1465
- const thickness = (isHanging ? 2 : 1.5) / 16
1466
- const wallSpacing = 0.25 / 16
1467
- if (isWall && !isHanging) {
1468
- mesh.position.set(0, 0, -0.5 + thickness + wallSpacing + 0.0001)
1469
- } else {
1470
- mesh.position.set(0, 0, thickness / 2 + 0.0001)
1471
- }
1472
-
1473
- const group = new THREE.Group()
1474
- group.rotation.set(
1475
- 0,
1476
- -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
1477
- 0
1478
- )
1479
- group.add(mesh)
1480
- const height = (isHanging ? 10 : 8) / 16
1481
- const heightOffset = (isHanging ? 0 : isWall ? 4.333 : 9.333) / 16
1482
- const textPosition = height / 2 + heightOffset
1483
- this.sceneOrigin.track(group)
1484
- group.position.set(position.x + 0.5, position.y + textPosition, position.z + 0.5)
1485
- return group
1486
- }
1487
-
1488
1420
  lightUpdate(chunkX: number, chunkZ: number) {
1489
1421
  // set all sections in the chunk dirty
1490
1422
  for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
@@ -1534,19 +1466,6 @@ export class WorldRendererThree extends WorldRendererCommon {
1534
1466
  }))
1535
1467
  }
1536
1468
 
1537
- cleanChunkTextures(x, z) {
1538
- const textures = this.chunkTextures.get(`${Math.floor(x / 16)},${Math.floor(z / 16)}`) ?? {}
1539
- for (const key of Object.keys(textures)) {
1540
- textures[key].dispose()
1541
- delete textures[key]
1542
- }
1543
- // Sign / head textures moved to ChunkMeshManager.signHeadsRenderer in PR
1544
- // #16; without invalidating that cache here, sign edits (and any other
1545
- // block-entity NBT change picked up via setSectionDirty) would re-render
1546
- // with the stale cached canvas until a full world reset.
1547
- this.chunkMeshManager.cleanSignChunkTextures(x, z)
1548
- }
1549
-
1550
1469
  readdChunks() {
1551
1470
  for (const key of Object.keys(this.sectionObjects)) {
1552
1471
  this.scene.remove(this.sectionObjects[key])
@@ -1568,7 +1487,6 @@ export class WorldRendererThree extends WorldRendererCommon {
1568
1487
  removeColumn(x, z) {
1569
1488
  super.removeColumn(x, z)
1570
1489
 
1571
- this.cleanChunkTextures(x, z)
1572
1490
  this.clearPendingSectionUpdatesForChunk(x, z)
1573
1491
  const sectionHeight = this.getSectionHeight()
1574
1492
  const worldMinY = this.worldMinYRender
@@ -1583,7 +1501,7 @@ export class WorldRendererThree extends WorldRendererCommon {
1583
1501
  this.chunkMeshManager.onChunkRemovedFromGate(`${x},${z}`)
1584
1502
  }
1585
1503
 
1586
- updateViewerPosition(pos: Vec3) {
1504
+ override updateViewerPosition(pos: Vec3) {
1587
1505
  super.updateViewerPosition(pos)
1588
1506
  if (this.chunkMeshManager.pendingNearReveal.size > 0) {
1589
1507
  this.chunkMeshManager.tryRevealPending()
@@ -23,6 +23,12 @@ import { renderWasmOutputToGeometry } from '../bridge/render-from-wasm'
23
23
 
24
24
  const VERSION = '1.16.5'
25
25
  const STONE = 1
26
+
27
+ function requireShaderCubeResources() {
28
+ const resources = getShaderCubeResources()
29
+ if (!resources) throw new Error('shader cube resources unavailable in test')
30
+ return resources
31
+ }
26
32
  /** mc-assets blocksAtlases.json → stone */
27
33
  const STONE_ATLAS_TILE_INDEX = 552
28
34
 
@@ -39,7 +45,7 @@ test('packWord2: AO diagonal flip sets bit 12', () => {
39
45
  light_data: [[1, 1, 1, 1]],
40
46
  light_combined: [[255, 255, 255, 255]],
41
47
  }
42
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
48
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
43
49
  const model = {
44
50
  elements: [{
45
51
  faces: {
@@ -78,7 +84,7 @@ test('packWord0: section-local lx/ly/lz and face id', () => {
78
84
  light_data: [[0.5, 0.5, 0.5, 0.5]],
79
85
  light_combined: [[128, 128, 128, 128]],
80
86
  }
81
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
87
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
82
88
  const model = {
83
89
  elements: [{
84
90
  faces: {
@@ -111,7 +117,7 @@ test('packWord0: section-local lx/ly/lz and face id', () => {
111
117
  })
112
118
 
113
119
  test('isShaderCubeBlock: rejects model rotation and sectionHeight !== 16', () => {
114
- const { textureIndexMapping } = getShaderCubeResources()
120
+ const { textureIndexMapping } = requireShaderCubeResources()
115
121
  const baseModel = {
116
122
  elements: [{
117
123
  faces: {
@@ -222,7 +228,7 @@ test('south face: AO corners remapped to shader order (elemFaces [0,3,1,2] → s
222
228
  light_data: [[1, 1, 1, 1]],
223
229
  light_combined: [[10, 20, 30, 40]],
224
230
  }
225
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
231
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
226
232
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
227
233
  tryBuildShaderCubeInstances(
228
234
  block,
@@ -248,7 +254,7 @@ test('south face: diagonal flip uses remapped AO (differs from raw elemFaces for
248
254
  light_data: [[1, 1, 1, 1]],
249
255
  light_combined: [[255, 255, 255, 255]],
250
256
  }
251
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
257
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
252
258
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
253
259
  const opts = {
254
260
  sectionOrigin: { x: 0, y: 0, z: 0 },
@@ -285,7 +291,7 @@ test('doAO false: full bright AO/light and no diagonal flip', () => {
285
291
  light_data: [[0, 0, 0, 0]],
286
292
  light_combined: [[0, 0, 0, 0]],
287
293
  }
288
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
294
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
289
295
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
290
296
  tryBuildShaderCubeInstances(
291
297
  block,
@@ -329,7 +335,7 @@ test.each(SECTION_ORIGIN_ROUND_TRIP_CASES)(
329
335
  light_data: [[1, 1, 1, 1]],
330
336
  light_combined: [[255, 255, 255, 255]],
331
337
  }
332
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
338
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
333
339
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
334
340
  tryBuildShaderCubeInstances(
335
341
  block,
@@ -361,7 +367,7 @@ test('section index relative decode past 2^20: exact integer subtract', () => {
361
367
  light_data: [[1, 1, 1, 1]],
362
368
  light_combined: [[255, 255, 255, 255]],
363
369
  }
364
- const { textureIndexMapping, tintPalette } = getShaderCubeResources()
370
+ const { textureIndexMapping, tintPalette } = requireShaderCubeResources()
365
371
  const model = { elements: [{ faces: SIX_FACE_TEXTURES }] }
366
372
  tryBuildShaderCubeInstances(
367
373
  block,