From 785ab490f2fe76bcdd760dac046baa014c09b529 Mon Sep 17 00:00:00 2001 From: gguio <109200692+gguio@users.noreply.github.com> Date: Sun, 18 May 2025 10:58:52 +0400 Subject: [PATCH] fix: restore minimal and full map (#348) Co-authored-by: gguio --- renderer/viewer/baseGraphicsBackend.ts | 3 +- renderer/viewer/lib/mesher/mesher.ts | 23 +++++++++++++ renderer/viewer/lib/mesher/models.ts | 6 ++-- renderer/viewer/lib/mesher/shared.ts | 11 +++++- renderer/viewer/lib/worldrendererCommon.ts | 33 ++++++++++++------ src/appViewer.ts | 5 +-- src/index.ts | 2 +- src/react/Minimap.tsx | 3 +- src/react/MinimapProvider.tsx | 39 +++++++++++----------- 9 files changed, 85 insertions(+), 40 deletions(-) diff --git a/renderer/viewer/baseGraphicsBackend.ts b/renderer/viewer/baseGraphicsBackend.ts index 79607695..4724076a 100644 --- a/renderer/viewer/baseGraphicsBackend.ts +++ b/renderer/viewer/baseGraphicsBackend.ts @@ -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, diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 21e2d8ef..66621f22 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -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 } } diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index 802ecaf0..3658d120 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -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 diff --git a/renderer/viewer/lib/mesher/shared.ts b/renderer/viewer/lib/mesher/shared.ts index eb1346f4..16b89a71 100644 --- a/renderer/viewer/lib/mesher/shared.ts +++ b/renderer/viewer/lib/mesher/shared.ts @@ -40,12 +40,21 @@ export type MesherGeometryOutput = { heads: Record, signs: Record, // isFull: boolean - highestBlocks: Record + highestBlocks: Map 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 }; + 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 = { diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 516e4264..ef82f7af 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -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 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 ONMESSAGE_TIME_LIMIT = 30 // ms handleResize = () => { } - highestBlocksByChunks = {} as Record - highestBlocksBySections = {} as Record + highestBlocksByChunks = new Map() + highestBlocksBySections = new Map() blockEntities = {} workersProcessAverageTime = 0 @@ -247,7 +248,7 @@ export abstract class WorldRendererCommon } 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 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 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 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 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 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 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 this.active = false this.renderUpdateEmitter.removeAllListeners() - this.displayOptions.worldView.removeAllListeners() // todo this.abortController.abort() removeAllStats() } diff --git a/src/appViewer.ts b/src/appViewer.ts index ca62bd1b..90ca847f 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -17,7 +17,8 @@ import { watchOptionsAfterWorldViewInit } from './watchOptions' export interface RendererReactiveState { world: { - chunksLoaded: string[] + chunksLoaded: Set + heightmaps: Map chunksTotalNumber: number allChunksLoaded: boolean mesherWork: boolean @@ -28,7 +29,7 @@ export interface RendererReactiveState { } export interface NonReactiveState { world: { - chunksLoaded: string[] + chunksLoaded: Set chunksTotalNumber: number allChunksLoaded: boolean mesherWork: boolean diff --git a/src/index.ts b/src/index.ts index afa349fa..bea10726 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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) } }) diff --git a/src/react/Minimap.tsx b/src/react/Minimap.tsx index 10c2b666..25ee59ed 100644 --- a/src/react/Minimap.tsx +++ b/src/react/Minimap.tsx @@ -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)} : null } diff --git a/src/react/MinimapProvider.tsx b/src/react/MinimapProvider.tsx index 6e7e01ff..afb6a198 100644 --- a/src/react/MinimapProvider.tsx +++ b/src/react/MinimapProvider.tsx @@ -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 implements yaw: number world: string warps: WorldWarp[] = gameAdditionalState.warps - chunksStore = new Map() + chunksStore = new Map() loadingChunksQueue = new Set() loadChunk: (key: string) => Promise = this.loadChunkMinimap mapDrawer = new MinimapDrawer(this.loadChunk, this.warps, this.loadingChunksQueue, this.chunksStore) @@ -119,9 +119,9 @@ export class DrawerAdapterImpl extends TypedEventEmitter 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 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 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 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