HeightMapModifier.cpp

Go to the documentation of this file.
00001 ////////////////////////////////////////////////////////////////////////////////
00002 //    Scorched3D (c) 2000-2009
00003 //
00004 //    This file is part of Scorched3D.
00005 //
00006 //    Scorched3D is free software; you can redistribute it and/or modify
00007 //    it under the terms of the GNU General Public License as published by
00008 //    the Free Software Foundation; either version 2 of the License, or
00009 //    (at your option) any later version.
00010 //
00011 //    Scorched3D is distributed in the hope that it will be useful,
00012 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014 //    GNU General Public License for more details.
00015 //
00016 //    You should have received a copy of the GNU General Public License
00017 //    along with Scorched3D; if not, write to the Free Software
00018 //    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00019 ////////////////////////////////////////////////////////////////////////////////
00020 
00021 #include <math.h>
00022 #include <stdlib.h>
00023 #include <landscapemap/HeightMapModifier.h>
00024 #include <landscapedef/LandscapeDefn.h>
00025 #include <common/Defines.h>
00026 #include <image/ImageBitmap.h>
00027 #include <image/ImageFactory.h>
00028 #include <lang/LangResource.h>
00029 
00030 void HeightMapModifier::levelSurround(HeightMap &hmap)
00031 {
00032         for (int x=0; x<=hmap.getMapWidth(); x++)
00033         {       
00034                 hmap.setHeight(x, 0, fixed(0));
00035                 hmap.setHeight(x, 1, fixed(0));
00036                 hmap.setHeight(x, hmap.getMapHeight(), fixed(0));
00037                 hmap.setHeight(x, hmap.getMapHeight()-1, fixed(0));
00038         }
00039 
00040         for (int y=0; y<=hmap.getMapHeight(); y++)
00041         {
00042                 hmap.setHeight(0, y, fixed(0));
00043                 hmap.setHeight(1, y, fixed(0));
00044                 hmap.setHeight(hmap.getMapWidth(), y, fixed(0));
00045                 hmap.setHeight(hmap.getMapWidth()-1, y, fixed(0));
00046         }
00047 }
00048 
00049 static int noiseFn(int x, int y, int random)
00050 {
00051     int n = x + y * 57 + random * 131;
00052     n = (n<<13) ^ n;
00053     return ((n * (n * n * 15731 + 789221) +
00054                 1376312589)&0x7fffffff);
00055 }
00056 
00057 void HeightMapModifier::noise(HeightMap &hmap, 
00058         LandscapeDefnHeightMapGenerate &defn,
00059         RandomGenerator &generator,
00060         ProgressCounter *counter)
00061 {
00062         if (defn.noisefactor == 0) return;
00063         if (counter) counter->setNewOp(LANG_RESOURCE("NOISE", "Noise"));
00064 
00065         unsigned int randomU = generator.getRandUInt() % 5000;
00066         int random = (int) randomU;
00067 
00068         fixed *noisemap = new fixed[defn.noisewidth * defn.noiseheight];
00069         for (int y=0; y<defn.noiseheight; y++)
00070         {
00071                 for (int x=0; x<defn.noisewidth; x++)
00072                 {
00073                         int noise = (noiseFn(x,  y,  random) -
00074                                 (INT_MAX / 2)) / 100000;
00075                         noisemap[x + y * defn.noisewidth] = 
00076                                 fixed(true, noise) * defn.noisefactor;
00077                 }
00078         }
00079 
00080         // Should perhaps lineraly interp, but smoothing is done afterwards
00081         for (int x=0; x<=hmap.getMapWidth(); x++)
00082         {
00083                 if (counter) counter->setNewPercentage((100.0f * float(x)) / float(hmap.getMapWidth()));
00084                 for (int y=0; y<=hmap.getMapHeight(); y++)
00085                 {
00086                         fixed nx = fixed(defn.noisewidth) * fixed(x) / fixed(hmap.getMapWidth());
00087                         fixed ny = fixed(defn.noiseheight) * fixed(y) / fixed(hmap.getMapHeight());
00088                         if (nx >= defn.noisewidth) nx = defn.noisewidth - 1;
00089                         if (ny >= defn.noiseheight) ny = defn.noiseheight - 1;
00090 
00091                         fixed height = hmap.getHeight(x, y);
00092                         fixed newHeight = height + 
00093                                 noisemap[nx.asInt() + ny.asInt() * defn.noisewidth];
00094                         newHeight = MAX(newHeight, 0);
00095                         hmap.setHeight(x, y, newHeight);
00096                 }
00097         }
00098 }
00099 
00100 void HeightMapModifier::edgeEnhance(HeightMap &hmap, 
00101         LandscapeDefnHeightMapGenerate &defn,
00102         RandomGenerator &generator,
00103         ProgressCounter *counter)
00104 {
00105         if (counter) counter->setNewOp(LANG_RESOURCE("EDGE_ENHANCE", "Edge Enhance"));
00106         
00107         fixed *newhMap_ = new fixed[(hmap.getMapWidth()+1) * (hmap.getMapHeight()+1)];
00108         fixed *point = newhMap_;
00109         fixed max, pixel;
00110         for (int y=0; y<=hmap.getMapHeight(); y++)
00111         {
00112                 if (counter) counter->setNewPercentage((100.0f * float(y)) / float(hmap.getMapHeight()));
00113                 for (int x=0; x<=hmap.getMapWidth(); x++, point++)
00114         {
00115                         fixed height = hmap.getHeight(x, y);
00116                         if (x>0 && y>0 && x<hmap.getMapWidth() && y<hmap.getMapHeight())
00117                         {
00118                                 max = (hmap.getHeight(x + 1, y + 1) - hmap.getHeight(x - 1, y - 1)).abs();
00119                                 
00120                                 pixel = (hmap.getHeight(x - 1, y + 1) - hmap.getHeight(x + 1, y - 1)).abs();
00121                                 if (pixel > max) max = pixel;
00122 
00123                                 pixel = (hmap.getHeight(x - 1, y) - hmap.getHeight(x + 1, y)).abs();
00124                                 if (pixel > max) max = pixel;
00125 
00126                                 pixel = (hmap.getHeight(x, y - 1) - hmap.getHeight(x, y + 1)).abs();
00127                                 if (pixel > max) max = pixel;
00128 
00129                                 *point = MAX(height, max + height);
00130                         }
00131                         else
00132                         {
00133                                 *point = height;
00134                         }
00135                         *point = MAX(*point, 0);
00136         }
00137     }    
00138 
00139         fixed *start = newhMap_;
00140         for (int y=0; y<=hmap.getMapHeight(); y++)
00141         {
00142                 for (int x=0; x<=hmap.getMapWidth(); x++)
00143                 {
00144                         hmap.setHeight(x, y, *(start++));
00145                 }
00146         }
00147         delete [] newhMap_;
00148 }
00149 
00150 static void getPos(int pos, int &x, int &y, int dist)
00151 {
00152         switch (pos)
00153         {
00154         case 0:
00155                 x = -dist;
00156                 y = -dist;
00157                 break;
00158         case 1:
00159                 x = dist;
00160                 y = dist;
00161                 break;
00162         case 2:
00163                 x = dist;
00164                 y = -dist;
00165                 break;
00166         case 3:
00167                 x = -dist;
00168                 y = dist;
00169                 break;
00170         case 4:
00171                 x = -dist;
00172                 y = 0;
00173                 break;
00174         case 5:
00175                 x = dist;
00176                 y = 0;
00177                 break;
00178         case 6:
00179                 x = 0;
00180                 y = dist;
00181                 break;
00182         case 7:
00183                 x = 0;
00184                 y = -dist;
00185                 break;
00186         }
00187 }
00188 
00189 void HeightMapModifier::waterErrosion(HeightMap &hmap, 
00190         LandscapeDefnHeightMapGenerate &defn,
00191         RandomGenerator &generator,
00192         ProgressCounter *counter)
00193 {
00194         if (defn.errosions == 0) return;
00195         if (counter) counter->setNewOp(LANG_RESOURCE("WATER_ERROSION", "Water Errosion"));
00196 
00197         // Copy the height map, so we don't keep running down the same paths
00198         // as we have already created
00199         fixed *newhMap = new fixed[(hmap.getMapWidth()+1) * (hmap.getMapHeight()+1)];
00200         fixed *start = newhMap;
00201         for (int y=0; y<=hmap.getMapHeight(); y++)
00202         {
00203                 for (int x=0; x<=hmap.getMapWidth(); x++, start++)
00204                 {
00205                         *start = hmap.getHeight(x, y);
00206                 }
00207         }
00208 
00209         // Keep a count of how many paths have used the same squares
00210         int *seen = new int[(hmap.getMapWidth()+1) * (hmap.getMapHeight()+1)];
00211         memset(seen, 0, sizeof(int) * (hmap.getMapWidth()+1) * (hmap.getMapHeight()+1));
00212 
00213         // For each errosion stream
00214         for (int o=0; o<defn.errosions; o++) 
00215         {
00216                 if (counter) counter->setNewPercentage((100.0f * float(o)) / float(30));
00217 
00218                 // Choose a random start
00219                 int x = generator.getRandUInt() % hmap.getMapWidth();
00220                 int y = generator.getRandUInt() % hmap.getMapHeight();
00221                 
00222                 // Set the position and starting height
00223                 int startx = x;
00224                 int starty = y;
00225                 fixed startHeight = hmap.getHeight(x, y);
00226 
00227                 // For a set number of itterations
00228                 int nx, ny;
00229                 for (int i=0; i<500; i++)
00230                 {
00231                         // Find next position to be lowered
00232                         fixed minHeight = fixed::MAX_FIXED;
00233                         int distFromStart = (x - startx) * (x - startx) +
00234                                 (y - starty) * (y - starty);
00235 
00236                         // Find the lowest square around the current square
00237                         // this must be further away from the start than the current
00238                         int minIndex = -1;
00239                         for (int a=0; a<4; a++)
00240                         {
00241                                 getPos(a, nx, ny, 1);
00242                                 if (x + nx >= 0 &&
00243                                         y + ny >= 0 &&
00244                                         x + nx <= hmap.getMapWidth() &&
00245                                         y + ny <= hmap.getMapHeight())
00246                                 {
00247                                         int newDistFromStart = 
00248                                                 (x + nx - startx) * (x + nx - startx) +
00249                                                 (y + ny - starty) * (y + ny - starty);
00250                                         if (newDistFromStart > distFromStart)
00251                                         {
00252                                                 fixed newHeight = newhMap[x + nx + (y + ny) * (hmap.getMapWidth()+1)];
00253                                                 if (newHeight < minHeight) 
00254                                                 {
00255                                                         minIndex = a;
00256                                                         minHeight = newHeight;
00257                                                 }
00258                                         }
00259                                 }
00260                         }
00261 
00262                         // Check if have found any, or if we have been here with too many streams
00263                         if (minIndex == -1 || seen[x + y * (hmap.getMapWidth()+1)] > defn.errosionlayering) 
00264                         {
00265                                 break;
00266                         }
00267                         seen[x + y * (hmap.getMapWidth()+1)]++;
00268 
00269                         // Set new height, make sure it is lower than the start by a certain amount
00270                         fixed lowered = fixed(i) * defn.errosionforce;
00271                         lowered = MIN(lowered, defn.errosionmaxdepth);
00272                         fixed currentheight = hmap.getHeight(x, y);
00273                         startHeight = MIN(startHeight, currentheight);
00274                         fixed newheight = MAX(startHeight - lowered, 0);
00275                         hmap.setHeight(x, y, newheight);
00276 
00277                         // Lower the surrounding area so its not so severe
00278                         int size = 1;
00279                         while (true)
00280                         {
00281                                 fixed raised = fixed(size) * defn.errosionsurroundforce;
00282                                 for (int a=-size; a<=size; a++)
00283                                 {
00284                                         for (int b=-size; b<=size; b++)
00285                                         {
00286                                                 if (a==-size || a==size || b==-size || b==size)
00287                                                 {
00288                                                         fixed newSurroundHeight = startHeight - lowered + raised + generator.getRandFixed() * 2;
00289                                                         newSurroundHeight = MAX(newSurroundHeight, 0);
00290                                                         fixed oldSurroundHeight = hmap.getHeight(x + a, y + b);
00291                                                         if (oldSurroundHeight > newSurroundHeight)
00292                                                         {
00293                                                                 hmap.setHeight(x + a , y + b, newSurroundHeight);
00294                                                         }
00295                                                 }
00296                                         }
00297                                 }
00298                                 size++;
00299                                 if (size > defn.errosionsurroundsize) break;
00300                         }
00301 
00302                         // Move to the next x,y
00303                         {
00304                                 getPos(minIndex, nx, ny, 1);
00305                                 x += nx;
00306                                 y += ny;
00307                         }
00308                 }
00309         }
00310 
00311         delete [] newhMap;
00312         delete [] seen;
00313 }
00314 
00315 void HeightMapModifier::smooth(HeightMap &hmap, 
00316                                                            LandscapeDefnHeightMapGenerate &defn,
00317                                                            ProgressCounter *counter)
00318 {
00319         if (defn.landsmoothing == 0) return;
00320         if (counter) counter->setNewOp(LANG_RESOURCE("SMOOTING", "Smoothing"));
00321 
00322         fixed *newhMap_ = new fixed[(hmap.getMapWidth()+1) * (hmap.getMapHeight()+1)];
00323 
00324         fixed matrix[5][5];
00325         for (int i=0; i<5; i++)
00326         {
00327                 for (int j=0; j<5; j++)
00328                 {
00329                         matrix[i][j] = fixed(defn.landsmoothing); // How much smoothing is done (> is more)
00330                         if (i==2 && j==2) matrix[i][j] = fixed(1);
00331                 }
00332         }
00333 
00334         int x;
00335         for (x=0; x<=hmap.getMapWidth(); x++)
00336         {
00337                 if (counter) counter->setNewPercentage((100.0f * float(x)) / float(hmap.getMapWidth()));
00338                 for (int y=0; y<=hmap.getMapHeight(); y++)
00339                 {
00340                         // Total is used to catch corner cases
00341                         fixed total = fixed(0);
00342                         fixed inc = fixed(0);
00343                         for (int i=0; i<5; i++)
00344                         {
00345                                 for (int j=0; j<5; j++)
00346                                 {
00347                                         int newi = i + x - 2;
00348                                         int newj = j + y - 2;
00349                                         if (newi>=0 && newi <= hmap.getMapWidth() &&
00350                                                 newj>=0 && newj <= hmap.getMapHeight())
00351                                         {
00352                                                 inc += matrix[i][j] * hmap.getHeight(newi, newj);
00353                                                 total += matrix[i][j];
00354                                         }
00355                                 }
00356                         }
00357 
00358                         newhMap_[(hmap.getMapWidth()+1) * y + x] = inc / total;
00359                 }
00360         }
00361 
00362         fixed *start = newhMap_;
00363         for (int y=0; y<=hmap.getMapHeight(); y++)
00364         {
00365                 for (x=0; x<=hmap.getMapWidth(); x++)
00366                 {
00367                         hmap.setHeight(x, y, *(start++));
00368                 }
00369         }
00370         delete [] newhMap_;
00371 }
00372 
00373 void HeightMapModifier::scale(HeightMap &hmap, 
00374                                                           LandscapeDefnHeightMapGenerate &defn,
00375                                                           RandomGenerator &generator, 
00376                                                           ProgressCounter *counter)
00377 {
00378         if (counter) counter->setNewOp(LANG_RESOURCE("SCALING_PHASE_1", "Scaling Phase 1"));
00379 
00380         fixed max = hmap.getHeight(0,0);
00381         fixed min = hmap.getHeight(0,0);
00382 
00383         int x;
00384         for (x=0; x<=hmap.getMapWidth(); x++)
00385         {
00386                 if (counter) counter->setNewPercentage((100.0f * float(x)) / float(hmap.getMapWidth()));
00387                 for (int y=0; y<=hmap.getMapHeight(); y++)
00388                 {
00389                         if (hmap.getHeight(x,y) > max) max = hmap.getHeight(x,y);
00390                         if (hmap.getHeight(x,y) < min) min = hmap.getHeight(x,y);
00391                 }
00392         }
00393 
00394         if (counter) counter->setNewOp(LANG_RESOURCE("SCALING_PHASE_2", "Scaling Phase 2"));
00395 
00396         fixed realMax = ((fixed(defn.landheightmax) - fixed(defn.landheightmin)) * generator.getRandFixed()) + 
00397                 defn.landheightmin;
00398         fixed per = realMax / max;
00399 
00400         for (x=0; x<=hmap.getMapWidth(); x++)
00401         {
00402                 if (counter) counter->setNewPercentage((100.0f * float(x)) / float(hmap.getMapWidth()));
00403                 for (int y=0; y<=hmap.getMapHeight(); y++)
00404                 {
00405                         fixed height = hmap.getHeight(x,y);
00406                         hmap.setHeight(x,y, height * per);
00407                 }
00408         }
00409 }
00410 
00411 void HeightMapModifier::addCirclePeak(HeightMap &hmap, FixedVector &start, 
00412                                                                           fixed sizew, fixed sizew2, fixed sizeh,
00413                                                                           RandomGenerator &offsetGenerator)
00414 {
00415         fixed maxdist = MAX(sizew2, sizew);
00416         fixed sizewsq = sizew * sizew * fixed(true, 12000);
00417         int startx = MAX(0, (start[0] - maxdist).asInt());
00418         int starty = MAX(0, (start[1] - maxdist).asInt());
00419         int endx = MIN(hmap.getMapWidth(), (start[0] + maxdist).asInt());
00420         int endy = MIN(hmap.getMapHeight(), (start[1] + maxdist).asInt());
00421 
00422         fixed posX, posY;
00423         fixed pt2 = fixed(1) / fixed(5);
00424         for (int x=startx; x<=endx; x++)
00425         {
00426                 for (int y=starty; y<=endy; y++)
00427                 {
00428                         posX = fixed(x);
00429                         posY = fixed(y);
00430 
00431                         fixed distX = (posX - start[0]) * sizew2 / sizew;
00432                         fixed distY = posY - start[1];
00433                         fixed distsq = distX * distX + distY * distY;
00434 
00435                         if (distsq < sizewsq)
00436                         {
00437                                 fixed dist = distsq.sqrt();
00438                                 fixed distRand = (dist / fixed(20)) * pt2;
00439                                 if (distRand > pt2) distRand = pt2;
00440                                 dist *= offsetGenerator.getRandFixed() * distRand + fixed(1) - (distRand / fixed(2));
00441                                 if (dist < sizew)
00442                                 {
00443                                         fixed newHeight = (dist * fixed::XPI / sizew).cos() * sizeh / fixed(4) + sizeh / fixed(4);
00444                                         newHeight += hmap.getHeight(x,y);
00445                                         hmap.setHeight(x, y, newHeight);
00446                                 }
00447                         }
00448                 }
00449         }
00450 }
00451 
00452 void HeightMapModifier::generateTerrain(HeightMap &hmap, 
00453                                                                                 LandscapeDefnHeightMapGenerate &defn,
00454                                                                                 RandomGenerator &generator,
00455                                                                                 RandomGenerator &offsetGenerator,
00456                                                                                 ProgressCounter *counter)
00457 {
00458         if (counter) counter->setNewOp(LANG_RESOURCE("TERAFORM_LANDSCAPE", "Teraform Landscape"));
00459 
00460         // Create a default mask that allows everything
00461         ImageBitmap bmap(256, 256);
00462         ImageHandle maskMap = bmap;
00463 
00464         // Check if we need to load a new mask
00465         if (!defn.mask.empty())
00466         {
00467                 std::string fileName = S3D::getDataFile(defn.mask.c_str());
00468                 maskMap = ImageFactory::loadImageHandle(fileName);
00469                 if (!maskMap.getBits())
00470                 {
00471                         S3D::dialogExit("Landscape", S3D::formatStringBuffer(
00472                                 "Error: Failed to find mask map \"%s\"",
00473                                 fileName.c_str()));
00474                 }
00475         }
00476 
00477         // Generate the landscape
00478         hmap.reset();
00479         fixed useWidthX = fixed(hmap.getMapWidth()); // Use all the width
00480         fixed useWidthY = fixed(hmap.getMapHeight());
00481         fixed useBorderX = (fixed(hmap.getMapWidth()) - useWidthX) / fixed(2);
00482         fixed useBorderY = (fixed(hmap.getMapHeight()) - useWidthY) / fixed(2);
00483         fixed maskMultX = fixed(maskMap.getWidth()) / fixed(hmap.getMapWidth());
00484         fixed maskMultY = fixed(maskMap.getHeight()) / fixed(hmap.getMapHeight());
00485 
00486         const int noItter = (fixed(defn.landhillsmax - defn.landhillsmin) *
00487                 generator.getRandFixed() + fixed(defn.landhillsmin)).asInt();
00488 
00489         for (int i=0; i<noItter; i++)
00490         {
00491                 if (counter) counter->setNewPercentage((100.0f * float(i)) / float(noItter));
00492 
00493                 // Choose settings for a random hemisphere
00494                 fixed sizew = (fixed(defn.landpeakwidthxmax) - fixed(defn.landpeakwidthxmin)) * generator.getRandFixed() 
00495                         + fixed(defn.landpeakwidthxmin);
00496                 fixed sizew2 = (fixed(defn.landpeakwidthymax) - fixed(defn.landpeakwidthymin)) * generator.getRandFixed() 
00497                         + fixed(defn.landpeakwidthymin) + sizew;
00498                 fixed sizeh = ((fixed(defn.landpeakheightmax) - fixed(defn.landpeakheightmin)) * generator.getRandFixed() 
00499                                            + defn.landpeakheightmin) * MAX(sizew, sizew2);
00500 
00501                 // Choose a border around this hemisphere
00502                 fixed bordersizex = fixed(0);
00503                 fixed bordersizey = fixed(0);
00504                 if (defn.levelsurround)
00505                 {
00506                         fixed bordersize = MAX(sizew, sizew2) * fixed(true, 12000); // 1.2
00507                         bordersizex = bordersize + useBorderX;
00508                         bordersizey = bordersize + useBorderY;
00509                 }
00510 
00511                 // Choose a point for this hemisphere
00512                 fixed sx = (generator.getRandFixed() * (fixed(hmap.getMapWidth()) - 
00513                                                                                                 (bordersizex * fixed(2)))) + bordersizex;
00514                 fixed sy = (generator.getRandFixed() * (fixed(hmap.getMapHeight()) - 
00515                                                                                                 (bordersizey * fixed(2)))) + bordersizey;
00516 
00517                 // Check if the point passes the mask
00518                 bool ok = true;
00519                 if (sx.asInt() >= 0 && sx.asInt() < hmap.getMapWidth() &&
00520                         sy.asInt() >= 0 && sy.asInt() < hmap.getMapHeight())
00521                 {
00522                         int bx = (sx * maskMultX).asInt();
00523                         int by = maskMap.getWidth() - (sy * maskMultY).asInt();
00524                         if (bx >= 0 && bx < maskMap.getWidth() && 
00525                                 by >= 0 && by < maskMap.getHeight())
00526                         {
00527                         unsigned char maskPt = maskMap.getBits()[(bx * 3) + (by * maskMap.getWidth() * 3)];
00528 
00529                         //printf("%i %i %i %s\n", maskPt, bx, by, defn.mask.c_str());
00530                         ok = ((generator.getRandFixed() * fixed(255)) < fixed(maskPt));
00531                         }
00532                 }
00533 
00534                 if (ok)
00535                 {
00536                         // Actually add the hemisphere
00537                         FixedVector start(sx, sy, fixed(0));
00538                         addCirclePeak(hmap, start, sizew, sizew2, sizeh, offsetGenerator);
00539                 }
00540         }
00541 
00542         scale(hmap, defn, generator, counter);
00543         noise(hmap, defn, generator, counter);
00544         //edgeEnhance(hmap, defn, generator, counter);
00545         waterErrosion(hmap, defn, generator, counter);
00546         if (defn.levelsurround) levelSurround(hmap);
00547         smooth(hmap, defn, counter);
00548         if (defn.levelsurround) levelSurround(hmap);
00549 }
00550 

Generated on Mon Feb 16 15:14:50 2009 for Scorched3D by  doxygen 1.5.3