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
This commit is contained in:
parent
6c97763b2f
commit
14763ed3c6
@ -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]);
|
||||
|
@ -305,6 +305,14 @@
|
||||
RelativePath=".\Globals.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\HeightMap.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\HeightMap.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Processor.cpp"
|
||||
>
|
||||
|
@ -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.
|
||||
|
265
AnvilStats/HeightMap.cpp
Normal file
265
AnvilStats/HeightMap.cpp
Normal file
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
81
AnvilStats/HeightMap.h
Normal file
81
AnvilStats/HeightMap.h
Normal file
@ -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;
|
||||
}
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user