fix: restore minimal and full map (#348)

Co-authored-by: gguio <nikvish150@gmail.com>
This commit is contained in:
gguio 2025-05-18 10:58:52 +04:00 committed by GitHub
commit 785ab490f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 85 additions and 40 deletions

View file

@ -3,7 +3,8 @@ import { RendererReactiveState } from '../../src/appViewer'
export const getDefaultRendererState = (): RendererReactiveState => {
return {
world: {
chunksLoaded: [],
chunksLoaded: new Set(),
heightmaps: new Map(),
chunksTotalNumber: 0,
allChunksLoaded: true,
mesherWork: false,

View file

@ -148,6 +148,29 @@ const handleMessage = data => {
global.postMessage({ type: 'customBlockModel', chunkKey, customBlockModel })
break
}
case 'getHeightmap': {
const heightmap = new Uint8Array(256)
const blockPos = new Vec3(0, 0, 0)
for (let z = 0; z < 16; z++) {
for (let x = 0; x < 16; x++) {
const blockX = x + data.x
const blockZ = z + data.z
blockPos.x = blockX; blockPos.z = blockZ
blockPos.y = 256
let block = world.getBlock(blockPos)
while (block?.name.includes('air')) {
blockPos.y -= 1
block = world.getBlock(blockPos)
}
const index = z * 16 + x
heightmap[index] = block ? blockPos.y : 0
}
}
postMessage({ type: 'heightmap', key: `${Math.floor(data.x / 16)},${Math.floor(data.z / 16)}`, heightmap })
break
}
// No default
}
}

View file

@ -510,7 +510,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
heads: {},
signs: {},
// isFull: true,
highestBlocks: {},
highestBlocks: new Map(),
hadErrors: false,
blocksCount: 0
}
@ -521,9 +521,9 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
let block = world.getBlock(cursor, blockProvider, attr)!
if (!INVISIBLE_BLOCKS.has(block.name)) {
const highest = attr.highestBlocks[`${cursor.x},${cursor.z}`]
const highest = attr.highestBlocks.get(`${cursor.x},${cursor.z}`)
if (!highest || highest.y < cursor.y) {
attr.highestBlocks[`${cursor.x},${cursor.z}`] = { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id }
attr.highestBlocks.set(`${cursor.x},${cursor.z}`, { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id })
}
}
if (INVISIBLE_BLOCKS.has(block.name)) continue

View file

@ -40,12 +40,21 @@ export type MesherGeometryOutput = {
heads: Record<string, any>,
signs: Record<string, any>,
// isFull: boolean
highestBlocks: Record<string, HighestBlockInfo>
highestBlocks: Map<string, HighestBlockInfo>
hadErrors: boolean
blocksCount: number
customBlockModels?: CustomBlockModels
}
export interface MesherMainEvents {
geometry: { type: 'geometry'; key: string; geometry: MesherGeometryOutput; workerIndex: number };
sectionFinished: { type: 'sectionFinished'; key: string; workerIndex: number; processTime?: number };
blockStateModelInfo: { type: 'blockStateModelInfo'; info: Record<string, BlockStateModelInfo> };
heightmap: { type: 'heightmap'; key: string; heightmap: Uint8Array };
}
export type MesherMainEvent = MesherMainEvents[keyof MesherMainEvents]
export type HighestBlockInfo = { y: number, stateId: number | undefined, biomeId: number | undefined }
export type BlockStateModelInfo = {

View file

@ -14,7 +14,7 @@ import { ResourcesManager } from '../../../src/resourcesManager'
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState } from '../../../src/appViewer'
import { SoundSystem } from '../three/threeJsSound'
import { buildCleanupDecorator } from './cleanupDecorator'
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared'
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from './mesher/shared'
import { chunkPos } from './simpleUtils'
import { addNewStat, removeAllStats, removeStat, updatePanesVisibility, updateStatText } from './ui/newStats'
import { WorldDataEmitter } from './worldDataEmitter'
@ -85,6 +85,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
dirty (pos: Vec3, value: boolean): void
update (/* pos: Vec3, value: boolean */): void
chunkFinished (key: string): void
heightmap (key: string, heightmap: Uint8Array): void
}>
customTexturesDataUrl = undefined as string | undefined
workers: any[] = []
@ -103,8 +104,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
ONMESSAGE_TIME_LIMIT = 30 // ms
handleResize = () => { }
highestBlocksByChunks = {} as Record<string, { [chunkKey: string]: HighestBlockInfo }>
highestBlocksBySections = {} as Record<string, { [sectionKey: string]: HighestBlockInfo }>
highestBlocksByChunks = new Map<string, { [chunkKey: string]: HighestBlockInfo }>()
highestBlocksBySections = new Map<string, { [sectionKey: string]: HighestBlockInfo }>()
blockEntities = {}
workersProcessAverageTime = 0
@ -247,7 +248,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
}
async getHighestBlocks (chunkKey: string) {
return this.highestBlocksByChunks[chunkKey]
return this.highestBlocksByChunks.get(chunkKey)
}
updateCustomBlock (chunkKey: string, blockPos: string, model: string) {
@ -365,19 +366,20 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
this.isProcessingQueue = false
}
handleMessage (data) {
handleMessage (rawData: any) {
const data = rawData as MesherMainEvent
if (!this.active) return
this.mesherLogReader?.workerMessageReceived(data.type, data)
if (data.type !== 'geometry' || !this.debugStopGeometryUpdate) {
const start = performance.now()
this.handleWorkerMessage(data)
this.handleWorkerMessage(data as WorkerReceive)
this.workerCustomHandleTime += performance.now() - start
}
if (data.type === 'geometry') {
this.logWorkerWork(() => `-> ${data.workerIndex} geometry ${data.key} ${JSON.stringify({ dataSize: JSON.stringify(data).length })}`)
this.geometryReceiveCount[data.workerIndex] ??= 0
this.geometryReceiveCount[data.workerIndex]++
const geometry = data.geometry as MesherGeometryOutput
const { geometry } = data
this.highestBlocksBySections[data.key] = geometry.highestBlocks
const chunkCoords = data.key.split(',').map(Number)
this.lastChunkDistance = Math.max(...this.getDistance(new Vec3(chunkCoords[0], 0, chunkCoords[2])))
@ -404,6 +406,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
if (loaded) {
// CHUNK FINISHED
this.finishedChunks[chunkKey] = true
this.reactiveState.world.chunksLoaded.add(`${Math.floor(chunkCoords[0] / 16)},${Math.floor(chunkCoords[2] / 16)}`)
this.renderUpdateEmitter.emit(`chunkFinished`, `${chunkCoords[0]},${chunkCoords[2]}`)
this.checkAllFinished()
// merge highest blocks by sections into highest blocks by chunks
@ -442,6 +445,10 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
this.blockStateModelInfo.set(cacheKey, info)
}
}
if (data.type === 'heightmap') {
appViewer.rendererState.world.heightmaps.set(data.key, new Uint8Array(data.heightmap))
}
}
downloadMesherLog () {
@ -599,7 +606,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
updateChunksStats () {
const loadedChunks = Object.keys(this.finishedChunks)
this.displayOptions.nonReactiveState.world.chunksLoaded = loadedChunks
this.displayOptions.nonReactiveState.world.chunksLoaded = new Set(loadedChunks)
this.displayOptions.nonReactiveState.world.chunksTotalNumber = this.chunksLength
this.reactiveState.world.allChunksLoaded = this.allChunksFinished
@ -628,6 +635,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
customBlockModels: customBlockModels || undefined
})
}
this.workers[0].postMessage({
type: 'getHeightmap',
x,
z,
})
this.logWorkerWork(() => `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`)
this.mesherLogReader?.chunkReceived(x, z, chunk.length)
for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += 16) {
@ -664,9 +676,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
this.setSectionDirty(new Vec3(x, y, z), false)
delete this.finishedSections[`${x},${y},${z}`]
delete this.highestBlocksBySections[`${x},${y},${z}`]
this.highestBlocksBySections.delete(`${x},${y},${z}`)
}
delete this.highestBlocksByChunks[`${x},${z}`]
this.highestBlocksByChunks.delete(`${x},${z}`)
this.updateChunksStats()
@ -992,7 +1004,6 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
this.active = false
this.renderUpdateEmitter.removeAllListeners()
this.displayOptions.worldView.removeAllListeners() // todo
this.abortController.abort()
removeAllStats()
}

