minecraft-renderer 0.1.74 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-renderer",
3
- "version": "0.1.74",
3
+ "version": "0.1.75",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,5 +1,4 @@
1
1
  //@ts-nocheck
2
- import PrismarineChatLoader from 'prismarine-chat'
3
2
  import * as THREE from 'three'
4
3
  import * as nbt from 'prismarine-nbt'
5
4
  import { Vec3 } from 'vec3'
@@ -20,13 +19,12 @@ import {
20
19
  sectionAabbIntersectsRay,
21
20
  type ShaderSectionRaycastEntry,
22
21
  } from './sectionRaycastAabb'
23
- import { chunkPos } from '../lib/simpleUtils'
24
- import { renderSign } from '../sign-renderer'
25
22
  import { getMesh } from './entity/EntityMesh'
26
23
  import type { WorldRendererThree } from './worldRendererThree'
27
24
  import { armorModel } from './entity/armorModels'
28
25
  import { disposeObject } from './threeJsUtils'
29
26
  import { getBannerTexture, createBannerMesh, releaseBannerTexture } from './bannerRenderer'
27
+ import { getSignTexture, releaseSignTexture, disposeAllSignTextures } from './signTextureCache'
30
28
  import { BlockEntityLightRegistry } from '../lib/blockEntityLightRegistry'
31
29
 
32
30
  export interface ChunkMeshPool {
@@ -1348,8 +1346,12 @@ export class ChunkMeshManager {
1348
1346
  sectionObject.shaderMesh = undefined
1349
1347
  }
1350
1348
  delete sectionObject.deferredShaderCubes
1351
- // Dispose signs and heads containers
1349
+ // Release shared sign textures (refcount) before disposing meshes
1352
1350
  if (sectionObject.signsContainer) {
1351
+ for (const child of sectionObject.signsContainer.children) {
1352
+ const sign = child as THREE.Group & { signTexture?: THREE.Texture }
1353
+ if (sign.signTexture) releaseSignTexture(sign.signTexture)
1354
+ }
1353
1355
  this.disposeContainer(sectionObject.signsContainer, false)
1354
1356
  }
1355
1357
  if (sectionObject.headsContainer) {
@@ -1473,16 +1475,6 @@ export class ChunkMeshManager {
1473
1475
  }
1474
1476
  }
1475
1477
 
1476
- /**
1477
- * Forward to {@link SignHeadsRenderer.cleanChunkTextures} so callers in
1478
- * `WorldRendererThree` (which historically owned the sign-texture cache)
1479
- * can invalidate cached sign textures when a section is marked dirty,
1480
- * without reaching into the manager's private members.
1481
- */
1482
- cleanSignChunkTextures (x: number, z: number) {
1483
- this.signHeadsRenderer.cleanChunkTextures(x, z)
1484
- }
1485
-
1486
1478
  /**
1487
1479
  * Get mesh for section if it exists
1488
1480
  */
@@ -1923,21 +1915,12 @@ export class ChunkMeshManager {
1923
1915
  }
1924
1916
 
1925
1917
 
1926
- type SignTextureCacheEntry = { tex: THREE.Texture, signature: string }
1927
-
1928
1918
  class SignHeadsRenderer {
1929
- chunkTextures = new Map<string, { [pos: string]: SignTextureCacheEntry }>()
1930
-
1931
1919
  constructor (public worldRendererThree: WorldRendererThree) {
1932
1920
  }
1933
1921
 
1934
1922
  dispose () {
1935
- for (const [, textures] of this.chunkTextures) {
1936
- for (const key of Object.keys(textures)) {
1937
- textures[key]!.tex.dispose()
1938
- }
1939
- }
1940
- this.chunkTextures.clear()
1923
+ disposeAllSignTextures()
1941
1924
  }
1942
1925
 
1943
1926
  renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) {
@@ -1981,18 +1964,10 @@ class SignHeadsRenderer {
1981
1964
  }
1982
1965
 
1983
1966
  renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
1984
- const tex = this.getSignTexture(position, blockEntity, isHanging)
1967
+ const tex = getSignTexture(this.worldRendererThree, blockEntity, isHanging)
1985
1968
 
1986
1969
  if (!tex) return
1987
1970
 
1988
- // todo implement
1989
- // const key = JSON.stringify({ position, rotation, isWall })
1990
- // if (this.signsCache.has(key)) {
1991
- // console.log('cached', key)
1992
- // } else {
1993
- // this.signsCache.set(key, tex)
1994
- // }
1995
-
1996
1971
  const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true }))
