Panda3D
|
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 ©) : 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