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
19using std::istream;
20using std::ostream;
21using std::string;
22
23TypeHandle STBasicTerrain::_type_handle;
24
25// VERTEX_ATTRIB_END is defined as a macro that must be evaluated within the
26// SpeedTree namespace.
27namespace 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
34namespace 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 */
48STBasicTerrain::
49STBasicTerrain() {
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 */
57STBasicTerrain::
58STBasicTerrain(const STBasicTerrain &copy) :
59 STTerrain(copy),
60 _size(copy._size),
61 _height_scale(copy._height_scale)
62{
63}
64
65/**
66 *
67 */
68STBasicTerrain::
69~STBasicTerrain() {
70}
71
72/**
73 * Resets the terrain to its initial, unloaded state.
74 */
76clear() {
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 */
100setup_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 */
140setup_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 */
234load_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 */
250get_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 */
260get_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 */
271get_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 */
314void STBasicTerrain::
315output(ostream &out) const {
316 Namable::output(out);
317}
318
319/**
320 *
321 */
322void STBasicTerrain::
323write(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 */
332bool STBasicTerrain::
333read_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 */
366void STBasicTerrain::
367compute_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 */
454void STBasicTerrain::
455read_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.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: dcindent.cxx:22
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.