00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 #include <iostream>
00034 #include <fstream>
00035 #include <math.h>
00036
00037 #include "common/wave_ex.h"
00038 #include "datahash/datahash_text.h"
00039 #include "datahash/datahash_util.h"
00040 #include "geometry/geometry_2d.h"
00041 #include "geometry/geometry_3d.h"
00042 #include "geometry/xform_2d.h"
00043 #include "perf/perf.h"
00044 #include "pgmppm/pgmppm.h"
00045 #include "util/date.h"
00046 #include "util/file.h"
00047
00048
00049
00050
00051 static const int s_minGrid = 2;
00052
00053 static const char * s_hfieldVersion = "0.1";
00054
00055 static const int s_yIntExtent = 255;
00056
00057
00058
00059 typedef std::vector<point3d_t> vec_points_t;
00060
00061 typedef point2d_t<float> point_t;
00062 typedef rect2d_t<float> rect_t;
00063
00064
00065
00066
00067 struct hdata_t {
00068
00069 hdata_t(void) throw() : yData(NULL) { this->clear(); }
00070 ~hdata_t(void) throw() { this->clear(); }
00071 void clear(void) throw() {
00072 xGrid = zGrid = 0;
00073 nFloats = 0;
00074 xzScale = 0;
00075 yScale = -1;
00076 yMin = yMax = 0;
00077 fixedPoints.clear();
00078 if (yData) {
00079 delete[] yData;
00080 yData = NULL;
00081 }
00082 }
00083
00084 void allocate(void) {
00085 ASSERT(!yData, "Already allocated?");
00086 nFloats = (xGrid + 1) * (zGrid + 1);
00087 DPRINTF("Allocating grid of %d floats total",
00088 nFloats);
00089 yData = new float[nFloats];
00090 ASSERT_THROW(yData, "out of memory");
00091 }
00092
00093 void setHeightInt(IN int x, IN int z, IN float y) {
00094 ASSERT(yData, "need to allocate");
00095 int idx = getIndex(x, z);
00096 yData[idx] = y;
00097 }
00098
00099 void setHeightFloat(IN float x, IN float z, IN float y) {
00100 int ix = (int)((x / xzScale) + 0.5);
00101 int iz = (int)((z / xzScale) + 0.5);
00102
00103 setHeightInt(ix, iz, y);
00104 }
00105
00106 float getHeightInt(IN int x, IN int z) const {
00107 ASSERT(yData, "need to allocate");
00108 int idx = getIndex(x, z);
00109 return yData[idx];
00110 }
00111
00112 int getIndex(IN int x, IN int z) const {
00113 ASSERT(x >= 0 && x <= xGrid, "bad x: %d", x);
00114 ASSERT(z >= 0 && z <= zGrid, "bad z: %d", z);
00115 return (z * (xGrid + 1)) + x;
00116 }
00117
00118 float getYScale(void) const throw() {
00119 return yScale;
00120 }
00121
00122 float getXZScale(void) const throw() {
00123 return xzScale;
00124 }
00125
00126
00127 int xGrid;
00128 int zGrid;
00129 int nFloats;
00130 float xzScale;
00131 float yScale;
00132 float yMin;
00133 float yMax;
00134 float * yData;
00135 vec_points_t fixedPoints;
00136 vec_points_t seedPoints;
00137 };
00138
00139
00140
00165
00166 static float
00167 randX
00168 (
00169 IN float max
00170 )
00171 throw()
00172 {
00173
00174 static const float s_invMax = 1.0 / RAND_MAX;
00175 return 2.0 * (rand() * s_invMax - 0.5) * max;
00176 }
00177
00178
00179
00180 static float
00181 randMid
00182 (
00183 IN float min,
00184 IN float max
00185 )
00186 throw()
00187 {
00188
00189 float W = max - min;
00190 float avg = 0.5 * (min + max);
00191
00192 return avg + randX(0.3 * W);
00193 }
00194
00195
00196
00197 static void
00198 parsePoints
00199 (
00200 IN hdata_t * hdata,
00201 IN const Datahash * input,
00202 IN const char * name,
00203 IN float yMax,
00204 OUT vec_points_t& vec
00205 )
00206 {
00207 ASSERT(hdata, "null");
00208 ASSERT(input, "null");
00209 ASSERT(name, "null");
00210 vec.clear();
00211
00212
00213 if (!input->count(name)) {
00214 return;
00215 }
00216
00217
00218 float xMax = hdata->xGrid * hdata->xzScale;
00219 float zMax = hdata->zGrid * hdata->xzScale;
00220
00221 Datahash::iterator_t i;
00222 input->getIterator(name, i);
00223 while (const hash_value_t * phv = input->getNextElementUnsafe(i)) {
00224 if (phv->hash)
00225 continue;
00226 const char * val = phv->text.c_str();
00227
00228 point3d_t fp;
00229 parsePoint3d(val, fp);
00230 fp.dump(name);
00231
00232
00233 ASSERT_THROW(fp.x >= 0 && fp.x <= 1, "Bad x: " << fp.x);
00234 ASSERT_THROW(fp.z >= 0 && fp.z <= 1, "Bad z: " << fp.z);
00235
00236
00237 fp.x *= xMax;
00238 fp.z *= zMax;
00239 fp.y *= yMax;
00240
00241 vec.push_back(fp);
00242 }
00243 DPRINTF("Parsed %d points of name '%s'", (int) vec.size(), name);
00244 }
00245
00246
00247
00248 static void
00249 overlayPoints
00250 (
00251 IN hdata_t * hdata,
00252 IN vec_points_t& vec
00253 )
00254 {
00255 perf::Timer timer("overlayFixed");
00256 ASSERT(hdata, "null");
00257
00258
00259 for (vec_points_t::const_iterator i = vec.begin(); i != vec.end();
00260 ++i) {
00261 const point3d_t& fp = *i;
00262
00263
00264 hdata->setHeightFloat(fp.x, fp.z, fp.y);
00265 }
00266 }
00267
00268
00269
00270 static void
00271 calculateYScale
00272 (
00273 IN hdata_t * hdata
00274 )
00275 {
00276 ASSERT(hdata, "null");
00277 ASSERT(hdata->yData, "should have allocated by now");
00278 ASSERT(hdata->nFloats > 0, "no data?");
00279
00280 float yMin = hdata->yData[0];
00281 float yMax = yMin;
00282
00283 for (int i = 1; i < hdata->nFloats; ++i) {
00284 float y = hdata->yData[i];
00285 if (y < yMin) {
00286 yMin = y;
00287 } else if (y > yMax) {
00288 yMax = y;
00289 }
00290 }
00291
00292 DPRINTF("Calculated (ymin,ymax) = (%f, %f)", yMin, yMax);
00293 float dY = yMax - yMin;
00294 if (dY <= 0.0) {
00295 hdata->yScale = 1.0;
00296 } else {
00297 hdata->yScale = dY / s_yIntExtent;
00298 }
00299 DPRINTF(" Calculated yScale: %f", hdata->getYScale());
00300 hdata->yMin = yMin;
00301 hdata->yMax = yMax;
00302 }
00303
00304
00305
00306 static void
00307 generateFlat
00308 (
00309 IN hdata_t * hdata,
00310 IN const Datahash * algo
00311 )
00312 {
00313 ASSERT(hdata, "null");
00314 ASSERT(algo, "null");
00315
00316
00317 float y = getFloat(algo, "y");
00318 DPRINTF("Flat algorithm is using base y-value: %f", y);
00319
00320
00321 int xGrid = hdata->xGrid;
00322 int zGrid = hdata->zGrid;
00323 for (int i = 0; i <= xGrid; ++i) {
00324 for (int j = 0; j <= zGrid; ++j) {
00325 hdata->setHeightInt(i, j, y);
00326 }
00327 }
00328 }
00329
00330
00331
00332 static void
00333 iterateFractal
00334 (
00335 IN hdata_t * hdata,
00336 IN int dx,
00337 IN float dy
00338 )
00339 {
00340 ASSERT(hdata, "null");
00341 ASSERT(dx > 0, "bad dx: %d", dx);
00342 ASSERT(dy > 0, "bad dy: %f", dy);
00343
00344 int W = hdata->xGrid;
00345
00346
00347 int mid = dx / 2;
00348 for (int z = 0; z <= W; z += dx) {
00349 for (int x = mid; x <= W; x += dx) {
00350 float y0 = hdata->getHeightInt(x - mid, z);
00351 float y1 = hdata->getHeightInt(x + mid, z);
00352
00353
00354 float avg = y0 + y1 + randX(dy);
00355 hdata->setHeightInt(x, z, 0.5 * avg);
00356 }
00357 }
00358
00359
00360 for (int x = 0; x <= W; x += dx) {
00361 for (int z = mid; z <= W; z += dx) {
00362 float y0 = hdata->getHeightInt(x, z - mid);
00363 float y1 = hdata->getHeightInt(x, z + mid);
00364
00365
00366
00367 float avg = y0 + y1 + randX(dy);
00368 hdata->setHeightInt(x, z, 0.5 * avg);
00369 }
00370 }
00371
00372
00373 for (int x = mid; x <= W; x += dx) {
00374 for (int z = mid; z <= W; z += dx) {
00375 float y0 = hdata->getHeightInt(x, z - mid);
00376 float y1 = hdata->getHeightInt(x, z + mid);
00377 float y2 = hdata->getHeightInt(x - mid, z);
00378 float y3 = hdata->getHeightInt(x + mid, z);
00379
00380
00381
00382 float avg = y0 + y1 + y2 + y3;
00383 hdata->setHeightInt(x, z, 0.25 * avg);
00384 }
00385 }
00386 }
00387
00388
00389
00390 static void
00391 generateFractal
00392 (
00393 IN hdata_t * hdata,
00394 IN const Datahash * algo
00395 )
00396 {
00397 ASSERT(hdata, "null");
00398 ASSERT(algo, "null");
00399
00400
00401 ASSERT_THROW(hdata->xGrid == hdata->zGrid,
00402 "Has to be a square grid!");
00403 int W = hdata->xGrid;
00404
00405
00406 float dy = getFloat(algo, "dy");
00407 ASSERT_THROW(dy >= 0, "Bad dy: " << dy);
00408
00409 float reduce = getFloat(algo, "reduce");
00410 ASSERT_THROW(reduce >=0 && reduce <= 1,
00411 "Bad reduce: " << reduce);
00412
00413
00414 vec_points_t seeds;
00415 parsePoints(hdata, algo, "seed", dy, seeds);
00416
00417
00418 overlayPoints(hdata, seeds);
00419
00420
00421 int nIter = (int)((log(W) / log(2)) + 0.5);
00422 DPRINTF("W=%d --> %d iterations", W, nIter);
00423
00424 int dx = W;
00425 while (dx > 1) {
00426
00427 iterateFractal(hdata, dx, dy);
00428 dx /= 2;
00429 dy *= reduce;
00430 }
00431 }
00432
00433
00434
00435 static void
00436 getTransform
00437 (
00438 IN float X,
00439 IN float Z,
00440 OUT xform_2d_t& T
00441 )
00442 {
00443
00444 T.clear();
00445
00446
00447 float x = randMid(0, 1) * X;
00448 float z = randMid(0, 1) * Z;
00449
00450 xform_2d_t trans;
00451 trans.setTranslate(-x, -z);
00452
00453
00454 float angle = randX(M_PI);
00455 xform_2d_t rotate;
00456 rotate.setZRotate(angle);
00457
00458 T.setToProductOf(rotate, trans);
00459 }
00460
00461
00462
00463 static void
00464 addSplit
00465 (
00466 IN hdata_t * hdata,
00467 IN const xform_2d_t& T,
00468 IN float dy
00469 )
00470 throw()
00471 {
00472 ASSERT(hdata, "NULL");
00473 ASSERT(dy > 0, "Bad dy");
00474
00475 float xzScale = hdata->xzScale;
00476 int isign = 1 - 2 * (rand() % 2);
00477
00478
00479 for (int ix = 0; ix <= hdata->xGrid; ++ix) {
00480 float x = ix * xzScale;
00481 for (int iz = 0; iz <= hdata->zGrid; ++iz) {
00482 float z = iz * xzScale;
00483
00484 point_t p(x, z);
00485 point_t q;
00486 T.transformPoint(p, q);
00487
00488 float y = hdata->getHeightInt(ix, iz);
00489 if (q.y < 0) {
00490 hdata->setHeightInt(ix, iz, y + isign * dy);
00491 } else {
00492 hdata->setHeightInt(ix, iz, y - isign * dy);
00493 }
00494 }
00495 }
00496 }
00497
00498
00499
00500 static void
00501 generateSplit
00502 (
00503 IN hdata_t * hdata,
00504 IN const Datahash * algo
00505 )
00506 {
00507 ASSERT(hdata, "null");
00508 ASSERT(algo, "null");
00509
00510 float dy = getFloat(algo, "dy");
00511 ASSERT_THROW(dy > 0, "Bad splitting dy: " << dy);
00512
00513 float nIter = getInt(algo, "nIter");
00514 ASSERT_THROW(nIter > 0, "Bad split iteration count: " << nIter);
00515
00516
00517 float xzScale = hdata->xzScale;
00518 float W = hdata->xGrid * xzScale;
00519 float L = hdata->zGrid * xzScale;
00520
00521
00522 int splitsPerIteration = 1;
00523 for (int n = 0; n < nIter; ++n) {
00524 for (int i = 0; i < splitsPerIteration; ++i) {
00525 xform_2d_t T;
00526 getTransform(W, L, T);
00527 addSplit(hdata, T, dy);
00528 }
00529 splitsPerIteration *= 2;
00530 dy /= 2;
00531 }
00532 }
00533
00534
00535
00536 static smart_ptr<hdata_t>
00537 createHeightfield
00538 (
00539 IN Datahash * input
00540 )
00541 {
00542 ASSERT(input, "null");
00543
00544 smart_ptr<hdata_t> hdata = new hdata_t;
00545 ASSERT_THROW(hdata, "out of memory");
00546
00547
00548 hdata->xGrid = getInt(input, "xGrid");
00549 hdata->zGrid = getInt(input, "zGrid");
00550 DPRINTF("Heightfield grid extent is %dx%d", hdata->xGrid, hdata->zGrid);
00551 DPRINTF(" (This means we store a %dx%d grid)",
00552 hdata->xGrid + 1, hdata->zGrid + 1);
00553 ASSERT_THROW(hdata->xGrid >= s_minGrid,
00554 "xGrid is too small: " << hdata->xGrid << " vs. " << s_minGrid);
00555 ASSERT_THROW(hdata->zGrid >= s_minGrid,
00556 "zGrid is too small: " << hdata->zGrid << " vs. " << s_minGrid);
00557
00558
00559 hdata->xzScale = getFloat(input, "xzScale");
00560 ASSERT_THROW(hdata->xzScale > 0.0,
00561 "Bad xzScale: " << hdata->xzScale);
00562
00563
00564 hdata->allocate();
00565
00566
00567 Datahash::iterator_t i;
00568 input->getIterator("algorithm", i);
00569 std::string key;
00570 hash_value_t hv;
00571 while (input->getNextElement(i, key, hv)) {
00572 if (!hv.hash)
00573 continue;
00574 const Datahash * algo = hv.hash;
00575 ASSERT(algo, "null");
00576 const char * type = getString(algo, "type");
00577 DPRINTF("Terrain generation algorithm: %s", type);
00578 std::string timerName = "algorithm timer (type=";
00579 timerName += type;
00580 timerName += ")";
00581 {
00582 perf::Timer timer(timerName.c_str());
00583 if (!strcmp("flat", type)) {
00584 generateFlat(hdata, algo);
00585 } else if (!strcmp("fractal", type)) {
00586 generateFractal(hdata, algo);
00587 } else if (!strcmp("split", type)) {
00588 generateSplit(hdata, algo);
00589 } else {
00590 ASSERT_THROW(false,
00591 "Unknown terrain generation algorithm "
00592 "type: '" << type << "'");
00593 }
00594 }
00595 }
00596
00597
00598 return hdata;
00599 }
00600
00601
00602
00603 static smart_ptr<hdata_t>
00604 createHeightfield
00605 (
00606 IN const char * inputFile
00607 )
00608 {
00609 ASSERT(inputFile, "null");
00610
00611 smart_ptr<Datahash> input = readHashFromTextFile(inputFile);
00612 ASSERT_THROW(input, "failed to create input hash from file: "
00613 << inputFile);
00614
00615 smart_ptr<hdata_t> hdata = createHeightfield(input);
00616 ASSERT_THROW(hdata, "Failed to create heightfield data?");
00617
00618
00619 calculateYScale(hdata);
00620 ASSERT(hdata->getYScale() > 0,
00621 "Should have calculated proper y-scale by now");
00622
00623 return hdata;
00624 }
00625
00626
00627
00628 static int
00629 writePgmPixel
00630 (
00631 IN void * context,
00632 IN int x,
00633 IN int z
00634 )
00635 {
00636 hdata_t * hdata = (hdata_t *) context;
00637 ASSERT(hdata, "null context?");
00638
00639 int idx = hdata->getIndex(x, z);
00640 ASSERT(idx >= 0 && idx < hdata->nFloats, "Bad index: %d", idx);
00641
00642 float y = hdata->yData[idx];
00643 float dy = y - hdata->yMin;
00644 int iy = int((dy / hdata->getYScale()) + 0.5);
00645
00646 return iy;
00647 }
00648
00649
00650
00651 static void
00652 writeHeightfield
00653 (
00654 IN const hdata_t * hdata,
00655 IN const char * outputName
00656 )
00657 {
00658 perf::Timer timer("writeHeightfield");
00659 ASSERT(hdata, "null");
00660 ASSERT(outputName, "null");
00661
00662 std::string hfieldFilename = outputName;
00663 hfieldFilename += ".hfield";
00664
00665 std::string pgmFilename = outputName;
00666 pgmFilename += ".pgm";
00667
00668 std::string date;
00669 getDisplayableDateFromNetTime(time(NULL), date);
00670
00671 DPRINTF("Writing heightfield metadata to file: %s",
00672 hfieldFilename.c_str());
00673
00674 std::ofstream output(hfieldFilename.c_str());
00675 ASSERT_THROW(output.good(), "Failed to open file for writing: "
00676 << hfieldFilename);
00677 output << "#\n";
00678 output << "# " << hfieldFilename << "\n";
00679 output << "#\n";
00680 output << "# This is an auto-generated hfield file.\n";
00681 output << "#\n";
00682 output << "# Generated: " << date << "\n";
00683 output << "#\n";
00684 output << "\n";
00685 output << "hfieldVersion\t" << s_hfieldVersion << "\n";
00686 output << "heightfield {\n";
00687 output << "\tyScale\t" << hdata->getYScale() << "\t# meters per y-tick\n";
00688 output << "\txzScale\t" << hdata->getXZScale() << "\t# meters per grid point\n";
00689 output << "\n";
00690 output << "\t# relative paths to heightfield and texture files\n";
00691 output << "\thfieldFile\t" << pgmFilename << "\n";
00692 output << "\ttextureFile\t" << outputName << ".ppm\n";
00693 output << "}\n";
00694
00695
00696 std::ofstream pgmOut(pgmFilename.c_str());
00697 ASSERT_THROW(pgmOut.good(), "Failed to open file for writing: "
00698 << pgmFilename);
00699 pgmppm::writePgm(pgmOut, hdata->xGrid + 1, hdata->zGrid + 1,
00700 255, writePgmPixel, (void *) hdata);
00701 }
00702
00703
00704
00706
00707
00708
00710
00711 int
00712 main
00713 (
00714 IN int argc,
00715 IN const char * argv[]
00716 )
00717 {
00718 ASSERT(3 == argc, "usage: terraHeightGen <input-file> <output-name>");
00719 const char * inputFile = argv[1];
00720 const char * outputName = argv[2];
00721
00722
00723 int retval = 0;
00724 try {
00725 perf::Timer timer("overall program timer");
00726 srand(time(NULL));
00727 smart_ptr<hdata_t> hdata = createHeightfield(inputFile);
00728 ASSERT(hdata, "null");
00729 writeHeightfield(hdata, outputName);
00730 } catch (std::exception& e) {
00731 DPRINTF("Exception during startup!");
00732 DPRINTF("%s", e.what());
00733 retval = 1;
00734 } catch (...) {
00735 DPRINTF("Unknown exception during startup!");
00736 retval = 2;
00737 }
00738
00739 perf::dumpTimingSummary(std::cerr);
00740
00741 return retval;
00742 }
00743