1997
1972
  mesh.renderOrder = 999
1998
1973
 
@@ -2008,13 +1983,14 @@ class SignHeadsRenderer {
2008
1983
  mesh.position.set(0, 0, thickness / 2 + 0.0001)
2009
1984
  }
2010
1985
 
2011
- const group = new THREE.Group()
1986
+ const group = new THREE.Group() as THREE.Group & { signTexture?: THREE.Texture }
2012
1987
  group.rotation.set(
2013
1988
  0,
2014
1989
  -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
2015
1990
  0
2016
1991
  )
2017
1992
  group.add(mesh)
1993
+ group.signTexture = tex
2018
1994
  const height = (isHanging ? 10 : 8) / 16
2019
1995
  const heightOffset = (isHanging ? 0 : isWall ? 4.333 : 9.333) / 16
2020
1996
  const textPosition = height / 2 + heightOffset
@@ -2022,47 +1998,4 @@ class SignHeadsRenderer {
2022
1998
  group.position.set(position.x + 0.5, position.y + textPosition, position.z + 0.5)
2023
1999
  return group
2024
2000
  }
2025
-
2026
- getSignTexture (position: Vec3, blockEntity, isHanging, backSide = false) {
2027
- const chunk = chunkPos(position)
2028
- let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`)
2029
- if (!textures) {
2030
- textures = {}
2031
- this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
2032
- }
2033
- const texturekey = `${position.x},${position.y},${position.z}`
2034
- const signature = JSON.stringify(blockEntity) + '|' + isHanging + '|' + backSide
2035
- const cached = textures[texturekey]
2036
- if (cached && cached.signature === signature) return cached.tex
2037
-
2038
- if (cached?.tex) cached.tex.dispose()
2039
-
2040
- const PrismarineChat = PrismarineChatLoader(this.worldRendererThree.version)
2041
- const canvas = renderSign(blockEntity, isHanging, PrismarineChat)
2042
- if (!canvas) return
2043
- const tex = new THREE.Texture(canvas)
2044
- tex.magFilter = THREE.NearestFilter
2045
- tex.minFilter = THREE.NearestFilter
2046
- tex.needsUpdate = true
2047
- textures[texturekey] = { tex, signature }
2048
- return tex
2049
- }
2050
-
2051
- /**
2052
- * Dispose all cached sign textures for the chunk containing world coords
2053
- * (x, z). Called from `WorldRendererThree.cleanChunkTextures` so that
2054
- * re-meshes triggered by `setSectionDirty` (e.g. a player edits a sign)
2055
- * pick up fresh block-entity NBT instead of returning the stale cached
2056
- * texture from {@link SignHeadsRenderer.getSignTexture}.
2057
- */
2058
- cleanChunkTextures (x: number, z: number) {
2059
- const key = `${Math.floor(x / 16)},${Math.floor(z / 16)}`
2060
- const textures = this.chunkTextures.get(key)
2061
- if (!textures) return
2062
- const disposedKeys = Object.keys(textures)
2063
- for (const k of disposedKeys) {
2064
- textures[k]!.tex.dispose()
2065
- delete textures[k]
2066
- }
2067
- }
2068
2001
  }
@@ -0,0 +1,64 @@
1
+ //@ts-nocheck
2
+ import * as THREE from 'three'
3
+ import PrismarineChatLoader from 'prismarine-chat'
4
+ import { renderSign } from '../sign-renderer'
5
+ import type { WorldRendererThree } from './worldRendererThree'
6
+
7
+ const signTextureCache = new Map<string, { texture: THREE.Texture, refCount: number }>()
8
+
9
+ // Build the key ONLY from fields that change rendered pixels, so two signs
10
+ // with identical visible text share a texture even if other NBT differs.
11
+ function createSignCacheKey (blockEntity: any, isHanging: boolean, backSide: boolean): string {
12
+ let lines: string[]
13
+ let color: string
14
+ if (blockEntity && 'front_text' in blockEntity) { // 1.20+
15
+ lines = blockEntity.front_text?.messages ?? []
16
+ color = blockEntity.front_text?.color || 'black'
17
+ } else { // legacy
18
+ lines = [blockEntity?.Text1, blockEntity?.Text2, blockEntity?.Text3, blockEntity?.Text4]
19
+ color = blockEntity?.Color || 'black'
20
+ }
21
+ // \0 separator: cannot appear in JSON text components, so no key collisions
22
+ return `${isHanging ? 1 : 0}|${backSide ? 1 : 0}|${color}|${lines.join('\0')}`
23
+ }
24
+
25
+ export function getSignTexture (
26
+ worldRenderer: WorldRendererThree,
27
+ blockEntity: any,
28
+ isHanging: boolean,
29
+ backSide = false
30
+ ): THREE.Texture | undefined {
31
+ const cacheKey = createSignCacheKey(blockEntity, isHanging, backSide)
32
+ const cached = signTextureCache.get(cacheKey)
33
+ if (cached) {
34
+ cached.refCount++
35
+ return cached.texture
36
+ }
37
+ const PrismarineChat = PrismarineChatLoader(worldRenderer.version)
38
+ const canvas = renderSign(blockEntity, isHanging, PrismarineChat)
39
+ if (!canvas) return undefined
40
+ const tex = new THREE.Texture(canvas)
41
+ tex.magFilter = THREE.NearestFilter
42
+ tex.minFilter = THREE.NearestFilter
43
+ tex.needsUpdate = true
44
+ signTextureCache.set(cacheKey, { texture: tex, refCount: 1 })
45
+ return tex
46
+ }
47
+
48
+ export function releaseSignTexture (texture: THREE.Texture): void {
49
+ for (const [key, cached] of signTextureCache.entries()) {
50
+ if (cached.texture === texture) {
51
+ cached.refCount--
52
+ if (cached.refCount <= 0) {
53
+ cached.texture.dispose()
54
+ signTextureCache.delete(key)
55
+ }
56
+ return
57
+ }
58
+ }
59
+ }
60
+
61
+ export function disposeAllSignTextures (): void {
62
+ for (const [, cached] of signTextureCache) cached.texture.dispose()
63
+ signTextureCache.clear()
64
+ }
@@ -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
@@ -929,28 +925,6 @@ export class WorldRendererThree extends WorldRendererCommon {
929
925
  }
930
926
 
931
927
 
932
- getSignTexture(position: Vec3, blockEntity, isHanging, backSide = false) {
933
- const chunk = chunkPos(position)
934
- let textures = this.chunkTextures.get(`${chunk[0]},${chunk[1]}`)
935
- if (!textures) {
936
- textures = {}
937
- this.chunkTextures.set(`${chunk[0]},${chunk[1]}`, textures)
938
- }
939
- const texturekey = `${position.x},${position.y},${position.z}`
940
- // todo investigate bug and remove this so don't need to clean in section dirty
941
- if (textures[texturekey]) return textures[texturekey]
942
-
943
- const PrismarineChat = PrismarineChatLoader(this.version)
944
- const canvas = renderSign(blockEntity, isHanging, PrismarineChat)
945
- if (!canvas) return
946
- const tex = new THREE.Texture(canvas)
947
- tex.magFilter = THREE.NearestFilter
948
- tex.minFilter = THREE.NearestFilter
949
- tex.needsUpdate = true
950
- textures[texturekey] = tex
951
- return tex
952
- }
953
-
954
928
  getCameraPosition(target?: THREE.Vector3): THREE.Vector3 {
955
929
  return (target ?? this._tmpCameraPos).set(this.cameraWorldPos.x, this.cameraWorldPos.y, this.cameraWorldPos.z)
956
930
  }
@@ -1443,49 +1417,6 @@ export class WorldRendererThree extends WorldRendererCommon {
1443
1417
  }
1444
1418
  }
1445
1419
 
1446
- renderSign(position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
1447
- const tex = this.getSignTexture(position, blockEntity, isHanging)
1448
-
1449
- if (!tex) return
1450
-
1451
- // todo implement
1452
- // const key = JSON.stringify({ position, rotation, isWall })
1453
- // if (this.signsCache.has(key)) {
1454
- // console.log('cached', key)
1455
- // } else {
1456
- // this.signsCache.set(key, tex)
1457
- // }
1458
-
1459
- const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true }))
1460
- mesh.renderOrder = 999
1461
-
1462
- const lineHeight = 7 / 16
1463
- const scaleFactor = isHanging ? 1.3 : 1
1464
- mesh.scale.set(1 * scaleFactor, lineHeight * scaleFactor, 1 * scaleFactor)
1465
-
1466
- const thickness = (isHanging ? 2 : 1.5) / 16
1467
- const wallSpacing = 0.25 / 16
1468
- if (isWall && !isHanging) {
1469
- mesh.position.set(0, 0, -0.5 + thickness + wallSpacing + 0.0001)
1470
- } else {
1471
- mesh.position.set(0, 0, thickness / 2 + 0.0001)
1472
- }
1473
-
1474
- const group = new THREE.Group()
1475
- group.rotation.set(
1476
- 0,
1477
- -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
1478
- 0
1479
- )
1480
- group.add(mesh)
1481
- const height = (isHanging ? 10 : 8) / 16
1482
- const heightOffset = (isHanging ? 0 : isWall ? 4.333 : 9.333) / 16
1483
- const textPosition = height / 2 + heightOffset
1484
- this.sceneOrigin.track(group)
1485
- group.position.set(position.x + 0.5, position.y + textPosition, position.z + 0.5)
1486
- return group
1487
- }
1488
-
1489
1420
  lightUpdate(chunkX: number, chunkZ: number) {
1490
1421
  // set all sections in the chunk dirty
1491
1422
  for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
@@ -1535,19 +1466,6 @@ export class WorldRendererThree extends WorldRendererCommon {
1535
1466
  }))
1536
1467
  }
1537
1468
 
1538
- cleanChunkTextures(x, z) {
1539
- const textures = this.chunkTextures.get(`${Math.floor(x / 16)},${Math.floor(z / 16)}`) ?? {}
1540
- for (const key of Object.keys(textures)) {
1541
- textures[key].dispose()
1542
- delete textures[key]
1543
- }
1544
- // Sign / head textures moved to ChunkMeshManager.signHeadsRenderer in PR
1545
- // #16; without invalidating that cache here, sign edits (and any other
1546
- // block-entity NBT change picked up via setSectionDirty) would re-render
1547
- // with the stale cached canvas until a full world reset.
1548
- this.chunkMeshManager.cleanSignChunkTextures(x, z)
1549
- }
1550
-
1551
1469
  readdChunks() {
1552
1470
  for (const key of Object.keys(this.sectionObjects)) {
1553
1471
  this.scene.remove(this.sectionObjects[key])
@@ -1569,7 +1487,6 @@ export class WorldRendererThree extends WorldRendererCommon {
1569
1487
  removeColumn(x, z) {
1570
1488
  super.removeColumn(x, z)
1571
1489
 
1572
- this.cleanChunkTextures(x, z)
1573
1490
  this.clearPendingSectionUpdatesForChunk(x, z)
1574
1491
  const sectionHeight = this.getSectionHeight()
1575
1492
  const worldMinY = this.worldMinYRender