View file

@ -17,7 +17,8 @@ import { watchOptionsAfterWorldViewInit } from './watchOptions'
export interface RendererReactiveState {
world: {
chunksLoaded: string[]
chunksLoaded: Set<string>
heightmaps: Map<string, Uint8Array>
chunksTotalNumber: number
allChunksLoaded: boolean
mesherWork: boolean
@ -28,7 +29,7 @@ export interface RendererReactiveState {
}
export interface NonReactiveState {
world: {
chunksLoaded: string[]
chunksLoaded: Set<string>
chunksTotalNumber: number
allChunksLoaded: boolean
mesherWork: boolean

View file

@ -708,7 +708,7 @@ export async function connect (connectOptions: ConnectOptions) {
resolve()
unsub()
} else {
const perc = Math.round(appViewer.rendererState.world.chunksLoaded.length / appViewer.rendererState.world.chunksTotalNumber * 100)
const perc = Math.round(appViewer.rendererState.world.chunksLoaded.size / appViewer.rendererState.world.chunksTotalNumber * 100)
progress?.reportProgress('chunks', perc / 100)
}
})

View file

@ -85,6 +85,7 @@ export default (
top: '0px',
padding: '5px 5px 0px 0px',
textAlign: 'center',
zIndex: 7,
}}
onClick={() => {
toggleFullMap?.()
@ -106,7 +107,7 @@ export default (
textShadow: '0.1em 0 black, 0 0.1em black, -0.1em 0 black, 0 -0.1em black, -0.1em -0.1em black, -0.1em 0.1em black, 0.1em -0.1em black, 0.1em 0.1em black'
}}
>
{position.x.toFixed(2)} {position.y.toFixed(2)} {position.z.toFixed(2)}
{Math.round(position.x)} {Math.round(position.y)} {Math.round(position.z)}
</div>
</div> : null
}

View file

@ -10,7 +10,7 @@ import { Chunk } from 'prismarine-world/types/world'
import { Block } from 'prismarine-block'
import { INVISIBLE_BLOCKS } from 'renderer/viewer/lib/mesher/worldConstants'
import { getRenamedData } from 'flying-squid/dist/blockRenames'
import { useSnapshot } from 'valtio'
import { useSnapshot, subscribe } from 'valtio'
import { subscribeKey } from 'valtio/utils'
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
import BlockData from '../../renderer/viewer/lib/moreBlockDataGenerated.json'
@ -42,7 +42,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
yaw: number
world: string
warps: WorldWarp[] = gameAdditionalState.warps
chunksStore = new Map<string, undefined | null | 'requested' | ChunkInfo >()
chunksStore = new Map<string, undefined | null | 'requested' | ChunkInfo>()
loadingChunksQueue = new Set<string>()
loadChunk: (key: string) => Promise<void> = this.loadChunkMinimap
mapDrawer = new MinimapDrawer(this.loadChunk, this.warps, this.loadingChunksQueue, this.chunksStore)
@ -119,9 +119,9 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
this.blockData.set(renamedKey, BlockData.colors[blockKey])
}
subscribeKey(appViewer.rendererState, 'world', () => {
subscribe(appViewer.rendererState.world, () => {
for (const key of this.loadingChunksQueue) {
if (appViewer.rendererState.world.chunksLoaded.includes(key)) {
if (appViewer.rendererState.world.chunksLoaded.has(key)) {
this.loadingChunksQueue.delete(key)
void this.loadChunk(key)
}
@ -205,33 +205,31 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
const [chunkX, chunkZ] = key.split(',').map(Number)
const chunkWorldX = chunkX * 16
const chunkWorldZ = chunkZ * 16
if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) {
const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks(`${chunkWorldX},${chunkWorldZ}`)
if (!highestBlocks) return undefined
const heightmap = new Uint8Array(256)
if (appViewer.rendererState.world.chunksLoaded.has(key)) {
// console.log('[MinimapProvider] loading chunk for minimap', key)
const heightmap = appViewer.rendererState.world.heightmaps.get(key)
if (heightmap) {
// console.log('[MinimapProvider] did get highest blocks')
} else {
console.warn('[MinimapProvider] no highestBlocks from renderMethods')
return undefined
}
const colors = Array.from({ length: 256 }).fill('') as string[]
// avoid creating new object every time
const blockPos = new Vec3(0, 0, 0)
// filling up colors and heightmap
// filling up colors
for (let z = 0; z < 16; z += 1) {
for (let x = 0; x < 16; x += 1) {
const blockX = chunkWorldX + x
const blockZ = chunkWorldZ + z
const hBlock = highestBlocks[`${blockX},${blockZ}`]
blockPos.x = blockX; blockPos.z = blockZ; blockPos.y = hBlock?.y ?? 0
let block = bot.world.getBlock(blockPos)
while (block?.name.includes('air')) {
blockPos.y -= 1
block = bot.world.getBlock(blockPos)
}
const index = z * 16 + x
blockPos.x = blockX; blockPos.z = blockZ; blockPos.y = heightmap[index]
const block = bot.world.getBlock(blockPos)
// blocks which are not set are shown as half transparent
if (!block || !hBlock) {
heightmap[index] = 0
if (!block) {
colors[index] = 'rgba(0, 0, 0, 0.5)'
continue
}
heightmap[index] = block.position.y
colors[index] = this.setColor(block)
}
}
@ -242,6 +240,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
} else {
this.loadingChunksQueue.add(`${chunkX},${chunkZ}`)
this.chunksStore.set(key, 'requested')
// console.log('[MinimapProvider] requested new chunk', key)
}
}
@ -339,7 +338,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
const chunkWorldX = chunkX * 16
const chunkWorldZ = chunkZ * 16
const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks(`${chunkWorldX},${chunkWorldZ}`)
if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) {
if (appViewer.rendererState.world.chunksLoaded.has(`${chunkWorldX},${chunkWorldZ}`)) {
const heightmap = new Uint8Array(256)
const colors = Array.from({ length: 256 }).fill('') as string[]
if (!highestBlocks) return null