some progress

This commit is contained in:
Vitaly Turovsky 2025-06-29 15:22:21 +03:00
commit 4aebfecf69
7 changed files with 167 additions and 113 deletions

View file

@ -1,7 +1,7 @@
import { Vec3 } from 'vec3'
import { World } from './world'
import { getSectionGeometry, setBlockStatesData as setMesherData } from './models'
import { BlockStateModelInfo } from './shared'
import { BlockStateModelInfo, InstancingMode } from './shared'
import { INVISIBLE_BLOCKS } from './worldConstants'
globalThis.structuredClone ??= (value) => JSON.parse(JSON.stringify(value))
@ -17,7 +17,7 @@ if (module.require) {
let workerIndex = 0
let world: World
let dirtySections = new Map<string, number>()
let dirtySections = new Map<string, { key: string, instancingMode: InstancingMode, times: number }>()
let allDataReady = false
function sectionKey (x, y, z) {
@ -47,7 +47,7 @@ function drainQueue (from, to) {
queuedMessages = queuedMessages.slice(to)
}
function setSectionDirty (pos, value = true) {
function setSectionDirty (pos, value = true, instancingMode = InstancingMode.None) {
const x = Math.floor(pos.x / 16) * 16
const y = Math.floor(pos.y / 16) * 16
const z = Math.floor(pos.z / 16) * 16
@ -60,7 +60,7 @@ function setSectionDirty (pos, value = true) {
const chunk = world.getColumn(x, z)
if (chunk?.getSection(pos)) {
dirtySections.set(key, (dirtySections.get(key) || 0) + 1)
dirtySections.set(key, { key, instancingMode, times: (dirtySections.get(key)?.times || 0) + 1 })
} else {
postMessage({ type: 'sectionFinished', key, workerIndex })
}
@ -98,12 +98,14 @@ const handleMessage = data => {
setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu')
allDataReady = true
workerIndex = data.workerIndex
world.instancedBlocks = data.instancedBlocks
world.instancedBlockIds = data.instancedBlockIds || new Map()
break
}
case 'dirty': {
const loc = new Vec3(data.x, data.y, data.z)
setSectionDirty(loc, data.value)
setSectionDirty(loc, data.value, data.instancingMode || InstancingMode.None)
break
}
@ -197,13 +199,13 @@ setInterval(() => {
// console.log(sections.length + ' dirty sections')
// const start = performance.now()
for (const key of dirtySections.keys()) {
for (const [key, { instancingMode }] of dirtySections.entries()) {
const [x, y, z] = key.split(',').map(v => parseInt(v, 10))
const chunk = world.getColumn(x, z)
let processTime = 0
if (chunk?.getSection(new Vec3(x, y, z))) {
const start = performance.now()
const geometry = getSectionGeometry(x, y, z, world)
const geometry = getSectionGeometry(x, y, z, world, instancingMode)
const transferable = [geometry.positions?.buffer, geometry.normals?.buffer, geometry.colors?.buffer, geometry.uvs?.buffer].filter(Boolean)
//@ts-expect-error
postMessage({ type: 'geometry', key, geometry, workerIndex }, transferable)
@ -213,7 +215,7 @@ setInterval(() => {
}
const dirtyTimes = dirtySections.get(key)
if (!dirtyTimes) throw new Error('dirtySections.get(key) is falsy')
for (let i = 0; i < dirtyTimes; i++) {
for (let i = 0; i < dirtyTimes.times; i++) {
postMessage({ type: 'sectionFinished', key, workerIndex, processTime })
processTime = 0
}

View file

@ -5,7 +5,7 @@ import { BlockType } from '../../../playground/shared'
import { World, BlockModelPartsResolved, WorldBlock as Block, WorldBlock } from './world'
import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon'
import { INVISIBLE_BLOCKS } from './worldConstants'
import { MesherGeometryOutput, HighestBlockInfo, InstancedBlockEntry } from './shared'
import { MesherGeometryOutput, HighestBlockInfo, InstancedBlockEntry, InstancingMode } from './shared'
// Hardcoded list of full blocks that can use instancing
export const INSTANCEABLE_BLOCKS = new Set([
@ -146,6 +146,9 @@ let blockProvider: WorldBlockProvider
const tints: any = {}
let needTiles = false
// Cache for texture info to avoid repeated calculations
const textureInfoCache = new Map<number, any>()
let tintsData
try {
tintsData = require('esbuild-data').tints
@ -652,31 +655,29 @@ const isBlockWaterlogged = (block: Block) => {
}
const shouldCullInstancedBlock = (world: World, cursor: Vec3, block: Block): boolean => {
// Early return for blocks that should never be culled
if (block.transparent) return false
// Check if all 6 faces would be culled (hidden by neighbors)
const cullIfIdentical = block.name.includes('glass') || block.name.includes('ice')
// Cache cursor offset to avoid creating new Vec3 instances
const offsetCursor = new Vec3(0, 0, 0)
// eslint-disable-next-line guard-for-in
for (const face in elemFaces) {
const { dir } = elemFaces[face]
const neighbor = world.getBlock(cursor.plus(new Vec3(dir[0], dir[1], dir[2])), blockProvider, {})
offsetCursor.set(cursor.x + dir[0], cursor.y + dir[1], cursor.z + dir[2])
const neighbor = world.getBlock(offsetCursor, blockProvider, {})
if (!neighbor) {
// Face is exposed to air/void
return false
}
// Face is exposed to air/void - block must be rendered
if (!neighbor) return false
if (cullIfIdentical && neighbor.stateId === block.stateId) {
// Same block type, face should be culled
continue
}
// Handle special case for identical blocks (glass/ice)
if (cullIfIdentical && neighbor.stateId === block.stateId) continue
if (!neighbor.transparent && isCube(neighbor)) {
// Neighbor is opaque and full cube, face should be culled
continue
}
// Face is not culled, block should be rendered
return false
// If neighbor is not a full opaque cube, face is visible
if (neighbor.transparent || !isCube(neighbor)) return false
}
// All faces are culled, block should not be rendered
@ -684,6 +685,12 @@ const shouldCullInstancedBlock = (world: World, cursor: Vec3, block: Block): boo
}
const getInstancedBlockTextureInfo = (block: Block) => {
// Cache texture info by block state ID
const cacheKey = block.stateId
if (textureInfoCache.has(cacheKey)) {
return textureInfoCache.get(cacheKey)
}
// Get texture info from the first available face
const model = block.models?.[0]?.[0]
if (!model?.elements?.[0]) return undefined
@ -692,20 +699,25 @@ const getInstancedBlockTextureInfo = (block: Block) => {
// Try to find a face with texture - prefer visible faces
const faceOrder = ['up', 'north', 'east', 'south', 'west', 'down']
let textureInfo: any
for (const faceName of faceOrder) {
const face = element.faces[faceName]
if (face?.texture) {
const texture = face.texture as any
return {
textureInfo = {
u: texture.u || 0,
v: texture.v || 0,
su: texture.su || 1,
sv: texture.sv || 1
}
break
}
}
return undefined
// Cache the result
textureInfoCache.set(cacheKey, textureInfo)
return textureInfo
}
const isBlockInstanceable = (block: Block): boolean => {
@ -726,11 +738,12 @@ const isBlockInstanceable = (block: Block): boolean => {
}
let unknownBlockModel: BlockModelPartsResolved
export function getSectionGeometry (sx: number, sy: number, sz: number, world: World) {
export function getSectionGeometry (sx: number, sy: number, sz: number, world: World, instancingMode = InstancingMode.None): MesherGeometryOutput {
let delayedRender = [] as Array<() => void>
// Check if instanced rendering is enabled
const enableInstancedRendering = world.config.useInstancedRendering
// Check if instanced rendering is enabled for this section
const enableInstancedRendering = instancingMode !== InstancingMode.None
const forceInstancedOnly = instancingMode === InstancingMode.TexturedInstancing // Only force when using full instancing
const attr: MesherGeometryOutput = {
sx: sx + 8,
@ -843,7 +856,15 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W
const blockKey = block.name
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)
}
attr.instancedBlocks[blockKey] = {
blockId,
blockName: block.name,
stateId: block.stateId,
textureInfo,
@ -860,7 +881,7 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W
}
// Skip buffer geometry generation if force instanced only mode is enabled
if (world.config.forceInstancedOnly) {
if (forceInstancedOnly) {
// In force instanced only mode, skip all non-instanceable blocks
continue
}

View file

@ -1,5 +1,11 @@
import { BlockType } from '../../../playground/shared'
export enum InstancingMode {
None = 'none',
ColorOnly = 'color_only',
TexturedInstancing = 'textured_instancing'
}
// only here for easier testing
export const defaultMesherConfig = {
version: '',
@ -13,10 +19,6 @@ export const defaultMesherConfig = {
debugModelVariant: undefined as undefined | number[],
clipWorldBelowY: undefined as undefined | number,
disableSignsMapsSupport: false,
// Instanced rendering options
useInstancedRendering: false,
forceInstancedOnly: false,
enableSingleColorMode: false,
}
export type CustomBlockModels = {
@ -26,6 +28,7 @@ export type CustomBlockModels = {
export type MesherConfig = typeof defaultMesherConfig
export type InstancedBlockEntry = {
blockId: number // Unique ID for this block type
blockName: string
stateId: number
textureInfo?: {
@ -37,6 +40,12 @@ export type InstancedBlockEntry = {
positions: Array<{ x: number, y: number, z: number }>
}
export type InstancingMesherData = {
blocks: {
[stateId: number]: number // instance id
}
}
export type MesherGeometryOutput = {
sx: number,
sy: number,

View file

@ -5,7 +5,7 @@ import { Vec3 } from 'vec3'
import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
import moreBlockDataGeneratedJson from '../moreBlockDataGenerated.json'
import legacyJson from '../../../../src/preflatMap.json'
import { defaultMesherConfig, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey } from './shared'
import { defaultMesherConfig, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './shared'
import { INVISIBLE_BLOCKS } from './worldConstants'
const ignoreAoBlocks = Object.keys(moreBlockDataGeneratedJson.noOcclusions)
@ -42,12 +42,14 @@ export class World {
customBlockModels = new Map<string, CustomBlockModels>() // chunkKey -> blockModels
sentBlockStateModels = new Set<string>()
blockStateModelInfo = new Map<string, BlockStateModelInfo>()
instancedBlocks: Record<string, any> = {}
instancedBlockIds = new Map<string, number>()
constructor (version) {
constructor (version: string) {
this.Chunk = Chunks(version) as any
this.biomeCache = mcData(version).biomes
this.preflat = !mcData(version).supportFeature('blockStateId')
this.config.version = version
this.config = { ...defaultMesherConfig, version }
}
getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {

View file

@ -12,7 +12,7 @@ import type { ResourcesManagerTransferred } from '../../../src/resourcesManager'
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState } from '../../../src/appViewer'
import { SoundSystem } from '../three/threeJsSound'
import { buildCleanupDecorator } from './cleanupDecorator'
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from './mesher/shared'
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent, InstancingMode } from './mesher/shared'
import { chunkPos } from './simpleUtils'
import { addNewStat, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats'
import { WorldDataEmitterWorker } from './worldDataEmitter'
@ -576,10 +576,6 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers,
worldMinY: this.worldMinYRender,
worldMaxY: this.worldMinYRender + this.worldSizeParams.worldHeight,
// Instanced rendering options
useInstancedRendering: this.worldRendererConfig.useInstancedRendering,
forceInstancedOnly: this.worldRendererConfig.forceInstancedOnly,
enableSingleColorMode: this.worldRendererConfig.enableSingleColorMode,
}
}
@ -927,7 +923,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
return Promise.all(data)
}
setSectionDirty (pos: Vec3, value = true, useChangeWorker = false) { // value false is used for unloading chunks
setSectionDirty (pos: Vec3, value = true, useChangeWorker = false, instancingMode = InstancingMode.None) { // value false is used for unloading chunks
if (!this.forceCallFromMesherReplayer && this.mesherLogReader) return
if (this.viewDistance === -1) throw new Error('viewDistance not set')
@ -941,7 +937,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
// Dispatch sections to workers based on position
// This guarantees uniformity accross workers and that a given section
// is always dispatched to the same worker
const hash = this.getWorkerNumber(pos, useChangeWorker && this.mesherLogger.active)
const hash = this.getWorkerNumber(pos, this.mesherLogger.active)
this.sectionsWaiting.set(key, (this.sectionsWaiting.get(key) ?? 0) + 1)
if (this.forceCallFromMesherReplayer) {
this.workers[hash].postMessage({
@ -950,17 +946,18 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
y: pos.y,
z: pos.z,
value,
instancingMode,
config: this.getMesherConfig(),
})
} else {
this.toWorkerMessagesQueue[hash] ??= []
this.toWorkerMessagesQueue[hash].push({
// this.workers[hash].postMessage({
type: 'dirty',
x: pos.x,
y: pos.y,
z: pos.z,
value,
instancingMode,
config: this.getMesherConfig(),
})
this.dispatchMessages()

View file

@ -1,5 +1,6 @@
import * as THREE from 'three'
import { Vec3 } from 'vec3'
import moreBlockData from '../lib/moreBlockDataGenerated.json'
import { WorldRendererThree } from './worldrendererThree'
// Hardcoded list of full blocks that can use instancing
@ -136,7 +137,20 @@ export const INSTANCEABLE_BLOCKS = new Set([
'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)
if (!match) return 0x99_99_99 // Default gray
const r = parseInt(match[1], 10)
const g = parseInt(match[2], 10)
const b = parseInt(match[3], 10)
return (r << 16) | (g << 8) | b
}
export interface InstancedBlockData {
blockId: number
positions: Vec3[]
blockName: string
stateId: number
@ -149,12 +163,15 @@ export interface InstancedSectionData {
}
export class InstancedRenderer {
private readonly instancedMeshes = new Map<string, THREE.InstancedMesh>()
private readonly blockCounts = new Map<string, number>()
private readonly sectionInstances = new Map<string, Map<string, number[]>>() // sectionKey -> blockName -> instanceIndices
private readonly maxInstancesPerBlock = 100_000 // Reasonable limit
private readonly instancedMeshes = new Map<number, THREE.InstancedMesh>()
private readonly blockCounts = new Map<number, number>()
private readonly sectionInstances = new Map<string, Map<number, number[]>>()
private readonly maxInstancesPerBlock = 100_000
private readonly cubeGeometry: THREE.BoxGeometry
private readonly tempMatrix = new THREE.Matrix4()
private readonly blockIdToName = new Map<number, string>()
private readonly blockNameToId = new Map<string, number>()
private nextBlockId = 0
constructor (private readonly worldRenderer: WorldRendererThree) {
this.cubeGeometry = this.createCubeGeometry()
@ -206,7 +223,7 @@ export class InstancedRenderer {
mesh.frustumCulled = false // Important for performance
mesh.count = 0 // Start with 0 instances
this.instancedMeshes.set(blockName, mesh)
this.instancedMeshes.set(this.getBlockId(blockName), mesh)
this.worldRenderer.scene.add(mesh)
}
}
@ -220,6 +237,7 @@ export class InstancedRenderer {
return new THREE.MeshBasicMaterial({ color })
} else {
// Use texture from the blocks atlas
// eslint-disable-next-line no-lonely-if
if (this.worldRenderer.material.map) {
const material = this.worldRenderer.material.clone()
// The texture is already the correct blocks atlas texture
@ -234,36 +252,14 @@ export class InstancedRenderer {
}
private getBlockColor (blockName: string): number {
// Simple color mapping for blocks
const colorMap: Record<string, number> = {
'grass_block': 0x7c_bd_3b,
'dirt': 0x8b_45_13,
'stone': 0x7d_7d_7d,
'cobblestone': 0x7a_7a_7a,
'mossy_cobblestone': 0x65_97_65,
'clay': 0xa0_a5_a6,
'moss_block': 0x58_7d_3c,
'spawner': 0x1a_1a_1a,
'sand': 0xf7_e9_a3,
'gravel': 0x8a_8a_8a,
'iron_block': 0xd8_d8_d8,
'gold_block': 0xfa_ee_4d,
'diamond_block': 0x5c_db_d5,
'emerald_block': 0x17_dd_62,
'coal_block': 0x34_34_34,
'redstone_block': 0xa1_27_22,
'lapis_block': 0x22_4d_bb,
'netherrack': 0x72_3a_32,
'obsidian': 0x0f_0f_23,
'bedrock': 0x56_56_56,
'end_stone': 0xda_dc_a6,
'quartz_block': 0xec_e4_d6,
'snow_block': 0xff_ff_ff,
'ice': 0x9c_f4_ff,
'packed_ice': 0x74_c7_ec,
'blue_ice': 0x3e_5f_bc,
// Get color from moreBlockDataGenerated.json
const rgbString = moreBlockData.colors[blockName]
if (rgbString) {
return parseRgbColor(rgbString)
}
return colorMap[blockName] || 0x99_99_99 // Default gray
// Fallback to default gray if color not found
return 0x99_99_99
}
handleInstancedGeometry (data: InstancedSectionData) {
@ -276,10 +272,10 @@ export class InstancedRenderer {
for (const [blockName, blockData] of instancedBlocks) {
if (!INSTANCEABLE_BLOCKS.has(blockName)) continue
const mesh = this.instancedMeshes.get(blockName)
const mesh = this.instancedMeshes.get(this.getBlockId(blockName))
if (!mesh) continue
const currentCount = this.blockCounts.get(blockName) || 0
const currentCount = this.blockCounts.get(this.getBlockId(blockName)) || 0
let instanceIndex = currentCount
for (const pos of blockData.positions) {
@ -295,7 +291,7 @@ export class InstancedRenderer {
mesh.count = instanceIndex
mesh.instanceMatrix.needsUpdate = true
this.blockCounts.set(blockName, instanceIndex)
this.blockCounts.set(this.getBlockId(blockName), instanceIndex)
}
}
@ -312,7 +308,10 @@ export class InstancedRenderer {
for (const [blockName, blockData] of Object.entries(instancedBlocks)) {
if (!this.isBlockInstanceable(blockName)) continue
let mesh = this.instancedMeshes.get(blockName)
const { blockId } = blockData
this.blockIdToName.set(blockId, blockName)
let mesh = this.instancedMeshes.get(blockId)
if (!mesh) {
// Create new mesh if it doesn't exist
const material = this.createBlockMaterial(blockName, blockData.textureInfo)
@ -324,12 +323,12 @@ export class InstancedRenderer {
mesh.name = `instanced_${blockName}`
mesh.frustumCulled = false
mesh.count = 0
this.instancedMeshes.set(blockName, mesh)
this.instancedMeshes.set(blockId, mesh)
this.worldRenderer.scene.add(mesh)
}
const instanceIndices: number[] = []
const currentCount = this.blockCounts.get(blockName) || 0
const currentCount = this.blockCounts.get(blockId) || 0
// Add new instances for this section
for (const pos of blockData.positions) {
@ -346,9 +345,9 @@ export class InstancedRenderer {
// Update tracking
if (instanceIndices.length > 0) {
sectionMap.set(blockName, instanceIndices)
this.blockCounts.set(blockName, currentCount + instanceIndices.length)
mesh.count = this.blockCounts.get(blockName) || 0
sectionMap.set(blockId, instanceIndices)
this.blockCounts.set(blockId, currentCount + instanceIndices.length)
mesh.count = this.blockCounts.get(blockId) || 0
mesh.instanceMatrix.needsUpdate = true
}
}
@ -357,10 +356,10 @@ export class InstancedRenderer {
private clearSectionInstances (sectionKey: string) {
// For now, we'll rebuild all instances each time
// This could be optimized to track instances per section
for (const [blockName, mesh] of this.instancedMeshes) {
for (const [blockId, mesh] of this.instancedMeshes) {
mesh.count = 0
mesh.instanceMatrix.needsUpdate = true
this.blockCounts.set(blockName, 0)
this.blockCounts.set(blockId, 0)
}
}
@ -369,24 +368,24 @@ export class InstancedRenderer {
if (!sectionMap) return // Section not tracked
// Remove instances for each block type in this section
for (const [blockName, instanceIndices] of sectionMap) {
const mesh = this.instancedMeshes.get(blockName)
for (const [blockId, instanceIndices] of sectionMap) {
const mesh = this.instancedMeshes.get(blockId)
if (!mesh) continue
// For efficiency, we'll rebuild the entire instance array by compacting it
// This removes gaps left by deleted instances
this.compactInstancesForBlock(blockName, instanceIndices)
this.compactInstancesForBlock(blockId, instanceIndices)
}
// Remove section from tracking
this.sectionInstances.delete(sectionKey)
}
private compactInstancesForBlock (blockName: string, indicesToRemove: number[]) {
const mesh = this.instancedMeshes.get(blockName)
private compactInstancesForBlock (blockId: number, indicesToRemove: number[]) {
const mesh = this.instancedMeshes.get(blockId)
if (!mesh) return
const currentCount = this.blockCounts.get(blockName) || 0
const currentCount = this.blockCounts.get(blockId) || 0
const removeSet = new Set(indicesToRemove)
let writeIndex = 0
@ -405,23 +404,23 @@ export class InstancedRenderer {
// Update count and indices in section tracking
const newCount = writeIndex
this.blockCounts.set(blockName, newCount)
this.blockCounts.set(blockId, newCount)
mesh.count = newCount
mesh.instanceMatrix.needsUpdate = true
// Update all section tracking to reflect compacted indices
const offset = 0
for (const [sectionKey, sectionMap] of this.sectionInstances) {
const sectionIndices = sectionMap.get(blockName)
const sectionIndices = sectionMap.get(blockId)
if (sectionIndices) {
const compactedIndices = sectionIndices
.filter(index => !removeSet.has(index))
.map(index => index - removeSet.size + offset)
if (compactedIndices.length > 0) {
sectionMap.set(blockName, compactedIndices)
sectionMap.set(blockId, compactedIndices)
} else {
sectionMap.delete(blockName)
sectionMap.delete(blockId)
}
}
}
@ -448,19 +447,22 @@ export class InstancedRenderer {
updateMaterials () {
// Update materials when texture atlas changes
for (const [blockName, mesh] of this.instancedMeshes) {
const newMaterial = this.createBlockMaterial(blockName)
const oldMaterial = mesh.material
mesh.material = newMaterial
if (oldMaterial instanceof THREE.Material) {
oldMaterial.dispose()
for (const [blockId, mesh] of this.instancedMeshes) {
const blockName = this.blockIdToName.get(blockId)
if (blockName) {
const newMaterial = this.createBlockMaterial(blockName)
const oldMaterial = mesh.material
mesh.material = newMaterial
if (oldMaterial instanceof THREE.Material) {
oldMaterial.dispose()
}
}
}
}
destroy () {
// Clean up resources
for (const [blockName, mesh] of this.instancedMeshes) {
for (const [blockId, mesh] of this.instancedMeshes) {
this.worldRenderer.scene.remove(mesh)
mesh.geometry.dispose()
if (mesh.material instanceof THREE.Material) {
@ -470,6 +472,9 @@ export class InstancedRenderer {
this.instancedMeshes.clear()
this.blockCounts.clear()
this.sectionInstances.clear()
this.blockIdToName.clear()
this.blockNameToId.clear()
this.nextBlockId = 0
this.cubeGeometry.dispose()
}
@ -477,7 +482,7 @@ export class InstancedRenderer {
let totalInstances = 0
let activeBlockTypes = 0
for (const [blockName, mesh] of this.instancedMeshes) {
for (const [blockId, mesh] of this.instancedMeshes) {
if (mesh.count > 0) {
totalInstances += mesh.count
activeBlockTypes++
@ -491,4 +496,16 @@ export class InstancedRenderer {
memoryEstimate: totalInstances * 64 // Rough estimate in bytes
}
}
private getBlockId (blockName: string): number {
// Get the block ID from our local map
let blockId = this.blockNameToId.get(blockName)
if (blockId === undefined) {
// If the block ID doesn't exist, create a new one
blockId = this.nextBlockId++
this.blockNameToId.set(blockName, blockId)
this.blockIdToName.set(blockId, blockName)
}
return blockId
}
}

View file

@ -8,7 +8,7 @@ import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer
import { chunkPos, sectionPos } from '../lib/simpleUtils'
import { WorldRendererCommon } from '../lib/worldrendererCommon'
import { addNewStat } from '../lib/ui/newStats'
import { MesherGeometryOutput } from '../lib/mesher/shared'
import { MesherGeometryOutput, InstancingMode } from '../lib/mesher/shared'
import { ItemSpecificContextProperties } from '../lib/basePlayerState'
import { getMyHand } from '../lib/hand'
import { setBlockPosition } from '../lib/mesher/standaloneRenderer'
@ -911,10 +911,16 @@ export class WorldRendererThree extends WorldRendererCommon {
}
}
setSectionDirty (...args: Parameters<WorldRendererCommon['setSectionDirty']>) {
const [pos] = args
setSectionDirty (pos: Vec3, value = true) {
const { useInstancedRendering, enableSingleColorMode } = this.worldRendererConfig
let instancingMode = InstancingMode.None
if (useInstancedRendering) {
instancingMode = enableSingleColorMode ? InstancingMode.ColorOnly : InstancingMode.TexturedInstancing
}
this.cleanChunkTextures(pos.x, pos.z) // todo don't do this!
super.setSectionDirty(...args)
super.setSectionDirty(pos, value, undefined, instancingMode)
}
static getRendererInfo (renderer: THREE.WebGLRenderer) {