diff --git a/Tools/AnvilStats/.gitignore b/Tools/AnvilStats/.gitignore index 6832eb9f8..5d98f06ec 100644 --- a/Tools/AnvilStats/.gitignore +++ b/Tools/AnvilStats/.gitignore @@ -5,3 +5,5 @@ Debug/ Release/ Profiling *.png +world/ +*.html \ No newline at end of file diff --git a/Tools/AnvilStats/AnvilStats.cpp b/Tools/AnvilStats/AnvilStats.cpp index f0b9dd7e6..d98c21985 100644 --- a/Tools/AnvilStats/AnvilStats.cpp +++ b/Tools/AnvilStats/AnvilStats.cpp @@ -8,6 +8,7 @@ #include "Statistics.h" #include "BiomeMap.h" #include "HeightMap.h" +#include "HeightBiomeMap.h" #include "ChunkExtract.h" #include "SpringStats.h" @@ -26,6 +27,7 @@ int main(int argc, char * argv[]) LOG(" 2 - height map"); LOG(" 3 - extract chunks"); LOG(" 4 - count lava- and water- springs"); + LOG(" 5 - biome and height map"); LOG("\nNo method number present, aborting."); return -1; } @@ -48,6 +50,7 @@ int main(int argc, char * argv[]) case 2: Factory = new cHeightMapFactory; break; case 3: Factory = new cChunkExtractFactory(WorldFolder); break; case 4: Factory = new cSpringStatsFactory; break; + case 5: Factory = new cHeightBiomeMapFactory; break; default: { LOG("Unknown method \"%s\", aborting.", argv[1]); diff --git a/Tools/AnvilStats/AnvilStats.vcproj b/Tools/AnvilStats/AnvilStats.vcproj index b6000ea3e..038f32b97 100644 --- a/Tools/AnvilStats/AnvilStats.vcproj +++ b/Tools/AnvilStats/AnvilStats.vcproj @@ -313,6 +313,14 @@ RelativePath=".\Globals.h" > + + + + diff --git a/Tools/AnvilStats/HeightBiomeMap.cpp b/Tools/AnvilStats/HeightBiomeMap.cpp new file mode 100644 index 000000000..36918f644 --- /dev/null +++ b/Tools/AnvilStats/HeightBiomeMap.cpp @@ -0,0 +1,230 @@ + +// HeightBiomeMap.cpp + +// Declares the cHeightBiomeMap class representing a stats module that produces an image of heights and biomes combined + +#include "Globals.h" +#include "HeightBiomeMap.h" +#include "HeightMap.h" + + + + + +cHeightBiomeMap::cHeightBiomeMap(void) : + super("HeBi"), + m_MinRegionX(100000), + m_MaxRegionX(-100000), + m_MinRegionZ(100000), + m_MaxRegionZ(-100000) +{ +} + + + + + +bool cHeightBiomeMap::OnNewRegion(int a_RegionX, int a_RegionZ) +{ + if (a_RegionX < m_MinRegionX) + { + m_MinRegionX = a_RegionX; + } + if (a_RegionX > m_MaxRegionX) + { + m_MaxRegionX = a_RegionX; + } + if (a_RegionZ < m_MinRegionZ) + { + m_MinRegionZ = a_RegionZ; + } + if (a_RegionZ > m_MaxRegionZ) + { + m_MaxRegionZ = a_RegionZ; + } + return super::OnNewRegion(a_RegionX, a_RegionZ); +} + + + + + +bool cHeightBiomeMap::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + m_CurrentChunkX = a_ChunkX; + m_CurrentChunkZ = a_ChunkZ; + m_CurrentChunkRelX = m_CurrentChunkX - m_CurrentRegionX * 32; + m_CurrentChunkRelZ = m_CurrentChunkZ - m_CurrentRegionZ * 32; + + ASSERT((m_CurrentChunkRelX >= 0) && (m_CurrentChunkRelX < 32)); + ASSERT((m_CurrentChunkRelZ >= 0) && (m_CurrentChunkRelZ < 32)); + + memset(m_BlockTypes, 0, sizeof(m_BlockTypes)); + + return CALLBACK_CONTINUE; +} + + + + + + +bool cHeightBiomeMap::OnBiomes(const unsigned char * a_BiomeData) +{ + memcpy(m_ChunkBiomes, a_BiomeData, sizeof(m_ChunkBiomes)); + + return CALLBACK_CONTINUE; +} + + + + + +bool cHeightBiomeMap::OnHeightMap(const int * a_HeightMapBE) +{ + for (int i = 0; i < ARRAYCOUNT(m_ChunkHeight); i++) + { + m_ChunkHeight[i] = ntohl(a_HeightMapBE[i]); + } // for i - m_ChunkHeight + + return CALLBACK_CONTINUE; +} + + + + + +bool cHeightBiomeMap::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 CALLBACK_CONTINUE; +} + + + + + +bool cHeightBiomeMap::OnSectionsFinished(void) +{ + static const int BiomePalette[] = + { + // ARGB: + 0xff0000ff, /* Ocean */ + 0xff00cf3f, /* Plains */ + 0xffffff00, /* Desert */ + 0xff7f7f7f, /* Extreme Hills */ + 0xff00cf00, /* Forest */ + 0xff007f3f, /* Taiga */ + 0xff3f7f00, /* Swampland */ + 0xff003fff, /* River */ + 0xff7f0000, /* Hell */ + 0xff007fff, /* Sky */ + 0xff3f3fff, /* Frozen Ocean */ + 0xff3f3fff, /* Frozen River */ + 0xff7fffcf, /* Ice Plains */ + 0xff3fcf7f, /* Ice Mountains */ + 0xffcf00cf, /* Mushroom Island */ + 0xff7f00ff, /* Mushroom Island Shore */ + 0xffffff3f, /* Beach */ + 0xffcfcf00, /* Desert Hills */ + 0xff00cf3f, /* Forest Hills */ + 0xff006f1f, /* Taiga Hills */ + 0xff7f8f7f, /* Extreme Hills Edge */ + 0xff004f00, /* Jungle */ + 0xff003f00, /* Jungle Hills */ + } ; + + // Remove trees and other unwanted stuff from the heightmap: + for (int z = 0; z < 16; z++) + { + int PixelLine[16]; // line of 16 pixels that is used as a buffer for setting the image pixels + for (int x = 0; x < 16; x++) + { + int Height = m_ChunkHeight[16 * z + x]; + for (int y = Height; y >= 0; y--) + { + if (cHeightMap::IsGround(m_BlockTypes[256 * y + 16 * z + x])) + { + Height = y; + break; // for y + } + } // for y + + // Set the color based on the biome and height: + char Biome = m_ChunkBiomes[16 * z + x]; + PixelLine[x] = ShadeColor(BiomePalette[Biome], Height); + } // for x + + // Set the pixelline into the image: + SetPixelURow(m_CurrentChunkRelX * 16, m_CurrentChunkRelZ * 16 + z, 16, PixelLine); + } // for z + return CALLBACK_ABORT; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeightBiomeMapFactory: + +cHeightBiomeMapFactory::~cHeightBiomeMapFactory() +{ + // Get the min and max region coords: + int MinRegionX = 100000; + int MaxRegionX = -100000; + int MinRegionZ = 100000; + int MaxRegionZ = -100000; + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + cHeightBiomeMap * cb = (cHeightBiomeMap *)(*itr); + if (cb->m_MinRegionX < MinRegionX) + { + MinRegionX = cb->m_MinRegionX; + } + if (cb->m_MaxRegionX > MaxRegionX) + { + MaxRegionX = cb->m_MaxRegionX; + } + if (cb->m_MinRegionZ < MinRegionZ) + { + MinRegionZ = cb->m_MinRegionZ; + } + if (cb->m_MaxRegionZ > MaxRegionZ) + { + MaxRegionZ = cb->m_MaxRegionZ; + } + } + + // If the size is small enough, write an HTML file referencing all the images in a table: + if ((MaxRegionX >= MinRegionX) && (MaxRegionZ >= MinRegionZ) && (MaxRegionX - MinRegionX < 100) && (MaxRegionZ - MinRegionZ < 100)) + { + cFile HTML("HeBi.html", cFile::fmWrite); + if (HTML.IsOpen()) + { + HTML.Printf("\n"); + for (int z = MinRegionZ; z <= MaxRegionZ; z++) + { + HTML.Printf(""); + for (int x = MinRegionX; x <= MaxRegionX; x++) + { + HTML.Printf("", x, z); + } + HTML.Printf("\n"); + } + HTML.Printf("
"); + } + } +} + + + + diff --git a/Tools/AnvilStats/HeightBiomeMap.h b/Tools/AnvilStats/HeightBiomeMap.h new file mode 100644 index 000000000..d38fa4733 --- /dev/null +++ b/Tools/AnvilStats/HeightBiomeMap.h @@ -0,0 +1,81 @@ + +// HeightBiomeMap.h + +// Declares the cHeightBiomeMap class representing a stats module that produces an image of heights and biomes combined + + + + + +#pragma once + +#include "ImageComposingCallback.h" + + + + + +class cHeightBiomeMap : + public cImageComposingCallback +{ + typedef cImageComposingCallback super; + +public: + // Minima and maxima for the regions processed through this callback + int m_MinRegionX, m_MaxRegionX; + int m_MinRegionZ, m_MaxRegionZ; + + cHeightBiomeMap(void); + +protected: + int m_CurrentChunkX; // Absolute chunk coords + int m_CurrentChunkZ; + int m_CurrentChunkRelX; // Chunk offset from the start of the region + int m_CurrentChunkRelZ; + + char m_ChunkBiomes[16 * 16]; ///< Biome-map for the current chunk + int m_ChunkHeight[16 * 16]; ///< Height-map for the current chunk + BLOCKTYPE m_BlockTypes [16 * 16 * 256]; ///< Block data for the current chunk (between OnSection() and OnSectionsFinished() ) + + // cCallback overrides: + virtual bool OnNewRegion(int a_RegionX, int a_RegionZ) override; + 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 CALLBACK_CONTINUE; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return CALLBACK_CONTINUE; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return CALLBACK_CONTINUE; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return CALLBACK_CONTINUE; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return CALLBACK_CONTINUE; } + virtual bool OnTerrainPopulated(bool a_Populated) override { return a_Populated ? CALLBACK_CONTINUE : CALLBACK_ABORT; } // If not populated, we don't want it! + virtual bool OnBiomes(const unsigned char * a_BiomeData) override; + 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; + +} ; + + + + + +class cHeightBiomeMapFactory : + public cCallbackFactory +{ +public: + virtual ~cHeightBiomeMapFactory(); + + virtual cCallback * CreateNewCallback(void) override + { + return new cHeightBiomeMap; + } +} ; + + + + diff --git a/Tools/AnvilStats/HeightMap.h b/Tools/AnvilStats/HeightMap.h index c0e71cbc1..e1d73f300 100644 --- a/Tools/AnvilStats/HeightMap.h +++ b/Tools/AnvilStats/HeightMap.h @@ -23,6 +23,8 @@ public: void Finish(void); + static bool IsGround(BLOCKTYPE a_BlockType); + protected: int m_CurrentChunkX; // Absolute chunk coords int m_CurrentChunkZ; @@ -55,8 +57,6 @@ protected: virtual bool OnSectionsFinished(void) override; void StartNewRegion(int a_RegionX, int a_RegionZ); - - static bool IsGround(BLOCKTYPE a_BlockType); } ; diff --git a/Tools/AnvilStats/ImageComposingCallback.cpp b/Tools/AnvilStats/ImageComposingCallback.cpp index 138821698..eb43ad49f 100644 --- a/Tools/AnvilStats/ImageComposingCallback.cpp +++ b/Tools/AnvilStats/ImageComposingCallback.cpp @@ -72,7 +72,7 @@ void cImageComposingCallback::OnRegionFinished(int a_RegionX, int a_RegionZ) AString cImageComposingCallback::GetFileName(int a_RegionX, int a_RegionZ) { - return Printf("%s.%d.%d", m_FileNamePrefix.c_str(), a_RegionX, a_RegionZ); + return Printf("%s.%d.%d.bmp", m_FileNamePrefix.c_str(), a_RegionX, a_RegionZ); } @@ -148,7 +148,7 @@ int cImageComposingCallback::GetPixel(int a_RelU, int a_RelV) void cImageComposingCallback::SetPixelURow(int a_RelUStart, int a_RelV, int a_CountU, int * a_Pixels) { - ASSERT((a_RelUStart >= 0) && (a_RelUStart + a_CountU < IMAGE_WIDTH)); + ASSERT((a_RelUStart >= 0) && (a_RelUStart + a_CountU <= IMAGE_WIDTH)); ASSERT((a_RelV >= 0) && (a_RelV < IMAGE_HEIGHT)); ASSERT(a_Pixels != NULL); @@ -163,6 +163,36 @@ void cImageComposingCallback::SetPixelURow(int a_RelUStart, int a_RelV, int a_Co +int cImageComposingCallback::ShadeColor(int a_Color, int a_Shade) +{ + if (a_Shade < 64) + { + return MixColor(0, a_Color, a_Shade * 4); + } + return MixColor(a_Color, 0xffffff, (a_Shade - 64) * 4); +} + + + + + +int cImageComposingCallback::MixColor(int a_Src, int a_Dest, int a_Amount) +{ + int r = a_Src & 0xff; + int g = (a_Src >> 8) & 0xff; + int b = (a_Src >> 16) & 0xff; + int rd = a_Dest & 0xff; + int gd = (a_Dest >> 8) & 0xff; + int bd = (a_Dest >> 16) & 0xff; + int nr = r + (rd - r) * a_Amount / 256; + int ng = g + (gd - g) * a_Amount / 256; + int nb = b + (bd - b) * a_Amount / 256; + return nr | (ng << 8) | (nb << 16); +} + + + + void cImageComposingCallback::SaveImage(const AString & a_FileName) { cFile f(a_FileName, cFile::fmWrite); diff --git a/Tools/AnvilStats/ImageComposingCallback.h b/Tools/AnvilStats/ImageComposingCallback.h index c04dc869f..2936361d6 100644 --- a/Tools/AnvilStats/ImageComposingCallback.h +++ b/Tools/AnvilStats/ImageComposingCallback.h @@ -77,6 +77,16 @@ public: /// Sets a row of pixels. a_Pixels is expected to be a_CountU pixels wide. a_RelUStart + a_CountU is assumed less than image width void SetPixelURow(int a_RelUStart, int a_RelV, int a_CountU, int * a_Pixels); + /** "Shades" the given color based on the shade amount given + Shade amount 0 .. 63 shades the color from black to a_Color. + Shade amount 64 .. 127 shades the color from a_Color to white. + All other shade amounts have undefined results. + */ + static int ShadeColor(int a_Color, int a_Shade); + + /// Mixes the two colors in the specified ratio; a_Ratio is between 0 and 256, 0 returning a_Src + static int MixColor(int a_Src, int a_Dest, int a_Ratio); + protected: /// Prefix for the filenames, when generated by the default GetFileName() function AString m_FileNamePrefix;