From 14763ed3c68bcf2a21e2494bc0d8920cc0ac1f62 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Tue, 22 Jan 2013 06:49:14 +0000 Subject: Added new statistics module to AnvilStats - cHeightMap. This paints the heightmap of each region file into a separate BMP file. git-svn-id: http://mc-server.googlecode.com/svn/trunk@1163 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- AnvilStats/AnvilStats.cpp | 3 + AnvilStats/AnvilStats.vcproj | 8 ++ AnvilStats/Callback.h | 9 +- AnvilStats/HeightMap.cpp | 265 +++++++++++++++++++++++++++++++++++++++++++ AnvilStats/HeightMap.h | 81 +++++++++++++ AnvilStats/Processor.cpp | 18 ++- 6 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 AnvilStats/HeightMap.cpp create mode 100644 AnvilStats/HeightMap.h diff --git a/AnvilStats/AnvilStats.cpp b/AnvilStats/AnvilStats.cpp index c0b456b96..a72548a5d 100644 --- a/AnvilStats/AnvilStats.cpp +++ b/AnvilStats/AnvilStats.cpp @@ -7,6 +7,7 @@ #include "Processor.h" #include "Statistics.h" #include "BiomeMap.h" +#include "HeightMap.h" @@ -20,6 +21,7 @@ int main(int argc, char * argv[]) LOG("Available methods:"); LOG(" 0 - statistics"); LOG(" 1 - biome map"); + LOG(" 2 - height map"); LOG("\nNo method number present, aborting."); return -1; } @@ -39,6 +41,7 @@ int main(int argc, char * argv[]) { case 0: Factory = new cStatisticsFactory; break; case 1: Factory = new cBiomeMapFactory; break; + case 2: Factory = new cHeightMapFactory; break; default: { LOG("Unknown method \"%s\", aborting.", argv[1]); diff --git a/AnvilStats/AnvilStats.vcproj b/AnvilStats/AnvilStats.vcproj index 168fba012..e67634f9c 100644 --- a/AnvilStats/AnvilStats.vcproj +++ b/AnvilStats/AnvilStats.vcproj @@ -305,6 +305,14 @@ RelativePath=".\Globals.h" > + + + + diff --git a/AnvilStats/Callback.h b/AnvilStats/Callback.h index 52d2e27eb..92d394d0e 100644 --- a/AnvilStats/Callback.h +++ b/AnvilStats/Callback.h @@ -56,7 +56,10 @@ public: virtual bool OnBiomes(const unsigned char * a_BiomeData) { return true; } - virtual bool OnHeightMap(const int * a_HeightMap) { return true; } + /** Called when a heightmap for the chunk is read from the file. + Note that the heightmap is given in big-endian ints, so if you want it, you need to ntohl() it first! + */ + virtual bool OnHeightMap(const int * a_HeightMapBE) { return true; } /** If there is data for the section, this callback is called; otherwise OnEmptySection() is called instead. All OnSection() callbacks are called first, and only then all the remaining sections are reported in OnEmptySection(). @@ -75,6 +78,10 @@ public: */ virtual bool OnEmptySection(unsigned char a_Y) { return false; } + /** Called after all sections have been processed via either OnSection() or OnEmptySection(). + */ + virtual bool OnSectionsFinished(void) { return true; } + /** Called for each entity in the chunk. Common parameters are parsed from the NBT. The callback may parse any other param from the a_NBT and a_NBTTag parameters. diff --git a/AnvilStats/HeightMap.cpp b/AnvilStats/HeightMap.cpp new file mode 100644 index 000000000..9f52c2109 --- /dev/null +++ b/AnvilStats/HeightMap.cpp @@ -0,0 +1,265 @@ + +// HeightMap.cpp + +// Implements the cHeightMap class representing a cCallback descendant that draws a B&W map of heights for the world + +#include "Globals.h" +#include "HeightMap.h" + + + + +static const unsigned char g_BMPHeader[] = +{ + 0x42, 0x4D, 0x36, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +} ; + + + + + +cHeightMap::cHeightMap(void) : + m_CurrentRegionX(0), + m_CurrentRegionZ(0), + m_IsCurrentRegionValid(false) +{ +} + + + + + +void cHeightMap::Finish(void) +{ + if (m_IsCurrentRegionValid) + { + StartNewRegion(0, 0); + } +} + + + + + +bool cHeightMap::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + int RegionX = (a_ChunkX < 0) ? (a_ChunkX - 31) / 32 : a_ChunkX / 32; + int RegionZ = (a_ChunkZ < 0) ? (a_ChunkZ - 31) / 32 : a_ChunkZ / 32; + if ((RegionX != m_CurrentRegionX) || (RegionZ != m_CurrentRegionZ)) + { + if (m_IsCurrentRegionValid) + { + StartNewRegion(RegionX, RegionZ); + } + m_CurrentRegionX = RegionX; + m_CurrentRegionZ = RegionZ; + } + m_IsCurrentRegionValid = true; + m_CurrentChunkX = a_ChunkX; + m_CurrentChunkZ = a_ChunkZ; + m_CurrentChunkOffX = m_CurrentChunkX - m_CurrentRegionX * 32; + m_CurrentChunkOffZ = m_CurrentChunkZ - m_CurrentRegionZ * 32; + memset(m_BlockTypes, 0, sizeof(m_BlockTypes)); + return false; +} + + + + + +bool cHeightMap::OnHeightMap(const int * a_HeightMapBE) +{ + ASSERT(m_CurrentChunkOffX >= 0); + ASSERT(m_CurrentChunkOffX < 32); + ASSERT(m_CurrentChunkOffZ >= 0); + ASSERT(m_CurrentChunkOffZ < 32); + int * BaseHeight = m_Height + m_CurrentChunkOffZ * 16 * 512 + m_CurrentChunkOffX * 16; + for (int z = 0; z < 16; z++) + { + int * Row = BaseHeight + z * 512; + for (int x = 0; x < 16; x++) + { + Row[x] = ntohl(a_HeightMapBE[z * 16 + x]); + } + } // for z + return false; // Still want blockdata to remove trees from the heightmap +} + + + + + +bool cHeightMap::OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight +) +{ + // Copy the section data into the appropriate place in the internal buffer + memcpy(m_BlockTypes + a_Y * 16 * 16 * 16, a_BlockTypes, 16 * 16 * 16); + return false; +} + + + + + +bool cHeightMap::OnSectionsFinished(void) +{ + // Remove trees from the heightmap: + for (int z = 0; z < 16; z++) + { + for (int x = 0; x < 16; x++) + { + for (int y = m_Height[512 * (16 * m_CurrentChunkOffZ + z) + 16 * m_CurrentChunkOffX + x]; y >= 0; y--) + { + if (IsGround(m_BlockTypes[256 * y + 16 * z + x])) + { + m_Height[512 * (16 * m_CurrentChunkOffZ + z) + 16 * m_CurrentChunkOffX + x] = y; + break; // for y + } + } // for y + } // for x + } // for z + return true; +} + + + + + +void cHeightMap::StartNewRegion(int a_RegionX, int a_RegionZ) +{ + AString FileName; + Printf(FileName, "Height.%d.%d.bmp", m_CurrentRegionX, m_CurrentRegionZ); + cFile f; + if (!f.Open(FileName, cFile::fmWrite)) + { + LOG("Cannot open file \"%s\" for writing the height map. Data for this region lost.", FileName.c_str()); + } + else + { + f.Write(g_BMPHeader, sizeof(g_BMPHeader)); + for (int z = 0; z < 512; z++) + { + int RowData[512]; + int * HeightRow = m_Height + z * 512; + for (int x = 0; x < 512; x++) + { + RowData[x] = std::max(std::min(HeightRow[x], 255), 0) * 0x010101; + } + f.Write(RowData, sizeof(RowData)); + } // for z + } + + memset(m_Height, 0, sizeof(m_Height)); + m_CurrentRegionX = a_RegionX; + m_CurrentRegionZ = a_RegionZ; +} + + + + + +bool cHeightMap::IsGround(BLOCKTYPE a_BlockType) +{ + // Name all blocks that are NOT ground, return false for them: + switch (a_BlockType) + { + case E_BLOCK_AIR: + case E_BLOCK_BED: + case E_BLOCK_BREWING_STAND: + case E_BLOCK_BROWN_MUSHROOM: + case E_BLOCK_CACTUS: + case E_BLOCK_CAKE: + case E_BLOCK_CARROTS: + case E_BLOCK_CAULDRON: + case E_BLOCK_CHEST: + case E_BLOCK_COBBLESTONE_WALL: + case E_BLOCK_COBWEB: + case E_BLOCK_COCOA_POD: + case E_BLOCK_CROPS: + case E_BLOCK_DEAD_BUSH: + case E_BLOCK_DETECTOR_RAIL: + case E_BLOCK_DIRT: + case E_BLOCK_DRAGON_EGG: + case E_BLOCK_END_PORTAL: + case E_BLOCK_ENDER_CHEST: + case E_BLOCK_FENCE: + case E_BLOCK_FENCE_GATE: + case E_BLOCK_FIRE: + case E_BLOCK_FLOWER_POT: + case E_BLOCK_HEAD: + case E_BLOCK_IRON_BARS: + case E_BLOCK_LADDER: + case E_BLOCK_LAVA: + case E_BLOCK_LEAVES: + case E_BLOCK_LEVER: + case E_BLOCK_LILY_PAD: + case E_BLOCK_LOG: // NOTE: This block is actually solid, but we don't want it because it's the thing that trees are made of, and we're getting rid of trees + case E_BLOCK_MELON: + case E_BLOCK_MELON_STEM: + case E_BLOCK_NETHER_BRICK_FENCE: + case E_BLOCK_NETHER_PORTAL: + case E_BLOCK_POWERED_RAIL: + case E_BLOCK_PUMPKIN: + case E_BLOCK_PUMPKIN_STEM: + case E_BLOCK_RAIL: + case E_BLOCK_RED_ROSE: + case E_BLOCK_RED_MUSHROOM: + case E_BLOCK_REDSTONE_REPEATER_OFF: + case E_BLOCK_REDSTONE_REPEATER_ON: + case E_BLOCK_REDSTONE_TORCH_OFF: + case E_BLOCK_REDSTONE_TORCH_ON: + case E_BLOCK_REDSTONE_WIRE: + case E_BLOCK_REEDS: + case E_BLOCK_SAPLING: + case E_BLOCK_SIGN_POST: + case E_BLOCK_SNOW: + case E_BLOCK_STATIONARY_LAVA: + case E_BLOCK_STATIONARY_WATER: + case E_BLOCK_STONE_BUTTON: + case E_BLOCK_STONE_PRESSURE_PLATE: + case E_BLOCK_TALL_GRASS: + case E_BLOCK_TORCH: + case E_BLOCK_TRIPWIRE: + case E_BLOCK_TRIPWIRE_HOOK: + case E_BLOCK_VINES: + case E_BLOCK_WALLSIGN: + case E_BLOCK_WATER: + case E_BLOCK_WOODEN_BUTTON: + case E_BLOCK_WOODEN_PRESSURE_PLATE: + case E_BLOCK_YELLOW_FLOWER: + { + return false; + } + } + return true; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeightMapFactory: + +cHeightMapFactory::~cHeightMapFactory() +{ + // Force all threads to save their last regions: + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + ((cHeightMap *)(*itr))->Finish(); + } + // TODO: Join all the files into one giant image file +} + + + + diff --git a/AnvilStats/HeightMap.h b/AnvilStats/HeightMap.h new file mode 100644 index 000000000..3263329a9 --- /dev/null +++ b/AnvilStats/HeightMap.h @@ -0,0 +1,81 @@ + +// HeightMap.h + +// Declares the cHeightMap class representing a cCallback descendant that draws a B&W map of heights for the world + + + + + +#pragma once + +#include "Callback.h" + + + + + +class cHeightMap : + public cCallback +{ +public: + cHeightMap(void); + + void Finish(void); + +protected: + int m_CurrentChunkX; // Absolute chunk coords + int m_CurrentChunkZ; + int m_CurrentChunkOffX; // Chunk offset from the start of the region + int m_CurrentChunkOffZ; + int m_CurrentRegionX; + int m_CurrentRegionZ; + bool m_IsCurrentRegionValid; + int m_Height[16 * 32 * 16 * 32]; ///< Height-map of the entire current region [x + 16 * 32 * z] + BLOCKTYPE m_BlockTypes[16 * 16 * 256]; ///< Block data of the currently processed chunk (between OnSection() and OnSectionsFinished() ) + + // cCallback overrides: + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } + virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! + virtual bool OnBiomes(const unsigned char * a_BiomeData) { return false; } + virtual bool OnHeightMap(const int * a_HeightMapBE) override; + virtual bool OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight + ) override; + virtual bool OnSectionsFinished(void) override; + + void StartNewRegion(int a_RegionX, int a_RegionZ); + + static bool IsGround(BLOCKTYPE a_BlockType); +} ; + + + + + +class cHeightMapFactory : + public cCallbackFactory +{ +public: + virtual ~cHeightMapFactory(); + + virtual cCallback * CreateNewCallback(void) + { + return new cHeightMap; + } + +} ; + + + + diff --git a/AnvilStats/Processor.cpp b/AnvilStats/Processor.cpp index 2f6f2d860..02070a8fc 100644 --- a/AnvilStats/Processor.cpp +++ b/AnvilStats/Processor.cpp @@ -326,10 +326,18 @@ bool cProcessor::cThread::ProcessChunkSections(int a_ChunkX, int a_ChunkZ, cPars { if (!SectionProcessed[y]) { - m_Callback.OnEmptySection(y); + if (m_Callback.OnEmptySection(y)) + { + return true; + } } } + if (m_Callback.OnSectionsFinished()) + { + return true; + } + return false; } @@ -498,6 +506,14 @@ void cProcessor::ProcessWorld(const AString & a_WorldFolder, cCallbackFactory & // Start as many threads as there are cores, plus one: // (One more thread can be in the file-read IO block while all other threads crunch the numbers) int NumThreads = GetNumCores() + 1; + + /* + // Limit the number of threads in DEBUG mode to 1 for easier debugging + #ifdef _DEBUG + NumThreads = 1; + #endif // _DEBUG + */ + for (int i = 0; i < NumThreads; i++) { cCallback * Callback = a_CallbackFactory.GetNewCallback(); -- cgit v1.2.3