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  */
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  */
234 load_data() {
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  */
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 }
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
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
bool is_local() const
Returns true if the filename is local, e.g.
Definition: filename.I:549
std::string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:358
This describes the structure of a single array within a Geom data.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
This class defines the physical layout of the vertex data stored within a Geom.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
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.
void output(std::ostream &out) const
Outputs the Namable.
Definition: namable.I:61
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
A specific implementation of STTerrain that supports basic heightmaps loaded from an image file,...
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...
bool setup_terrain(const Filename &terrain_filename)
Sets up the terrain by reading a terrain.txt file as defined by SpeedTree.
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 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,...
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,...
void clear()
Resets the terrain to its initial, unloaded state.
virtual void load_data()
This will be called at some point after initialization.
This is the abstract base class that defines the interface needed to describe a terrain for rendering...
Definition: stTerrain.h:34
virtual void clear()
Resets the terrain to its initial, unloaded state.
Definition: stTerrain.cxx:56
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
A hierarchy of directories and files that appears to be one continuous file system,...
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists.
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
bool is_directory(const Filename &filename) const
Convenience function; returns true if the named file exists and is a directory.
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,...
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.