Panda3D
stBasicTerrain.cxx
1 // Filename: stBasicTerrain.cxx
2 // Created by: drose (12Oct10)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "stBasicTerrain.h"
16 #include "geomVertexWriter.h"
17 #include "pnmImage.h"
18 #include "indent.h"
19 
20 TypeHandle STBasicTerrain::_type_handle;
21 
22 // VERTEX_ATTRIB_END is defined as a macro that must be evaluated
23 // within the SpeedTree namespace.
24 namespace SpeedTree {
25  static const SVertexAttribDesc st_attrib_end = VERTEX_ATTRIB_END();
26 }
27 
28 /* Hmm, maybe we want to use this lower-level structure directly
29  instead of the GeomVertexWriter.
30 
31 namespace SpeedTree {
32  static const SVertexAttribDesc std_vertex_format[] = {
33  { VERTEX_ATTRIB_SEMANTIC_POS, VERTEX_ATTRIB_TYPE_FLOAT, 3 },
34  { VERTEX_ATTRIB_SEMANTIC_TEXCOORD0, VERTEX_ATTRIB_TYPE_FLOAT, 3 },
35  VERTEX_ATTRIB_END( )
36  };
37  static const int std_vertex_format_length =
38  sizeof(std_vertex_format) / sizeof(std_vertex_format[0]);
39 };
40 */
41 
42 ////////////////////////////////////////////////////////////////////
43 // Function: STBasicTerrain::Constructor
44 // Access: Published
45 // Description:
46 ////////////////////////////////////////////////////////////////////
47 STBasicTerrain::
48 STBasicTerrain() {
49  clear();
50 }
51 
52 ////////////////////////////////////////////////////////////////////
53 // Function: STBasicTerrain::Copy Constructor
54 // Access: Published
55 // Description: Not sure whether any derived classes will implement
56 // the copy constructor, but it's defined here at the
57 // base level just in case.
58 ////////////////////////////////////////////////////////////////////
59 STBasicTerrain::
60 STBasicTerrain(const STBasicTerrain &copy) :
61  STTerrain(copy),
62  _size(copy._size),
63  _height_scale(copy._height_scale)
64 {
65 }
66 
67 ////////////////////////////////////////////////////////////////////
68 // Function: STBasicTerrain::Destructor
69 // Access: Published, Virtual
70 // Description:
71 ////////////////////////////////////////////////////////////////////
72 STBasicTerrain::
73 ~STBasicTerrain() {
74 }
75 
76 ////////////////////////////////////////////////////////////////////
77 // Function: STBasicTerrain::clear
78 // Access: Published, Virtual
79 // Description: Resets the terrain to its initial, unloaded state.
80 ////////////////////////////////////////////////////////////////////
82 clear() {
84 
85  _height_map = "";
86  _size = 1.0f;
87  _height_scale = 1.0f;
88 
89  CPT(GeomVertexFormat) format = GeomVertexFormat::register_format
90  (new GeomVertexArrayFormat(InternalName::get_vertex(), 3,
91  GeomEnums::NT_stdfloat, GeomEnums::C_point,
92  InternalName::get_texcoord(), 3,
93  GeomEnums::NT_stdfloat, GeomEnums::C_texcoord));
94  set_vertex_format(format);
95 }
96 
97 ////////////////////////////////////////////////////////////////////
98 // Function: STBasicTerrain::setup_terrain
99 // Access: Published
100 // Description: Sets up the terrain by reading a terrain.txt file as
101 // defined by SpeedTree. This file names the various
102 // map files that define the terrain, as well as
103 // defining parameters size as its size and color.
104 //
105 // If a relative filename is supplied, the model-path is
106 // searched. If a directory is named, "terrain.txt" is
107 // implicitly appended.
108 ////////////////////////////////////////////////////////////////////
109 bool STBasicTerrain::
110 setup_terrain(const Filename &terrain_filename) {
111  _is_valid = false;
112  set_name(terrain_filename.get_basename());
113 
115 
116  Filename fullpath = Filename::text_filename(terrain_filename);
117  vfs->resolve_filename(fullpath, get_model_path());
118 
119  if (!vfs->exists(fullpath)) {
120  speedtree_cat.warning()
121  << "Couldn't find " << terrain_filename << "\n";
122  return false;
123  }
124 
125  if (vfs->is_directory(fullpath)) {
126  fullpath = Filename(fullpath, "terrain.txt");
127  }
128 
129  istream *in = vfs->open_read_file(fullpath, true);
130  if (in == NULL) {
131  speedtree_cat.warning()
132  << "Couldn't open " << terrain_filename << "\n";
133  return false;
134  }
135 
136  bool success = setup_terrain(*in, fullpath);
137  vfs->close_read_file(in);
138 
139  return success;
140 }
141 
142 ////////////////////////////////////////////////////////////////////
143 // Function: STBasicTerrain::setup_terrain
144 // Access: Published
145 // Description: Sets up the terrain by reading a terrain.txt file as
146 // defined by SpeedTree. This variant on this method
147 // accepts an istream for an already-opened terrain.txt
148 // file. The filename is provided for reference, to
149 // assist relative file operations. It should name the
150 // terrain.txt file that has been opened.
151 ////////////////////////////////////////////////////////////////////
152 bool STBasicTerrain::
153 setup_terrain(istream &in, const Filename &pathname) {
154  clear();
155 
156  Filename dirname = pathname.get_dirname();
157 
158  string keyword;
159  in >> keyword;
160  while (in && !in.eof()) {
161  if (keyword == "area") {
162  // "area" defines the size of the terrain in square kilometers.
163  // We apply speedtree_area_scale to convert that to local units.
164  PN_stdfloat area;
165  in >> area;
166  _size = csqrt(area) * speedtree_area_scale;
167 
168  } else if (keyword == "height_scale") {
169  in >> _height_scale;
170 
171  } else if (keyword == "normalmap_b_scale") {
172  PN_stdfloat normalmap_b_scale;
173  in >> normalmap_b_scale;
174 
175  } else if (keyword == "heightmap") {
176  read_quoted_filename(_height_map, in, dirname);
177 
178  } else if (keyword == "texture") {
179  SplatLayer splat;
180  read_quoted_filename(splat._filename, in, dirname);
181  in >> splat._tiling;
182  splat._color.set(1.0f, 1.0f, 1.0f, 1.0f);
183  _splat_layers.push_back(splat);
184 
185  } else if (keyword == "color") {
186  // color means the overall color of the previous texture.
187  PN_stdfloat r, g, b;
188  in >> r >> g >> b;
189  if (!_splat_layers.empty()) {
190  _splat_layers.back()._color.set(r, g, b, 1.0f);
191  }
192 
193  } else if (keyword == "ambient" || keyword == "diffuse" || keyword == "specular" || keyword == "emissive") {
194  PN_stdfloat r, g, b;
195  in >> r >> g >> b;
196 
197  } else if (keyword == "shininess") {
198  PN_stdfloat s;
199  in >> s;
200 
201  } else {
202  speedtree_cat.error()
203  << "Invalid token " << keyword << " in " << pathname << "\n";
204  return false;
205  }
206 
207  in >> keyword;
208  }
209 
210  // Consume any whitespace at the end of the file.
211  in >> ws;
212 
213  if (!in.eof()) {
214  // If we didn't read all the way to end-of-file, there was an
215  // error.
216  in.clear();
217  string text;
218  in >> text;
219  speedtree_cat.error()
220  << "Unexpected text in " << pathname << " at \"" << text << "\"\n";
221  return false;
222  }
223 
224  // The first two textures are the normal map and splat map,
225  // respectively.
226  if (!_splat_layers.empty()) {
227  _normal_map = _splat_layers[0]._filename;
228  _splat_layers.erase(_splat_layers.begin());
229  }
230  if (!_splat_layers.empty()) {
231  _splat_map = _splat_layers[0]._filename;
232  _splat_layers.erase(_splat_layers.begin());
233  }
234 
235  // Now try to load the actual height map data.
236  load_data();
237 
238  return _is_valid;
239 }
240 
241 ////////////////////////////////////////////////////////////////////
242 // Function: STBasicTerrain::load_data
243 // Access: Published, Virtual
244 // Description: This will be called at some point after
245 // initialization. It should be overridden by a derived
246 // class to load up the terrain data from its source and
247 // fill in the data members of this class appropriately,
248 // especially _is_valid. After this call, if _is_valid
249 // is true, then get_height() etc. will be called to
250 // query the terrain's data.
251 ////////////////////////////////////////////////////////////////////
252 void STBasicTerrain::
254  _is_valid = false;
255 
256  if (!read_height_map()) {
257  return;
258  }
259 
260  _is_valid = true;
261 }
262 
263 ////////////////////////////////////////////////////////////////////
264 // Function: STBasicTerrain::get_height
265 // Access: Published, Virtual
266 // Description: After load_data() has been called, this should return
267 // the computed height value at point (x, y) of the
268 // terrain, where x and y are unbounded and may refer to
269 // any 2-d point in space.
270 ////////////////////////////////////////////////////////////////////
271 PN_stdfloat STBasicTerrain::
272 get_height(PN_stdfloat x, PN_stdfloat y) const {
273  return _height_data.calc_bilinear_interpolation(x / _size, y / _size);
274 }
275 
276 ////////////////////////////////////////////////////////////////////
277 // Function: STBasicTerrain::get_smooth_height
278 // Access: Published, Virtual
279 // Description: After load_data() has been called, this should return
280 // the approximate average height value over a circle of
281 // the specified radius, centered at point (x, y) of the
282 // terrain.
283 ////////////////////////////////////////////////////////////////////
284 PN_stdfloat STBasicTerrain::
285 get_smooth_height(PN_stdfloat x, PN_stdfloat y, PN_stdfloat radius) const {
286  return _height_data.calc_smooth(x / _size, y / _size, radius / _size);
287 }
288 
289 ////////////////////////////////////////////////////////////////////
290 // Function: STBasicTerrain::get_slope
291 // Access: Published, Virtual
292 // Description: After load_data() has been called, this should return
293 // the directionless slope at point (x, y) of the
294 // terrain, where 0.0 is flat and 1.0 is vertical. This
295 // is used for determining the legal points to place
296 // trees and grass.
297 ////////////////////////////////////////////////////////////////////
298 PN_stdfloat STBasicTerrain::
299 get_slope(PN_stdfloat x, PN_stdfloat y) const {
300  return _slope_data.calc_bilinear_interpolation(x / _size, y / _size);
301 }
302 
303 ////////////////////////////////////////////////////////////////////
304 // Function: STBasicTerrain::fill_vertices
305 // Access: Published, Virtual
306 // Description: After load_data() has been called, this will be
307 // called occasionally to populate the vertices for a
308 // terrain cell.
309 //
310 // It will be passed a GeomVertexData whose format will
311 // match get_vertex_format(), and already allocated with
312 // num_xy * num_xy rows. This method should fill the
313 // rows of the data with the appropriate vertex data for
314 // the terrain, over the grid described by the corners
315 // (start_x, start_y) up to and including (start_x +
316 // size_x, start_y + size_xy)--a square of the terrain
317 // with num_xy vertices on a size, arranged in row-major
318 // order.
319 ////////////////////////////////////////////////////////////////////
320 void STBasicTerrain::
322  PN_stdfloat start_x, PN_stdfloat start_y,
323  PN_stdfloat size_xy, int num_xy) const {
324  nassertv(data->get_format() == _vertex_format);
325  GeomVertexWriter vertex(data, InternalName::get_vertex());
326  GeomVertexWriter texcoord(data, InternalName::get_texcoord());
327 
328  PN_stdfloat vertex_scale = 1.0 / (PN_stdfloat)(num_xy - 1);
329  PN_stdfloat texcoord_scale = 1.0 / _size;
330  for (int xi = 0; xi < num_xy; ++xi) {
331  PN_stdfloat xt = xi * vertex_scale;
332  PN_stdfloat x = start_x + xt * size_xy;
333  for (int yi = 0; yi < num_xy; ++yi) {
334  PN_stdfloat yt = yi * vertex_scale;
335  PN_stdfloat y = start_y + yt * size_xy;
336 
337  PN_stdfloat z = get_height(x, y);
338 
339  vertex.set_data3(x, y, z);
340  texcoord.set_data3(x * texcoord_scale, -y * texcoord_scale, 1.0f);
341  }
342  }
343 }
344 
345 ////////////////////////////////////////////////////////////////////
346 // Function: STBasicTerrain::output
347 // Access: Published, Virtual
348 // Description:
349 ////////////////////////////////////////////////////////////////////
350 void STBasicTerrain::
351 output(ostream &out) const {
352  Namable::output(out);
353 }
354 
355 ////////////////////////////////////////////////////////////////////
356 // Function: STBasicTerrain::write
357 // Access: Published, Virtual
358 // Description:
359 ////////////////////////////////////////////////////////////////////
360 void STBasicTerrain::
361 write(ostream &out, int indent_level) const {
362  indent(out, indent_level)
363  << *this << "\n";
364 }
365 
366 ////////////////////////////////////////////////////////////////////
367 // Function: STBasicTerrain::read_height_map
368 // Access: Protected
369 // Description: Reads the height map image stored in _height_map, and
370 // stores it in _height_data. Returns true on success,
371 // false on failure.
372 ////////////////////////////////////////////////////////////////////
373 bool STBasicTerrain::
374 read_height_map() {
375  PNMImage image(_height_map);
376  if (!image.is_valid()) {
377  return false;
378  }
379 
380  _height_data.reset(image.get_x_size(), image.get_y_size());
381  _min_height = FLT_MAX;
382  _max_height = FLT_MIN;
383 
384  PN_stdfloat scalar = _size * _height_scale / image.get_num_channels();
385  int pi = 0;
386  for (int yi = image.get_y_size() - 1; yi >= 0; --yi) {
387  for (int xi = 0; xi < image.get_x_size(); ++xi) {
388  LColord rgba = image.get_xel_a(xi, yi);
389  PN_stdfloat v = rgba[0] + rgba[1] + rgba[2] + rgba[3];
390  v *= scalar;
391  _height_data._data[pi] = v;
392  ++pi;
393  _min_height = min(_min_height, v);
394  _max_height = max(_max_height, v);
395  }
396  }
397 
398  compute_slope(0.5f);
399 
400  return true;
401 }
402 
403 ////////////////////////////////////////////////////////////////////
404 // Function: STBasicTerrain::compute_slope
405 // Access: Protected
406 // Description: Once _height_data has been filled in, compute the
407 // corresponding values for _slope_data.
408 ////////////////////////////////////////////////////////////////////
409 void STBasicTerrain::
410 compute_slope(PN_stdfloat smoothing) {
411  nassertv(!_height_data._data.empty());
412 
413  int width = _height_data._width;
414  int height = _height_data._height;
415  _slope_data.reset(width, height);
416 
417  PN_stdfloat u_spacing = _size / (PN_stdfloat)width;
418  PN_stdfloat v_spacing = _size / (PN_stdfloat)height;
419 
420  for (int i = 0; i < width; ++i) {
421  int left = (i + width - 1) % width;
422  int right = (i + 1) % width;
423 
424  for (int j = 0; j < height; ++j) {
425  int top = (j + height - 1) % height;
426  int bottom = (j + 1) % height;
427 
428  PN_stdfloat slope = 0.0f;
429  PN_stdfloat this_height = _height_data._data[i + j * width];
430  slope += catan2(cabs(this_height - _height_data._data[right + j * width]), u_spacing);
431  slope += catan2(cabs(this_height - _height_data._data[left + j * width]), u_spacing);
432  slope += catan2(cabs(this_height - _height_data._data[i + top * width]), v_spacing);
433  slope += catan2(cabs(this_height - _height_data._data[i + bottom * width]), v_spacing);
434 
435  slope *= (0.5f / MathNumbers::pi_f);
436 
437  if (slope > 1.0f) {
438  slope = 1.0f;
439  }
440  _slope_data._data[i + j * width] = slope;
441  }
442  }
443 
444  if (smoothing > 0.0f) {
445  // Create a temporary array for smoothing data.
446  InterpolationData<PN_stdfloat> smoothing_data;
447  smoothing_data.reset(width, height);
448  PN_stdfloat *smoothed = &smoothing_data._data[0];
449 
450  int steps = int(smoothing);
451  PN_stdfloat last_interpolation = smoothing - steps;
452  ++steps;
453  for (int si = 0; si < steps; ++si) {
454 
455  // compute smoothed normals
456  for (int i = 0; i < width; ++i) {
457  int left = (i + width - 1) % width;
458  int right = (i + 1) % width;
459 
460  for (int j = 0; j < height; ++j) {
461  int top = (j + height - 1) % height;
462  int bottom = (j + 1) % height;
463 
464  smoothed[i + j * width] = (_slope_data._data[right + j * width] +
465  _slope_data._data[left + j * width] +
466  _slope_data._data[i + top * width] +
467  _slope_data._data[i + bottom * width] +
468  _slope_data._data[right + top * width] +
469  _slope_data._data[right + bottom * width] +
470  _slope_data._data[left + top * width] +
471  _slope_data._data[left + bottom * width]);
472  smoothed[i + j * width] *= 0.125f;
473  }
474  }
475 
476  // interpolate or set
477  if (si == steps - 1) {
478  // last step, interpolate
479  for (int i = 0; i < width; ++i) {
480  for (int j = 0; j < height; ++j) {
481  _slope_data._data[i + j * width] = interpolate(_slope_data._data[i + j * width], smoothed[i + j * width], last_interpolation);
482  }
483  }
484 
485  } else {
486  // full smoothing step, copy everything
487  _slope_data = smoothing_data;
488  }
489  }
490  }
491 }
492 
493 ////////////////////////////////////////////////////////////////////
494 // Function: STBasicTerrain::read_quoted_filename
495 // Access: Private, Static
496 // Description: Reads a quoted filename from the input stream, which
497 // is understood to be relative to the indicated
498 // directory.
499 ////////////////////////////////////////////////////////////////////
500 void STBasicTerrain::
501 read_quoted_filename(Filename &result, istream &in, const Filename &dirname) {
502  string filename;
503  in >> filename;
504 
505  // The terrain.txt file should, in theory, support spaces, but the
506  // SpeedTree reference application doesn't, so we don't bother
507  // either.
508  if (filename.size() >= 2 && filename[0] == '"' && filename[filename.size() - 1] == '"') {
509  filename = filename.substr(1, filename.size() - 2);
510  }
511 
512  result = Filename::from_os_specific(filename);
513  if (result.is_local()) {
514  result = Filename(dirname, result);
515  }
516 }
517 
string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:424
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A specific implementation of STTerrain that supports basic heightmaps loaded from an image file...
void clear()
Resets the terrain to its initial, unloaded state.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:68
void output(ostream &out) const
Outputs the Namable.
Definition: namable.I:97
void set_data3(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Sets the write row to a particular 3-component value, and advances the write row. ...
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
virtual PN_stdfloat get_height(PN_stdfloat x, PN_stdfloat y) const
After load_data() has been called, this should return the computed height value at point (x...
This is the base class for all three-component vectors and points.
Definition: lvecBase4.h:1677
This is the abstract base class that defines the interface needed to describe a terrain for rendering...
Definition: stTerrain.h:39
int get_y_size() const
Returns the number of pixels in the Y direction.
int get_x_size() const
Returns the number of pixels in the X direction.
virtual void fill_vertices(GeomVertexData *data, PN_stdfloat start_x, PN_stdfloat start_y, PN_stdfloat size_xy, int num_xy) const
After load_data() has been called, this will be called occasionally to populate the vertices for a te...
virtual void load_data()
This will be called at some point after initialization.
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width...
Definition: pnmImage.I:309
bool setup_terrain(const Filename &terrain_filename)
Sets up the terrain by reading a terrain.txt file as defined by SpeedTree.
virtual PN_stdfloat get_slope(PN_stdfloat x, PN_stdfloat y) const
After load_data() has been called, this should return the directionless slope at point (x...
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:720
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
const GeomVertexFormat * get_format() const
Returns a pointer to the GeomVertexFormat structure that defines this data.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
bool is_local() const
Returns true if the filename is local, e.g.
Definition: filename.I:664
virtual void clear()
Resets the terrain to its initial, unloaded state.
Definition: stTerrain.cxx:65
int get_num_channels() const
Returns the number of channels in the image.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
virtual PN_stdfloat get_smooth_height(PN_stdfloat x, PN_stdfloat y, PN_stdfloat radius) const
After load_data() has been called, this should return the approximate average height value over a cir...
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332