minecraft-renderer 0.1.73 → 0.1.74

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.73",
3
+ "version": "0.1.74",
4
4
  "description": "The most Modular Minecraft world renderer with Three.js WebGL backend",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -81,6 +81,10 @@ export const defaultWorldRendererConfig = {
81
81
  isPlayground: false,
82
82
  instantCameraUpdate: false,
83
83
  isRaining: false,
84
+ // rainColor: 'rgb(64, 87, 148)', // original minecraft blue
85
+ rainColor: 'rgb(118, 148, 226)',
86
+ /** Rain particle opacity 0–1. */
87
+ rainOpacity: 0.5,
84
88
 
85
89
  // Module states: 'enabled' = force on, 'disabled' = force off, 'auto' = use autoEnableCheck
86
90
  moduleStates: {} as Record<string, 'enabled' | 'disabled' | 'auto'>
@@ -101,6 +101,7 @@ export interface GraphicsInitOptions<S = any> {
101
101
  /** Live app options (e.g. valtio proxy); used for WebGL `gpuPreference` at context creation. */
102
102
  getRendererOptions?: () => RendererStorageOptions
103
103
  rendererSpecificSettings: S
104
+ hello?: boolean
104
105
  callbacks: {
105
106
  displayCriticalError: (error: Error) => void
106
107
  setRendererSpecificSettings: (key: string, value: any) => void
@@ -10,7 +10,7 @@ import type { WorldViewWorker } from '../worldView'
10
10
  export function bindAbortableListener<E extends keyof WorldViewEvents>(
11
11
  emitter: Pick<WorldViewWorker, 'on' | 'off'>,
12
12
  event: E,
13
- handler: (...args: WorldViewEvents[E]) => void,
13
+ handler: (...args: Parameters<WorldViewEvents[E]>) => void,
14
14
  signal: AbortSignal
15
15
  ): void {
16
16
  emitter.on(event, handler as (...args: any[]) => void)
@@ -12,7 +12,7 @@ export type PlayerObjectType = PlayerObject & {
12
12
 
13
13
  /** Starfield + log-depth world: cutout skin mats need alphaTest and depthWrite (not mesh traverse). */
14
14
  export function configurePlayerSkinMaterials (playerObject: PlayerObject): void {
15
- const skin = playerObject.skin
15
+ const skin = playerObject.skin as any
16
16
  const materials = [
17
17
  skin.layer1Material,
18
18
  skin.layer1MaterialBiased,
@@ -6,6 +6,7 @@ import { proxy } from 'valtio'
6
6
  import * as worldRendererModule from './worldrendererCommon'
7
7
  import { WorldRendererCommon } from './worldrendererCommon'
8
8
  import { defaultWorldRendererConfig } from '../graphicsBackend/config'
9
+ import { defaultPerformanceInstabilityFactors } from '../performanceMonitor'
9
10
  import { getInitialPlayerState } from '../playerState/playerState'
10
11
  import type { DisplayWorldOptions, GraphicsInitOptions } from '../graphicsBackend/types'
11
12
 
@@ -46,6 +47,8 @@ class TestWorldRenderer extends WorldRendererCommon {
46
47
  updateCamera() {}
47
48
  render() {}
48
49
  updateShowChunksBorder() {}
50
+ updatePlayerEntity() {}
51
+ worldStop() {}
49
52
  }
50
53
 
51
54
  function createRenderer(workerCount = 2, worldView?: DisplayWorldOptions['worldView']) {
@@ -56,7 +59,7 @@ function createRenderer(workerCount = 2, worldView?: DisplayWorldOptions['worldV
56
59
  heightmaps: {} as Record<string, Int16Array>,
57
60
  allChunksLoaded: false,
58
61
  mesherWork: false,
59
- instabilityFactors: {},
62
+ instabilityFactors: defaultPerformanceInstabilityFactors(),
60
63
  intersectMedia: null,
61
64
  },
62
65
  renderer: '',
@@ -5,6 +5,7 @@ import { Vec3 } from 'vec3'
5
5
  import { proxy } from 'valtio'
6
6
  import { WorldRendererCommon } from './worldrendererCommon'
7
7
  import { defaultWorldRendererConfig } from '../graphicsBackend/config'
8
+ import { defaultPerformanceInstabilityFactors } from '../performanceMonitor'
8
9
  import { getInitialPlayerState } from '../playerState/playerState'
9
10
  import type { DisplayWorldOptions, GraphicsInitOptions } from '../graphicsBackend/types'
10
11
 
@@ -45,16 +46,18 @@ class TestWorldRenderer extends WorldRendererCommon {
45
46
  updateCamera() {}
46
47
  render() {}
47
48
  updateShowChunksBorder() {}
49
+ updatePlayerEntity() {}
50
+ worldStop() {}
48
51
  }
49
52
 
50
53
  function createRenderer() {
51
54
  const rendererState = proxy({
52
55
  world: {
53
- chunksLoaded: new Set<string>(),
54
- heightmaps: new Map<string, Int16Array>(),
56
+ chunksLoaded: {} as Record<string, true>,
57
+ heightmaps: {} as Record<string, Int16Array>,
55
58
  allChunksLoaded: false,
56
59
  mesherWork: false,
57
- instabilityFactors: {},
60
+ instabilityFactors: defaultPerformanceInstabilityFactors(),
58
61
  intersectMedia: null,
59
62
  },
60
63
  renderer: '',
@@ -73,6 +76,7 @@ function createRenderer() {
73
76
  avgRenderTime: 0,
74
77
  world: {
75
78
  chunksLoaded: new Set<string>(),
79
+ chunksLoadedCount: 0,
76
80
  chunksTotalNumber: 0,
77
81
  chunksFullInfo: '',
78
82
  },
@@ -93,7 +97,7 @@ function createRenderer() {
93
97
  },
94
98
  }
95
99
 
96
- const renderer = new TestWorldRenderer(displayOptions.resourcesManager, displayOptions, initOptions)
100
+ const renderer = new TestWorldRenderer(displayOptions.resourcesManager, displayOptions as DisplayWorldOptions, initOptions)
97
101
  renderer.active = true
98
102
  renderer.workers = [{ postMessage: vi.fn() }, { postMessage: vi.fn() }]
99
103
  renderer.viewDistance = 16
@@ -1443,7 +1443,7 @@ export const initMesherWorker = (onGotMessage: (data: any) => void, workerName =
1443
1443
  let mesherMcDataTintsMissingWarned = false
1444
1444
 
1445
1445
  export const meshersSendMcData = (workers: Worker[], version: string, mcDataKeys = dynamicMcDataFiles, mcDataFull: IndexedData) => {
1446
- const mcData = {
1446
+ const mcData: { version: IndexedData['version']; tints?: unknown; [key: string]: unknown } = {
1447
1447
  version: JSON.parse(JSON.stringify(mcDataFull.version))
1448
1448
  }
1449
1449
  for (const [finalKey, sourceKey] of Object.entries(mcDataKeys)) {
@@ -270,7 +270,10 @@ export function createBannerMesh(
270
270
  mesh.position.set(clothXOffset, clothYOffset, clothZPosition + thickness / 2 + 0.004)
271
271
  }
272
272
 
273
- const group = new THREE.Group() as THREE.Group & { bannerTexture?: THREE.Texture }
273
+ const group = new THREE.Group() as THREE.Group & {
274
+ bannerTexture?: THREE.Texture
275
+ bannerMaterial?: THREE.MeshBasicMaterial
276
+ }
274
277
  group.rotation.set(
275
278
  0,
276
279
  -THREE.MathUtils.degToRad(rotation * (isWall ? 90 : 45 / 2)),
@@ -552,7 +552,8 @@ export class Entities {
552
552
  currentSkinUrls = {} as Record<string, string>
553
553
 
554
554
  private isCanvasBlank(canvas: HTMLCanvasElement | OffscreenCanvas): boolean {
555
- return !canvas.getContext('2d')
555
+ const ctx = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null
556
+ return !ctx
556
557
  ?.getImageData(0, 0, canvas.width, canvas.height).data
557
558
  .some(channel => channel !== 0)
558
559
  }
@@ -69,7 +69,7 @@ export const getBackendMethods = (worldRenderer: WorldRendererThree): any => {
69
69
  // New method for updating skybox
70
70
  setSkyboxImage: worldRenderer.skyboxRenderer.setSkyboxImage.bind(worldRenderer.skyboxRenderer),
71
71
  // Rain methods
72
- setRain: (newState: boolean) => worldRenderer.toggleModule('rain', newState),
72
+ setRain: worldRenderer.setRain.bind(worldRenderer),
73
73
  spawnBlockBreakParticles(x: number, y: number, z: number, blockName: string, floorMap: number[], biomeName?: string) {
74
74
  const module = worldRenderer.getModule<import('./modules/blockBreakParticles').BlockBreakParticlesModule>('blockBreakParticles')
75
75
  module?.spawnBlockBreakParticles(x, y, z, blockName, floorMap, biomeName)
@@ -145,6 +145,10 @@ export const createGraphicsBackendBase = () => {
145
145
  let frameTimingCollector: FrameTimingCollector | null = null
146
146
 
147
147
  const init = (initOptionsArg: GraphicsInitOptions, mainData?: ThreeRendererMainData) => {
148
+ if (initOptionsArg.hello) {
149
+ console.log('Thanks for using minecraft-renderer project: one of the most performant Minecraft world renderers for the web!')
150
+ }
151
+
148
152
  if (isWebWorker) {
149
153
  initOptions = restoreTransferred(initOptionsArg, initOptionsRestorers, globalThis as unknown as Worker)
150
154
  } else {
@@ -166,8 +170,8 @@ export const createGraphicsBackendBase = () => {
166
170
  worldRenderer.destroy()
167
171
  worldRenderer = null
168
172
  frameTimingCollector = null
169
- ;(globalThis as any).world = undefined
170
- ;(globalThis as any).frameTimingCollector = undefined
173
+ ; (globalThis as any).world = undefined
174
+ ; (globalThis as any).frameTimingCollector = undefined
171
175
  }
172
176
 
173
177
  if (menuBackgroundRenderer) {
@@ -202,8 +206,8 @@ export const createGraphicsBackendBase = () => {
202
206
  worldRenderer.destroy()
203
207
  worldRenderer = null
204
208
  frameTimingCollector = null
205
- ;(globalThis as any).world = undefined
206
- ;(globalThis as any).frameTimingCollector = undefined
209
+ ; (globalThis as any).world = undefined
210
+ ; (globalThis as any).frameTimingCollector = undefined
207
211
  }
208
212
 
209
213
  const displayOptionsRestorers = [ResourcesManager, WorldViewWorker]
@@ -40,7 +40,7 @@ export function create3DItemMesh (
40
40
  throw new Error(`Invalid canvas dimensions: ${canvas.width}x${canvas.height}`)
41
41
  }
42
42
 
43
- const ctx = canvas.getContext('2d')!
43
+ const ctx = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D
44
44
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
45
45
  const { data } = imageData
46
46
 
@@ -17,11 +17,6 @@ const FALL_SPEED_MAX = 24
17
17
  const HORIZONTAL_DRIFT = 1.2
18
18
  const RESPAWN_BELOW = -5
19
19
 
20
- const moduleOptions = {
21
- particleCount: 2000,
22
- speedFactor: 1,
23
- }
24
-
25
20
  export class RainModule implements RendererModuleController {
26
21
  private instancedMesh?: THREE.InstancedMesh
27
22
  private geometry?: THREE.BoxGeometry
@@ -32,8 +27,14 @@ export class RainModule implements RendererModuleController {
32
27
  private readonly tempPosition = new THREE.Vector3()
33
28
  private readonly tempQuaternion = new THREE.Quaternion()
34
29
  private readonly tempScale = new THREE.Vector3()
30
+ private readonly configUnsubs: Array<() => void> = []
35
31
 
36
- constructor(private readonly worldRenderer: WorldRendererThree) { }
32
+ constructor(private readonly worldRenderer: WorldRendererThree) {
33
+ this.configUnsubs.push(
34
+ this.worldRenderer.onReactiveConfigUpdated('rainColor', () => this.syncRainAppearance()),
35
+ this.worldRenderer.onReactiveConfigUpdated('rainOpacity', () => this.syncRainAppearance()),
36
+ )
37
+ }
37
38
 
38
39
  enable(): void {
39
40
  if (this.enabled) return
@@ -42,6 +43,7 @@ export class RainModule implements RendererModuleController {
42
43
  this.createRain()
43
44
  } else {
44
45
  this.instancedMesh.visible = true
46
+ this.syncRainAppearance()
45
47
  }
46
48
  }
47
49
 
@@ -60,8 +62,6 @@ export class RainModule implements RendererModuleController {
60
62
  render?: (deltaTime: number) => void = (deltaTime) => {
61
63
  if (!this.enabled || !this.instancedMesh || !this.material) return
62
64
 
63
- this.syncMaterialToSceneFog()
64
-
65
65
  const cameraPos = this.worldRenderer.getCameraPosition()
66
66
  this.instancedMesh.position.set(0, 0, 0)
67
67
 
@@ -126,6 +126,9 @@ export class RainModule implements RendererModuleController {
126
126
  }
127
127
 
128
128
  dispose(): void {
129
+ for (const unsub of this.configUnsubs) unsub()
130
+ this.configUnsubs.length = 0
131
+
129
132
  if (this.instancedMesh) {
130
133
  this.worldRenderer.scene.remove(this.instancedMesh)
131
134
  }
@@ -137,33 +140,31 @@ export class RainModule implements RendererModuleController {
137
140
  this.particles = []
138
141
  }
139
142
 
140
- /** Match scene fog so rain fades with distance instead of a flat blue sheet. */
141
- private syncMaterialToSceneFog(): void {
143
+ private syncRainAppearance(): void {
142
144
  if (!this.material) return
143
- const fog = this.worldRenderer.scene.fog
144
- if (fog instanceof THREE.Fog || fog instanceof THREE.FogExp2) {
145
- this.material.color.copy(fog.color)
146
- } else {
147
- this.material.color.set(0xcc_dd_ee)
148
- }
149
- this.material.fog = true
145
+
146
+ const { rainColor, rainOpacity } = this.worldRenderer.worldRendererConfig
147
+ this.material.color.set(rainColor)
148
+ this.material.opacity = Math.max(0, Math.min(1, rainOpacity))
149
+ this.material.needsUpdate = true
150
150
  }
151
151
 
152
152
  private createRain(): void {
153
+ const { rainColor, rainOpacity } = this.worldRenderer.worldRendererConfig
154
+
153
155
  this.geometry = new THREE.BoxGeometry(0.03, 0.3, 0.03)
154
156
  this.material = new THREE.MeshBasicMaterial({
155
- color: 0xcc_dd_ee,
157
+ color: rainColor,
156
158
  transparent: true,
157
- opacity: 0.35,
159
+ opacity: Math.max(0, Math.min(1, rainOpacity)),
158
160
  // Must write depth so log-depth blocks occlude rain correctly (see cubeBlockShader).
159
161
  depthWrite: true,
160
- fog: true,
162
+ fog: false,
161
163
  })
162
164
 
163
165
  this.instancedMesh = new THREE.InstancedMesh(this.geometry, this.material, PARTICLE_COUNT)
164
166
  this.instancedMesh.name = 'rain-particles'
165
167
  this.instancedMesh.frustumCulled = false
166
- this.syncMaterialToSceneFog()
167
168
 
168
169
  const dummy = new THREE.Matrix4()
169
170
  const position = new THREE.Vector3()
@@ -26,8 +26,10 @@ class StarfieldMaterial extends THREE.ShaderMaterial {
26
26
  uniform float fade;
27
27
  varying vec3 vColor;
28
28
  void main() {
29
- float opacity = 1.0;
30
- gl_FragColor = vec4(vColor, 1.0);
29
+ // fade scales star brightness (0 = invisible). With additive blending this is
30
+ // the only way to dim stars — scene fog never reaches this shader. Driven by the
31
+ // rain state so stars disappear in rain like in vanilla.
32
+ gl_FragColor = vec4(vColor * fade, 1.0);
31
33
 
32
34
  #include <tonemapping_fragment>
33
35
  #include <${threeVersion >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
@@ -41,6 +43,8 @@ export class StarfieldModule implements RendererModuleController {
41
43
  private timer = new THREE.Timer()
42
44
  private enabled = false
43
45
  private currentTime?: number
46
+ /** Current star brightness multiplier; lerps toward 0 while raining, 1 otherwise. */
47
+ private fade = 1
44
48
 
45
49
  constructor(private readonly worldRenderer: WorldRendererThree) { }
46
50
 
@@ -72,12 +76,20 @@ export class StarfieldModule implements RendererModuleController {
72
76
  return this.currentTime > nightTime && this.currentTime < morningStart
73
77
  }
74
78
 
75
- render?: (deltaTime: number) => void = (_deltaTime) => {
79
+ render?: (deltaTime: number) => void = (deltaTime) => {
76
80
  if (!this.points) return
77
81
  this.points.position.set(0, 0, 0)
82
+
83
+ const material = this.points.material as StarfieldMaterial
78
84
  this.timer.update(performance.now())
79
- ; (this.points.material as StarfieldMaterial).uniforms.time.value =
80
- this.timer.getElapsed() * 0.2
85
+ material.uniforms.time.value = this.timer.getElapsed() * 0.2
86
+
87
+ // Fade stars out while raining (vanilla scales star brightness by 1 - rainLevel).
88
+ // isRaining is a boolean here, so ease toward the target instead of snapping.
89
+ const target = this.worldRenderer.worldRendererConfig.isRaining ? 0 : 1
90
+ const t = Math.min(1, deltaTime * 2)
91
+ this.fade += (target - this.fade) * t
92
+ material.uniforms.fade.value = this.fade
81
93
  }
82
94
 
83
95
  /**
@@ -355,6 +355,11 @@ export class WorldRendererThree extends WorldRendererCommon {
355
355
  return targetState
356
356
  }
357
357
 
358
+ setRain(enabled: boolean): void {
359
+ this.worldRendererConfig.isRaining = enabled
360
+ this.toggleModule('rain', enabled)
361
+ }
362
+
358
363
  /**
359
364
  * Dispose all modules
360
365
  */
@@ -696,7 +701,7 @@ export class WorldRendererThree extends WorldRendererCommon {
696
701
  this.syncSkyLevelFromTime(newTime)
697
702
  }
698
703
 
699
- private syncSkyLevelFromTime (timeOfDay: number): void {
704
+ private syncSkyLevelFromTime(timeOfDay: number): void {
700
705
  const skyLevel = calculateSkyLightSimple(timeOfDay) / 15
701
706
  this.chunkMeshManager.setSkyLevel(skyLevel)
702
707
  }
@@ -792,10 +797,6 @@ export class WorldRendererThree extends WorldRendererCommon {
792
797
  }
793
798
  }
794
799
 
795
- override updateViewerPosition(pos: Vec3): void {
796
- this.viewerChunkPosition = pos
797
- }
798
-
799
800
  cameraSectionPositionUpdate() {
800
801
  // eslint-disable-next-line guard-for-in
801
802
  for (const key in this.sectionObjects) {
@@ -1583,7 +1584,7 @@ export class WorldRendererThree extends WorldRendererCommon {
1583
1584
  this.chunkMeshManager.onChunkRemovedFromGate(`${x},${z}`)
1584
1585
  }
1585
1586
 
1586
- updateViewerPosition(pos: Vec3) {
1587
+ override updateViewerPosition(pos: Vec3) {
1587
1588
  super.updateViewerPosition(pos)
1588
1589
  if (this.chunkMeshManager.pendingNearReveal.size > 0) {
1589
1590
  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,