diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 444093f2..f781275c 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -99,7 +99,7 @@ const handleMessage = data => { allDataReady = true workerIndex = data.workerIndex world.instancedBlocks = data.instancedBlocks - world.instancedBlockIds = data.instancedBlockIds || new Map() + world.instancedBlockIds = data.instancedBlockIds || {} break } diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index e2333a03..cb07d01a 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -7,140 +7,6 @@ import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, veca import { INVISIBLE_BLOCKS } from './worldConstants' import { MesherGeometryOutput, HighestBlockInfo, InstancedBlockEntry, InstancingMode } from './shared' -// Hardcoded list of full blocks that can use instancing -export const INSTANCEABLE_BLOCKS = new Set([ - 'grass_block', - 'dirt', - 'stone', - 'cobblestone', - 'mossy_cobblestone', - 'clay', - 'moss_block', - 'spawner', - 'sand', - 'gravel', - 'oak_planks', - 'birch_planks', - 'spruce_planks', - 'jungle_planks', - 'acacia_planks', - 'dark_oak_planks', - 'mangrove_planks', - 'cherry_planks', - 'bamboo_planks', - 'crimson_planks', - 'warped_planks', - 'iron_block', - 'gold_block', - 'diamond_block', - 'emerald_block', - 'netherite_block', - 'coal_block', - 'redstone_block', - 'lapis_block', - 'copper_block', - 'exposed_copper', - 'weathered_copper', - 'oxidized_copper', - 'cut_copper', - 'exposed_cut_copper', - 'weathered_cut_copper', - 'oxidized_cut_copper', - 'waxed_copper_block', - 'waxed_exposed_copper', - 'waxed_weathered_copper', - 'waxed_oxidized_copper', - 'raw_iron_block', - 'raw_copper_block', - 'raw_gold_block', - 'smooth_stone', - 'cobbled_deepslate', - 'deepslate', - 'calcite', - 'tuff', - 'dripstone_block', - 'amethyst_block', - 'budding_amethyst', - 'obsidian', - 'crying_obsidian', - 'bedrock', - 'end_stone', - 'purpur_block', - 'quartz_block', - 'smooth_quartz', - 'nether_bricks', - 'red_nether_bricks', - 'blackstone', - 'gilded_blackstone', - 'polished_blackstone', - 'chiseled_nether_bricks', - 'cracked_nether_bricks', - 'basalt', - 'smooth_basalt', - 'polished_basalt', - 'netherrack', - 'magma_block', - 'soul_sand', - 'soul_soil', - 'ancient_debris', - 'bone_block', - 'packed_ice', - 'blue_ice', - 'ice', - 'snow_block', - 'powder_snow', - 'white_wool', - 'orange_wool', - 'magenta_wool', - 'light_blue_wool', - 'yellow_wool', - 'lime_wool', - 'pink_wool', - 'gray_wool', - 'light_gray_wool', - 'cyan_wool', - 'purple_wool', - 'blue_wool', - 'brown_wool', - 'green_wool', - 'red_wool', - 'black_wool', - 'white_concrete', - 'orange_concrete', - 'magenta_concrete', - 'light_blue_concrete', - 'yellow_concrete', - 'lime_concrete', - 'pink_concrete', - 'gray_concrete', - 'light_gray_concrete', - 'cyan_concrete', - 'purple_concrete', - 'blue_concrete', - 'brown_concrete', - 'green_concrete', - 'red_concrete', - 'black_concrete', - 'white_terracotta', - 'orange_terracotta', - 'magenta_terracotta', - 'light_blue_terracotta', - 'yellow_terracotta', - 'lime_terracotta', - 'pink_terracotta', - 'gray_terracotta', - 'light_gray_terracotta', - 'cyan_terracotta', - 'purple_terracotta', - 'blue_terracotta', - 'brown_terracotta', - 'green_terracotta', - 'red_terracotta', - 'black_terracotta', - 'terracotta', - 'glazed_terracotta', -]) - let blockProvider: WorldBlockProvider const tints: any = {} @@ -720,9 +586,14 @@ const getInstancedBlockTextureInfo = (block: Block) => { return textureInfo } -const isBlockInstanceable = (block: Block): boolean => { - // Only instanceable if it's a full cube block without complex geometry - if (!INSTANCEABLE_BLOCKS.has(block.name)) return false +const isBlockInstanceable = (world: World, block: Block): boolean => { + // Use dynamic instanceable blocks data if available + const instancedBlocks = world?.instancedBlocks + if (Array.isArray(instancedBlocks)) { + if (!instancedBlocks.includes(block.name)) return false + } else { + return false + } // Check if it's actually a full cube (no rotations, no complex models) if (!block.models || block.models.length !== 1) return false @@ -839,7 +710,7 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W } if (block.name !== 'water' && block.name !== 'lava' && !INVISIBLE_BLOCKS.has(block.name)) { // Check if this block can use instanced rendering - if (enableInstancedRendering && isBlockInstanceable(block)) { + if (enableInstancedRendering && isBlockInstanceable(world, block)) { // Check if block should be culled (all faces hidden by neighbors) if (shouldCullInstancedBlock(world, cursor, block)) { // Block is completely surrounded, skip rendering @@ -850,18 +721,16 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W if (!attr.instancedBlocks[blockKey]) { const textureInfo = getInstancedBlockTextureInfo(block) // Get or create block ID - let blockId = world.instancedBlockIds.get(block.name) - if (blockId === undefined) { - blockId = world.instancedBlockIds.size - world.instancedBlockIds.set(block.name, blockId) - } + const blockId = world.instancedBlockIds[block.stateId] - attr.instancedBlocks[blockKey] = { - blockId, - blockName: block.name, - stateId: block.stateId, - textureInfo, - positions: [] + if (blockId !== undefined) { + attr.instancedBlocks[blockKey] = { + blockId, + blockName: block.name, + stateId: block.stateId, + textureInfo, + positions: [] + } } } attr.instancedBlocks[blockKey].positions.push({ diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index 110fb93c..bcff11b7 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -43,7 +43,7 @@ export class World { sentBlockStateModels = new Set() blockStateModelInfo = new Map() instancedBlocks: Record = {} - instancedBlockIds = new Map() + instancedBlockIds = {} as Record constructor (version: string) { this.Chunk = Chunks(version) as any @@ -123,7 +123,6 @@ export class World { if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number]) const key = columnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16) const blockPosKey = `${pos.x},${pos.y},${pos.z}` - const modelOverride = this.customBlockModels.get(key)?.[blockPosKey] const column = this.columns[key] // null column means chunk not loaded @@ -133,6 +132,7 @@ export class World { const locInChunk = posInChunk(loc) const stateId = column.getBlockStateId(locInChunk) + const modelOverride = stateId ? this.customBlockModels.get(key)?.[blockPosKey] : undefined const cacheKey = getBlockAssetsCacheKey(stateId, modelOverride) if (!this.blockCache[cacheKey]) { diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index daaf6bfd..b176deea 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -160,6 +160,9 @@ export abstract class WorldRendererCommon abstract changeBackgroundColor (color: [number, number, number]): void + // Optional method for getting instanced blocks data (implemented by Three.js renderer) + getInstancedBlocksData? (): { instanceableBlocks: Set, allBlocksStateIdToModelIdMap: Record } | undefined + worldRendererConfig: WorldRendererConfig playerStateReactive: PlayerStateReactive playerStateUtils: PlayerStateUtils @@ -596,6 +599,10 @@ export abstract class WorldRendererCommon const resources = this.resourcesManager.currentResources if (this.workers.length === 0) throw new Error('workers not initialized yet') + + // Get instanceable blocks data if available (Three.js specific) + const instancedBlocksData = this.getInstancedBlocksData?.() + for (const [i, worker] of this.workers.entries()) { const { blockstatesModels } = resources @@ -607,6 +614,8 @@ export abstract class WorldRendererCommon }, blockstatesModels, config: this.getMesherConfig(), + instancedBlocks: instancedBlocksData?.instanceableBlocks ? [...instancedBlocksData.instanceableBlocks] : [], + instancedBlockIds: instancedBlocksData?.allBlocksStateIdToModelIdMap || {} }) } diff --git a/renderer/viewer/three/getPreflatBlock.ts b/renderer/viewer/three/getPreflatBlock.ts new file mode 100644 index 00000000..2c4e36a6 --- /dev/null +++ b/renderer/viewer/three/getPreflatBlock.ts @@ -0,0 +1,30 @@ +import legacyJson from '../../../src/preflatMap.json' + +export const getPreflatBlock = (block, reportIssue?: () => void) => { + const b = block + b._properties = {} + + const namePropsStr = legacyJson.blocks[b.type + ':' + b.metadata] || findClosestLegacyBlockFallback(b.type, b.metadata, reportIssue) + if (namePropsStr) { + b.name = namePropsStr.split('[')[0] + const propsStr = namePropsStr.split('[')?.[1]?.split(']') + if (propsStr) { + const newProperties = Object.fromEntries(propsStr.join('').split(',').map(x => { + let [key, val] = x.split('=') + if (!isNaN(val)) val = parseInt(val, 10) + return [key, val] + })) + b._properties = newProperties + } + } + return b +} + +const findClosestLegacyBlockFallback = (id, metadata, reportIssue) => { + reportIssue?.() + for (const [key, value] of Object.entries(legacyJson.blocks)) { + const [idKey, meta] = key.split(':') + if (idKey === id) return value + } + return null +} diff --git a/renderer/viewer/three/instancedRenderer.ts b/renderer/viewer/three/instancedRenderer.ts index 105cd3bb..f1306156 100644 --- a/renderer/viewer/three/instancedRenderer.ts +++ b/renderer/viewer/three/instancedRenderer.ts @@ -1,142 +1,12 @@ import * as THREE from 'three' import { Vec3 } from 'vec3' +import { versionToNumber } from 'flying-squid/dist/utils' +import PrismarineBlock, { Block } from 'prismarine-block' +import { IndexedBlock } from 'minecraft-data' import moreBlockData from '../lib/moreBlockDataGenerated.json' +import { getPreflatBlock } from './getPreflatBlock' import { WorldRendererThree } from './worldrendererThree' -// Hardcoded list of full blocks that can use instancing -export const INSTANCEABLE_BLOCKS = new Set([ - 'grass_block', - 'dirt', - 'stone', - 'cobblestone', - 'mossy_cobblestone', - 'clay', - 'moss_block', - 'spawner', - 'sand', - 'gravel', - 'oak_planks', - 'birch_planks', - 'spruce_planks', - 'jungle_planks', - 'acacia_planks', - 'dark_oak_planks', - 'mangrove_planks', - 'cherry_planks', - 'bamboo_planks', - 'crimson_planks', - 'warped_planks', - 'iron_block', - 'gold_block', - 'diamond_block', - 'emerald_block', - 'netherite_block', - 'coal_block', - 'redstone_block', - 'lapis_block', - 'copper_block', - 'exposed_copper', - 'weathered_copper', - 'oxidized_copper', - 'cut_copper', - 'exposed_cut_copper', - 'weathered_cut_copper', - 'oxidized_cut_copper', - 'waxed_copper_block', - 'waxed_exposed_copper', - 'waxed_weathered_copper', - 'waxed_oxidized_copper', - 'raw_iron_block', - 'raw_copper_block', - 'raw_gold_block', - 'smooth_stone', - 'cobbled_deepslate', - 'deepslate', - 'calcite', - 'tuff', - 'dripstone_block', - 'amethyst_block', - 'budding_amethyst', - 'obsidian', - 'crying_obsidian', - 'bedrock', - 'end_stone', - 'purpur_block', - 'quartz_block', - 'smooth_quartz', - 'nether_bricks', - 'red_nether_bricks', - 'blackstone', - 'gilded_blackstone', - 'polished_blackstone', - 'chiseled_nether_bricks', - 'cracked_nether_bricks', - 'basalt', - 'smooth_basalt', - 'polished_basalt', - 'netherrack', - 'magma_block', - 'soul_sand', - 'soul_soil', - 'ancient_debris', - 'bone_block', - 'packed_ice', - 'blue_ice', - 'ice', - 'snow_block', - 'powder_snow', - 'white_wool', - 'orange_wool', - 'magenta_wool', - 'light_blue_wool', - 'yellow_wool', - 'lime_wool', - 'pink_wool', - 'gray_wool', - 'light_gray_wool', - 'cyan_wool', - 'purple_wool', - 'blue_wool', - 'brown_wool', - 'green_wool', - 'red_wool', - 'black_wool', - 'white_concrete', - 'orange_concrete', - 'magenta_concrete', - 'light_blue_concrete', - 'yellow_concrete', - 'lime_concrete', - 'pink_concrete', - 'gray_concrete', - 'light_gray_concrete', - 'cyan_concrete', - 'purple_concrete', - 'blue_concrete', - 'brown_concrete', - 'green_concrete', - 'red_concrete', - 'black_concrete', - 'white_terracotta', - 'orange_terracotta', - 'magenta_terracotta', - 'light_blue_terracotta', - 'yellow_terracotta', - 'lime_terracotta', - 'pink_terracotta', - 'gray_terracotta', - 'light_gray_terracotta', - 'cyan_terracotta', - 'purple_terracotta', - 'blue_terracotta', - 'brown_terracotta', - 'green_terracotta', - 'red_terracotta', - 'black_terracotta', - 'terracotta', - 'glazed_terracotta', -]) - // Helper function to parse RGB color strings from moreBlockDataGenerated.json function parseRgbColor (rgbString: string): number { const match = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(rgbString) @@ -162,6 +32,21 @@ export interface InstancedSectionData { shouldUseInstancedOnly: boolean } +export interface InstancedBlockModelData { + textures: number[] + rotation: number[] + transparent?: boolean + emitLight?: number + filterLight?: number +} + +export interface InstancedBlocksConfig { + instanceableBlocks: Set + blocksDataModel: Record + allBlocksStateIdToModelIdMap: Record + interestedTextureTiles: Set +} + export class InstancedRenderer { private readonly instancedMeshes = new Map() private readonly blockCounts = new Map() @@ -173,9 +58,191 @@ export class InstancedRenderer { private readonly blockNameToId = new Map() private nextBlockId = 0 + // New properties for dynamic block detection + private instancedBlocksConfig: InstancedBlocksConfig | null = null + constructor (private readonly worldRenderer: WorldRendererThree) { this.cubeGeometry = this.createCubeGeometry() - this.initInstancedMeshes() + } + + prepareInstancedBlocksData (): InstancedBlocksConfig { + const blocksMap = { + 'double_stone_slab': 'stone', + 'stone_slab': 'stone', + 'oak_stairs': 'planks', + 'stone_stairs': 'stone', + 'glass_pane': 'stained_glass', + 'brick_stairs': 'brick_block', + 'stone_brick_stairs': 'stonebrick', + 'nether_brick_stairs': 'nether_brick', + 'double_wooden_slab': 'planks', + 'wooden_slab': 'planks', + 'sandstone_stairs': 'sandstone', + 'cobblestone_wall': 'cobblestone', + 'quartz_stairs': 'quartz_block', + 'stained_glass_pane': 'stained_glass', + 'red_sandstone_stairs': 'red_sandstone', + 'stone_slab2': 'stone_slab', + 'purpur_stairs': 'purpur_block', + 'purpur_slab': 'purpur_block', + } + + const isPreflat = versionToNumber(this.worldRenderer.version) < versionToNumber('1.13') + const PBlockOriginal = PrismarineBlock(this.worldRenderer.version) + + const instanceableBlocks = new Set() + const blocksDataModel = {} as Record + const interestedTextureTiles = new Set() + const blocksProcessed = {} as Record + let i = 0 + const allBlocksStateIdToModelIdMap = {} as Record + + const addBlockModel = (state: number, name: string, props: Record, mcBlockData?: IndexedBlock, defaultState = false) => { + const possibleIssues = [] as string[] + const { currentResources } = this.worldRenderer.resourcesManager + if (!currentResources?.worldBlockProvider) return + + const models = currentResources.worldBlockProvider.getAllResolvedModels0_1({ + name, + properties: props + }, isPreflat, possibleIssues, [], [], true) + + // skipping composite blocks + if (models.length !== 1 || !models[0]![0].elements) { + return + } + const elements = models[0]![0]?.elements + if (!elements || (elements.length !== 1 && name !== 'grass_block')) { + return + } + const elem = elements[0] + if (elem.from[0] !== 0 || elem.from[1] !== 0 || elem.from[2] !== 0 || elem.to[0] !== 16 || elem.to[1] !== 16 || elem.to[2] !== 16) { + // not full block + return + } + + const facesMapping = [ + ['front', 'south'], + ['bottom', 'down'], + ['top', 'up'], + ['right', 'east'], + ['left', 'west'], + ['back', 'north'], + ] + + const blockData: InstancedBlockModelData = { + textures: [0, 0, 0, 0, 0, 0], + rotation: [0, 0, 0, 0, 0, 0] + } + + for (const [face, { texture, cullface, rotation = 0 }] of Object.entries(elem.faces)) { + const faceIndex = facesMapping.findIndex(x => x.includes(face)) + if (faceIndex === -1) { + throw new Error(`Unknown face ${face}`) + } + + blockData.textures[faceIndex] = texture.tileIndex + blockData.rotation[faceIndex] = rotation / 90 + if (Math.floor(blockData.rotation[faceIndex]) !== blockData.rotation[faceIndex]) { + throw new Error(`Invalid rotation ${rotation} ${name}`) + } + interestedTextureTiles.add(texture.debugName) + } + + const k = i++ + allBlocksStateIdToModelIdMap[state] = k + blocksDataModel[k] = blockData + instanceableBlocks.add(name) + blocksProcessed[name] = true + + if (mcBlockData) { + blockData.transparent = mcBlockData.transparent + blockData.emitLight = mcBlockData.emitLight + blockData.filterLight = mcBlockData.filterLight + } + } + + // Add unknown block model + addBlockModel(-1, 'unknown', {}) + + // Handle texture overrides for special blocks + const textureOverrideFullBlocks = { + water: 'water_still', + lava: 'lava_still', + } + + // Process all blocks to find instanceable ones + for (const b of (globalThis as any).loadedData.blocksArray) { + for (let state = b.minStateId; state <= b.maxStateId; state++) { + const mapping = blocksMap[b.name] + const block = PBlockOriginal.fromStateId(mapping && (globalThis as any).loadedData.blocksByName[mapping] ? (globalThis as any).loadedData.blocksByName[mapping].defaultState : state, 0) + if (isPreflat) { + getPreflatBlock(block) + } + + const textureOverride = textureOverrideFullBlocks[block.name] as string | undefined + if (textureOverride) { + const k = i++ + const { currentResources } = this.worldRenderer.resourcesManager + if (!currentResources?.worldBlockProvider) continue + const texture = currentResources.worldBlockProvider.getTextureInfo(textureOverride) + if (!texture) { + console.warn('Missing texture override for', block.name) + continue + } + const texIndex = texture.tileIndex + allBlocksStateIdToModelIdMap[state] = k + const blockData: InstancedBlockModelData = { + textures: [texIndex, texIndex, texIndex, texIndex, texIndex, texIndex], + rotation: [0, 0, 0, 0, 0, 0], + filterLight: b.filterLight + } + blocksDataModel[k] = blockData + instanceableBlocks.add(block.name) + interestedTextureTiles.add(textureOverride) + continue + } + + // Check if block is a full cube + if (block.shapes.length === 0 || !block.shapes.every(shape => { + return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1 + })) { + continue + } + + addBlockModel(state, block.name, block.getProperties(), b, state === b.defaultState) + } + } + + return { + instanceableBlocks, + blocksDataModel, + allBlocksStateIdToModelIdMap, + interestedTextureTiles + } + } + + initializeInstancedMeshes () { + if (!this.instancedBlocksConfig) { + console.warn('Instanced blocks config not prepared') + return + } + + // Create InstancedMesh for each instanceable block type + for (const blockName of this.instancedBlocksConfig.instanceableBlocks) { + const material = this.createBlockMaterial(blockName) + const mesh = new THREE.InstancedMesh( + this.cubeGeometry, + material, + this.maxInstancesPerBlock + ) + mesh.name = `instanced_${blockName}` + mesh.frustumCulled = false // Important for performance + mesh.count = 0 // Start with 0 instances + + this.instancedMeshes.set(this.getBlockId(blockName), mesh) + this.worldRenderer.scene.add(mesh) + } } private createCubeGeometry (): THREE.BoxGeometry { @@ -210,24 +277,6 @@ export class InstancedRenderer { return geometry } - private initInstancedMeshes () { - // Create InstancedMesh for each instanceable block type - for (const blockName of INSTANCEABLE_BLOCKS) { - const material = this.createBlockMaterial(blockName) - const mesh = new THREE.InstancedMesh( - this.cubeGeometry, - material, - this.maxInstancesPerBlock - ) - mesh.name = `instanced_${blockName}` - mesh.frustumCulled = false // Important for performance - mesh.count = 0 // Start with 0 instances - - this.instancedMeshes.set(this.getBlockId(blockName), mesh) - this.worldRenderer.scene.add(mesh) - } - } - private createBlockMaterial (blockName: string, textureInfo?: { u: number, v: number, su: number, sv: number }): THREE.Material { const { enableSingleColorMode } = this.worldRenderer.worldRendererConfig @@ -270,7 +319,7 @@ export class InstancedRenderer { // Add new instances for (const [blockName, blockData] of instancedBlocks) { - if (!INSTANCEABLE_BLOCKS.has(blockName)) continue + if (!this.isBlockInstanceable(blockName)) continue const mesh = this.instancedMeshes.get(this.getBlockId(blockName)) if (!mesh) continue @@ -442,7 +491,7 @@ export class InstancedRenderer { } isBlockInstanceable (blockName: string): boolean { - return INSTANCEABLE_BLOCKS.has(blockName) + return this.instancedBlocksConfig?.instanceableBlocks.has(blockName) ?? false } updateMaterials () { @@ -508,4 +557,20 @@ export class InstancedRenderer { } return blockId } + + // New method to prepare and initialize everything + prepareAndInitialize () { + console.log('Preparing instanced blocks data...') + this.instancedBlocksConfig = this.prepareInstancedBlocksData() + console.log(`Found ${this.instancedBlocksConfig.instanceableBlocks.size} instanceable blocks:`, + [...this.instancedBlocksConfig.instanceableBlocks].slice(0, 10).join(', '), + this.instancedBlocksConfig.instanceableBlocks.size > 10 ? '...' : '') + + this.initializeInstancedMeshes() + } + + // Method to get the current configuration + getInstancedBlocksConfig (): InstancedBlocksConfig | null { + return this.instancedBlocksConfig + } } diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 82080b3f..21f6f68f 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -24,7 +24,7 @@ import { ThreeJsSound } from './threeJsSound' import { CameraShake } from './cameraShake' import { ThreeJsMedia } from './threeJsMedia' import { Fountain } from './threeJsParticles' -import { InstancedRenderer, INSTANCEABLE_BLOCKS } from './instancedRenderer' +import { InstancedRenderer } from './instancedRenderer' type SectionKey = string @@ -97,8 +97,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.addDebugOverlay() this.resetScene() - void this.init() - this.soundSystem = new ThreeJsSound(this) this.cameraShake = new CameraShake(this, this.onRender) this.media = new ThreeJsMedia(this) @@ -111,6 +109,8 @@ export class WorldRendererThree extends WorldRendererCommon { this.finishChunk(chunkKey) }) this.worldSwitchActions() + + void this.init() } get cameraObject () { @@ -143,6 +143,16 @@ export class WorldRendererThree extends WorldRendererCommon { } } + getInstancedBlocksData () { + const config = this.instancedRenderer.getInstancedBlocksConfig() + if (!config) return undefined + + return { + instanceableBlocks: config.instanceableBlocks, + allBlocksStateIdToModelIdMap: config.allBlocksStateIdToModelIdMap + } + } + updatePlayerEntity (e: any) { this.entities.handlePlayerEntity(e) } @@ -228,8 +238,12 @@ export class WorldRendererThree extends WorldRendererCommon { oldItemsTexture.dispose() } + // Prepare and initialize instanced renderer with dynamic block detection + this.instancedRenderer.prepareAndInitialize() + await super.updateAssetsData() this.onAllTexturesLoaded() + // Update instanced renderer materials when textures change this.instancedRenderer.updateMaterials() if (Object.keys(this.loadedChunks).length > 0) {