some progress
This commit is contained in:
parent
97f8061b06
commit
4aebfecf69
7 changed files with 167 additions and 113 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = '') {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue