Panda3D
 All Classes Functions Variables Enumerations
stBasicTerrain.cxx
00001 // Filename: stBasicTerrain.cxx
00002 // Created by:  drose (12Oct10)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "stBasicTerrain.h"
00016 #include "geomVertexWriter.h"
00017 #include "pnmImage.h"
00018 #include "indent.h"
00019 
00020 TypeHandle STBasicTerrain::_type_handle;
00021 
00022 // VERTEX_ATTRIB_END is defined as a macro that must be evaluated
00023 // within the SpeedTree namespace.
00024 namespace SpeedTree {
00025   static const SVertexAttribDesc st_attrib_end = VERTEX_ATTRIB_END();
00026 }
00027 
00028 /* Hmm, maybe we want to use this lower-level structure directly
00029    instead of the GeomVertexWriter.
00030 
00031 namespace SpeedTree {
00032   static const SVertexAttribDesc std_vertex_format[] = {
00033     { VERTEX_ATTRIB_SEMANTIC_POS, VERTEX_ATTRIB_TYPE_FLOAT, 3 },
00034     { VERTEX_ATTRIB_SEMANTIC_TEXCOORD0, VERTEX_ATTRIB_TYPE_FLOAT, 3 },
00035     VERTEX_ATTRIB_END( )
00036   };
00037   static const int std_vertex_format_length = 
00038     sizeof(std_vertex_format) / sizeof(std_vertex_format[0]);
00039 };
00040 */
00041 
00042 ////////////////////////////////////////////////////////////////////
00043 //     Function: STBasicTerrain::Constructor
00044 //       Access: Published
00045 //  Description: 
00046 ////////////////////////////////////////////////////////////////////
00047 STBasicTerrain::
00048 STBasicTerrain() {
00049   clear();
00050 }
00051 
00052 ////////////////////////////////////////////////////////////////////
00053 //     Function: STBasicTerrain::Copy Constructor
00054 //       Access: Published
00055 //  Description: Not sure whether any derived classes will implement
00056 //               the copy constructor, but it's defined here at the
00057 //               base level just in case.
00058 ////////////////////////////////////////////////////////////////////
00059 STBasicTerrain::
00060 STBasicTerrain(const STBasicTerrain &copy) :
00061   STTerrain(copy),
00062   _size(copy._size),
00063   _height_scale(copy._height_scale)
00064 {
00065 }
00066 
00067 ////////////////////////////////////////////////////////////////////
00068 //     Function: STBasicTerrain::Destructor
00069 //       Access: Published, Virtual
00070 //  Description: 
00071 ////////////////////////////////////////////////////////////////////
00072 STBasicTerrain::
00073 ~STBasicTerrain() {
00074 }
00075 
00076 ////////////////////////////////////////////////////////////////////
00077 //     Function: STBasicTerrain::clear
00078 //       Access: Published, Virtual
00079 //  Description: Resets the terrain to its initial, unloaded state.
00080 ////////////////////////////////////////////////////////////////////
00081 void STBasicTerrain::
00082 clear() {
00083   STTerrain::clear();
00084 
00085   _height_map = "";
00086   _size = 1.0f;
00087   _height_scale = 1.0f;
00088 
00089   CPT(GeomVertexFormat) format = GeomVertexFormat::register_format
00090     (new GeomVertexArrayFormat(InternalName::get_vertex(), 3, 
00091                                GeomEnums::NT_stdfloat, GeomEnums::C_point,
00092                                InternalName::get_texcoord(), 3, 
00093                                GeomEnums::NT_stdfloat, GeomEnums::C_texcoord));
00094   set_vertex_format(format);
00095 }
00096 
00097 ////////////////////////////////////////////////////////////////////
00098 //     Function: STBasicTerrain::setup_terrain
00099 //       Access: Published
00100 //  Description: Sets up the terrain by reading a terrain.txt file as
00101 //               defined by SpeedTree.  This file names the various
00102 //               map files that define the terrain, as well as
00103 //               defining parameters size as its size and color.
00104 //
00105 //               If a relative filename is supplied, the model-path is
00106 //               searched.  If a directory is named, "terrain.txt" is
00107 //               implicitly appended.
00108 ////////////////////////////////////////////////////////////////////
00109 bool STBasicTerrain::
00110 setup_terrain(const Filename &terrain_filename) {
00111   _is_valid = false;
00112   set_name(terrain_filename.get_basename());
00113 
00114   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00115 
00116   Filename fullpath = Filename::text_filename(terrain_filename);
00117   vfs->resolve_filename(fullpath, get_model_path());
00118 
00119   if (!vfs->exists(fullpath)) {
00120     speedtree_cat.warning()
00121       << "Couldn't find " << terrain_filename << "\n";
00122     return false;
00123   }
00124 
00125   if (vfs->is_directory(fullpath)) {
00126     fullpath = Filename(fullpath, "terrain.txt");
00127   }
00128 
00129   istream *in = vfs->open_read_file(fullpath, true);
00130   if (in == NULL) {
00131     speedtree_cat.warning()
00132       << "Couldn't open " << terrain_filename << "\n";
00133     return false;
00134   }
00135     
00136   bool success = setup_terrain(*in, fullpath);
00137   vfs->close_read_file(in);
00138 
00139   return success;
00140 }
00141 
00142 ////////////////////////////////////////////////////////////////////
00143 //     Function: STBasicTerrain::setup_terrain
00144 //       Access: Published
00145 //  Description: Sets up the terrain by reading a terrain.txt file as
00146 //               defined by SpeedTree.  This variant on this method
00147 //               accepts an istream for an already-opened terrain.txt
00148 //               file.  The filename is provided for reference, to
00149 //               assist relative file operations.  It should name the
00150 //               terrain.txt file that has been opened.
00151 ////////////////////////////////////////////////////////////////////
00152 bool STBasicTerrain::
00153 setup_terrain(istream &in, const Filename &pathname) {
00154   clear();
00155 
00156   Filename dirname = pathname.get_dirname();
00157 
00158   string keyword;
00159   in >> keyword;
00160   while (in && !in.eof()) {
00161     if (keyword == "area") {
00162       // "area" defines the size of the terrain in square kilometers.
00163       // We apply speedtree_area_scale to convert that to local units.
00164       PN_stdfloat area;
00165       in >> area;
00166       _size = csqrt(area) * speedtree_area_scale;
00167 
00168     } else if (keyword == "height_scale") {
00169       in >> _height_scale;
00170 
00171     } else if (keyword == "normalmap_b_scale") {
00172       PN_stdfloat normalmap_b_scale;
00173       in >> normalmap_b_scale;
00174 
00175     } else if (keyword == "heightmap") {
00176       read_quoted_filename(_height_map, in, dirname);
00177 
00178     } else if (keyword == "texture") {
00179       SplatLayer splat;
00180       read_quoted_filename(splat._filename, in, dirname);
00181       in >> splat._tiling;
00182       splat._color.set(1.0f, 1.0f, 1.0f, 1.0f);
00183       _splat_layers.push_back(splat);
00184 
00185     } else if (keyword == "color") {
00186       // color means the overall color of the previous texture.
00187       PN_stdfloat r, g, b;
00188       in >> r >> g >> b;
00189       if (!_splat_layers.empty()) {
00190         _splat_layers.back()._color.set(r, g, b, 1.0f);
00191       }
00192 
00193     } else if (keyword == "ambient" || keyword == "diffuse" || keyword == "specular" || keyword == "emissive") {
00194       PN_stdfloat r, g, b;
00195       in >> r >> g >> b;
00196 
00197     } else if (keyword == "shininess") {
00198       PN_stdfloat s;
00199       in >> s;
00200 
00201     } else {
00202       speedtree_cat.error()
00203         << "Invalid token " << keyword << " in " << pathname << "\n";
00204       return false;
00205     }
00206 
00207     in >> keyword;
00208   }
00209 
00210   // Consume any whitespace at the end of the file.
00211   in >> ws;
00212 
00213   if (!in.eof()) {
00214     // If we didn't read all the way to end-of-file, there was an
00215     // error.
00216     in.clear();
00217     string text;
00218     in >> text;
00219     speedtree_cat.error()
00220       << "Unexpected text in " << pathname << " at \"" << text << "\"\n";
00221     return false;
00222   }
00223 
00224   // The first two textures are the normal map and splat map,
00225   // respectively.
00226   if (!_splat_layers.empty()) {
00227     _normal_map = _splat_layers[0]._filename;
00228     _splat_layers.erase(_splat_layers.begin());
00229   }
00230   if (!_splat_layers.empty()) {
00231     _splat_map = _splat_layers[0]._filename;
00232     _splat_layers.erase(_splat_layers.begin());
00233   }
00234 
00235   // Now try to load the actual height map data.
00236   load_data();
00237 
00238   return _is_valid;
00239 }
00240 
00241 ////////////////////////////////////////////////////////////////////
00242 //     Function: STBasicTerrain::load_data
00243 //       Access: Published, Virtual
00244 //  Description: This will be called at some point after
00245 //               initialization.  It should be overridden by a derived
00246 //               class to load up the terrain data from its source and
00247 //               fill in the data members of this class appropriately,
00248 //               especially _is_valid.  After this call, if _is_valid
00249 //               is true, then get_height() etc. will be called to
00250 //               query the terrain's data.
00251 ////////////////////////////////////////////////////////////////////
00252 void STBasicTerrain::
00253 load_data() {
00254   _is_valid = false;
00255 
00256   if (!read_height_map()) {
00257     return;
00258   }
00259 
00260   _is_valid = true;
00261 }
00262 
00263 ////////////////////////////////////////////////////////////////////
00264 //     Function: STBasicTerrain::get_height
00265 //       Access: Published, Virtual
00266 //  Description: After load_data() has been called, this should return
00267 //               the computed height value at point (x, y) of the
00268 //               terrain, where x and y are unbounded and may refer to
00269 //               any 2-d point in space.
00270 ////////////////////////////////////////////////////////////////////
00271 PN_stdfloat STBasicTerrain::
00272 get_height(PN_stdfloat x, PN_stdfloat y) const {
00273   return _height_data.calc_bilinear_interpolation(x / _size, y / _size);
00274 }
00275 
00276 ////////////////////////////////////////////////////////////////////
00277 //     Function: STBasicTerrain::get_smooth_height
00278 //       Access: Published, Virtual
00279 //  Description: After load_data() has been called, this should return
00280 //               the approximate average height value over a circle of
00281 //               the specified radius, centered at point (x, y) of the
00282 //               terrain.
00283 ////////////////////////////////////////////////////////////////////
00284 PN_stdfloat STBasicTerrain::
00285 get_smooth_height(PN_stdfloat x, PN_stdfloat y, PN_stdfloat radius) const {
00286   return _height_data.calc_smooth(x / _size, y / _size, radius / _size);
00287 }
00288 
00289 ////////////////////////////////////////////////////////////////////
00290 //     Function: STBasicTerrain::get_slope
00291 //       Access: Published, Virtual
00292 //  Description: After load_data() has been called, this should return
00293 //               the directionless slope at point (x, y) of the
00294 //               terrain, where 0.0 is flat and 1.0 is vertical.  This
00295 //               is used for determining the legal points to place
00296 //               trees and grass.
00297 ////////////////////////////////////////////////////////////////////
00298 PN_stdfloat STBasicTerrain::
00299 get_slope(PN_stdfloat x, PN_stdfloat y) const {
00300   return _slope_data.calc_bilinear_interpolation(x / _size, y / _size);
00301 }
00302 
00303 ////////////////////////////////////////////////////////////////////
00304 //     Function: STBasicTerrain::fill_vertices
00305 //       Access: Published, Virtual
00306 //  Description: After load_data() has been called, this will be
00307 //               called occasionally to populate the vertices for a
00308 //               terrain cell.
00309 //
00310 //               It will be passed a GeomVertexData whose format will
00311 //               match get_vertex_format(), and already allocated with
00312 //               num_xy * num_xy rows.  This method should fill the
00313 //               rows of the data with the appropriate vertex data for
00314 //               the terrain, over the grid described by the corners
00315 //               (start_x, start_y) up to and including (start_x +
00316 //               size_x, start_y + size_xy)--a square of the terrain
00317 //               with num_xy vertices on a size, arranged in row-major
00318 //               order.
00319 ////////////////////////////////////////////////////////////////////
00320 void STBasicTerrain::
00321 fill_vertices(GeomVertexData *data,
00322               PN_stdfloat start_x, PN_stdfloat start_y,
00323               PN_stdfloat size_xy, int num_xy) const {
00324   nassertv(data->get_format() == _vertex_format);
00325   GeomVertexWriter vertex(data, InternalName::get_vertex());
00326   GeomVertexWriter texcoord(data, InternalName::get_texcoord());
00327 
00328   PN_stdfloat vertex_scale = 1.0 / (PN_stdfloat)(num_xy - 1);
00329   PN_stdfloat texcoord_scale = 1.0 / _size;
00330   for (int xi = 0; xi < num_xy; ++xi) {
00331     PN_stdfloat xt = xi * vertex_scale;
00332     PN_stdfloat x = start_x + xt * size_xy;
00333     for (int yi = 0; yi < num_xy; ++yi) {
00334       PN_stdfloat yt = yi * vertex_scale;
00335       PN_stdfloat y = start_y + yt * size_xy;
00336 
00337       PN_stdfloat z = get_height(x, y);
00338       
00339       vertex.set_data3(x, y, z);
00340       texcoord.set_data3(x * texcoord_scale, -y * texcoord_scale, 1.0f);
00341     }
00342   }
00343 }
00344 
00345 ////////////////////////////////////////////////////////////////////
00346 //     Function: STBasicTerrain::output
00347 //       Access: Published, Virtual
00348 //  Description: 
00349 ////////////////////////////////////////////////////////////////////
00350 void STBasicTerrain::
00351 output(ostream &out) const {
00352   Namable::output(out);
00353 }
00354 
00355 ////////////////////////////////////////////////////////////////////
00356 //     Function: STBasicTerrain::write
00357 //       Access: Published, Virtual
00358 //  Description: 
00359 ////////////////////////////////////////////////////////////////////
00360 void STBasicTerrain::
00361 write(ostream &out, int indent_level) const {
00362   indent(out, indent_level)
00363     << *this << "\n";
00364 }
00365 
00366 ////////////////////////////////////////////////////////////////////
00367 //     Function: STBasicTerrain::read_height_map
00368 //       Access: Protected
00369 //  Description: Reads the height map image stored in _height_map, and
00370 //               stores it in _height_data.  Returns true on success,
00371 //               false on failure.
00372 ////////////////////////////////////////////////////////////////////
00373 bool STBasicTerrain::
00374 read_height_map() {
00375   PNMImage image(_height_map);
00376   if (!image.is_valid()) {
00377     return false;
00378   }
00379 
00380   _height_data.reset(image.get_x_size(), image.get_y_size());
00381   _min_height = FLT_MAX;
00382   _max_height = FLT_MIN;
00383 
00384   PN_stdfloat scalar = _size * _height_scale / image.get_num_channels();
00385   int pi = 0;
00386   for (int yi = image.get_y_size() - 1; yi >= 0; --yi) {
00387     for (int xi = 0; xi < image.get_x_size(); ++xi) {
00388       LColord rgba = image.get_xel_a(xi, yi);
00389       PN_stdfloat v = rgba[0] + rgba[1] + rgba[2] + rgba[3];
00390       v *= scalar;
00391       _height_data._data[pi] = v;
00392       ++pi;
00393       _min_height = min(_min_height, v);
00394       _max_height = max(_max_height, v);
00395     }
00396   }
00397 
00398   compute_slope(0.5f);
00399   
00400   return true;
00401 }
00402 
00403 ////////////////////////////////////////////////////////////////////
00404 //     Function: STBasicTerrain::compute_slope
00405 //       Access: Protected
00406 //  Description: Once _height_data has been filled in, compute the
00407 //               corresponding values for _slope_data.
00408 ////////////////////////////////////////////////////////////////////
00409 void STBasicTerrain::
00410 compute_slope(PN_stdfloat smoothing) {
00411   nassertv(!_height_data._data.empty());
00412 
00413   int width = _height_data._width;
00414   int height = _height_data._height;
00415   _slope_data.reset(width, height);
00416 
00417   PN_stdfloat u_spacing = _size / (PN_stdfloat)width;
00418   PN_stdfloat v_spacing = _size / (PN_stdfloat)height;
00419 
00420   for (int i = 0; i < width; ++i) {
00421     int left = (i + width - 1) % width;
00422     int right = (i + 1) % width;
00423 
00424     for (int j = 0; j < height; ++j) {
00425       int top = (j + height - 1) % height;
00426       int bottom = (j + 1) % height;
00427 
00428       PN_stdfloat slope = 0.0f;
00429       PN_stdfloat this_height = _height_data._data[i + j * width];
00430       slope += catan2(cabs(this_height - _height_data._data[right + j * width]), u_spacing);
00431       slope += catan2(cabs(this_height - _height_data._data[left + j * width]), u_spacing);
00432       slope += catan2(cabs(this_height - _height_data._data[i + top * width]), v_spacing);
00433       slope += catan2(cabs(this_height - _height_data._data[i + bottom * width]), v_spacing);
00434 
00435       slope *= (0.5f / MathNumbers::pi_f);
00436 
00437       if (slope > 1.0f) {
00438         slope = 1.0f;
00439       }
00440       _slope_data._data[i + j * width] = slope;
00441     }
00442   }
00443 
00444   if (smoothing > 0.0f) {
00445     // Create a temporary array for smoothing data.
00446     InterpolationData<PN_stdfloat> smoothing_data;
00447     smoothing_data.reset(width, height);
00448     PN_stdfloat *smoothed = &smoothing_data._data[0];
00449 
00450     int steps = int(smoothing);
00451     PN_stdfloat last_interpolation = smoothing - steps;
00452     ++steps;
00453     for (int si = 0; si < steps; ++si) {
00454 
00455       // compute smoothed normals
00456       for (int i = 0; i < width; ++i) {
00457         int left = (i + width - 1) % width;
00458         int right = (i + 1) % width;
00459 
00460         for (int j = 0; j < height; ++j) {
00461           int top = (j + height - 1) % height;
00462           int bottom = (j + 1) % height;
00463 
00464           smoothed[i + j * width] = (_slope_data._data[right + j * width] + 
00465                                      _slope_data._data[left + j * width] + 
00466                                      _slope_data._data[i + top * width] + 
00467                                      _slope_data._data[i + bottom * width] +
00468                                      _slope_data._data[right + top * width] +
00469                                      _slope_data._data[right + bottom * width] +
00470                                      _slope_data._data[left + top * width] +
00471                                      _slope_data._data[left + bottom * width]);
00472           smoothed[i + j * width] *= 0.125f;
00473         }
00474       }
00475 
00476       // interpolate or set
00477       if (si == steps - 1) {
00478         // last step, interpolate
00479         for (int i = 0; i < width; ++i) {
00480           for (int j = 0; j < height; ++j) {
00481             _slope_data._data[i + j * width] = interpolate(_slope_data._data[i + j * width], smoothed[i + j * width], last_interpolation);
00482           }
00483         }
00484 
00485       } else {
00486         // full smoothing step, copy everything
00487         _slope_data = smoothing_data;
00488       }
00489     }
00490   }
00491 }
00492 
00493 ////////////////////////////////////////////////////////////////////
00494 //     Function: STBasicTerrain::read_quoted_filename
00495 //       Access: Private, Static
00496 //  Description: Reads a quoted filename from the input stream, which
00497 //               is understood to be relative to the indicated
00498 //               directory.
00499 ////////////////////////////////////////////////////////////////////
00500 void STBasicTerrain::
00501 read_quoted_filename(Filename &result, istream &in, const Filename &dirname) {
00502   string filename;
00503   in >> filename;
00504 
00505   // The terrain.txt file should, in theory, support spaces, but the
00506   // SpeedTree reference application doesn't, so we don't bother
00507   // either.
00508   if (filename.size() >= 2 && filename[0] == '"' && filename[filename.size() - 1] == '"') {
00509     filename = filename.substr(1, filename.size() - 2);
00510   }
00511 
00512   result = Filename::from_os_specific(filename);
00513   if (result.is_local()) {
00514     result = Filename(dirname, result);
00515   }
00516 }
00517 
 All Classes Functions Variables Enumerations