stBasicTerrain.cxx 14 KB

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