tcod-haskell-0.1.0.0: libtcod/samples/worldgen/util_worldgen.cpp
/*
* Copyright (c) 2009 Jice
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The name of Jice may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Jice ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Jice BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// world generator
// this was mostly generated with libtcod 1.4.2 heightmap tool !
#include <math.h>
#include <stdio.h>
#include "main.hpp"
// temperature / precipitation Biome diagram (Whittaker diagram)
EBiome biomeDiagram[5][5] = {
// artic/alpine climate (below -5°C)
{ TUNDRA, TUNDRA, TUNDRA, TUNDRA, TUNDRA, },
// cold climate (-5 / 5 °C)
{ COLD_DESERT, GRASSLAND, BOREAL_FOREST, BOREAL_FOREST, BOREAL_FOREST, },
// temperate climate (5 / 15 °C)
{ COLD_DESERT, GRASSLAND, TEMPERATE_FOREST, TEMPERATE_FOREST, TROPICAL_MONTANE_FOREST, },
// warm climate (15 - 20°C)
{ HOT_DESERT, SAVANNA, TROPICAL_DRY_FOREST, TROPICAL_EVERGREEN_FOREST, TROPICAL_EVERGREEN_FOREST, },
// tropical climate (above 20 °C)
{ HOT_DESERT, THORN_FOREST, TROPICAL_DRY_FOREST, TROPICAL_EVERGREEN_FOREST, TROPICAL_EVERGREEN_FOREST, },
};
static const float sandHeight=0.12f;
static const float grassHeight=0.16f; //0.315f;
static const float rockHeight=0.655f;
static const float snowHeight=0.905f; //0.785f;
// TCOD's land color map
static const int MAX_COLOR_KEY=10;
#define COLOR_KEY_MAX_SEA ((int)(sandHeight*255)-1)
#define COLOR_KEY_MIN_LAND ((int)(sandHeight*255))
static const int keyIndex[MAX_COLOR_KEY] = {0,
COLOR_KEY_MAX_SEA,
COLOR_KEY_MIN_LAND,
(int)(grassHeight*255),
(int)(grassHeight*255)+10,
(int)(rockHeight*255),
(int)(rockHeight*255)+10,
(int)(snowHeight*255),
(int)(snowHeight*255)+10,
255
};
static const TCODColor keyColor[MAX_COLOR_KEY]= {
TCODColor(0,0,50), // deep water
TCODColor(20,20,200), // water-sand transition
TCODColor(134,180,101),// sand
TCODColor(80,120,10),// sand-grass transition
TCODColor(17,109,7), // grass
TCODColor(30,85,12), // grass-rock transisiton
TCODColor(64,70,20), // rock
TCODColor(120,140,40), // rock-snow transisiton
TCODColor(208,208,239), // snow
TCODColor(255,255,255)
};
// altitude color map
static const int MAX_ALT_KEY=8;
static const int altIndexes[MAX_ALT_KEY] = {
0,15,(int)(sandHeight*255),(int)(sandHeight*255)+1,
80,130,195,255
};
static const float altitudes[MAX_ALT_KEY] = {
-2000,-1000,-100,0,500,1000,2500,4000 // in meters
};
static const TCODColor altColors[MAX_ALT_KEY]= {
TCODColor(24,165,255), // -2000
TCODColor(132,214,255), // -1000
TCODColor(247,255,255), // -100
TCODColor(49,149,44), // 0
TCODColor(249,209,151), // 500
TCODColor(165,148,24), // 1000
TCODColor(153,110,6), // 2500
TCODColor(172,141,138), // 4000
};
// precipitation color map
static const int MAX_PREC_KEY=19;
static const int precIndexes[MAX_PREC_KEY] = {
4,8,12,16,20,24,28,32,36,40,50,60,70,80,100,120,140,160,255
};
static const float precipitations[MAX_PREC_KEY] = {
0,1,2,3,4,5,6,7,8,9,10,13,15,18,20,25,30,35,40 // cm / m² / year
};
static const TCODColor precColors[MAX_PREC_KEY]= {
TCODColor(128,0,0), // < 4
TCODColor(173,55,0), // 4-8
TCODColor(227,102,0), // 8-12
TCODColor(255,149,0), // 12-16
TCODColor(255,200,0), // 16-20
TCODColor(255,251,0), // 20-24
TCODColor(191,255,0), // 24-28
TCODColor(106,251,0), // 28-32
TCODColor(25,255,48), // 32-36
TCODColor(48,255,141), // 36-40
TCODColor(28,255,232), // 40-50
TCODColor(54,181,255), // 50-60
TCODColor(41,71,191), // 60-70
TCODColor(38,0,255), // 70-80
TCODColor(140,0,255), // 80-100
TCODColor(221,0,255), // 100-120
TCODColor(255,87,255), // 120-140
TCODColor(255,173,255), // 140-160
TCODColor(255,206,255), // > 160
};
// temperature color map
static const int MAX_TEMP_KEY=7;
static const int tempIndexes[MAX_TEMP_KEY] = {0,42,84,126,168,210,255};
static const int temperatures[MAX_TEMP_KEY] = {-30,-20,-10,0,10,20,30};
static const TCODColor tempKeyColor[MAX_TEMP_KEY]= {
TCODColor(180,8,130), // -30 °C
TCODColor(32,1,139), // -20 °C
TCODColor(0,65,252),// -10 °C
TCODColor(37,255,236),// 0 °C
TCODColor(255,255,1), // 10 °C
TCODColor(255,29,4), // 20 °C
TCODColor(80,3,0), // 30 °C
};
int WorldGenerator::getWidth() const {
return HM_WIDTH;
}
int WorldGenerator::getHeight() const {
return HM_HEIGHT;
}
float WorldGenerator::getAltitude(int x, int y) const {
return hm->getValue(x,y);
}
float WorldGenerator::getRealAltitude(float x, float y) const {
int ih=(int)(256*getInterpolatedAltitude(x,y));
int idx;
ih = CLAMP(0,255,ih);
for (idx=0; idx < MAX_ALT_KEY-1; idx++) {
if ( altIndexes[idx+1] > ih ) break;
}
float alt = altitudes[idx] + (altitudes[idx+1]-altitudes[idx]) * (ih-altIndexes[idx])/(altIndexes[idx+1]-altIndexes[idx]);
return alt;
}
float WorldGenerator::getPrecipitations(float x, float y) const {
int iprec=(int)(256*precipitation->getValue((int)x,(int)y));
int idx;
iprec=CLAMP(0,255,iprec);
for (idx=0; idx < MAX_PREC_KEY-1; idx++) {
if ( precIndexes[idx+1] > iprec ) break;
}
float prec = precipitations[idx] + (precipitations[idx+1]-precipitations[idx]) * (iprec-precIndexes[idx])/(precIndexes[idx+1]-precIndexes[idx]);
return prec;
}
float WorldGenerator::getTemperature(float x, float y) const {
return temperature->getValue((int)x,(int)y);
}
EBiome WorldGenerator::getBiome(float x, float y) const {
return biomeMap[(int)x+(int)y*HM_WIDTH];
}
float WorldGenerator::getInterpolatedAltitude(float x, float y) const {
return hm->getInterpolatedValue(x,y);
}
void WorldGenerator::getInterpolatedNormal(float x, float y, float n[3]) const {
return hm2->getNormal(x,y,n,sandHeight);
}
float WorldGenerator::getSandHeight() const {
return sandHeight;
}
bool WorldGenerator::isOnSea(float x, float y) const {
return getInterpolatedAltitude(x,y) <= sandHeight;
}
void WorldGenerator::addHill(int nbHill, float baseRadius, float radiusVar, float height) {
for (int i=0; i< nbHill; i++ ) {
float hillMinRadius=baseRadius*(1.0f-radiusVar);
float hillMaxRadius=baseRadius*(1.0f+radiusVar);
float radius = wgRng->getFloat(hillMinRadius, hillMaxRadius);
int xh = wgRng->getInt(0,HM_WIDTH-1);
int yh = wgRng->getInt(0,HM_HEIGHT-1);
hm->addHill((float)xh,(float)yh,radius,height);
}
}
void WorldGenerator::setLandMass(float landMass, float waterLevel) {
// fix land mass. We want a proportion of landMass above sea level
#ifndef NDEBUG
float t0=TCODSystem::getElapsedSeconds();
#endif
int heightcount[256];
memset(heightcount,0,sizeof(heightcount));
for ( int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
int ih=(int)(h*255);
ih = CLAMP(0,255,ih);
heightcount[ih]++;
}
}
int i=0, totalcount=0;
while (totalcount < HM_WIDTH*HM_HEIGHT*(1.0f-landMass) ) {
totalcount += heightcount[i];
i++;
}
float newWaterLevel=i/255.0f;
float landCoef = (1.0f-waterLevel)/(1.0f-newWaterLevel);
float waterCoef = waterLevel / newWaterLevel;
// water level should be raised/lowered to newWaterLevel
for ( int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
if ( h > newWaterLevel ) {
h = waterLevel + (h-newWaterLevel)*landCoef;
} else {
h = h * waterCoef;
}
hm->setValue(x,y,h);
}
}
#ifndef NDEBUG
float t1=TCODSystem::getElapsedSeconds();
DBG((" Landmass... %g\n", t1-t0 ));
#endif
}
// function building the heightmap
void WorldGenerator::buildBaseMap() {
float t0=TCODSystem::getElapsedSeconds();
addHill(600,16.0*HM_WIDTH/200,0.7,0.3);
hm->normalize();
float t1=TCODSystem::getElapsedSeconds();
DBG((" Hills... %g\n", t1-t0 ));
t0=t1;
hm->addFbm(noise,2.20*HM_WIDTH/400,2.20*HM_WIDTH/400,0,0,10.0f,1.0,2.05);
hm->normalize();
hm2->copy(hm);
t1=TCODSystem::getElapsedSeconds();
DBG((" Fbm... %g\n", t1-t0 ));
t0=t1;
setLandMass(0.6f,sandHeight);
// fix land/mountain ratio using x^3 curve above sea level
for ( int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
if ( h >= sandHeight ) {
float coef = (h-sandHeight) / (1.0f - sandHeight);
h = sandHeight + coef * coef * coef * (1.0f - sandHeight);
hm->setValue(x,y,h);
}
}
}
t1=TCODSystem::getElapsedSeconds();
DBG((" Flatten plains... %g\n", t1-t0 ));
t0=t1;
// we use a custom erosion algo
//hm->rainErosion(15000*HM_WIDTH/400,0.03,0.01,wgRng);
//t1=TCODSystem::getElapsedSeconds();
//DBG((" Erosion... %g\n", t1-t0 ));
//t0=t1;
// compute clouds
float f[2];
for ( int x=0; x < HM_WIDTH; x++) {
f[0] = 6.0f*((float)(x) / HM_WIDTH);
for (int y=0; y < HM_HEIGHT; y++) {
f[1] = 6.0f*((float)(y) / HM_HEIGHT);
// clouds[x][y] = 0.5f * (1.0f + 0.8f * noise->getFbmSimplex(f,4.0f));
clouds[x][y] = 0.5f * (1.0f + 0.8f * noise->getFbm(f,4.0f,TCOD_NOISE_SIMPLEX));
}
}
t1=TCODSystem::getElapsedSeconds();
DBG((" Init clouds... %g\n", t1-t0 ));
t0=t1;
}
// function blurring the heightmap
void WorldGenerator::smoothMap() {
// 3x3 kernel for smoothing operations
static const int smoothKernelSize=9;
static const int smoothKernelDx[9]={-1,0,1,-1,0,1,-1,0,1};
static const int smoothKernelDy[9]={-1,-1,-1,0,0,0,1,1,1};
static const float smoothKernelWeight[9]={2,8,2,8,20,8,2,8,2};
#ifndef NDEBUG
float t0=TCODSystem::getElapsedSeconds();
#endif
hm->kernelTransform(smoothKernelSize,smoothKernelDx,smoothKernelDy,smoothKernelWeight,-1000,1000);
hm2->kernelTransform(smoothKernelSize,smoothKernelDx,smoothKernelDy,smoothKernelWeight,-1000,1000);
hm->normalize();
#ifndef NDEBUG
float t1=TCODSystem::getElapsedSeconds();
DBG((" Blur... %g\n", t1-t0 ));
#endif
}
static const int dirx[9] = { 0, -1,0,1,-1,1,-1,0,1 };
static const int diry[9] = { 0, -1,-1,-1,0,0,1,1,1 };
static const float dircoef[9] = { 1.0f, 1.0f/1.414f, 1.0f, 1.0f/1.414f,1.0f,1.0f,1.0f/1.414f, 1.0f,1.0f/1.414f };
static const int oppdir[9] = {0, 8, 7, 6, 5, 4, 3, 2, 1 };
// erosion parameters
#define EROSION_FACTOR 0.01f
#define SEDIMENTATION_FACTOR 0.01f
#define MAX_EROSION_ALT 0.9f
#define MUDSLIDE_COEF 0.4f
void WorldGenerator::erodeMap() {
TCODHeightMap newMap(HM_WIDTH,HM_HEIGHT);
for (int i=5; i != 0; i --) {
// compute flow and slope maps
map_data_t *md = mapData;
for (int y=0; y < HM_HEIGHT; y++) {
for (int x=0; x < HM_WIDTH; x++) {
float h=hm->getValue(x,y);
float hmin=h, hmax=h;
int minDir=0, maxDir=0;
for (int i=1; i< 9; i++ ) {
int ix = x+dirx[i];
int iy = y+diry[i];
if ( IN_RECTANGLE(ix,iy,HM_WIDTH,HM_HEIGHT)) {
float h2=hm->getValue(ix,iy);
if ( h2 < hmin ) {
hmin=h2;
minDir=i;
} else if ( h2 > hmax ) {
hmax=h2;
maxDir=i;
}
}
}
md->flowDir = minDir;
md->upDir = maxDir;
float slope = hmin - h; // this is negative
slope *= dircoef[minDir];
md->slope = slope;
md++;
}
}
md=mapData;
for (int y=0; y < HM_HEIGHT; y++) {
for (int x=0; x < HM_WIDTH; x++) {
float sediment=0.0f;
bool end=false;
int ix=x,iy=y;
uint8_t oldFlow=md->flowDir;
map_data_t *md2=md;
while ( !end ) {
float h = hm->getValue(ix,iy);
if (h < sandHeight-0.01f) break;
if ( md2->flowDir == oppdir[oldFlow] ) {
h += SEDIMENTATION_FACTOR * sediment;
hm->setValue(ix,iy,h);
end = true;
} else {
// remember, slope is negative
h += precipitation->getValue(ix,iy)* EROSION_FACTOR * md2->slope;
h=MAX(h,sandHeight);
sediment -= md2->slope;
hm->setValue(ix,iy,h);
oldFlow = md2->flowDir;
ix += dirx[oldFlow];
iy += diry[oldFlow];
md2 = &mapData[ix+iy*HM_WIDTH];
}
}
md++;
}
}
DBG( (" Erosion pass %d\n",i));
// mudslides (smoothing)
float sandCoef = 1.0f/(1.0f-sandHeight);
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
if ( h < sandHeight-0.01f || h >= MAX_EROSION_ALT ) {
newMap.setValue(x,y,h);
continue;
}
float sumDelta1 = 0.0f, sumDelta2 = 0.0f;
int nb1 = 1, nb2 = 1;
for (int i=1; i < 9; i++ ) {
int ix = x+dirx[i];
int iy = y+diry[i];
if ( IN_RECTANGLE(ix,iy,HM_WIDTH,HM_HEIGHT)) {
float ih = hm->getValue(ix,iy);
if ( ih < h ) {
if ( i ==1 || i == 3 || i == 6 || i == 8 ) {
// diagonal neighbour
sumDelta1 += (ih - h)*0.4f;
nb1++;
} else {
// adjacent neighbour
sumDelta2 += (ih -h)*1.6f;
nb2++;
}
}
}
}
// average height difference with lower neighbours
float dh = sumDelta1/nb1 + sumDelta2/nb2;
dh *= MUDSLIDE_COEF;
float hcoef=(h-sandHeight)* sandCoef;
dh *= (1.0f-hcoef*hcoef*hcoef); // less smoothing at high altitudes
newMap.setValue(x,y,h+dh);
}
}
hm->copy(&newMap);
}
}
// interpolated cloud thickness
float WorldGenerator::getCloudThickness(float x, float y) const {
x += cloudDx;
int ix=(int)x;
int iy=(int)y;
int ix1 = MIN(HM_WIDTH-1,ix+1);
int iy1 = MIN(HM_HEIGHT-1,iy+1);
float fdx = x - ix;
float fdy = y - iy;
float v1 = clouds[ix][iy];
float v2 = clouds[ix1][iy];
float v3 = clouds[ix][iy1];
float v4 = clouds[ix1][iy1];
float vx1 = ((1.0f - fdx) * v1 + fdx * v2);
float vx2 = ((1.0f - fdx) * v3 + fdx * v4);
float v = ((1.0f - fdy) * vx1 + fdy * vx2);
return v;
}
TCODColor WorldGenerator::getMapColor(float h) {
int colorIdx;
if ( h < sandHeight ) colorIdx = (int)(h/sandHeight * COLOR_KEY_MAX_SEA);
else colorIdx = COLOR_KEY_MIN_LAND + (int)((h-sandHeight)/(1.0f-sandHeight) * (255-COLOR_KEY_MIN_LAND));
colorIdx=CLAMP(0,255,colorIdx);
return mapGradient[colorIdx];
}
void WorldGenerator::computeSunLight(float lightDir[3]) {
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
worldint[x+y*HM_WIDTH]=getMapIntensity(x+0.5f,y+0.5f,lightDir);
}
}
}
float WorldGenerator::getMapIntensity(float worldX,float worldY, float lightDir[3]) {
// sun color & direction
static const TCODColor sunCol(255,255,160);
float normal[3];
float wx = CLAMP(0.0f, HM_WIDTH-1,worldX);
float wy = CLAMP(0.0f, HM_HEIGHT-1,worldY);
// apply sun light
getInterpolatedNormal(wx,wy,normal);
normal[2] *= 3.0f;
float intensity = 0.75f
- (normal[0]*lightDir[0]+normal[1]*lightDir[1]+normal[2]*lightDir[2])*0.75f;
intensity=CLAMP(0.75f,1.5f,intensity);
return intensity;
}
TCODColor WorldGenerator::getInterpolatedColor(float worldX,float worldY) {
return getInterpolatedColor(worldmap,worldX,worldY);
}
TCODColor WorldGenerator::getInterpolatedColor(TCODImage *img,float x,float y) {
int w,h;
img->getSize(&w,&h);
float wx = CLAMP(0.0f, w-1,x);
float wy = CLAMP(0.0f, h-1,y);
int iwx = (int)wx;
int iwy = (int)wy;
float dx = wx - iwx;
float dy = wy - iwy;
TCODColor colNW = img->getPixel(iwx,iwy);
TCODColor colNE = (iwx < w-1 ? img->getPixel(iwx+1,iwy) : colNW);
TCODColor colSW = (iwy < h-1 ? img->getPixel(iwx,iwy+1) : colNW);
TCODColor colSE = (iwx < w-1 && iwy < h-1 ? img->getPixel(iwx+1,iwy+1) : colNW);
TCODColor colN = TCODColor::lerp(colNW,colNE,dx);
TCODColor colS = TCODColor::lerp(colSW,colSE,dx);
TCODColor col = TCODColor::lerp(colN,colS,dy);
return col;
}
float WorldGenerator::getInterpolatedIntensity(float worldX, float worldY) {
return getInterpolatedFloat(worldint,worldX,worldY,HM_WIDTH,HM_HEIGHT);
}
void WorldGenerator::updateClouds(float elapsedTime) {
cloudTotalDx += elapsedTime * 5;
cloudDx += elapsedTime * 5;
if ( cloudDx >= 1.0f ) {
int colsToTranslate=(int)cloudDx;
cloudDx -= colsToTranslate;
// translate the cloud map
for ( int x=colsToTranslate; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
clouds[x-colsToTranslate][y]=clouds[x][y];
}
}
// compute a new column
float f[2];
float cdx = (int)cloudTotalDx ;
for ( int x=HM_WIDTH-colsToTranslate; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
f[0] = 6.0f*((float)(x+cdx) / HM_WIDTH);
f[1] = 6.0f*((float)(y) / HM_HEIGHT);
// clouds[x][y] = 0.5f * (1.0f + 0.8f * noise->getFbmSimplex(f,4.0f));
clouds[x][y] = 0.5f * (1.0f + 0.8f * noise->getFbm(f,4.0f,TCOD_NOISE_SIMPLEX));
}
}
}
}
class RiverPathCbk : public ITCODPathCallback {
public:
float getWalkCost( int xFrom, int yFrom, int xTo, int yTo, void *userData ) const {
WorldGenerator *world = (WorldGenerator *)userData;
float h1=world->hm->getValue(xFrom,yFrom);
float h2=world->hm->getValue(xTo,yTo);
if ( h2 < sandHeight ) return 0.0f;
// float f[2] = {xFrom*10.0f/HM_WIDTH,yFrom*10.0f/HM_HEIGHT};
// return (1.0f+h2-h1)*10+5*(1.0f+noise2d.getSimplex(f));
return (0.01f+h2-h1)*100;
}
};
/*
void WorldGenerator::generateRivers() {
static int riverId=0;
static RiverPathCbk cbk;
// static TCODPath *path=NULL;
static TCODDijkstra *path=NULL;
// the source
int sx,sy;
// the destination
int dx=-1,dy=-1;
// get a random point near the coast
sx = wgRng->getInt(0,HM_WIDTH-1);
sy = wgRng->getInt(0,HM_HEIGHT-1);
float h = hm->getValue(sx,sy);
while ( h < sandHeight - 0.02 || h >= sandHeight ) {
sx++;
if ( sx == HM_WIDTH ) {
sx=0;
sy++;
if ( sy == HM_HEIGHT ) sy=0;
}
h = hm->getValue(sx,sy);
}
riverId++;
// get a closes mountain point
float minDist=1E10f;
int minx = sx - HM_WIDTH/4;
int maxx = sx + HM_WIDTH/4;
int miny = sy - HM_HEIGHT/4;
int maxy = sy + HM_HEIGHT/4;
minx = MAX(0,minx);
maxx = MIN(HM_WIDTH-1,maxx);
miny = MAX(0,miny);
maxy = MIN(HM_HEIGHT-1,maxy);
h = MIN(snowHeight,h + wgRng->getFloat(0.1f,0.5f));
for (int y=miny; y < maxy; y++) {
for (int x=minx; x < maxx; x++) {
float dh=hm->getValue(x,y);
if ( dh >= h ) {
dx=x;
dy=y;
break;
}
}
}
if (! path) {
// path = new TCODPath(HM_WIDTH,HM_HEIGHT,&cbk,this);
path = new TCODDijkstra(HM_WIDTH,HM_HEIGHT,&cbk,this);
}
path->compute(dx,dy);
// if ( dx >= 0 && path->compute(dx,dy,sx,sy) ) {
if ( dx >= 0 ) { path->setPath(sx,sy) ;
DBG( ("river : %d %d -> %d %d\n",sx,sy,dx,dy));
int x,y;
while (path->walk(&x,&y)) {
map_data_t *md=&mapData[x+y*HM_WIDTH];
if ( md->riverId != 0 ) break;
md->riverId = riverId;
}
}
}
*/
void WorldGenerator::generateRivers() {
static int riverId=0;
// the source
int sx,sy;
// the destination
int dx,dy;
// get a random point near the coast
sx = wgRng->getInt(0,HM_WIDTH-1);
sy = wgRng->getInt(HM_HEIGHT/5,4*HM_HEIGHT/5);
float h = hm->getValue(sx,sy);
while ( h < sandHeight - 0.02 || h >= sandHeight ) {
sx++;
if ( sx == HM_WIDTH ) {
sx=0;
sy++;
if ( sy == HM_HEIGHT ) sy=0;
}
h = hm->getValue(sx,sy);
}
TCODList<int> tree;
TCODList<int> randPt;
tree.push(sx+sy*HM_WIDTH);
riverId++;
dx = sx;
dy = sy;
for (int i=0; i< wgRng->getInt(50,200); i++) {
int rx = wgRng->getInt(sx-200,sx+200);
int ry = wgRng->getInt(sy-200,sy+200);
// if ( IN_RECTANGLE(rx,ry,HM_WIDTH,HM_HEIGHT) ) {
// float h=hm->getValue(rx,ry);
// if ( h >= sandHeight ) {
randPt.push(rx+ry*HM_WIDTH);
// }
// }
}
for (int i=0; i < randPt.size(); i++) {
int rx = randPt.get(i)%HM_WIDTH;
int ry = randPt.get(i)/HM_WIDTH;
float minDist=1E10;
int bestx=-1,besty=-1;
for (int j=0;j < tree.size(); j++) {
int tx=tree.get(j)%HM_WIDTH;
int ty=tree.get(j)/HM_WIDTH;
float dist = (tx-rx)*(tx-rx)+(ty-ry)*(ty-ry);
if ( dist < minDist) {
minDist=dist;
bestx=tx;
besty=ty;
}
}
TCODLine::init(bestx,besty,rx,ry);
int len = 3,cx=bestx,cy=besty;
map_data_t *md=&mapData[cx+cy*HM_WIDTH];
if (md->riverId == riverId ) md->riverId=0;
do {
md=&mapData[cx+cy*HM_WIDTH];
if (md->riverId > 0 ) return;
float h=hm->getValue(cx,cy);
if ( h >= sandHeight ) {
md->riverId = riverId;
precipitation->setValue(cx,cy,1.0f);
}
if (cx ==0 || cx == HM_WIDTH-1 || cy == 0 || cy == HM_HEIGHT-1 ) len = 0;
else if (TCODLine::step(&cx,&cy)) len=0;
len --;
} while(len > 0 );
int newNode = cx+cy*HM_WIDTH;
if (newNode != bestx+besty*HM_WIDTH ) {
tree.push(newNode);
}
}
}
/*
void WorldGenerator::generateRivers() {
static int riverId=0;
// the source
int sx,sy;
// the destination
int dx,dy;
// get a random point near the coast
sx = wgRng->getInt(0,HM_WIDTH-1);
sy = wgRng->getInt(HM_HEIGHT/5,4*HM_HEIGHT/5);
float h = hm->getValue(sx,sy);
map_data_t *md=&mapData[sx+sy*HM_WIDTH];
while ( md->riverId == 0 && (h < sandHeight - 0.02 || h >= sandHeight) ) {
sx++;
if ( sx == HM_WIDTH ) {
sx=0;
sy++;
if ( sy == HM_HEIGHT ) sy=0;
}
h = hm->getValue(sx,sy);
md=&mapData[sx+sy*HM_WIDTH];
}
riverId++;
dx = sx;
dy = sy;
DBG( ("source : %d %d\n",sx,sy));
// travel down to the see
// get the hiwest point around current position
bool deadEnd=false;
int len=0;
river_t *river=new river_t();
rivers.push(river);
int maxlen=HM_WIDTH,lastdx=1,lastdy=1;
do {
int coord = sx + sy*HM_WIDTH;
map_data_t *md=&mapData[coord];
if ( md->riverId != 0 ) {
river_t *joined = rivers.get(md->riverId-1);
int i=0;
while (joined->coords.get(i) != coord ) i++;
while ( i < joined->coords.size() ) {
int newStrength=joined->strength.get(i)+1;
joined->strength.set(newStrength,i);
i++;
}
break;
}
md->riverId = riverId;
md->riverLength = len++;
river->coords.push(coord);
river->strength.push(1);
if ( md->upDir != 0 ) {
lastdx=dirx[md->upDir];
sx += lastdx;
lastdy=diry[md->upDir];
sy += lastdy;
deadEnd=false;
} else if ( deadEnd ) {
break;
} else {
sx += lastdx;
sy += lastdy;
if ( ! IN_RECTANGLE(sx,sy,HM_WIDTH,HM_HEIGHT ) ) break;
deadEnd=true;
}
h=hm->getValue(sx,sy);
maxlen--;
} while ( maxlen > 0 && h <= snowHeight);
}
*/
EClimate WorldGenerator::getClimateFromTemp(float temp) {
if ( temp <= -5 ) return ARTIC_ALPINE;
if ( temp <= 5 ) return COLD;
if ( temp <= 15 ) return TEMPERATE;
if ( temp <= 20 ) return WARM;
return TROPICAL;
}
float WorldGenerator::getInterpolatedFloat(float *arr,float x,float y, int width, int height) {
float wx = CLAMP(0.0f, width-1,x);
float wy = CLAMP(0.0f, height-1,y);
int iwx = (int)wx;
int iwy = (int)wy;
float dx = wx - iwx;
float dy = wy - iwy;
float iNW = arr[iwx+iwy*width];
float iNE = (iwx < width-1 ? arr[iwx+1+iwy*width] : iNW);
float iSW = (iwy < height-1 ? arr[iwx+(iwy+1)*width] : iNW);
float iSE = (iwx < width-1 && iwy < height-1 ? arr[iwx+1+(iwy+1)*width] : iNW);
float iN = (1.0f-dx)*iNW + dx*iNE;
float iS = (1.0f-dx)*iSW + dx*iSE;
return (1.0f-dy)*iN + dy * iS;
}
int WorldGenerator::getRiverStrength(int riverId) {
//river_t *river = rivers.get(riverId-1);
//return river->strength.get(river->strength.size()-1);
return 2;
}
void WorldGenerator::computePrecipitations() {
static const float waterAdd = 0.03f;
static const float slopeCoef = 2.0f;
static const float basePrecip = 0.01f; // precipitation coef when slope == 0
float t0=TCODSystem::getElapsedSeconds();
// north/south winds
for (int diry=-1; diry <= 1; diry += 2 ) {
for (int x=0; x < HM_WIDTH; x++) {
float noisex = (float)(x)*5/HM_WIDTH;
// float waterAmount=(1.0f+noise1d.getFbmSimplex(&noisex,3.0f));
float waterAmount=(1.0f+noise1d.getFbm(&noisex,3.0f,TCOD_NOISE_SIMPLEX));
int starty = (diry == -1 ? HM_HEIGHT-1 : 0);
int endy = (diry == -1 ? -1 : HM_HEIGHT);
for (int y=starty; y != endy; y += diry) {
float h = hm->getValue(x,y);
if ( h < sandHeight ) {
waterAmount += waterAdd;
} else if (waterAmount > 0.0f ){
float slope;
if ( (unsigned)(y + diry) < (unsigned)HM_HEIGHT ) slope = hm->getValue(x,y+diry) - h;
else slope = h - hm->getValue(x,y-diry);
if ( slope >= 0.0f ) {
float precip = waterAmount * (basePrecip + slope * slopeCoef);
precipitation->setValue(x,y,precipitation->getValue(x,y)+precip);
waterAmount -= precip;
waterAmount = MAX(0.0f,waterAmount);
}
}
}
}
}
float t1=TCODSystem::getElapsedSeconds();
DBG((" North/south winds... %g\n", t1-t0 ));
t0=t1;
// east/west winds
for (int dirx=-1; dirx <= 1; dirx += 2 ) {
for (int y=0; y < HM_HEIGHT; y++) {
float noisey = (float)(y)*5/HM_HEIGHT;
// float waterAmount=(1.0f+noise1d.getFbmSimplex(&noisey,3.0f));
float waterAmount=(1.0f+noise1d.getFbm(&noisey,3.0f,TCOD_NOISE_SIMPLEX));
int startx = (dirx == -1 ? HM_WIDTH-1 : 0);
int endx = (dirx == -1 ? -1 : HM_WIDTH);
for (int x=startx; x != endx; x += dirx) {
float h = hm->getValue(x,y);
if ( h < sandHeight ) {
waterAmount += waterAdd;
} else if (waterAmount > 0.0f ){
float slope;
if ( (unsigned)(x + dirx) < (unsigned)HM_WIDTH ) slope = hm->getValue(x+dirx,y) - h;
else slope = h - hm->getValue(x-dirx,y);
if ( slope >= 0.0f ) {
float precip = waterAmount * (basePrecip + slope * slopeCoef);
precipitation->setValue(x,y,precipitation->getValue(x,y)+precip);
waterAmount -= precip;
waterAmount = MAX(0.0f,waterAmount);
}
}
}
}
}
t1=TCODSystem::getElapsedSeconds();
DBG((" East/west winds... %g\n", t1-t0 ));
t0=t1;
float min,max;
precipitation->getMinMax(&min,&max);
// latitude impact
for (int y=HM_HEIGHT/4; y < 3*HM_HEIGHT/4; y++) {
// latitude (0 : equator, -1/1 : pole)
float lat = (float)(y-HM_HEIGHT/4) * 2 / HM_HEIGHT ;
float coef = sinf(2*3.1415926*lat );
for (int x=0; x < HM_WIDTH; x++) {
float f[2] = { (float)(x)/HM_WIDTH, (float)(y)/HM_HEIGHT };
// float xcoef = coef + 0.5f*noise2d.getFbmSimplex(f,3.0f);
float xcoef = coef + 0.5f*noise2d.getFbm(f,3.0f,TCOD_NOISE_SIMPLEX);
float precip = precipitation->getValue(x,y);
precip += (max-min) * xcoef * 0.1f;
precipitation->setValue(x,y,precip);
}
}
t1=TCODSystem::getElapsedSeconds();
DBG((" latitude... %g\n", t1-t0 ));
t0=t1;
// very fast blur by scaling down and up
static const int factor=8;
static const int smallWidth = (HM_WIDTH+factor-1)/factor;
static const int smallHeight = (HM_HEIGHT+factor-1)/factor;
float *lowResMap = new float[smallWidth * smallHeight];
memset(lowResMap,0,sizeof(float)*smallWidth*smallHeight);
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float v = precipitation->getValue(x,y);
int ix=x/factor;
int iy=y/factor;
lowResMap[ix + iy*smallWidth ] += v;
}
}
float coef = 1.0f/factor;
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float v=getInterpolatedFloat(lowResMap,x*coef,y*coef,smallWidth,smallHeight);
precipitation->setValue(x,y,v);
}
}
delete [] lowResMap;
}
void WorldGenerator::smoothPrecipitations() {
float t0=TCODSystem::getElapsedSeconds();
// better quality polishing blur using a 5x5 kernel
// faster than TCODHeightmap kernelTransform function
TCODHeightMap temphm(HM_WIDTH, HM_HEIGHT);
temphm.copy(precipitation);
for (int i=4; i != 0; i--) {
for (int x=0; x < HM_WIDTH; x++) {
int minx = x - 2;
int maxx = x + 2;
int miny=0;
int maxy=2;
float sum=0.0f;
int count=0;
minx = MAX( 0, minx );
maxx = MIN( HM_WIDTH-1, maxx);
// compute the kernel sum at x,0
for (int ix=minx; ix <= maxx; ix++) {
for (int iy =miny; iy <= maxy; iy++) {
sum += precipitation->getValue(ix,iy);
count++;
}
}
temphm.setValue(x,0,sum/count);
for (int y=1; y < HM_HEIGHT; y++) {
if ( y-2 >= 0 ) {
// remove the top-line sum
for (int ix=minx; ix <= maxx; ix++) {
sum -= precipitation->getValue(ix,y-2);
count--;
}
}
if ( y+2 < HM_HEIGHT ) {
// add the bottom-line sum
for (int ix=minx; ix <= maxx; ix++) {
sum += precipitation->getValue(ix,y+2);
count++;
}
}
temphm.setValue(x,y,sum/count);
}
}
}
precipitation->copy(&temphm);
float t1=TCODSystem::getElapsedSeconds();
DBG((" Blur... %g\n", t1-t0 ));
t0=t1;
precipitation->normalize();
t1=TCODSystem::getElapsedSeconds();
DBG((" Normalization... %g\n", t1-t0 ));
t0=t1;
}
void WorldGenerator::computeTemperaturesAndBiomes() {
// temperature shift with altitude : -25°C at 6000 m
// mean temp at sea level : 25°C at lat 0 5°C at lat 45 -25°C at lat 90 (sinusoide)
float sandCoef=1.0f/(1.0f-sandHeight);
float waterCoef=1.0f/sandHeight;
for (int y=0; y < HM_HEIGHT; y++) {
float lat = (float)(y-HM_HEIGHT/2) * 2 / HM_HEIGHT ;
float latTemp = 0.5f*(1.0f+pow(sin(3.1415926*(lat+0.5f)),5)); // between 0 and 1
if ( latTemp > 0.0f) latTemp = sqrt(latTemp);
latTemp = -30 + latTemp*60;
for (int x=0; x < HM_WIDTH; x++) {
float h0 = hm->getValue(x,y);
float h = h0 - sandHeight;
if (h < 0.0f ) h *= waterCoef;
else h *= sandCoef;
float altShift = -35 * h;
float temp=latTemp+altShift;
temperature->setValue(x,y,temp);
float humid = precipitation->getValue(x,y);
// compute biome
EClimate climate = getClimateFromTemp(temp);
int iHumid = (int)(humid * 5);
iHumid = MIN(4,iHumid);
EBiome biome = biomeDiagram[climate][iHumid];
biomeMap[x+y*HM_WIDTH]=biome;
}
}
float min,max;
temperature->getMinMax(&min,&max);
DBG( ("Temperatures min/max: %g / %g\n",min,max));
}
TCODColor WorldGenerator::getBiomeColor(EBiome biome,int x,int y) {
static const TCODColor biomeColors[] = {
// TUNDRA,
TCODColor(200,240,255),
// COLD_DESERT,
TCODColor(180,210,210),
// GRASSLAND,
TCODColor::sea,
// BOREAL_FOREST,
TCODColor(14,93,43),
// TEMPERATE_FOREST,
TCODColor(44,177,83),
// TROPICAL_MONTANE_FOREST,
TCODColor(185,232,164),
// HOT_DESERT,
TCODColor(235,255,210),
// SAVANNA,
TCODColor(255,205,20),
// TROPICAL_DRY_FOREST,
TCODColor(60,130,40),
// TROPICAL_EVERGREEN_FOREST,
TCODColor::green,
// THORN_FOREST,
TCODColor(192,192,112),
};
int r=0,g=0,b=0, count=1;
r +=biomeColors[biome].r;
g +=biomeColors[biome].g;
b +=biomeColors[biome].b;
for (int i = 0; i < 4; i++ ) {
int ix=x+wgRng->getInt(-10,10);
int iy=y+wgRng->getInt(-10,10);
if ( IN_RECTANGLE(ix,iy,HM_WIDTH,HM_HEIGHT) ) {
TCODColor c=biomeColors[biomeMap[ix+iy*HM_WIDTH]];
r+=c.r + wgRng->getInt(-10,10);
g+=c.g + wgRng->getInt(-10,10);
b+=c.b + wgRng->getInt(-10,10);
count++;
}
}
r/=count;
g/=count;
b/=count;
r=CLAMP(0,255,r);
g=CLAMP(0,255,g);
b=CLAMP(0,255,b);
return TCODColor(r,g,b);
}
void WorldGenerator::computeColors() {
// alter map color using temperature & precipitation maps
map_data_t *md=mapData;
for (int y=0; y < HM_HEIGHT; y++) {
for (int x=0; x < HM_WIDTH; x++) {
float h=hm->getValue(x,y);
float temp = temperature->getValue(x,y);
EBiome biome = biomeMap[x+y*HM_WIDTH];
TCODColor c;
if (h < sandHeight ) c = getMapColor(h);
else {
c = getMapColor(h);
c=TCODColor::lerp(c,getBiomeColor(biome,x,y),0.5f);
}
// snow near poles
temp += 10*(clouds[HM_WIDTH-1-x][HM_HEIGHT-1-y]); // cheap 2D noise ;)
if ( temp < -10.0f && h < sandHeight ) worldmap->putPixel(x,y,TCODColor::lerp(TCODColor::white,c,0.3f));
else if ( temp < -8.0f && h < sandHeight ) worldmap->putPixel(x,y,TCODColor::lerp(TCODColor::white,c,0.3f + 0.7f * (10.0f+temp)/2.0f));
else if ( temp < -2.0f && h >= sandHeight) worldmap->putPixel(x,y,TCODColor::white);
else if ( temp < 2.0f && h >= sandHeight ) {
//TCODColor snow = mapGradient[(int)(snowHeight*255) + (int)((255 - (int)(snowHeight*255)) * (0.6f-temp)/0.4f)];
c = TCODColor::lerp(TCODColor::white,c,(temp+2)/4.0f);
worldmap->putPixel(x,y,c);
} else {
worldmap->putPixel(x,y,c);
}
md++;
}
}
// draw rivers
/*
for (river_t **it=rivers.begin(); it != rivers.end(); it++) {
for (int i=0; i < (*it)->coords.size(); i++ ) {
int coord = (*it)->coords.get(i);
int strength = (*it)->strength.get(i);
int x = coord % HM_WIDTH;
int y = coord / HM_WIDTH;
TCODColor c= worldmap->getPixel(x,y);
c = TCODColor::lerp(c,TCODColor::blue,(float)(strength)/5.0f);
worldmap->putPixel(x,y,c);
}
}
*/
md=mapData;
for (int y=0; y < HM_HEIGHT; y++) {
for (int x=0; x < HM_WIDTH; x++) {
if ( md->riverId > 0 ) {
TCODColor c= worldmap->getPixel(x,y);
c = TCODColor::lerp(c,TCODColor::blue,0.3f);
worldmap->putPixel(x,y,c);
}
md++;
}
}
// blur
static const int dx[]={0,-1,0,1,0};
static const int dy[]={0,0,-1,0,1};
static const int coef[]={1,2,2,2,2};
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
int r=0,g=0,b=0,count=0;
for (int i=0; i < 5; i++) {
int ix=x+dx[i];
int iy=y+dy[i];
if (IN_RECTANGLE(ix,iy,HM_WIDTH,HM_HEIGHT)){
TCODColor c=worldmap->getPixel(ix,iy);
r += coef[i]*c.r;
g += coef[i]*c.g;
b += coef[i]*c.b;
count += coef[i];
}
}
r /= count;
g /= count;
b /= count;
worldmap->putPixel(x,y,TCODColor(r,g,b));
}
}
drawCoasts(worldmap);
}
void WorldGenerator::generate(TCODRandom *wRng) {
float t00,t0=TCODSystem::getElapsedSeconds();
t00=t0;
cloudDx=cloudTotalDx=0.0f;
TCODColor::genMap(mapGradient,MAX_COLOR_KEY,keyColor,keyIndex);
if ( wRng == NULL ) wRng=TCODRandom::getInstance();
wgRng = wRng;
noise=new TCODNoise(2,wgRng);
hm=new TCODHeightMap(HM_WIDTH,HM_HEIGHT);
hm2=new TCODHeightMap(HM_WIDTH,HM_HEIGHT);
worldmap = new TCODImage(HM_WIDTH,HM_HEIGHT);
worldint = new float[HM_WIDTH*HM_HEIGHT];
temperature = new TCODHeightMap(HM_WIDTH,HM_HEIGHT);
precipitation = new TCODHeightMap(HM_WIDTH,HM_HEIGHT);
biomeMap = new EBiome[HM_WIDTH*HM_HEIGHT];
mapData = new map_data_t[HM_WIDTH*HM_HEIGHT];
memset(mapData,0,sizeof(map_data_t)*HM_WIDTH*HM_HEIGHT);
float t1=TCODSystem::getElapsedSeconds();
DBG(("Initialization... %g\n", t1-t0 ));
t0=t1;
buildBaseMap();
t1=TCODSystem::getElapsedSeconds();
DBG(("Heightmap construction... %g\n", t1-t0 ));
t0=t1;
computePrecipitations();
t1=TCODSystem::getElapsedSeconds();
DBG(("Precipitation map... %g\n", t1-t0 ));
t0=t1;
erodeMap();
t1=TCODSystem::getElapsedSeconds();
DBG(("Erosion... %g\n", t1-t0 ));
t0=t1;
smoothMap();
t1=TCODSystem::getElapsedSeconds();
DBG(("Smooth... %g\n", t1-t0 ));
t0=t1;
setLandMass(0.6f,sandHeight);
for (int i=0; i < HM_WIDTH*HM_HEIGHT/3000; i++) {
// for (int i=0; i < 1; i++) {
generateRivers();
}
t1=TCODSystem::getElapsedSeconds();
DBG(("Rivers... %g\n", t1-t0 ));
t0=t1;
smoothPrecipitations();
t1=TCODSystem::getElapsedSeconds();
DBG(("Smooth precipitations... %g\n", t1-t0 ));
t0=t1;
computeTemperaturesAndBiomes();
t1=TCODSystem::getElapsedSeconds();
DBG(("Temperature map... %g\n", t1-t0 ));
t0=t1;
computeColors();
t1=TCODSystem::getElapsedSeconds();
DBG(("Color map... %g\n", t1-t0 ));
t0=t1;
t1=TCODSystem::getElapsedSeconds();
DBG(("TOTAL TIME... %g\n", t1-t00 ));
}
void WorldGenerator::drawCoasts(TCODImage *img) {
// detect coasts
for (int x=0; x < HM_WIDTH-1; x++) {
for (int y=0; y < HM_HEIGHT-1; y++) {
float h = hm->getValue(x,y);
float h2 = hm->getValue(x+1,y);
if ( ( h < sandHeight && h2 >= sandHeight )
|| ( h2 < sandHeight && h >= sandHeight ) ) img->putPixel(x,y,TCODColor::black);
else {
h = hm->getValue(x,y);
h2 = hm->getValue(x,y+1);
if ( ( h < sandHeight && h2 >= sandHeight )
|| ( h2 < sandHeight && h >= sandHeight ) ) img->putPixel(x,y,TCODColor::black);
}
}
}
}
void WorldGenerator::saveBiomeMap(const char *filename) {
static TCODImage *legend=NULL;
static int legendHeight,legendWidth;
static const TCODColor biomeColors[] = {
// TUNDRA,
TCODColor(88,234,250),
// COLD_DESERT,
TCODColor(129,174,170),
// GRASSLAND,
TCODColor::sea,
// BOREAL_FOREST,
TCODColor(14,93,43),
// TEMPERATE_FOREST,
TCODColor(44,177,83),
// TROPICAL_MONTANE_FOREST,
TCODColor(185,232,164),
// HOT_DESERT,
TCODColor(229,247,184),
// SAVANNA,
TCODColor::orange,
// TROPICAL_DRY_FOREST,
TCODColor::darkYellow,
// TROPICAL_EVERGREEN_FOREST,
TCODColor::green,
// THORN_FOREST,
TCODColor(192,192,112),
};
if ( legend == NULL ) {
legend = new TCODImage("data/img/legend_biome.png");
legend->getSize(&legendWidth,&legendHeight);
}
if ( filename == NULL ) filename="world_biome.png";
TCODImage img(MAX(HM_WIDTH,legendWidth),HM_HEIGHT+legendHeight);
// draw biome map
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
if ( h < sandHeight ) img.putPixel(x,y,TCODColor(100,100,255));
else img.putPixel(x,y,biomeColors[biomeMap[x+y*HM_WIDTH]]);
}
}
drawCoasts(&img);
// blit legend
int legendx = MAX(HM_WIDTH,legendWidth) / 2 - legendWidth/2;
for (int x=0; x < legendWidth; x++) {
for (int y=0; y < legendHeight; y++) {
img.putPixel(legendx+x,HM_HEIGHT+y,legend->getPixel(x,y));
}
}
// fill legend colors
for (int i=0; i < 6; i++ ) {
for (int x=17; x < 47; x++)
for (int y=4+i*14; y < 14+i*14; y++ )
img.putPixel(legendx+x,HM_HEIGHT+y,biomeColors[i]);
}
for (int i=6; i < NB_BIOMES; i++ ) {
for (int x=221; x < 251; x++)
for (int y=4+(i-6)*14; y < 14+(i-6)*14; y++ )
img.putPixel(legendx+x,HM_HEIGHT+y,biomeColors[i]);
}
img.save(filename);
}
void WorldGenerator::saveTemperatureMap(const char *filename) {
static TCODColor tempGradient[256];
static TCODImage *legend=NULL;
static int legendHeight,legendWidth;
if ( legend == NULL ) {
legend = new TCODImage("data/img/legend_temperature.png");
legend->getSize(&legendWidth,&legendHeight);
TCODColor::genMap(tempGradient,MAX_TEMP_KEY,tempKeyColor,tempIndexes);
}
if ( filename == NULL ) filename="world_temperature.png";
TCODImage img(MAX(HM_WIDTH,legendWidth),HM_HEIGHT+legendHeight);
float minTemp,maxTemp;
temperature->getMinMax(&minTemp,&maxTemp);
// render temperature map
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
if ( h < sandHeight ) img.putPixel(x,y,TCODColor(100,100,255));
else {
float temp=temperature->getValue(x,y);
temp = (temp - minTemp) / (maxTemp-minTemp);
int colorIdx = (int)(temp*255);
colorIdx=CLAMP(0,255,colorIdx);
img.putPixel(x,y,tempGradient[colorIdx]);
}
}
}
drawCoasts(&img);
// blit legend
int legendx = MAX(HM_WIDTH,legendWidth) / 2 - legendWidth/2;
for (int x=0; x < legendWidth; x++) {
for (int y=0; y < legendHeight; y++) {
img.putPixel(legendx+x,HM_HEIGHT+y,legend->getPixel(x,y));
}
}
img.save(filename);
}
void WorldGenerator::savePrecipitationMap(const char *filename) {
static TCODImage *legend=NULL;
static int legendHeight,legendWidth;
if ( legend == NULL ) {
legend = new TCODImage("data/img/legend_precipitation.png");
legend->getSize(&legendWidth,&legendHeight);
}
if ( filename == NULL ) filename="world_precipitation.png";
TCODImage img(MAX(HM_WIDTH,legendWidth),HM_HEIGHT+legendHeight);
// render precipitation map
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
if ( h < sandHeight ) img.putPixel(x,y,TCODColor(100,100,255));
else {
float prec=precipitation->getValue(x,y);
int iprec = (int)(prec * 180);
int colorIdx=0;
while (colorIdx < MAX_PREC_KEY && iprec > precIndexes[colorIdx]) colorIdx++;
colorIdx = CLAMP(0,MAX_PREC_KEY,colorIdx);
img.putPixel(x,y,precColors[colorIdx]);
}
}
}
drawCoasts(&img);
// blit legend
int legendx = MAX(HM_WIDTH,legendWidth) / 2 - legendWidth/2;
for (int x=0; x < legendWidth; x++) {
for (int y=0; y < legendHeight; y++) {
img.putPixel(legendx+x,HM_HEIGHT+y,legend->getPixel(x,y));
}
}
img.save(filename);
}
void WorldGenerator::saveAltitudeMap(const char *filename) {
static TCODColor altGradient[256];
static TCODImage *legend=NULL;
static int legendHeight,legendWidth;
if ( legend == NULL ) {
legend = new TCODImage("data/img/legend_altitude.png");
legend->getSize(&legendWidth,&legendHeight);
TCODColor::genMap(altGradient,MAX_ALT_KEY,altColors,altIndexes);
}
if ( filename == NULL ) filename="world_altitude.png";
TCODImage img(HM_WIDTH+legendWidth,MAX(HM_HEIGHT,legendHeight));
// render altitude map
for (int x=0; x < HM_WIDTH; x++) {
for (int y=0; y < HM_HEIGHT; y++) {
float h=hm->getValue(x,y);
int ialt = (int)(h * 256);
ialt = CLAMP(0,255,ialt);
img.putPixel(x,y,altGradient[ialt]);
}
}
// blit legend
int legendy = MAX(HM_HEIGHT,legendHeight) / 2 - legendHeight/2;
for (int x=0; x < legendWidth; x++) {
for (int y=0; y < legendHeight; y++) {
img.putPixel(HM_WIDTH+x,legendy+y,legend->getPixel(x,y));
}
}
img.save(filename);
}