aesop-core/tool/terrain/terraTextureGen/main.cpp

Go to the documentation of this file.
00001 /*
00002  * main.cpp
00003  *
00004  * Copyright (C) 2009,2010  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  */
00031 
00032 // includes --------------------------------------------------------------------
00033 #include <iostream>
00034 #include <fstream>
00035 
00036 #include "common/wave_ex.h"
00037 #include "datahash/datahash_text.h"
00038 #include "datahash/datahash_util.h"
00039 #include "hfield/heightfield.h"
00040 #include "perf/perf.h"
00041 #include "pgmppm/pgmppm.h"              // PGM/PPM files
00042 #include "util/date.h"
00043 #include "util/file.h"
00044 
00045 
00046 // statics and typedefs --------------------------------------------------------
00047 typedef pgmppm::color_t color_t;
00048 
00049 
00050 typedef std::map<float, color_t> map_colors_t;
00051 
00052 
00053 
00054 // contains data concerning the texture we'll be writing
00055 struct texture_t {
00056         // constructor, manipulators
00057         texture_t(void) throw() : texture(NULL) { this->clear(); }
00058         ~texture_t(void) throw() { this->clear(); }
00059         void clear(void) throw() {
00060                         hfield = NULL;
00061                         texelsPerGrid = 0;
00062                         xTexels = zTexels = 0;
00063                         if (texture) {
00064                                 delete[] texture;
00065                                 texture = NULL;
00066                         }
00067                         colors.clear();
00068                 }
00069 
00070         void allocate(void) {
00071                         ASSERT(!texture, "already allocated?");
00072                         ASSERT(hfield, "null");
00073                         ASSERT(texelsPerGrid > 0, "bad");
00074                         int x = hfield->getWidth();
00075                         int z = hfield->getLength();
00076                         xTexels = (x - 1) * texelsPerGrid;
00077                         zTexels = (z - 1) * texelsPerGrid;
00078 
00079                         DPRINTF("Texture will be %dx%d", xTexels, zTexels);
00080 
00081                         nTexels = xTexels * zTexels;
00082                         DPRINTF("  %d texels total", nTexels);
00083 
00084                         texture = new color_t[nTexels];
00085                         ASSERT_THROW(texture, "out of memory");
00086                 }
00087 
00088         color_t& getColor(IN int x, IN int z) const throw() {
00089                         ASSERT(x >= 0 && x < xTexels, "bad x: %d", x);
00090                         ASSERT(z >= 0 && z < zTexels, "bad z: %d", z);
00091                         ASSERT(texture, "null");
00092 
00093                         int idx = (z * xTexels) + x;
00094                         ASSERT(idx >= 0 && idx < nTexels, "bad idx: %d", idx);
00095 
00096                         return texture[idx];
00097                 }
00098 
00099         // data fields
00100         smart_ptr<hfield::Heightfield> hfield;  // heightfield
00101         int                     texelsPerGrid;  // texels per grid point
00102         int                     xTexels;
00103         int                     zTexels;
00104         int                     nTexels;        // total
00105         color_t *               texture;        // texture data
00106         map_colors_t            colors;
00107 };
00108 
00109 
00110 
00111 ////////////////////////////////////////////////////////////////////////////////
00112 ///
00113 /// \ingroup terra_tool
00114 /// \defgroup terraTextureGen terraTextureGen - heightmap generation
00115 ///
00116 /// Given an input file, this generates a heightmap, from which you can then
00117 /// generate a texure to get a complete terrain object.
00118 ///
00119 /// Sample usage:
00120 /// \code
00121 ///     % terraTextureGen sample-input.txt sample
00122 /// \endcode
00123 ///
00124 /// That will read the file "sample-input.txt" for parameters, and then
00125 /// output two files:
00126 ///  - <b>sample.hfield</b> This is the hfield file that the Wavepacket GLUT
00127 ///     heightfield library uses.
00128 ///  - <b>sample.pgm</b> This is a PGM file containing height values.
00129 ///
00130 /// Note that <b>sample.ppm</b> is referred to by the hfield file, but the
00131 /// PPM does not exist!  Use terraTextureGen to create the ppm file from these
00132 /// two input files.
00133 ///
00134 ////////////////////////////////////////////////////////////////////////////////
00135 
00136 static void
00137 verifyColor
00138 (
00139 IN int c,               // actually a color component (blue, red, ...)
00140 IN const char * name
00141 )
00142 {
00143         ASSERT_THROW(c >= 0 && c<= 255, "Bad color component: " <<
00144             name << " = " << c);
00145 }
00146 
00147 
00148 
00149 static int
00150 randDelta
00151 (
00152 void
00153 )
00154 throw()
00155 {
00156         const int s_max = 16;
00157         const int s_mod = 2 * s_max + 1;
00158         int r = rand() % s_mod;
00159         return s_max - r;
00160 }
00161 
00162 
00163 
00164 static void
00165 rComp
00166 (
00167 IO int& i
00168 )
00169 throw()
00170 {
00171         // rComp: randomize component (for color)
00172         i += randDelta();
00173         if (i < 0) {
00174                 i = 0;
00175         }
00176         if (i > 255) {
00177                 i = 255;
00178         }
00179 }
00180 
00181 
00182 
00183 static color_t
00184 randomize
00185 (
00186 IN const color_t& i_c
00187 )
00188 throw()
00189 {
00190         color_t c = i_c;
00191         rComp(c.red);
00192         rComp(c.green);
00193         rComp(c.blue);
00194         return c;
00195 }
00196 
00197 
00198 
00199 static color_t
00200 getColorForHeight
00201 (
00202 IN texture_t * t,
00203 IN float y              // relative y: between 0 (min) and 1 (max)
00204 )
00205 {
00206         ASSERT(t, "null");
00207         ASSERT(t->hfield, "null");
00208         ASSERT(y >= 0 && y <= 1, "bad relative y: %f", y);
00209 
00210         //DPRINTF("Checking color for height: %f", y);
00211 
00212         const color_t * pc = NULL;
00213         for (map_colors_t::const_iterator i = t->colors.begin();
00214              i != t->colors.end(); ++i) {
00215                 float h = i->first;     // height is here and above
00216         //      DPRINTF("  comparing vs. height: %f", h);
00217                 if (h > y) {
00218                         break;
00219                 }
00220                 pc = &i->second;
00221         }
00222         if (pc)
00223                 return randomize(*pc);
00224 
00225         // got here?  invalid color value or some such
00226         color_t bad;
00227         bad.red = 255;
00228         bad.green = bad.blue = 0;
00229         return bad;
00230 }
00231 
00232 
00233 
00234 static void
00235 setBaseTexture
00236 (
00237 IN texture_t * t
00238 )
00239 {
00240         perf::Timer timer("setBaseTexture");
00241         ASSERT(t, "null");
00242         ASSERT(t->hfield, "null");
00243         ASSERT(t->texelsPerGrid > 0, "bad");
00244         ASSERT(t->texture, "null");
00245 
00246         float xzScale = t->hfield->getXZScale();
00247         xzScale *= (1.0 / t->texelsPerGrid);
00248 
00249         short iMin = t->hfield->getMinHeight();
00250         short iMax = t->hfield->getMaxHeight();
00251 
00252         float yScale = t->hfield->getYScale();
00253 
00254         float yMin = yScale * iMin;
00255         float yMax = yScale * iMax;
00256         DPRINTF("yMin,yMax = %f, %f", yMin, yMax);
00257 
00258         float dY = yMax - yMin;
00259         float invDY = 1;
00260         if (dY > 0) {
00261                 invDY = 1.0 / dY;
00262         }
00263         DPRINTF("dY, 1/dY = %f, %f", dY, invDY);
00264 
00265         color_t * p = t->texture;
00266         for (int iz = 0; iz < t->zTexels; ++iz) {
00267                 float z = iz * xzScale;
00268                 for (int ix = 0; ix < t->xTexels; ++ix, ++p) {
00269                         float x = ix * xzScale;
00270                         float y = t->hfield->getHeight(x, z);
00271                         float r = (y - yMin) * invDY;
00272                         //DPRINTF("  y = %f --> r = %f", y, r);
00273                         *p = getColorForHeight(t, r);
00274                 }
00275         }
00276 }
00277 
00278 
00279 
00280 static void
00281 addColor
00282 (
00283 IO color_t& sum,
00284 IN texture_t * t,
00285 IN int ix,
00286 IN int iz
00287 )
00288 {
00289         color_t c = t->getColor(ix, iz);
00290         sum.red += c.red;
00291         sum.green += c.green;
00292         sum.blue += c.blue;
00293 }
00294 
00295 
00296 
00297 static void
00298 divideColor
00299 (
00300 IO color_t& sum,
00301 IN int count
00302 )
00303 {
00304         ASSERT(count > 0, "BAd count: %d", count);
00305 
00306         sum.red /= count;
00307         sum.green /= count;
00308         sum.blue /= count;
00309 }
00310 
00311 
00312 
00313 static void
00314 smooth
00315 (
00316 IN texture_t * t
00317 )
00318 {
00319         perf::Timer timer("smooth");
00320         ASSERT(t, "null");
00321         ASSERT(t->texture, "null");
00322 
00323         color_t * newTexture = new color_t[t->nTexels];
00324         ASSERT_THROW(newTexture, "out of memory");
00325 
00326         const color_t * p = t->texture;
00327         color_t * q = newTexture;
00328         for (int j = 0; j < t->zTexels; ++j) {
00329                 for (int i = 0; i < t->xTexels; ++i, ++p, ++q) {
00330                         int nAdj = (rand() % 3);                // number adjacent
00331                         color_t sum = *p;
00332                         sum.red *= nAdj;
00333                         sum.green *= nAdj;
00334                         sum.blue *= nAdj;
00335 
00336                         if (i > 0) {
00337                                 ++nAdj;
00338                                 addColor(sum, t, i - 1, j);
00339                         } else if (i < t->xTexels - 1) {
00340                                 ++nAdj;
00341                                 addColor(sum, t, i + 1, j);
00342                         }
00343                         if (j > 0) {
00344                                 ++nAdj;
00345                                 addColor(sum, t, i, j - 1);
00346                         } else if (j < t->zTexels - 1) {
00347                                 ++nAdj;
00348                                 addColor(sum, t, i, j + 1);
00349                         }
00350                         divideColor(sum, nAdj);
00351 
00352                         *q = sum;
00353                 }
00354         }
00355 
00356         // swap the old for the new
00357         color_t * old = t->texture;
00358         t->texture = newTexture;
00359         delete[] old;
00360 }
00361 
00362 
00363 
00364 static smart_ptr<texture_t>
00365 createTexture
00366 (
00367 IN smart_ptr<hfield::Heightfield>& hfield,
00368 IN const Datahash * params
00369 )
00370 {
00371         ASSERT(hfield, "null");
00372         ASSERT(params, "null");
00373 
00374         hfield->dump("just read");
00375 
00376         smart_ptr<texture_t> t = new texture_t;
00377         ASSERT_THROW(t, "out of memory");
00378 
00379         t->hfield = hfield;
00380 
00381         // get the parameters we need
00382         t->texelsPerGrid = getInt(params, "texelsPerGridPoint");
00383         DPRINTF("Using %d texels per heightfield grid point",
00384             t->texelsPerGrid);
00385         ASSERT_THROW(t->texelsPerGrid > 0, "Bad texels per grid point: "
00386             << t->texelsPerGrid);
00387 
00388         // allocate
00389         t->allocate();
00390 
00391         // read the color entries
00392         Datahash::iterator_t i;
00393         params->getIterator("color", i);
00394         while (const hash_value_t * phv = params->getNextElementUnsafe(i)) {
00395                 if (!phv->hash)
00396                         continue;       // skip simple values
00397                 const Datahash * sub = phv->hash;
00398                 ASSERT(sub, "null");
00399 
00400                 float height = getFloat(sub, "height");
00401                 ASSERT_THROW(height >= 0 && height <= 1,
00402                     "bad color height: " << height);
00403 
00404                 color_t c;
00405                 c.red = getInt(sub, "red");
00406                 c.green = getInt(sub, "green");
00407                 c.blue = getInt(sub, "blue");
00408 
00409                 verifyColor(c.red, "red");
00410                 verifyColor(c.green, "green");
00411                 verifyColor(c.blue, "blue");
00412 
00413                 t->colors[height] = c;
00414         }
00415         DPRINTF("Read %d color entries", (int) t->colors.size());
00416 
00417         // create base texture
00418         setBaseTexture(t);
00419 
00420         // run smoothing passes
00421         int nSmooth = 0;
00422         if (params->count("smoothCount")) {
00423                 nSmooth = getInt(params, "smoothCount");
00424         }
00425         ASSERT_THROW(nSmooth >= 0, "Bad smooth count: " << nSmooth);
00426         for (int i = 0; i < nSmooth; ++i) {
00427                 smooth(t);
00428         }
00429 
00430         // all done!
00431         return t;
00432 }
00433 
00434 
00435 
00436 static color_t
00437 writePixel
00438 (
00439 IN void * context,
00440 IN int x,
00441 IN int z
00442 )
00443 {
00444         const texture_t * t = (const texture_t *) context;
00445         ASSERT(t, "null context");
00446 
00447         return t->getColor(x, z);
00448 }
00449 
00450 
00451 
00452 static void
00453 writeTexture
00454 (
00455 IN const texture_t * t,
00456 IN const char * outputFile
00457 )
00458 {
00459         perf::Timer timer("writeTexture");
00460         ASSERT(t, "null");
00461         ASSERT(outputFile, "null");
00462 
00463         std::ofstream output(outputFile);
00464         ASSERT_THROW(output.good(), "Failed to open file for writing: "
00465             << outputFile);
00466 
00467         // okay, now write output PPM file
00468         pgmppm::writePpm(output, t->xTexels, t->zTexels, 255,
00469             writePixel, (void *) t);
00470 }
00471 
00472 
00473 
00474 ////////////////////////////////////////////////////////////////////////////////
00475 //
00476 //      entry point
00477 //
00478 ////////////////////////////////////////////////////////////////////////////////
00479 
00480 int
00481 main
00482 (
00483 IN int argc,
00484 IN const char * argv[]
00485 )
00486 {
00487         ASSERT(3 == argc,
00488             "usage: terraTextureGen <hfield-file> <texture-param-file>");
00489         const char * hfieldFile = argv[1];
00490         const char * paramFile = argv[2];
00491 
00492         // start everything up
00493         int retval = 0;
00494         try {
00495                 perf::Timer timer("overall program timer");
00496 
00497                 // get filesystem manager
00498                 smart_ptr<nstream::Manager> mgr =
00499                     nstream::getFilesystemManager(".");
00500                 ASSERT(mgr, "null");
00501 
00502                 // open heightfield
00503                 smart_ptr<nstream::Stream> stream =
00504                     nstream::openNamedStream(mgr, hfieldFile);
00505                 ASSERT(stream, "null");
00506                 smart_ptr<hfield::Heightfield> hfield =
00507                     hfield::Heightfield::create(stream);
00508                 ASSERT_THROW(hfield, "Failed to load hfield");
00509 
00510                 // open texture parameters
00511                 smart_ptr<Datahash> params = readHashFromTextFile(paramFile);
00512                 ASSERT_THROW(params, "Failed to read texture parameter file: "
00513                     << paramFile);
00514 
00515                 // create and write texture
00516                 smart_ptr<texture_t> texture = createTexture(hfield, params);
00517                 writeTexture(texture, hfield->getTexturePath());
00518         } catch (std::exception& e) {
00519                 DPRINTF("Exception during startup!");
00520                 DPRINTF("%s", e.what());
00521                 retval = 1;
00522         } catch (...) {
00523                 DPRINTF("Unknown exception during startup!");
00524                 retval = 2;
00525         }
00526 
00527         perf::dumpTimingSummary(std::cerr);
00528 
00529         return retval;
00530 }
00531