summaryrefslogblamecommitdiffstats
path: root/Tools/QtBiomeVisualiser/BiomeView.cpp
blob: 8dd79d6191455f62b27e4d1e15ae48e6621ea240 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                      

                       
                   




 





                                                                       


                                                                                 
                                                                                    



                          

































































































































































                                   

                      




















































































                                       






                                   
         


























                                                                                  
                                        
                                                                                                


















                                                                               
                                                                                                    
 
                                                
                                        
                               



































                                                                          




















                                                       














                                                                           
                                          







                                                            
                                                         















                                                                  
                                                             
 






                                                                          






                 














                         






                                                     




                                                                    





                                                                     
                                          



                                                                            

                                                                                 



                                                         


                                               
                                 



























                                                                                    
                                               
                                    
         




                                                                          
                                                
                 

         
                                

                                                                         
                                                                                             

                                                        
                 
                                                                                                            

                                            
                         

                                                                                            
                                                                                        
                         



                                                                          
                                                                                                   






                                                                         
                                                    





                                                        

































                                                                                         












                                                      





                                                  










                                                                        
                                           
                                                                        
                                                                         





                                                                                                 
                                                 
















                                                 


                                               
                               



                                                
                                 

                                                






                                                  
 




































                                             
                                            

                              
 


                                      
                                            


                              
 
#include "Globals.h"
#include "BiomeView.h"
#include <QPainter>
#include <QResizeEvent>
#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<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::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<size_t>(std::floor((double) z / m_Zoom)) * 16;
		int imgxoffset = imgoffset;
		for (int x = skipx; x < blockwidth; x++)
		{
			short biome = src[srcoffset + static_cast<size_t>(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;
		}
	}
}