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