diff options
author | Alexander Harkness <bearbin@gmail.com> | 2014-10-11 19:32:21 +0200 |
---|---|---|
committer | Alexander Harkness <bearbin@gmail.com> | 2014-10-11 19:32:21 +0200 |
commit | 93833069a80fe4aec33a95148df39ad40671ddaf (patch) | |
tree | 2e6a335a60e618b5fe456ca8a586a2c5448c8c87 /Tools/QtBiomeVisualiser | |
parent | Reverted submodule changes. (diff) | |
parent | Merge pull request #1528 from kjanku1/master (diff) | |
download | cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.gz cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.bz2 cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.lz cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.xz cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.tar.zst cuberite-93833069a80fe4aec33a95148df39ad40671ddaf.zip |
Diffstat (limited to 'Tools/QtBiomeVisualiser')
-rw-r--r-- | Tools/QtBiomeVisualiser/.gitignore | 2 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/BiomeView.cpp | 414 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/BiomeView.h | 114 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/ChunkCache.cpp | 126 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/ChunkCache.h | 72 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/ChunkLoader.cpp | 29 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/ChunkLoader.h | 45 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/ChunkSource.cpp | 284 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/ChunkSource.h | 107 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/GeneratorSetup.cpp | 158 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/GeneratorSetup.h | 64 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/Globals.h | 395 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/MainWindow.cpp | 440 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/MainWindow.h | 127 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/QtBiomeVisualiser.cpp | 20 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/QtBiomeVisualiser.pro | 100 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/QtChunk.cpp | 190 | ||||
-rw-r--r-- | Tools/QtBiomeVisualiser/QtChunk.h | 51 |
18 files changed, 2738 insertions, 0 deletions
diff --git a/Tools/QtBiomeVisualiser/.gitignore b/Tools/QtBiomeVisualiser/.gitignore new file mode 100644 index 000000000..c1b62a8a7 --- /dev/null +++ b/Tools/QtBiomeVisualiser/.gitignore @@ -0,0 +1,2 @@ +*.pro.user +*.pro.user.* diff --git a/Tools/QtBiomeVisualiser/BiomeView.cpp b/Tools/QtBiomeVisualiser/BiomeView.cpp new file mode 100644 index 000000000..b44b935d7 --- /dev/null +++ b/Tools/QtBiomeVisualiser/BiomeView.cpp @@ -0,0 +1,414 @@ +#include "Globals.h" +#include "BiomeView.h" +#include "QtChunk.h" +#include <QPainter> +#include <QResizeEvent> + + + + + +static const int DELTA_STEP = 120; // The normal per-notch wheel delta + + + + + +BiomeView::BiomeView(QWidget * parent) : + super(parent), + m_X(0), + m_Z(0), + m_Zoom(1), + m_IsMouseDragging(false), + m_MouseWheelDelta(0) +{ + // Create the image used for undefined chunks: + int offset = 0; + for (int y = 0; y < 16; y++) + { + for (int x = 0; x < 16; x++) + { + uchar color = (((x & 8) ^ (y & 8)) == 0) ? 0x44 : 0x88; + m_EmptyChunkImage[offset++] = color; + m_EmptyChunkImage[offset++] = color; + m_EmptyChunkImage[offset++] = color; + m_EmptyChunkImage[offset++] = 0xff; + } + } + + // Create the startup image: + redraw(); + + // Add a chunk-update callback mechanism: + connect(&m_Cache, SIGNAL(chunkAvailable(int, int)), this, SLOT(chunkAvailable(int, int))); + + // Allow mouse and keyboard interaction: + setFocusPolicy(Qt::StrongFocus); + setMouseTracking(true); +} + + + + +QSize BiomeView::minimumSizeHint() const +{ + return QSize(300, 300); +} + + + + + +QSize BiomeView::sizeHint() const +{ + return QSize(800, 600); +} + + + + + +void BiomeView::setChunkSource(std::shared_ptr<ChunkSource> a_ChunkSource) +{ + // Replace the source in the cache: + m_Cache.setChunkSource(a_ChunkSource); + + // Redraw with the new source: + redraw(); +} + + + + + +void BiomeView::setPosition(int a_BlockX, int a_BlockZ) +{ + m_X = a_BlockX; + m_Z = a_BlockZ; + redraw(); +} + + + + + +void BiomeView::setZoomLevel(double a_ZoomLevel) +{ + m_Zoom = a_ZoomLevel; + redraw(); +} + + + + + +void BiomeView::redraw() +{ + if (!hasData()) + { + // No data means no image is displayed, no need to compose: + update(); + return; + } + + int chunksize = 16 * m_Zoom; + + // first find the center block position + int centerchunkx = floor(m_X / 16); + int centerchunkz = floor(m_Z / 16); + // and the center of the screen + int centerx = m_Image.width() / 2; + int centery = m_Image.height() / 2; + // and align for panning + centerx -= (m_X - centerchunkx * 16) * m_Zoom; + centery -= (m_Z - centerchunkz * 16) * m_Zoom; + // now calculate the topleft block on the screen + int startx = centerchunkx - centerx / chunksize - 1; + int startz = centerchunkz - centery / chunksize - 1; + // and the dimensions of the screen in blocks + int blockswide = m_Image.width() / chunksize + 3; + int blockstall = m_Image.height() / chunksize + 3; + + for (int z = startz; z < startz + blockstall; z++) + { + for (int x = startx; x < startx + blockswide; x++) + { + drawChunk(x, z); + } + } + update(); +} + + + + + +void BiomeView::chunkAvailable(int a_ChunkX, int a_ChunkZ) +{ + drawChunk(a_ChunkX, a_ChunkZ); + update(); +} + + + + + +void BiomeView::reload() +{ + if (!hasData()) + { + return; + } + m_Cache.reload(); + + redraw(); +} + + + + + +void BiomeView::drawChunk(int a_ChunkX, int a_ChunkZ) +{ + if (!hasData()) + { + return; + } + + //fetch the chunk: + ChunkPtr chunk = m_Cache.fetch(a_ChunkX, a_ChunkZ); + + // Figure out where on the screen this chunk should be drawn: + // first find the center chunk + int centerchunkx = floor(m_X / 16); + int centerchunkz = floor(m_Z / 16); + // and the center chunk screen coordinates + int centerx = m_Image.width() / 2; + int centery = m_Image.height() / 2; + // which need to be shifted to account for panning inside that chunk + centerx -= (m_X - centerchunkx * 16) * m_Zoom; + centery -= (m_Z - centerchunkz * 16) * m_Zoom; + // centerx,y now points to the top left corner of the center chunk + // so now calculate our x,y in relation + double chunksize = 16 * m_Zoom; + centerx += (a_ChunkX - centerchunkx) * chunksize; + centery += (a_ChunkZ - centerchunkz) * chunksize; + + int srcoffset = 0; + uchar * bits = m_Image.bits(); + int imgstride = m_Image.bytesPerLine(); + + int skipx = 0,skipy = 0; + int blockwidth = chunksize, blockheight = chunksize; + // now if we're off the screen we need to crop + if (centerx < 0) + { + skipx = -centerx; + centerx = 0; + } + if (centery < 0) + { + skipy = -centery; + centery = 0; + } + // or the other side, we need to trim + if (centerx + blockwidth > m_Image.width()) + { + blockwidth = m_Image.width() - centerx; + } + if (centery + blockheight > m_Image.height()) + { + blockheight = m_Image.height() - centery; + } + if ((blockwidth <= 0) || (skipx >= blockwidth)) + { + return; + } + int imgoffset = centerx * 4 + centery * imgstride; + + // If the chunk is valid, use its data; otherwise use the empty placeholder: + const uchar * src = m_EmptyChunkImage; + if (chunk.get() != nullptr) + { + src = chunk->getImage(); + } + + // Blit or scale-blit the image: + for (int z = skipy; z < blockheight; z++, imgoffset += imgstride) + { + srcoffset = floor((double)z / m_Zoom) * 16 * 4; + if (m_Zoom == 1.0) + { + memcpy(bits + imgoffset, src + srcoffset + skipx * 4, (blockwidth - skipx) * 4); + } + else + { + int xofs = 0; + for (int x = skipx; x < blockwidth; x++, xofs +=4) + { + memcpy(bits + imgoffset + xofs, src + srcoffset + (int)floor((double)x / m_Zoom) * 4, 4); + } + } + } +} + + + + + +void BiomeView::resizeEvent(QResizeEvent * a_Event) +{ + m_Image = QImage(a_Event->size(), QImage::Format_RGB32); + redraw(); +} + + + + + +void BiomeView::paintEvent(QPaintEvent * a_Event) +{ + QPainter p(this); + if (hasData()) + { + p.drawImage(QPoint(0, 0), m_Image); + } + else + { + p.drawText(a_Event->rect(), Qt::AlignCenter, "No chunk source selected"); + } + p.end(); +} + + + + + +void BiomeView::mousePressEvent(QMouseEvent * a_Event) +{ + m_LastX = a_Event->x(); + m_LastY = a_Event->y(); + m_IsMouseDragging = true; +} + + + + + +void BiomeView::mouseMoveEvent(QMouseEvent * a_Event) +{ + // If there's no data displayed, bail out: + if (!hasData()) + { + return; + } + + if (m_IsMouseDragging) + { + // The user is dragging the mouse, move the view around: + m_X += (m_LastX - a_Event->x()) / m_Zoom; + m_Z += (m_LastY - a_Event->y()) / m_Zoom; + m_LastX = a_Event->x(); + m_LastY = a_Event->y(); + redraw(); + return; + } + + // Update the status bar info text: + int blockX = floor((a_Event->x() - width() / 2) / m_Zoom + m_X); + int blockZ = floor((a_Event->y() - height() / 2) / m_Zoom + m_Z); + int chunkX, chunkZ; + int relX = blockX, relY, relZ = blockZ; + cChunkDef::AbsoluteToRelative(relX, relY, relZ, chunkX, chunkZ); + auto chunk = m_Cache.fetch(chunkX, chunkZ); + int biome = (chunk.get() != nullptr) ? chunk->getBiome(relX, relZ) : biInvalidBiome; + emit hoverChanged(blockX, blockZ, biome); +} + + + + + +void BiomeView::mouseReleaseEvent(QMouseEvent *) +{ + m_IsMouseDragging = false; +} + + + + + +void BiomeView::wheelEvent(QWheelEvent * a_Event) +{ + m_MouseWheelDelta += a_Event->delta(); + while (m_MouseWheelDelta >= DELTA_STEP) + { + emit wheelUp(); + m_MouseWheelDelta -= DELTA_STEP; + } + while (m_MouseWheelDelta <= -DELTA_STEP) + { + emit wheelDown(); + m_MouseWheelDelta += DELTA_STEP; + } +} + + + + + +void BiomeView::keyPressEvent(QKeyEvent * a_Event) +{ + switch (a_Event->key()) + { + case Qt::Key_Up: + case Qt::Key_W: + { + m_Z -= 10.0 / m_Zoom; + redraw(); + break; + } + + case Qt::Key_Down: + case Qt::Key_S: + { + m_Z += 10.0 / m_Zoom; + redraw(); + break; + } + + case Qt::Key_Left: + case Qt::Key_A: + { + m_X -= 10.0 / m_Zoom; + redraw(); + break; + } + + case Qt::Key_Right: + case Qt::Key_D: + { + m_X += 10.0 / m_Zoom; + redraw(); + break; + } + + case Qt::Key_PageUp: + case Qt::Key_Q: + { + emit increaseZoom(); + break; + } + + case Qt::Key_PageDown: + case Qt::Key_E: + { + emit decreaseZoom(); + break; + } + } +} + + + + diff --git a/Tools/QtBiomeVisualiser/BiomeView.h b/Tools/QtBiomeVisualiser/BiomeView.h new file mode 100644 index 000000000..40d8b96ae --- /dev/null +++ b/Tools/QtBiomeVisualiser/BiomeView.h @@ -0,0 +1,114 @@ +#pragma once + +#include <QWidget> +#include <memory> +#include "ChunkCache.h" +#include "ChunkSource.h" + + + + + +class BiomeView : + public QWidget +{ + typedef QWidget super; + Q_OBJECT + +public: + explicit BiomeView(QWidget * parent = NULL); + + QSize minimumSizeHint() const; + QSize sizeHint() const; + + /** Replaces the chunk source used by the biome view to get the chunk biome data. + The entire view is then invalidated and regenerated. */ + void setChunkSource(std::shared_ptr<ChunkSource> a_ChunkSource); + + /** Sets the position of the central pixel of the map to the specified point and redraws the view. */ + void setPosition(int a_BlockX, int a_BlockZ); + + /** Sets the zoom level to the specified value and redraws the view. */ + void setZoomLevel(double a_ZoomLevel); + +signals: + /** Signalled when the user uses the wheel to scroll upwards. */ + void wheelUp(); + + /** Signalled when the user uses the wheel to scroll downwards. */ + void wheelDown(); + + /** Signalled when the user presses a key to increase zoom. */ + void increaseZoom(); + + /** Signalled when the user presses a key to decrease zoom. */ + void decreaseZoom(); + + /** Emitted when the user moves the mouse, to reflect the current block under the cursor. */ + void hoverChanged(int a_BlockX, int a_BlockZ, int a_Biome); + +public slots: + /** Redraw the entire widget area. */ + void redraw(); + + /** A specified chunk has become available, redraw it. */ + void chunkAvailable(int a_ChunkX, int a_ChunkZ); + + /** Reloads the current chunk source and redraws the entire workspace. */ + void reload(); + +protected: + double m_X, m_Z; + double m_Zoom; + + /** Cache for the loaded chunk data. */ + ChunkCache m_Cache; + + /** The entire view's contents in an offscreen image. */ + QImage m_Image; + + /** Coords of the mouse for the previous position, used while dragging. */ + int m_LastX, m_LastY; + + /** Set to true when the user has a mouse button depressed, and is dragging the view. */ + bool m_IsMouseDragging; + + /** Accumulator for the mouse wheel's delta. When the accumulator hits a threshold, the view zooms. */ + int m_MouseWheelDelta; + + /** Data used for rendering a chunk that hasn't been loaded yet */ + uchar m_EmptyChunkImage[16 * 16 * 4]; + + + /** Draws the specified chunk into m_Image */ + void drawChunk(int a_ChunkX, int a_ChunkZ); + + /** Returns true iff the biome view has been initialized to contain proper biome data. */ + bool hasData(void) const { return m_Cache.hasData(); } + + /** Called when the widget is resized */ + virtual void resizeEvent(QResizeEvent *) override; + + /** Paints the entire widget */ + virtual void paintEvent(QPaintEvent *) override; + + /** Called when the user presses any mouse button. */ + virtual void mousePressEvent(QMouseEvent * a_Event); + + /** Called when the user moves the mouse. */ + virtual void mouseMoveEvent(QMouseEvent * a_Event); + + /** Called when the user releases a previously held mouse button. */ + virtual void mouseReleaseEvent(QMouseEvent * a_Event) override; + + /** Called when the user rotates the mouse wheel. */ + virtual void wheelEvent(QWheelEvent * a_Event) override; + + /** Called when the user presses a key. */ + virtual void keyPressEvent(QKeyEvent * a_Event) override; +}; + + + + + diff --git a/Tools/QtBiomeVisualiser/ChunkCache.cpp b/Tools/QtBiomeVisualiser/ChunkCache.cpp new file mode 100644 index 000000000..05c267d30 --- /dev/null +++ b/Tools/QtBiomeVisualiser/ChunkCache.cpp @@ -0,0 +1,126 @@ +#include "Globals.h" +#include "ChunkCache.h" +#include <QMutexLocker> +#include <QThreadPool> +#include "ChunkSource.h" +#include "ChunkLoader.h" + + + + + +ChunkCache::ChunkCache(QObject * parent) : + super(parent) +{ + m_Cache.setMaxCost(1024 * 1024 * 1024); // 1 GiB of memory for the cache +} + + + + + +ChunkPtr ChunkCache::fetch(int a_ChunkX, int a_ChunkZ) +{ + // Retrieve from the cache: + quint32 hash = getChunkHash(a_ChunkX, a_ChunkZ); + ChunkPtr * res; + { + QMutexLocker lock(&m_Mtx); + res = m_Cache[hash]; + // If succesful and chunk loaded, return the retrieved value: + if ((res != nullptr) && (*res)->isValid()) + { + return *res; + } + } + + // If the chunk is in cache but not valid, it means it has been already queued for rendering, do nothing now: + if (res != nullptr) + { + return ChunkPtr(nullptr); + } + + // There's no such item in the cache, create it now: + res = new ChunkPtr(new Chunk); + if (res == nullptr) + { + return ChunkPtr(nullptr); + } + { + QMutexLocker lock(&m_Mtx); + m_Cache.insert(hash, res, sizeof(Chunk)); + } + + // Queue the chunk for rendering: + queueChunkRender(a_ChunkX, a_ChunkZ, *res); + + // Return failure, the chunk is not yet rendered: + return ChunkPtr(nullptr); +} + + + + + +void ChunkCache::setChunkSource(std::shared_ptr<ChunkSource> a_ChunkSource) +{ + // Replace the chunk source: + m_ChunkSource = a_ChunkSource; + + // Clear the cache: + QMutexLocker lock(&m_Mtx); + m_Cache.clear(); +} + + + + + +void ChunkCache::reload() +{ + assert(m_ChunkSource.get() != nullptr); + + // Reload the chunk source: + m_ChunkSource->reload(); + + // Clear the cache: + QMutexLocker lock(&m_Mtx); + m_Cache.clear(); +} + + + + + +void ChunkCache::gotChunk(int a_ChunkX, int a_ChunkZ) +{ + emit chunkAvailable(a_ChunkX, a_ChunkZ); +} + + + + + +quint32 ChunkCache::getChunkHash(int a_ChunkX, int a_ChunkZ) +{ + // Simply join the two coords into a single int + // The coords will never be larger than 16-bits, so we can do this safely + return (((static_cast<quint32>(a_ChunkX) & 0xffff) << 16) | (static_cast<quint32>(a_ChunkZ) & 0xffff)); +} + + + + + +void ChunkCache::queueChunkRender(int a_ChunkX, int a_ChunkZ, ChunkPtr & a_Chunk) +{ + // Create a new loader task: + ChunkLoader * loader = new ChunkLoader(a_ChunkX, a_ChunkZ, a_Chunk, m_ChunkSource); + connect(loader, SIGNAL(loaded(int, int)), this, SLOT(gotChunk(int, int))); + + QThreadPool::globalInstance()->start(loader); +} + + + + diff --git a/Tools/QtBiomeVisualiser/ChunkCache.h b/Tools/QtBiomeVisualiser/ChunkCache.h new file mode 100644 index 000000000..8d198f02f --- /dev/null +++ b/Tools/QtBiomeVisualiser/ChunkCache.h @@ -0,0 +1,72 @@ +#pragma once + +#include <QObject> +#include <QCache> +#include <QMutex> +#include <memory> + + + + + +class Chunk; +typedef std::shared_ptr<Chunk> ChunkPtr; + +class ChunkSource; + + + + + +/** Caches chunk data for reuse */ +class ChunkCache : + public QObject +{ + typedef QObject super; + Q_OBJECT + +public: + explicit ChunkCache(QObject * parent = NULL); + + /** Retrieves the specified chunk from the cache. + Only returns valid chunks; if the chunk is invalid, queues it for rendering and returns an empty ptr. */ + ChunkPtr fetch(int a_ChunkX, int a_ChunkZ); + + /** Replaces the chunk source used by the biome view to get the chunk biome data. + The cache is then invalidated. */ + void setChunkSource(std::shared_ptr<ChunkSource> a_ChunkSource); + + /** Returns true iff the chunk source has been initialized. */ + bool hasData() const { return (m_ChunkSource.get() != nullptr); } + + /** Reloads the current chunk source. */ + void reload(); + +signals: + void chunkAvailable(int a_ChunkX, int a_ChunkZ); + +protected slots: + void gotChunk(int a_ChunkX, int a_ChunkZ); + +protected: + /** The cache of the chunks */ + QCache<quint32, ChunkPtr> m_Cache; + + /** Locks te cache against multithreaded access */ + QMutex m_Mtx; + + /** The source used to get the biome data. */ + std::shared_ptr<ChunkSource> m_ChunkSource; + + + /** Returns the hash used by the chunk in the cache */ + quint32 getChunkHash(int a_ChunkX, int a_ChunkZ); + + /** Queues the specified chunk for rendering by m_ChunkSource. */ + void queueChunkRender(int a_ChunkX, int a_ChunkZ, ChunkPtr & a_Chunk); +}; + + + + + diff --git a/Tools/QtBiomeVisualiser/ChunkLoader.cpp b/Tools/QtBiomeVisualiser/ChunkLoader.cpp new file mode 100644 index 000000000..3d0123b23 --- /dev/null +++ b/Tools/QtBiomeVisualiser/ChunkLoader.cpp @@ -0,0 +1,29 @@ +#include "Globals.h" +#include "ChunkLoader.h" +#include "ChunkSource.h" + + + + + +ChunkLoader::ChunkLoader(int a_ChunkX, int a_ChunkZ, ChunkPtr a_Chunk, ChunkSourcePtr a_ChunkSource) : + m_ChunkX(a_ChunkX), + m_ChunkZ(a_ChunkZ), + m_Chunk(a_Chunk), + m_ChunkSource(a_ChunkSource) +{ +} + + + + + +void ChunkLoader::run() +{ + m_ChunkSource->getChunkBiomes(m_ChunkX, m_ChunkZ, m_Chunk); + emit loaded(m_ChunkX, m_ChunkZ); +} + + + + diff --git a/Tools/QtBiomeVisualiser/ChunkLoader.h b/Tools/QtBiomeVisualiser/ChunkLoader.h new file mode 100644 index 000000000..4d026a45e --- /dev/null +++ b/Tools/QtBiomeVisualiser/ChunkLoader.h @@ -0,0 +1,45 @@ +#pragma once + +#include <QObject> +#include <QRunnable> +#include <memory> + + + + +// fwd: +class Chunk; +typedef std::shared_ptr<Chunk> ChunkPtr; + +class ChunkSource; +typedef std::shared_ptr<ChunkSource> ChunkSourcePtr; + + + + + +class ChunkLoader : + public QObject, + public QRunnable +{ + Q_OBJECT + +public: + ChunkLoader(int a_ChunkX, int a_ChunkZ, ChunkPtr a_Chunk, ChunkSourcePtr a_ChunkSource); + virtual ~ChunkLoader() {} + +signals: + void loaded(int a_ChunkX, int a_ChunkZ); + +protected: + virtual void run() override; + +private: + int m_ChunkX, m_ChunkZ; + ChunkPtr m_Chunk; + ChunkSourcePtr m_ChunkSource; +}; + + + + diff --git a/Tools/QtBiomeVisualiser/ChunkSource.cpp b/Tools/QtBiomeVisualiser/ChunkSource.cpp new file mode 100644 index 000000000..e5cd7a75a --- /dev/null +++ b/Tools/QtBiomeVisualiser/ChunkSource.cpp @@ -0,0 +1,284 @@ +#include "Globals.h" +#include "ChunkSource.h" +#include <QThread> +#include "src/Generating/BioGen.h" +#include "src/StringCompression.h" +#include "src/WorldStorage/FastNBT.h" +#include "inifile/iniFile.h" + + + + + +//////////////////////////////////////////////////////////////////////////////// +// BioGenSource: + +BioGenSource::BioGenSource(cIniFilePtr a_IniFile) : + m_IniFile(a_IniFile), + m_Mtx(QMutex::Recursive) +{ + reload(); +} + + + + + +void BioGenSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) +{ + cChunkDef::BiomeMap biomes; + { + QMutexLocker lock(&m_Mtx); + m_BiomeGen->GenBiomes(a_ChunkX, a_ChunkZ, biomes); + } + a_DestChunk->setBiomes(biomes); +} + + + + + +void BioGenSource::reload() +{ + int seed = m_IniFile->GetValueSetI("Seed", "Seed", 0); + bool unused = false; + QMutexLocker lock(&m_Mtx); + m_BiomeGen.reset(cBiomeGen::CreateBiomeGen(*m_IniFile, seed, unused)); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// AnvilSource::AnvilFile + +class AnvilSource::AnvilFile +{ +public: + /** Coordinates of the region file. */ + int m_RegionX, m_RegionZ; + + /** True iff the file contains proper data. */ + bool m_IsValid; + + + + /** Creates a new instance with the specified region coords. Reads the file header. */ + AnvilFile(int a_RegionX, int a_RegionZ, const AString & a_WorldPath) : + m_RegionX(a_RegionX), + m_RegionZ(a_RegionZ), + m_IsValid(false) + { + readFile(Printf("%s/r.%d.%d.mca", a_WorldPath.c_str(), a_RegionX, a_RegionZ)); + } + + + + /** Returns the compressed data of the specified chunk. + Returns an empty string when chunk not present. */ + AString getChunkData(int a_ChunkX, int a_ChunkZ) + { + if (!m_IsValid) + { + return ""; + } + + // Translate to local coords: + int RelChunkX = a_ChunkX - m_RegionX * 32; + int RelChunkZ = a_ChunkZ - m_RegionZ * 32; + ASSERT((RelChunkX >= 0) && (RelChunkX < 32)); + ASSERT((RelChunkZ >= 0) && (RelChunkZ < 32)); + + // Get the chunk data location: + UInt32 chunkOffset = m_Header[RelChunkX + 32 * RelChunkZ] >> 8; + UInt32 numChunkSectors = m_Header[RelChunkX + 32 * RelChunkZ] & 0xff; + if ((chunkOffset < 2) || (numChunkSectors == 0)) + { + return ""; + } + + // Get the real data size: + const char * chunkData = m_FileData.data() + chunkOffset * 4096; + UInt32 chunkSize = GetBEInt(chunkData); + if ((chunkSize < 2) || (chunkSize / 4096 > numChunkSectors)) + { + // Bad data, bail out + return ""; + } + + // Check the compression method: + if (chunkData[4] != 2) + { + // Chunk is in an unknown compression + return ""; + } + chunkSize--; + + // Read the chunk data: + return m_FileData.substr(chunkOffset * 4096 + 5, chunkSize); + } + +protected: + AString m_FileData; + UInt32 m_Header[2048]; + + + /** Reads the whole specified file contents and parses the header. */ + void readFile(const AString & a_FileName) + { + // Read the entire file: + m_FileData = cFile::ReadWholeFile(a_FileName); + if (m_FileData.size() < sizeof(m_Header)) + { + return; + } + + // Parse the header - change endianness: + const char * hdr = m_FileData.data(); + for (size_t i = 0; i < ARRAYCOUNT(m_Header); i++) + { + m_Header[i] = GetBEInt(hdr + 4 * i); + } + m_IsValid = true; + } +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// AnvilSource: + +AnvilSource::AnvilSource(QString a_WorldRegionFolder) : + m_WorldRegionFolder(a_WorldRegionFolder) +{ +} + + + + + +void AnvilSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) +{ + // Load the compressed data: + AString compressedChunkData = getCompressedChunkData(a_ChunkX, a_ChunkZ); + if (compressedChunkData.empty()) + { + return; + } + + // Uncompress the chunk data: + AString uncompressed; + int res = InflateString(compressedChunkData.data(), compressedChunkData.size(), uncompressed); + if (res != Z_OK) + { + return; + } + + // Parse the NBT data: + cParsedNBT nbt(uncompressed.data(), uncompressed.size()); + if (!nbt.IsValid()) + { + return; + } + + // Get the biomes out of the NBT: + int Level = nbt.FindChildByName(0, "Level"); + if (Level < 0) + { + return; + } + cChunkDef::BiomeMap biomeMap; + int mcsBiomes = nbt.FindChildByName(Level, "MCSBiomes"); + if ((mcsBiomes >= 0) && (nbt.GetDataLength(mcsBiomes) == sizeof(biomeMap))) + { + // Convert the biomes from BigEndian to platform native numbers: + const char * beBiomes = nbt.GetData(mcsBiomes); + for (size_t i = 0; i < ARRAYCOUNT(biomeMap); i++) + { + biomeMap[i] = (EMCSBiome)GetBEInt(beBiomes + 4 * i); + } + a_DestChunk->setBiomes(biomeMap); + return; + } + + // MCS biomes not found, load Vanilla biomes instead: + int biomes = nbt.FindChildByName(Level, "Biomes"); + if ((biomes < 0) || (nbt.GetDataLength(biomes) != ARRAYCOUNT(biomeMap))) + { + return; + } + // Convert the biomes from Vanilla to EMCSBiome: + const char * vanillaBiomes = nbt.GetData(biomes); + for (size_t i = 0; i < ARRAYCOUNT(biomeMap); i++) + { + biomeMap[i] = EMCSBiome(vanillaBiomes[i]); + } + a_DestChunk->setBiomes(biomeMap); +} + + + + + +void AnvilSource::reload() +{ + // Remove all files from the cache: + QMutexLocker lock(&m_Mtx); + m_Files.clear(); +} + + + + + +void AnvilSource::chunkToRegion(int a_ChunkX, int a_ChunkZ, int & a_RegionX, int & a_RegionZ) +{ + a_RegionX = a_ChunkX >> 5; + a_RegionZ = a_ChunkZ >> 5; +} + + + + + +AString AnvilSource::getCompressedChunkData(int a_ChunkX, int a_ChunkZ) +{ + return getAnvilFile(a_ChunkX, a_ChunkZ)->getChunkData(a_ChunkX, a_ChunkZ); +} + + + + + +AnvilSource::AnvilFilePtr AnvilSource::getAnvilFile(int a_ChunkX, int a_ChunkZ) +{ + int RegionX, RegionZ; + chunkToRegion(a_ChunkX, a_ChunkZ, RegionX, RegionZ); + + // Search the cache for the file: + QMutexLocker lock(&m_Mtx); + for (auto itr = m_Files.cbegin(), end = m_Files.cend(); itr != end; ++itr) + { + if (((*itr)->m_RegionX == RegionX) && ((*itr)->m_RegionZ == RegionZ)) + { + // Found the file in the cache, move it to front and return it: + AnvilFilePtr file(*itr); + m_Files.erase(itr); + m_Files.push_front(file); + return file; + } + } + + // File not in cache, create it: + AnvilFilePtr file(new AnvilFile(RegionX, RegionZ, m_WorldRegionFolder.toStdString())); + m_Files.push_front(file); + return file; +} + + + + + diff --git a/Tools/QtBiomeVisualiser/ChunkSource.h b/Tools/QtBiomeVisualiser/ChunkSource.h new file mode 100644 index 000000000..7bd1865ff --- /dev/null +++ b/Tools/QtBiomeVisualiser/ChunkSource.h @@ -0,0 +1,107 @@ +#pragma once +#include "Globals.h" +#include <QString> +#include <QMutex> +#include "QtChunk.h" + + + + + +// fwd: +class cBiomeGen; +typedef std::shared_ptr<cBiomeGen> cBiomeGenPtr; +class cIniFile; +typedef std::shared_ptr<cIniFile> cIniFilePtr; + + + + + +/** Abstract interface for getting biome data for chunks. */ +class ChunkSource +{ +public: + virtual ~ChunkSource() {} + + /** Fills the a_DestChunk with the biomes for the specified coords. + It is expected to be thread-safe and re-entrant. Usually QThread::idealThreadCount() threads are used. */ + virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) = 0; + + /** Forces a fresh reload of the source. Useful mainly for the generator, whose underlying definition file may have been changed. */ + virtual void reload() = 0; +}; + + + + + + +class BioGenSource : + public ChunkSource +{ +public: + /** Constructs a new BioGenSource based on the biome generator that is defined in the specified world.ini file. */ + BioGenSource(cIniFilePtr a_IniFile); + + // ChunkSource overrides: + virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) override; + virtual void reload(void) override; + +protected: + /** The world.ini contents from which the generator is created and re-created on reload(). */ + cIniFilePtr m_IniFile; + + /** The generator used for generating biomes. */ + std::unique_ptr<cBiomeGen> m_BiomeGen; + + /** Guards m_BiomeGen against multithreaded access. */ + QMutex m_Mtx; +}; + + + + +class AnvilSource : + public ChunkSource +{ +public: + /** Constructs a new AnvilSource based on the world path. */ + AnvilSource(QString a_WorldRegionFolder); + + // ChunkSource overrides: + virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) override; + virtual void reload() override; + +protected: + class AnvilFile; + typedef std::shared_ptr<AnvilFile> AnvilFilePtr; + + + /** Folder where the individual Anvil Region files are located. */ + QString m_WorldRegionFolder; + + /** List of currently loaded files. Acts as a cache so that a file is not opened and closed over and over again. + Protected against multithreaded access by m_Mtx. */ + std::list<AnvilFilePtr> m_Files; + + /** Guards m_Files agains multithreaded access. */ + QMutex m_Mtx; + + + /** Converts chunk coords to region coords. */ + void chunkToRegion(int a_ChunkX, int a_ChunkZ, int & a_RegionX, int & a_RegionZ); + + /** Returns the compressed data of the specified chunk. + Returns an empty string if the chunk is not available. */ + AString getCompressedChunkData(int a_ChunkX, int a_ChunkZ); + + /** Returns the file object that contains the specified chunk. + The file is taken from the cache if available there, otherwise it is created anew. */ + AnvilFilePtr getAnvilFile(int a_ChunkX, int a_ChunkZ); + +}; + + + + diff --git a/Tools/QtBiomeVisualiser/GeneratorSetup.cpp b/Tools/QtBiomeVisualiser/GeneratorSetup.cpp new file mode 100644 index 000000000..d6348ee00 --- /dev/null +++ b/Tools/QtBiomeVisualiser/GeneratorSetup.cpp @@ -0,0 +1,158 @@ +#include "Globals.h" +#include "GeneratorSetup.h" +#include <QLabel> +#include <QLineEdit> +#include "src/Generating/BioGen.h" +#include "inifile/iniFile.h" + + + + + +static const QString s_GeneratorNames[] = +{ + QString("Checkerboard"), + QString("Constant"), + QString("DistortedVoronoi"), + QString("MultiStepMap"), + QString("TwoLevel"), + QString("Voronoi"), +}; + + + + + +GeneratorSetup::GeneratorSetup(const AString & a_IniFileName, QWidget * a_Parent) : + super(a_Parent), + m_IniFile(new cIniFile()) +{ + // The seed and generator name is in a separate form layout at the top, always present: + m_eSeed = new QLineEdit(); + m_eSeed->setValidator(new QIntValidator()); + m_eSeed->setText("0"); + m_eSeed->setProperty("INI.SectionName", QVariant("Seed")); + m_eSeed->setProperty("INI.ItemName", QVariant("Seed")); + m_cbGenerator = new QComboBox(); + m_cbGenerator->setMinimumWidth(120); + for (size_t i = 0; i < ARRAYCOUNT(s_GeneratorNames); i++) + { + m_cbGenerator->addItem(s_GeneratorNames[i]); + } + QFormLayout * baseLayout = new QFormLayout(); + baseLayout->addRow(new QLabel(tr("Seed")), m_eSeed); + baseLayout->addRow(new QLabel(tr("Generator")), m_cbGenerator); + + // The rest of the controls are in a dynamically created form layout: + m_FormLayout = new QFormLayout(); + + // The main layout joins these two vertically: + m_MainLayout = new QVBoxLayout(); + m_MainLayout->addLayout(baseLayout); + m_MainLayout->addLayout(m_FormLayout); + m_MainLayout->addStretch(); + setLayout(m_MainLayout); + + // Load the INI file, if specified, otherwise set defaults: + if (a_IniFileName.empty() || !m_IniFile->ReadFile(a_IniFileName)) + { + m_IniFile->SetValue("Generator", "Generator", "Composable"); + m_IniFile->SetValue("Generator", "BiomeGen", m_cbGenerator->currentText().toStdString()); + bool dummy; + delete cBiomeGen::CreateBiomeGen(*m_IniFile, 0, dummy); + } + updateFromIni(); + + // Connect the change events only after the data has been loaded: + connect(m_cbGenerator, SIGNAL(currentIndexChanged(QString)), this, SLOT(generatorChanged(QString))); + connect(m_eSeed, SIGNAL(textChanged(QString)), this, SLOT(editChanged(QString))); +} + + + + + +void GeneratorSetup::generatorChanged(const QString & a_NewName) +{ + // Clear the current contents of the form layout by assigning it to a stack temporary: + { + m_MainLayout->takeAt(1); + QWidget().setLayout(m_FormLayout); + } + + // Re-create the layout: + m_FormLayout = new QFormLayout(); + m_MainLayout->insertLayout(1, m_FormLayout); + + // Recreate the INI file: + m_IniFile->Clear(); + m_IniFile->SetValue("Generator", "Generator", "Composable"); + m_IniFile->SetValue("Generator", "BiomeGen", a_NewName.toStdString()); + + // Create a dummy biome gen from the INI file, this will create the defaults in the INI file: + bool dummy; + delete cBiomeGen::CreateBiomeGen(*m_IniFile, m_Seed, dummy); + + // Read all values from the INI file and put them into the form layout: + updateFromIni(); + + // Notify of the changes: + emit generatorUpdated(); +} + + + + + +void GeneratorSetup::editChanged(const QString & a_NewValue) +{ + QString sectionName = sender()->property("INI.SectionName").toString(); + QString itemName = sender()->property("INI.ItemName").toString(); + m_IniFile->SetValue(sectionName.toStdString(), itemName.toStdString(), a_NewValue.toStdString()); + emit generatorUpdated(); +} + + + + + +void GeneratorSetup::updateFromIni() +{ + m_eSeed->setText(QString::number(m_IniFile->GetValueI("Seed", "Seed", 0))); + int keyID = m_IniFile->FindKey("Generator"); + if (keyID <= -1) + { + return; + } + int numItems = m_IniFile->GetNumValues(keyID); + AString generatorName = m_IniFile->GetValue("Generator", "BiomeGen"); + size_t generatorNameLen = generatorName.length(); + for (int i = 0; i < numItems; i++) + { + AString itemName = m_IniFile->GetValueName(keyID, i); + if ((itemName == "Generator") || (itemName == "BiomeGen")) + { + // These special cases are not to be added + continue; + } + AString itemValue = m_IniFile->GetValue(keyID, i); + + QLineEdit * edit = new QLineEdit(); + edit->setText(QString::fromStdString(itemValue)); + edit->setProperty("INI.SectionName", QVariant("Generator")); + edit->setProperty("INI.ItemName", QVariant(QString::fromStdString(itemName))); + + // Remove the generator name prefix from the item name, for clarity purposes: + if (NoCaseCompare(itemName.substr(0, generatorNameLen), generatorName) == 0) + { + itemName.erase(0, generatorNameLen); + } + + connect(edit, SIGNAL(textChanged(QString)), this, SLOT(editChanged(QString))); + m_FormLayout->addRow(new QLabel(QString::fromStdString(itemName)), edit); + } // for i - INI values[] +} + + + + diff --git a/Tools/QtBiomeVisualiser/GeneratorSetup.h b/Tools/QtBiomeVisualiser/GeneratorSetup.h new file mode 100644 index 000000000..e72d3abbc --- /dev/null +++ b/Tools/QtBiomeVisualiser/GeneratorSetup.h @@ -0,0 +1,64 @@ +#pragma once + +#include <memory> +#include <QDialog> +#include <QComboBox> +#include <QVBoxLayout> +#include <QFormLayout> + + + + + +class cIniFile; +typedef std::shared_ptr<cIniFile> cIniFilePtr; + + + + + +class GeneratorSetup : + public QWidget +{ + typedef QWidget super; + + Q_OBJECT + +public: + /** Creates the widget and loads the contents of the INI file, if not empty. */ + explicit GeneratorSetup(const std::string & a_IniFileName, QWidget * parent = nullptr); + + /** Returns the cIniFile instance that is being edited by this widget. */ + cIniFilePtr getIniFile() { return m_IniFile; } + +signals: + /** Emitted when the generator parameters have changed. */ + void generatorUpdated(); + +public slots: + /** Called when the user selects a different generator from the top combobox. + Re-creates m_IniFile and updates the form layout. */ + void generatorChanged(const QString & a_NewName); + +protected slots: + /** Called when any of the edit widgets are changed. */ + void editChanged(const QString & a_NewValue); + +protected: + QComboBox * m_cbGenerator; + QLineEdit * m_eSeed; + QVBoxLayout * m_MainLayout; + QFormLayout * m_FormLayout; + + cIniFilePtr m_IniFile; + + int m_Seed; + + + /** Updates the form layout with the values from m_IniFile. */ + void updateFromIni(); +}; + + + + diff --git a/Tools/QtBiomeVisualiser/Globals.h b/Tools/QtBiomeVisualiser/Globals.h new file mode 100644 index 000000000..e2e9a9970 --- /dev/null +++ b/Tools/QtBiomeVisualiser/Globals.h @@ -0,0 +1,395 @@ +#pragma once + + + + + +// Compiler-dependent stuff: +#if defined(_MSC_VER) + // MSVC produces warning C4481 on the override keyword usage, so disable the warning altogether + #pragma warning(disable:4481) + + // Disable some warnings that we don't care about: + #pragma warning(disable:4100) // Unreferenced formal parameter + + // Useful warnings from warning level 4: + #pragma warning(3 : 4127) // Conditional expression is constant + #pragma warning(3 : 4189) // Local variable is initialized but not referenced + #pragma warning(3 : 4245) // Conversion from 'type1' to 'type2', signed/unsigned mismatch + #pragma warning(3 : 4310) // Cast truncates constant value + #pragma warning(3 : 4389) // Signed/unsigned mismatch + #pragma warning(3 : 4505) // Unreferenced local function has been removed + #pragma warning(3 : 4701) // Potentially unitialized local variable used + #pragma warning(3 : 4702) // Unreachable code + #pragma warning(3 : 4706) // Assignment within conditional expression + + // Disabling this warning, because we know what we're doing when we're doing this: + #pragma warning(disable: 4355) // 'this' used in initializer list + + // Disabled because it's useless: + #pragma warning(disable: 4512) // 'class': assignment operator could not be generated - reported for each class that has a reference-type member + + // 2014_01_06 xoft: Disabled this warning because MSVC is stupid and reports it in obviously wrong places + // #pragma warning(3 : 4244) // Conversion from 'type1' to 'type2', possible loss of data + + #define OBSOLETE __declspec(deprecated) + + // No alignment needed in MSVC + #define ALIGN_8 + #define ALIGN_16 + + #define FORMATSTRING(formatIndex, va_argsIndex) + + // MSVC has its own custom version of zu format + #define SIZE_T_FMT "%Iu" + #define SIZE_T_FMT_PRECISION(x) "%" #x "Iu" + #define SIZE_T_FMT_HEX "%Ix" + + #define NORETURN __declspec(noreturn) + +#elif defined(__GNUC__) + + // TODO: Can GCC explicitly mark classes as abstract (no instances can be created)? + #define abstract + + // override is part of c++11 + #if __cplusplus < 201103L + #define override + #endif + + #define OBSOLETE __attribute__((deprecated)) + + #define ALIGN_8 __attribute__((aligned(8))) + #define ALIGN_16 __attribute__((aligned(16))) + + // Some portability macros :) + #define stricmp strcasecmp + + #define FORMATSTRING(formatIndex, va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex))) + + #if defined(_WIN32) + // We're compiling on MinGW, which uses an old MSVCRT library that has no support for size_t printfing. + // We need direct size formats: + #if defined(_WIN64) + #define SIZE_T_FMT "%I64u" + #define SIZE_T_FMT_PRECISION(x) "%" #x "I64u" + #define SIZE_T_FMT_HEX "%I64x" + #else + #define SIZE_T_FMT "%u" + #define SIZE_T_FMT_PRECISION(x) "%" #x "u" + #define SIZE_T_FMT_HEX "%x" + #endif + #else + // We're compiling on Linux, so we can use libc's size_t printf format: + #define SIZE_T_FMT "%zu" + #define SIZE_T_FMT_PRECISION(x) "%" #x "zu" + #define SIZE_T_FMT_HEX "%zx" + #endif + + #define NORETURN __attribute((__noreturn__)) + +#else + + #error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler" + + /* + // Copy and uncomment this into another #elif section based on your compiler identification + + // Explicitly mark classes as abstract (no instances can be created) + #define abstract + + // Mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) + #define override + + // Mark functions as obsolete, so that their usage results in a compile-time warning + #define OBSOLETE + + // Mark types / variables for alignment. Do the platforms need it? + #define ALIGN_8 + #define ALIGN_16 + */ + +#endif + + +#ifdef _DEBUG + #define NORETURNDEBUG NORETURN +#else + #define NORETURNDEBUG +#endif + + +#include <stddef.h> + + +// Integral types with predefined sizes: +typedef long long Int64; +typedef int Int32; +typedef short Int16; + +typedef unsigned long long UInt64; +typedef unsigned int UInt32; +typedef unsigned short UInt16; + +typedef unsigned char Byte; + + +// If you get an error about specialization check the size of integral types +template <typename T, size_t Size, bool x = sizeof(T) == Size> +class SizeChecker; + +template <typename T, size_t Size> +class SizeChecker<T, Size, true> +{ + T v; +}; + +template class SizeChecker<Int64, 8>; +template class SizeChecker<Int32, 4>; +template class SizeChecker<Int16, 2>; + +template class SizeChecker<UInt64, 8>; +template class SizeChecker<UInt32, 4>; +template class SizeChecker<UInt16, 2>; + +// A macro to disallow the copy constructor and operator = functions +// This should be used in the private: declarations for any class that shouldn't allow copying itself +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &); \ + void operator =(const TypeName &) + +// A macro that is used to mark unused local variables, to avoid pedantic warnings in gcc / clang / MSVC +// Note that in MSVC it requires the full type of X to be known +#define UNUSED_VAR(X) (void)(X) + +// A macro that is used to mark unused function parameters, to avoid pedantic warnings in gcc +// Written so that the full type of param needn't be known +#ifdef _MSC_VER + #define UNUSED(X) +#else + #define UNUSED UNUSED_VAR +#endif + + + + +// OS-dependent stuff: +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + + #define _WIN32_WINNT 0x501 // We want to target WinXP and higher + + #include <Windows.h> + #include <winsock2.h> + #include <Ws2tcpip.h> // IPv6 stuff + + // Windows SDK defines min and max macros, messing up with our std::min and std::max usage + #undef min + #undef max + + // Windows SDK defines GetFreeSpace as a constant, probably a Win16 API remnant + #ifdef GetFreeSpace + #undef GetFreeSpace + #endif // GetFreeSpace +#else + #include <sys/types.h> + #include <sys/time.h> + #include <sys/socket.h> + #include <netinet/in.h> + #include <arpa/inet.h> + #include <netdb.h> + #include <time.h> + #include <dirent.h> + #include <errno.h> + #include <iostream> + #include <cstdio> + #include <cstring> + #include <pthread.h> + #include <semaphore.h> + #include <errno.h> + #include <fcntl.h> +#endif + +#if defined(ANDROID_NDK) + #define FILE_IO_PREFIX "/sdcard/mcserver/" +#else + #define FILE_IO_PREFIX "" +#endif + + + + + +// CRT stuff: +#include <sys/stat.h> +#include <assert.h> +#include <stdio.h> +#include <math.h> +#include <stdarg.h> + + + + + +// STL stuff: +#include <vector> +#include <list> +#include <deque> +#include <string> +#include <map> +#include <algorithm> +#include <memory> +#include <set> +#include <queue> +#include <limits> + + + +#ifndef TEST_GLOBALS + // Common headers (part 1, without macros): + #include "src/StringUtils.h" + #include "src/OSSupport/Sleep.h" + #include "src/OSSupport/CriticalSection.h" + #include "src/OSSupport/Semaphore.h" + #include "src/OSSupport/Event.h" + #include "src/OSSupport/Thread.h" + #include "src/OSSupport/File.h" + #include "src/Logger.h" +#else + // Logging functions +void inline LOGERROR(const char* a_Format, ...) FORMATSTRING(1, 2); + +void inline LOGERROR(const char* a_Format, ...) +{ + va_list argList; + va_start(argList, a_Format); + vprintf(a_Format, argList); + va_end(argList); +} +#endif + + + + + +// Common definitions: + +/// Evaluates to the number of elements in an array (compile-time!) +#define ARRAYCOUNT(X) (sizeof(X) / sizeof(*(X))) + +/// Allows arithmetic expressions like "32 KiB" (but consider using parenthesis around it, "(32 KiB)") +#define KiB * 1024 +#define MiB * 1024 * 1024 + +/// Faster than (int)floorf((float)x / (float)div) +#define FAST_FLOOR_DIV( x, div) (((x) - (((x) < 0) ? ((div) - 1) : 0)) / (div)) + +// Own version of assert() that writes failed assertions to the log for review +#ifdef TEST_GLOBALS + + class cAssertFailure + { + }; + + #ifdef _WIN32 + #if (defined(_MSC_VER) && defined(_DEBUG)) + #define DBG_BREAK _CrtDbgBreak() + #else + #define DBG_BREAK + #endif + #define REPORT_ERROR(FMT, ...) \ + { \ + AString msg = Printf(FMT, __VA_ARGS__); \ + puts(msg.c_str()); \ + fflush(stdout); \ + OutputDebugStringA(msg.c_str()); \ + DBG_BREAK; \ + } + #else + #define REPORT_ERROR(FMT, ...) \ + { \ + AString msg = Printf(FMT, __VA_ARGS__); \ + puts(msg.c_str()); \ + fflush(stdout); \ + } + #endif + #define ASSERT(x) do { if (!(x)) { throw cAssertFailure();} } while (0) + #define testassert(x) do { if (!(x)) { REPORT_ERROR("Test failure: %s, file %s, line %d\n", #x, __FILE__, __LINE__); exit(1); } } while (0) + #define CheckAsserts(x) do { try {x} catch (cAssertFailure) { break; } REPORT_ERROR("Test failure: assert didn't fire for %s, file %s, line %d\n", #x, __FILE__, __LINE__); exit(1); } while (0) + +#else + #ifdef _DEBUG + #define ASSERT( x) ( !!(x) || ( LOGERROR("Assertion failed: %s, file %s, line %i", #x, __FILE__, __LINE__), assert(0), 0)) + #else + #define ASSERT(x) ((void)(x)) + #endif +#endif + +// Pretty much the same as ASSERT() but stays in Release builds +#define VERIFY( x) ( !!(x) || ( LOGERROR("Verification failed: %s, file %s, line %i", #x, __FILE__, __LINE__), exit(1), 0)) + +// Same as assert but in all Self test builds +#ifdef SELF_TEST + #define assert_test(x) ( !!(x) || (assert(!#x), exit(1), 0)) +#endif + +// Allow both Older versions of MSVC and newer versions of everything use a shared_ptr: +// Note that we cannot typedef, because C++ doesn't allow (partial) templates to be typedeffed. +#if (defined(_MSC_VER) && (_MSC_VER < 1600)) + // MSVC before 2010 doesn't have std::shared_ptr, but has std::tr1::shared_ptr, defined in <memory> included earlier + #define SharedPtr std::tr1::shared_ptr +#elif (defined(_MSC_VER) || (__cplusplus >= 201103L)) + // C++11 has std::shared_ptr in <memory>, included earlier + #define SharedPtr std::shared_ptr +#else + // C++03 has std::tr1::shared_ptr in <tr1/memory> + #include <tr1/memory> + #define SharedPtr std::tr1::shared_ptr +#endif + + + + + +/** A generic interface used mainly in ForEach() functions */ +template <typename Type> class cItemCallback +{ +public: + virtual ~cItemCallback() {} + + /** Called for each item in the internal list; return true to stop the loop, or false to continue enumerating */ + virtual bool Item(Type * a_Type) = 0; +} ; + + + + +/** Clamp X to the specified range. */ +template <typename T> +T Clamp(T a_Value, T a_Min, T a_Max) +{ + return (a_Value < a_Min) ? a_Min : ((a_Value > a_Max) ? a_Max : a_Value); +} + + + + + +#ifndef TOLUA_TEMPLATE_BIND + #define TOLUA_TEMPLATE_BIND(x) +#endif + + + + + +// Common headers (part 2, with macros): +#include "src/ChunkDef.h" +#include "src/BiomeDef.h" +#include "src/BlockID.h" +#include "src/BlockInfo.h" + + + + + diff --git a/Tools/QtBiomeVisualiser/MainWindow.cpp b/Tools/QtBiomeVisualiser/MainWindow.cpp new file mode 100644 index 000000000..63d72f992 --- /dev/null +++ b/Tools/QtBiomeVisualiser/MainWindow.cpp @@ -0,0 +1,440 @@ +#include "Globals.h" +#include "MainWindow.h" +#include <QVBoxLayout> +#include <QAction> +#include <QMenuBar> +#include <QApplication> +#include <QFileDialog> +#include <QSettings> +#include <QDirIterator> +#include <QStatusBar> +#include "inifile/iniFile.h" +#include "ChunkSource.h" +#include "src/Generating/BioGen.h" +#include "src/StringCompression.h" +#include "src/WorldStorage/FastNBT.h" +#include "GeneratorSetup.h" + + + + + +const double MainWindow::m_ViewZooms[] = +{ + 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 24, +}; + + + + + +MainWindow::MainWindow(QWidget * parent) : + QMainWindow(parent), + m_GeneratorSetup(nullptr), + m_LineSeparator(nullptr) +{ + initMinecraftPath(); + + m_BiomeView = new BiomeView(); + connect(m_BiomeView, SIGNAL(increaseZoom()), this, SLOT(increaseZoom())); + connect(m_BiomeView, SIGNAL(decreaseZoom()), this, SLOT(decreaseZoom())); + connect(m_BiomeView, SIGNAL(wheelUp()), this, SLOT(increaseZoom())); + connect(m_BiomeView, SIGNAL(wheelDown()), this, SLOT(decreaseZoom())); + + m_StatusBar = new QStatusBar(); + this->setStatusBar(m_StatusBar); + m_StatusBlockX = new QLabel(tr("X")); + m_StatusBlockZ = new QLabel(tr("Z")); + m_StatusBiome = new QLabel(tr("B")); + m_StatusBar->addPermanentWidget(m_StatusBlockX); + m_StatusBar->addPermanentWidget(m_StatusBlockZ); + m_StatusBar->addPermanentWidget(m_StatusBiome); + + m_MainLayout = new QHBoxLayout(); + m_MainLayout->addWidget(m_BiomeView, 1); + m_MainLayout->setMenuBar(menuBar()); + m_MainLayout->setMargin(0); + QWidget * central = new QWidget(); + central->setLayout(m_MainLayout); + setCentralWidget(central); + + createActions(); + createMenus(); + + connect(m_BiomeView, SIGNAL(hoverChanged(int, int, int)), this, SLOT(hoverChanged(int, int, int))); +} + + + + + +MainWindow::~MainWindow() +{ + +} + + + + + +void MainWindow::newGenerator() +{ + // (Re-)open the generator setup dialog with empty settings: + openGeneratorSetup(""); + + // Set the chunk source: + cIniFilePtr iniFile = m_GeneratorSetup->getIniFile(); + m_BiomeView->setChunkSource(std::shared_ptr<BioGenSource>(new BioGenSource(iniFile))); + m_BiomeView->redraw(); +} + + + + + +void MainWindow::openGenerator() +{ + // Let the user specify the world.ini file: + QString worldIni = QFileDialog::getOpenFileName(this, tr("Open world.ini"), QString(), tr("world.ini (world.ini)")); + if (worldIni.isEmpty()) + { + return; + } + + // (Re-)open the generator setup dialog: + openGeneratorSetup(worldIni.toStdString()); + + // Set the chunk source: + m_BiomeView->setChunkSource(std::shared_ptr<BioGenSource>(new BioGenSource(m_GeneratorSetup->getIniFile()))); + m_BiomeView->redraw(); +} + + + + + +void MainWindow::openWorld() +{ + // Let the user specify the world: + QString regionFolder = QFileDialog::getExistingDirectory(this, tr("Select the region folder"), QString()); + if (regionFolder.isEmpty()) + { + return; + } + + // Remove the generator setup dialog, if open: + closeGeneratorSetup(); + + // Set the chunk source: + m_BiomeView->setChunkSource(std::shared_ptr<AnvilSource>(new AnvilSource(regionFolder))); + m_BiomeView->redraw(); +} + + + + + +void MainWindow::openVanillaWorld() +{ + // The world is stored in the sender action's data, retrieve it: + QAction * action = qobject_cast<QAction *>(sender()); + if (action == nullptr) + { + return; + } + + // Remove the generator setup dialog, if open: + closeGeneratorSetup(); + + // Set the chunk source: + m_BiomeView->setChunkSource(std::shared_ptr<AnvilSource>(new AnvilSource(action->data().toString()))); + m_BiomeView->redraw(); +} + + + + + +void MainWindow::centerView() +{ + m_BiomeView->setPosition(0, 0); +} + + + + + +void MainWindow::setViewZoom() +{ + // The zoom level is stored in the sender action's data, retrieve it: + QAction * action = qobject_cast<QAction *>(sender()); + if (action == nullptr) + { + return; + } + double newZoom = m_ViewZooms[action->data().toInt()]; + m_BiomeView->setZoomLevel(newZoom); + action->setChecked(true); +} + + + + + +void MainWindow::increaseZoom() +{ + // If already at max zoom, bail out: + if (m_CurrentZoomLevel >= ARRAYCOUNT(m_ViewZooms) - 1) + { + return; + } + + // Increase the zoom level: + m_CurrentZoomLevel += 1; + m_actViewZoom[m_CurrentZoomLevel]->setChecked(true); + m_BiomeView->setZoomLevel(m_ViewZooms[m_CurrentZoomLevel]); +} + + + + + +void MainWindow::decreaseZoom() +{ + // If already at min zoom, bail out: + if (m_CurrentZoomLevel == 0) + { + return; + } + + // Decrease the zoom level: + m_CurrentZoomLevel -= 1; + m_actViewZoom[m_CurrentZoomLevel]->setChecked(true); + m_BiomeView->setZoomLevel(m_ViewZooms[m_CurrentZoomLevel]); +} + + + + + +void MainWindow::hoverChanged(int a_BlockX, int a_BlockZ, int a_Biome) +{ + m_StatusBlockX->setText(tr("X: %1").arg(a_BlockX)); + m_StatusBlockZ->setText(tr("Z: %1").arg(a_BlockZ)); + m_StatusBiome->setText (tr("B: %1 (%2)").arg(BiomeToString(a_Biome).c_str()).arg(a_Biome)); +} + + + + + +void MainWindow::initMinecraftPath() +{ + #ifdef Q_OS_MAC + m_MinecraftPath = QDir::homePath() + QDir::toNativeSeparators("/Library/Application Support/minecraft"); + #elif defined Q_OS_WIN32 + QSettings ini(QSettings::IniFormat, QSettings::UserScope, ".minecraft", "minecraft1"); + m_MinecraftPath = QFileInfo(ini.fileName()).absolutePath(); + #else + m_MinecraftPath = QDir::homePath() + QDir::toNativeSeparators("/.minecraft"); + #endif +} + + + + + +void MainWindow::createActions() +{ + // Map menu: + createWorldActions(); + + m_actNewGen = new QAction(tr("&New generator"), this); + m_actNewGen->setShortcut(tr("Ctrl+N")); + m_actNewGen->setStatusTip(tr("Open a generator INI file and display the generated biomes")); + connect(m_actNewGen, SIGNAL(triggered()), this, SLOT(newGenerator())); + + m_actOpenGen = new QAction(tr("&Open generator..."), this); + m_actOpenGen->setShortcut(tr("Ctrl+G")); + m_actOpenGen->setStatusTip(tr("Open a generator INI file and display the generated biomes")); + connect(m_actOpenGen, SIGNAL(triggered()), this, SLOT(openGenerator())); + + m_actOpenWorld = new QAction(tr("&Open world..."), this); + m_actOpenWorld->setShortcut(tr("Ctrl+O")); + m_actOpenWorld->setStatusTip(tr("Open an existing world and display its biomes")); + connect(m_actOpenWorld, SIGNAL(triggered()), this, SLOT(openWorld())); + + m_actReload = new QAction(tr("&Reload"), this); + m_actReload->setShortcut(tr("F5")); + m_actReload->setStatusTip(tr("Clear the view cache and force a reload of all the data")); + connect(m_actReload, SIGNAL(triggered()), m_BiomeView, SLOT(reload())); + + m_actExit = new QAction(tr("E&xit"), this); + m_actExit->setShortcut(tr("Alt+X")); + m_actExit->setStatusTip(tr("Exit %1").arg(QApplication::instance()->applicationName())); + connect(m_actExit, SIGNAL(triggered()), this, SLOT(close())); + + // View menu: + m_actViewCenter = new QAction(tr("&Reset to center"), this); + m_actViewCenter->setStatusTip(tr("Scrolls the view back to the map center")); + connect(m_actViewCenter, SIGNAL(triggered()), this, SLOT(centerView())); + + QActionGroup * zoomGroup = new QActionGroup(this); + for (int i = 0; i < ARRAYCOUNT(m_ViewZooms); i++) + { + m_actViewZoom[i] = new QAction(tr("&Zoom %1%").arg(std::floor(m_ViewZooms[i] * 100)), this); + m_actViewZoom[i]->setCheckable(true); + if ((int)(m_ViewZooms[i] * 16) == 16) + { + m_actViewZoom[i]->setChecked(true); + m_CurrentZoomLevel = i; + } + m_actViewZoom[i]->setData(QVariant(i)); + zoomGroup->addAction(m_actViewZoom[i]); + connect(m_actViewZoom[i], SIGNAL(triggered()), this, SLOT(setViewZoom())); + } +} + + + + + +void MainWindow::createWorldActions() +{ + QDir mc(m_MinecraftPath); + if (!mc.cd("saves")) + { + return; + } + + QDirIterator it(mc); + int key = 1; + while (it.hasNext()) + { + it.next(); + if (!it.fileInfo().isDir()) + { + continue; + } + QString name = getWorldName(it.filePath().toStdString()); + if (name.isEmpty()) + { + continue; + } + QAction * w = new QAction(this); + w->setText(name); + w->setData(it.filePath() + "/region"); + if (key < 10) + { + w->setShortcut("Ctrl+" + QString::number(key)); + key++; + } + connect(w, SIGNAL(triggered()), this, SLOT(openVanillaWorld())); + m_WorldActions.append(w); + } +} + + + + + +void MainWindow::createMenus() +{ + // Map menu: + QMenu * file = menuBar()->addMenu(tr("&Map")); + file->addAction(m_actNewGen); + file->addAction(m_actOpenGen); + file->addSeparator(); + QMenu * worlds = file->addMenu(tr("Open &existing")); + worlds->addActions(m_WorldActions); + if (m_WorldActions.empty()) + { + worlds->setEnabled(false); + } + file->addAction(m_actOpenWorld); + file->addSeparator(); + file->addAction(m_actReload); + file->addSeparator(); + file->addAction(m_actExit); + + // View menu: + QMenu * view = menuBar()->addMenu(tr("&View")); + view->addAction(m_actViewCenter); + view->addSeparator(); + for (size_t i = 0; i < ARRAYCOUNT(m_actViewZoom); i++) + { + view->addAction(m_actViewZoom[i]); + } +} + + + + + +QString MainWindow::getWorldName(const AString & a_Path) +{ + AString levelData = cFile::ReadWholeFile(a_Path + "/level.dat"); + if (levelData.empty()) + { + // No such file / no data + return QString(); + } + + AString uncompressed; + if (UncompressStringGZIP(levelData.data(), levelData.size(), uncompressed) != Z_OK) + { + return QString(); + } + cParsedNBT nbt(uncompressed.data(), uncompressed.size()); + if (!nbt.IsValid()) + { + return QString(); + } + AString name = nbt.GetName(1); + int levelNameTag = nbt.FindTagByPath(nbt.GetRoot(), "Data\\LevelName"); + if ((levelNameTag <= 0) || (nbt.GetType(levelNameTag) != TAG_String)) + { + return QString(); + } + return QString::fromStdString(nbt.GetString(levelNameTag)); +} + + + + + +void MainWindow::openGeneratorSetup(const AString & a_IniFileName) +{ + // Close any previous editor: + closeGeneratorSetup(); + + // Open up a new editor: + m_GeneratorSetup = new GeneratorSetup(a_IniFileName); + m_LineSeparator = new QWidget(); + m_LineSeparator->setFixedWidth(2); + m_LineSeparator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + m_LineSeparator->setStyleSheet(QString("background-color: #c0c0c0;")); + m_MainLayout->addWidget(m_LineSeparator); + m_MainLayout->addWidget(m_GeneratorSetup); + + // Connect the signals from the setup pane: + connect(m_GeneratorSetup, SIGNAL(generatorUpdated()), m_BiomeView, SLOT(reload())); +} + + + + + +void MainWindow::closeGeneratorSetup() +{ + delete m_MainLayout->takeAt(2); + delete m_MainLayout->takeAt(1); + delete m_GeneratorSetup; + delete m_LineSeparator; + m_GeneratorSetup = nullptr; + m_LineSeparator = nullptr; +} + + + + diff --git a/Tools/QtBiomeVisualiser/MainWindow.h b/Tools/QtBiomeVisualiser/MainWindow.h new file mode 100644 index 000000000..27faae7a8 --- /dev/null +++ b/Tools/QtBiomeVisualiser/MainWindow.h @@ -0,0 +1,127 @@ +#pragma once + +#include <memory> +#include <QList> +#include <QMainWindow> +#include <QHBoxLayout> +#include <QLabel> +#include "BiomeView.h" + + + + + +// fwd: +class GeneratorSetup; + + + + + +class MainWindow : + public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget * parent = nullptr); + ~MainWindow(); + +private slots: + /** Creates a generator definition from scratch, lets user modify generator params in realtime. */ + void newGenerator(); + + /** Opens a generator definition and generates the biomes based on that. */ + void openGenerator(); + + /** Opens an existing world and displays the loaded biomes. */ + void openWorld(); + + /** Opens a vanilla world that is specified by the calling action. */ + void openVanillaWorld(); + + /** Moves the view to the map's center. */ + void centerView(); + + /** Sets the zoom level specified in the triggering action. */ + void setViewZoom(); + + /** Sets a zoom level one step larger than current, if allowed. */ + void increaseZoom(); + + /** Sets a zoom level one step smaller than current, if allowed. */ + void decreaseZoom(); + + /** Updates the statusbar for the specified info about the current block under the cursor. */ + void hoverChanged(int a_BlockX, int a_BlockZ, int a_Biome); + +protected: + /** The zoom levels */ + static const double m_ViewZooms[10]; + + // Actions: + QAction * m_actNewGen; + QAction * m_actOpenGen; + QAction * m_actOpenWorld; + QAction * m_actReload; + QAction * m_actExit; + QAction * m_actViewCenter; + QAction * m_actViewZoom[ARRAYCOUNT(m_ViewZooms)]; + + /** List of actions that open the specific vanilla world. */ + QList<QAction *> m_WorldActions; + + /** Path to the vanilla folder. */ + QString m_MinecraftPath; + + /** The pane for setting up the generator, available when visualising a generator. */ + GeneratorSetup * m_GeneratorSetup; + + /** The main biome display widget. */ + BiomeView * m_BiomeView; + + /** The layout for the window. */ + QHBoxLayout * m_MainLayout; + + /** The status bar that displays the current hover information. */ + QStatusBar * m_StatusBar; + + QLabel * m_StatusBlockX; + QLabel * m_StatusBlockZ; + QLabel * m_StatusBiome; + + /** The separator line between biome view and generator setup. */ + QWidget * m_LineSeparator; + + /** Index into m_ViewZooms[] for the current zoom level. */ + size_t m_CurrentZoomLevel; + + + /** Initializes the m_MinecraftPath based on the proper MC path */ + void initMinecraftPath(); + + /** Creates the actions that the UI supports. */ + void createActions(); + + /** Creates the actions that open a specific vanilla world. Iterates over the minecraft saves folder. */ + void createWorldActions(); + + /** Creates the menu bar and connects its events. */ + void createMenus(); + + /** Returns the name of the vanilla world in the specified path. + Reads the level.dat file for the name. Returns an empty string on failure. */ + QString getWorldName(const AString & a_Path); + + /** Opens the generator setup pane, if not already open, and loads the specified INI file to it. */ + void openGeneratorSetup(const AString & a_IniFileName); + + /** Closes and destroys the generator setup pane, if there is one. */ + void closeGeneratorSetup(); +}; + + + + + + diff --git a/Tools/QtBiomeVisualiser/QtBiomeVisualiser.cpp b/Tools/QtBiomeVisualiser/QtBiomeVisualiser.cpp new file mode 100644 index 000000000..f41cdcfb2 --- /dev/null +++ b/Tools/QtBiomeVisualiser/QtBiomeVisualiser.cpp @@ -0,0 +1,20 @@ +#include "Globals.h" +#include "MainWindow.h" +#include <QApplication> + + + + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} + + + + diff --git a/Tools/QtBiomeVisualiser/QtBiomeVisualiser.pro b/Tools/QtBiomeVisualiser/QtBiomeVisualiser.pro new file mode 100644 index 000000000..9e5d1303c --- /dev/null +++ b/Tools/QtBiomeVisualiser/QtBiomeVisualiser.pro @@ -0,0 +1,100 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2014-09-11T15:22:43 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = QtBiomeVisualiser +TEMPLATE = app + + +SOURCES +=\ + MainWindow.cpp \ + BiomeView.cpp \ + ../../src/Generating/BioGen.cpp \ + ../../src/VoronoiMap.cpp \ + ../../src/Noise.cpp \ + ../../src/StringUtils.cpp \ + ../../src/LoggerListeners.cpp \ + ../../src/Logger.cpp \ + ../../lib/inifile/iniFile.cpp \ + ../../src/OSSupport/File.cpp \ + ../../src/OSSupport/CriticalSection.cpp \ + ../../src/OSSupport/IsThread.cpp \ + ../../src/BiomeDef.cpp \ + ChunkCache.cpp \ + ChunkSource.cpp \ + ChunkLoader.cpp \ + ../../src/StringCompression.cpp \ + ../../src/WorldStorage/FastNBT.cpp \ + ../../lib/zlib/adler32.c \ + ../../lib/zlib/compress.c \ + ../../lib/zlib/crc32.c \ + ../../lib/zlib/deflate.c \ + ../../lib/zlib/gzclose.c \ + ../../lib/zlib/gzlib.c \ + ../../lib/zlib/gzread.c \ + ../../lib/zlib/gzwrite.c \ + ../../lib/zlib/infback.c \ + ../../lib/zlib/inffast.c \ + ../../lib/zlib/inflate.c \ + ../../lib/zlib/inftrees.c \ + ../../lib/zlib/trees.c \ + ../../lib/zlib/uncompr.c \ + ../../lib/zlib/zutil.c \ + GeneratorSetup.cpp \ + QtBiomeVisualiser.cpp \ + QtChunk.cpp + +HEADERS += MainWindow.h \ + Globals.h \ + BiomeView.h \ + ../../src/Generating/BioGen.h \ + ../../src/VoronoiMap.h \ + ../../src/Noise.h \ + ../../src/StringUtils.h \ + ../../src/LoggerListeners.h \ + ../../src/Logger.h \ + ../../lib/inifile/iniFile.h \ + ../../src/OSSupport/File.h \ + ../../src/OSSupport/CriticalSection.h \ + ../../src/OSSupport/IsThread.h \ + ../../src/BiomeDef.h \ + ChunkCache.h \ + ChunkSource.h \ + ChunkLoader.h \ + ../../src/StringCompression.h \ + ../../src/WorldStorage/FastNBT.h \ + ../../lib/zlib/crc32.h \ + ../../lib/zlib/deflate.h \ + ../../lib/zlib/gzguts.h \ + ../../lib/zlib/inffast.h \ + ../../lib/zlib/inffixed.h \ + ../../lib/zlib/inflate.h \ + ../../lib/zlib/inftrees.h \ + ../../lib/zlib/trees.h \ + ../../lib/zlib/zconf.h \ + ../../lib/zlib/zlib.h \ + ../../lib/zlib/zutil.h \ + GeneratorSetup.h \ + QtChunk.h + +INCLUDEPATH += $$_PRO_FILE_PWD_ \ + $$_PRO_FILE_PWD_/../../lib \ + $$_PRO_FILE_PWD_/../../lib/jsoncpp/include \ + $$_PRO_FILE_PWD_/../../lib/polarssl/include \ + $$_PRO_FILE_PWD_/../../lib/sqlite \ + $$_PRO_FILE_PWD_/../../lib/SQLiteCpp/include \ + $$_PRO_FILE_PWD_/../../ + + + +CONFIG += C++11 + +OTHER_FILES += + + diff --git a/Tools/QtBiomeVisualiser/QtChunk.cpp b/Tools/QtBiomeVisualiser/QtChunk.cpp new file mode 100644 index 000000000..031aa3654 --- /dev/null +++ b/Tools/QtBiomeVisualiser/QtChunk.cpp @@ -0,0 +1,190 @@ +#include "Globals.h" +#include "QtChunk.h" + + + + + +/** Map for converting biome values to colors. Initialized from biomeColors[]. */ +static uchar biomeToColor[256 * 4]; + +/** Map for converting biome values to colors. Used to initialize biomeToColor[].*/ +static struct +{ + EMCSBiome m_Biome; + uchar m_Color[3]; +} biomeColors[] = +{ + { biOcean, { 0x00, 0x00, 0x70 }, }, + { biPlains, { 0x8d, 0xb3, 0x60 }, }, + { biDesert, { 0xfa, 0x94, 0x18 }, }, + { biExtremeHills, { 0x60, 0x60, 0x60 }, }, + { biForest, { 0x05, 0x66, 0x21 }, }, + { biTaiga, { 0x0b, 0x66, 0x59 }, }, + { biSwampland, { 0x2f, 0xff, 0xda }, }, + { biRiver, { 0x30, 0x30, 0xaf }, }, + { biHell, { 0x7f, 0x00, 0x00 }, }, + { biSky, { 0x00, 0x7f, 0xff }, }, + { biFrozenOcean, { 0xa0, 0xa0, 0xdf }, }, + { biFrozenRiver, { 0xa0, 0xa0, 0xff }, }, + { biIcePlains, { 0xff, 0xff, 0xff }, }, + { biIceMountains, { 0xa0, 0xa0, 0xa0 }, }, + { biMushroomIsland, { 0xff, 0x00, 0xff }, }, + { biMushroomShore, { 0xa0, 0x00, 0xff }, }, + { biBeach, { 0xfa, 0xde, 0x55 }, }, + { biDesertHills, { 0xd2, 0x5f, 0x12 }, }, + { biForestHills, { 0x22, 0x55, 0x1c }, }, + { biTaigaHills, { 0x16, 0x39, 0x33 }, }, + { biExtremeHillsEdge, { 0x7f, 0x8f, 0x7f }, }, + { biJungle, { 0x53, 0x7b, 0x09 }, }, + { biJungleHills, { 0x2c, 0x42, 0x05 }, }, + + { biJungleEdge, { 0x62, 0x8b, 0x17 }, }, + { biDeepOcean, { 0x00, 0x00, 0x30 }, }, + { biStoneBeach, { 0xa2, 0xa2, 0x84 }, }, + { biColdBeach, { 0xfa, 0xf0, 0xc0 }, }, + { biBirchForest, { 0x30, 0x74, 0x44 }, }, + { biBirchForestHills, { 0x1f, 0x5f, 0x32 }, }, + { biRoofedForest, { 0x40, 0x51, 0x1a }, }, + { biColdTaiga, { 0x31, 0x55, 0x4a }, }, + { biColdTaigaHills, { 0x59, 0x7d, 0x72 }, }, + { biMegaTaiga, { 0x59, 0x66, 0x51 }, }, + { biMegaTaigaHills, { 0x59, 0x66, 0x59 }, }, + { biExtremeHillsPlus, { 0x50, 0x70, 0x50 }, }, + { biSavanna, { 0xbd, 0xb2, 0x5f }, }, + { biSavannaPlateau, { 0xa7, 0x9d, 0x64 }, }, + { biMesa, { 0xd9, 0x45, 0x15 }, }, + { biMesaPlateauF, { 0xb0, 0x97, 0x65 }, }, + { biMesaPlateau, { 0xca, 0x8c, 0x65 }, }, + + // M variants: + { biSunflowerPlains, { 0xb5, 0xdb, 0x88 }, }, + { biDesertM, { 0xff, 0xbc, 0x40 }, }, + { biExtremeHillsM, { 0x88, 0x88, 0x88 }, }, + { biFlowerForest, { 0x2d, 0x8e, 0x49 }, }, + { biTaigaM, { 0x33, 0x8e, 0x81 }, }, + { biSwamplandM, { 0x07, 0xf9, 0xb2 }, }, + { biIcePlainsSpikes, { 0xb4, 0xdc, 0xdc }, }, + { biJungleM, { 0x7b, 0xa3, 0x31 }, }, + { biJungleEdgeM, { 0x62, 0x8b, 0x17 }, }, + { biBirchForestM, { 0x58, 0x9c, 0x6c }, }, + { biBirchForestHillsM, { 0x47, 0x87, 0x5a }, }, + { biRoofedForestM, { 0x68, 0x79, 0x42 }, }, + { biColdTaigaM, { 0x24, 0x3f, 0x36 }, }, + { biMegaSpruceTaiga, { 0x45, 0x4f, 0x3e }, }, + { biMegaSpruceTaigaHills, { 0x45, 0x4f, 0x4e }, }, + { biExtremeHillsPlusM, { 0x78, 0x98, 0x78 }, }, + { biSavannaM, { 0xe5, 0xda, 0x87 }, }, + { biSavannaPlateauM, { 0xa7, 0x9d, 0x74 }, }, + { biMesaBryce, { 0xff, 0x6d, 0x3d }, }, + { biMesaPlateauFM, { 0xd8, 0xbf, 0x8d }, }, + { biMesaPlateauM, { 0xf2, 0xb4, 0x8d }, }, +} ; + + + + + +static class BiomeColorsInitializer +{ +public: + BiomeColorsInitializer(void) + { + // Reset all colors to gray: + for (size_t i = 0; i < ARRAYCOUNT(biomeToColor); i++) + { + biomeToColor[i] = 0x7f; + } + + // Set known biomes to their colors: + for (size_t i = 0; i < ARRAYCOUNT(biomeColors); i++) + { + uchar * color = &biomeToColor[4 * biomeColors[i].m_Biome]; + color[0] = biomeColors[i].m_Color[2]; + color[1] = biomeColors[i].m_Color[1]; + color[2] = biomeColors[i].m_Color[0]; + color[3] = 0xff; + } + } +} biomeColorInitializer; + + + + + +/** Converts biomes in an array into the chunk image data. */ +static void biomesToImage(const cChunkDef::BiomeMap & a_Biomes, Chunk::Image & a_Image) +{ + // Make sure the two arrays are of the same size, compile-time. + // Note that a_Image is actually 4 items per pixel, so the array is 4 times bigger: + static const char Check1[4 * ARRAYCOUNT(a_Biomes) - ARRAYCOUNT(a_Image) + 1] = {}; + static const char Check2[ARRAYCOUNT(a_Image) - 4 * ARRAYCOUNT(a_Biomes) + 1] = {}; + + // Convert the biomes into color: + for (size_t i = 0; i < ARRAYCOUNT(a_Biomes); i++) + { + a_Image[4 * i + 0] = biomeToColor[4 * a_Biomes[i] + 0]; + a_Image[4 * i + 1] = biomeToColor[4 * a_Biomes[i] + 1]; + a_Image[4 * i + 2] = biomeToColor[4 * a_Biomes[i] + 2]; + a_Image[4 * i + 3] = biomeToColor[4 * a_Biomes[i] + 3]; + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// Chunk: + +Chunk::Chunk() : + m_IsValid(false) +{ +} + + + + + +const uchar * Chunk::getImage(void) const +{ + ASSERT(m_IsValid); + return m_Image; +} + + + + + +void Chunk::setBiomes(const cChunkDef::BiomeMap & a_Biomes) +{ + memcpy(m_Biomes, a_Biomes, sizeof(m_Biomes)); + renderBiomes(); + m_IsValid = true; +} + + + + + +EMCSBiome Chunk::getBiome(int a_RelX, int a_RelZ) +{ + if (!m_IsValid) + { + return biInvalidBiome; + } + return cChunkDef::GetBiome(m_Biomes, a_RelX, a_RelZ); +} + + + + + +void Chunk::renderBiomes() +{ + biomesToImage(m_Biomes, m_Image); +} + + + + diff --git a/Tools/QtBiomeVisualiser/QtChunk.h b/Tools/QtBiomeVisualiser/QtChunk.h new file mode 100644 index 000000000..74321577a --- /dev/null +++ b/Tools/QtBiomeVisualiser/QtChunk.h @@ -0,0 +1,51 @@ +#pragma once + +#include <qglobal.h> + + + + + +class Chunk +{ +public: + /** The type used for storing image data for a chunk. */ + typedef uchar Image[16 * 16 * 4]; + + + Chunk(void); + + /** Returns true iff the chunk data is valid - loaded or generated. */ + bool isValid(void) const { return m_IsValid; } + + /** Returns the image of the chunk's biomes. Assumes that the chunk is valid. */ + const uchar * getImage(void) const; + + /** Sets the biomes to m_Biomes and renders them into m_Image. */ + void setBiomes(const cChunkDef::BiomeMap & a_Biomes); + + /** Returns the biome at the specified relative coords, or biInvalidBiome if not valid. + Coords must be valid inside this chunk. */ + EMCSBiome getBiome(int a_RelX, int a_RelZ); + +protected: + /** Flag that specifies if the chunk data is valid - loaded or generated. */ + bool m_IsValid; + + /** Cached rendered image of this chunk's biomes. Updated in render(). */ + Image m_Image; + + /** Biomes comprising the chunk, in the X + 16 * Z ordering. */ + cChunkDef::BiomeMap m_Biomes; + + + /** Renders biomes from m_Biomes into m_Image. */ + void renderBiomes(); +}; + +typedef std::shared_ptr<Chunk> ChunkPtr; + + + + + |