#include "Globals.h" #include "BiomeView.h" #include #include #include "Region.h" static const int DELTA_STEP = 120; // The normal per-notch wheel delta /** 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; //////////////////////////////////////////////////////////////////////////////// // BiomeView: 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(regionAvailable(int, int)), this, SLOT(regionAvailable(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 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::regionAvailable(int a_RegionX, int a_RegionZ) { for (int z = 0; z < 32; z++) { for (int x = 0; x < 32; x++) { drawChunk(a_RegionX * 32 + x, a_RegionZ * 32 + z); } } 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 region: int regionX; int regionZ; Region::chunkToRegion(a_ChunkX, a_ChunkZ, regionX, regionZ); RegionPtr region = m_Cache.fetch(regionX, regionZ); // 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, centery 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; 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 short * src = m_EmptyChunkBiomes; if (region.get() != nullptr) { int relChunkX = a_ChunkX - regionX * 32; int relChunkZ = a_ChunkZ - regionZ * 32; Chunk & chunk = region->getRelChunk(relChunkX, relChunkZ); if (chunk.isValid()) { src = chunk.getBiomes(); } } // Scale-blit the image: for (int z = skipy; z < blockheight; z++, imgoffset += imgstride) { size_t srcoffset = static_cast(std::floor((double)z / m_Zoom)) * 16; int imgxoffset = imgoffset; for (int x = skipx; x < blockwidth; x++) { short biome = src[srcoffset + static_cast(std::floor((double)x / m_Zoom))]; const uchar * color; if (biome < 0) { static const uchar emptyBiome1[] = { 0x44, 0x44, 0x44, 0xff }; static const uchar emptyBiome2[] = { 0x88, 0x88, 0x88, 0xff }; color = ((x & 8) ^ (z & 8)) ? emptyBiome1 : emptyBiome2; } else { if (biome * 4 >= ARRAYCOUNT(biomeToColor)) { static const uchar errorImage[] = { 0xff, 0x00, 0x00, 0xff }; color = errorImage; } else { color = biomeToColor + biome * 4; } } bits[imgxoffset] = color[0]; bits[imgxoffset + 1] = color[1]; bits[imgxoffset + 2] = color[2]; bits[imgxoffset + 3] = color[3]; imgxoffset += 4; } // for x } // for z } 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 regionX, regionZ; Region::blockToRegion(blockX, blockZ, regionX, regionZ); int relX = blockX - regionX * 512; int relZ = blockZ - regionZ * 512; auto region = m_Cache.fetch(regionX, regionZ); int biome = (region.get() != nullptr) ? region->getRelBiome(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; } } }