Panda3D
Loading...
Searching...
No Matches
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:44
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 in the virtual file system hierarchy.
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 as a directory in the virtual 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,...
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.