Browse Source

Support for setting a heightfield handle directly on the ShaderTerrainMesh

tobspr 9 years ago
parent
commit
dac2bfc7b9

+ 11 - 9
panda/src/grutil/shaderTerrainMesh.I

@@ -12,26 +12,28 @@
  */
 
 /**
- * @brief Sets the path to the heightfield
- * @details This sets the path to the terrain heightfield. It should be 16bit
+ * @brief Sets the heightfield texture
+ * @details This sets the heightfield texture. It should be 16bit
  *   single channel, and have a power-of-two resolution greater than 32.
  *   Common sizes are 2048x2048 or 4096x4096.
  *
- * @param filename Path to the heightfield
+ *   You should call generate() after setting the heightfield.
+ *
+ * @param filename Heightfield texture
  */
-INLINE void ShaderTerrainMesh::set_heightfield_filename(const Filename& filename) {
-  _heightfield_source = filename;
+INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) {
+  _heightfield_tex = heightfield;
 }
 
 /**
- * @brief Returns the heightfield path
- * @details This returns the path of the terrain heightfield, previously set with
+ * @brief Returns the heightfield
+ * @details This returns the terrain heightfield, previously set with
  *   set_heightfield()
  *
  * @return Path to the heightfield
  */
-INLINE const Filename& ShaderTerrainMesh::get_heightfield_filename() const {
-  return _heightfield_source;
+INLINE Texture* ShaderTerrainMesh::get_heightfield() const {
+  return _heightfield_tex;
 }
 
 /**

+ 26 - 32
panda/src/grutil/shaderTerrainMesh.cxx

@@ -35,7 +35,7 @@
 #include "typeHandle.h"
 
 ConfigVariableBool stm_use_hexagonal_layout
-("stm-use-hexagonal-layout", true,
+("stm-use-hexagonal-layout", false,
  PRC_DESC("Set this to true to use a hexagonal vertex layout. This approximates "
           "the heightfield in a better way, however the CLOD transitions might be "
           "visible due to the vertices not matching exactly."));
@@ -81,7 +81,6 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
   PandaNode("ShaderTerrainMesh"),
   _size(0),
   _chunk_size(32),
-  _heightfield_source(""),
   _generate_patches(false),
   _data_texture(NULL),
   _chunk_geom(NULL),
@@ -107,7 +106,7 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
  * @return true if the terrain was initialized, false if an error occured
  */
 bool ShaderTerrainMesh::generate() {
-  if (!do_load_heightfield())
+  if (!do_check_heightfield())
     return false;
 
   if (_chunk_size < 8 || !check_power_of_two(_chunk_size)) {
@@ -120,28 +119,29 @@ bool ShaderTerrainMesh::generate() {
     return false;
   }
 
+  do_extract_heightfield();
   do_create_chunks();
   do_compute_bounds(&_base_chunk);
   do_create_chunk_geom();
   do_init_data_texture();
-  do_convert_heightfield();
+
+  // Clear image after using it, otherwise we have two copies of the heightfield
+  // in memory.
+  _heightfield.clear();
 
   return true;
 }
 
 /**
- * @brief Converts the internal used PNMImage to a Texture
- * @details This converts the internal used PNMImage to a texture object. The
- *   reason for this is, that we need the PNMimage for computing the chunk
- *   bounds, but don't need it afterwards. However, since we have it in ram,
- *   we can just put its contents into a Texture object, which enables the
- *   user to call get_heightfield() instead of manually loading the texture
- *   from disk again to set it as shader input (Panda does not cache PNMImages)
+ * @brief Converts the internal used Texture to a PNMImage
+ * @details This converts the texture passed with set_heightfield to a PNMImage,
+ *   so we can read the pixels in a fast way. This is only used while generating
+ *   the chunks, and the PNMImage is destroyed afterwards.
  */
-void ShaderTerrainMesh::do_convert_heightfield() {
-  _heightfield_tex = new Texture();
-  _heightfield_tex->load(_heightfield);
-  _heightfield_tex->set_keep_ram_image(true);
+void ShaderTerrainMesh::do_extract_heightfield() {
+  nassertv(_heightfield_tex->has_ram_image()); // Heightfield not in RAM, extract ram image first
+
+  _heightfield_tex->store(_heightfield);
 
   if (_heightfield.get_maxval() != 65535) {
     shader_terrain_cat.warning() << "Using non 16-bit heightfield!" << endl;
@@ -150,31 +150,23 @@ void ShaderTerrainMesh::do_convert_heightfield() {
   }
   _heightfield_tex->set_minfilter(SamplerState::FT_linear);
   _heightfield_tex->set_magfilter(SamplerState::FT_linear);
-  _heightfield.clear();
 }
 
 /**
- * @brief Intermal method to load the heightfield
- * @details This method loads the heightfield from the heightfield path,
+ * @brief Intermal method to check the heightfield
+ * @details This method cecks the heightfield generated from the heightfield texture,
  *   and performs some basic checks, including a check for a power of two,
  *   and same width and height.
  *
- * @return true if the heightfield was loaded and meets the requirements
+ * @return true if the heightfield meets the requirements
  */
-bool ShaderTerrainMesh::do_load_heightfield() {
-
-  if(!_heightfield.read(_heightfield_source)) {
-    shader_terrain_cat.error() << "Could not load heightfield from " << _heightfield_source << endl;
-    return false;
-  }
-
-  if (_heightfield.get_x_size() != _heightfield.get_y_size()) {
+bool ShaderTerrainMesh::do_check_heightfield() {
+  if (_heightfield_tex->get_x_size() != _heightfield_tex->get_y_size()) {
     shader_terrain_cat.error() << "Only square heightfields are supported!";
     return false;
   }
 
-  _size = _heightfield.get_x_size();
-
+  _size = _heightfield_tex->get_x_size();
   if (_size < 32 || !check_power_of_two(_size)) {
     shader_terrain_cat.error() << "Invalid heightfield! Needs to be >= 32 and a power of two (was: "
          << _size << ")!" << endl;
@@ -687,8 +679,8 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
   data_entry.size = chunk->size / _chunk_size;
   data_entry.clod = chunk->last_clod;
 
-  data->emitted_chunks ++;
-  data->storage_ptr ++;
+  data->emitted_chunks++;
+  data->storage_ptr++;
 }
 
 /**
@@ -701,7 +693,9 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
  * @return World-Space point
  */
 LPoint3 ShaderTerrainMesh::uv_to_world(const LTexCoord& coord) const {
-  nassertr(_heightfield_tex != NULL, LPoint3(0));
+  nassertr(_heightfield_tex != NULL, LPoint3(0)); // Heightfield not set yet
+  nassertr(_heightfield_tex->has_ram_image(), LPoint3(0)); // Heightfield not in memory
+
   PT(TexturePeeker) peeker = _heightfield_tex->peek();
   nassertr(peeker != NULL, LPoint3(0));
 

+ 5 - 6
panda/src/grutil/shaderTerrainMesh.h

@@ -55,9 +55,9 @@ PUBLISHED:
 
   ShaderTerrainMesh();
 
-  INLINE void set_heightfield_filename(const Filename& filename);
-  INLINE const Filename& get_heightfield_filename() const;
-  MAKE_PROPERTY(heightfield_filename, get_heightfield_filename, set_heightfield_filename);
+  INLINE void set_heightfield(Texture* heightfield);
+  INLINE Texture* get_heightfield() const;
+  MAKE_PROPERTY(heightfield, get_heightfield, set_heightfield);
 
   INLINE void set_chunk_size(size_t chunk_size);
   INLINE size_t get_chunk_size() const;
@@ -152,8 +152,8 @@ private:
     ChunkDataEntry* storage_ptr;
   };
 
-  bool do_load_heightfield();
-  void do_convert_heightfield();
+  bool do_check_heightfield();
+  void do_extract_heightfield();
   void do_init_data_texture();
   void do_create_chunks();
   void do_init_chunk(Chunk* chunk);
@@ -164,7 +164,6 @@ private:
   bool do_check_lod_matches(Chunk* chunk, TraversalData* data);
 
   Chunk _base_chunk;
-  Filename _heightfield_source;
   size_t _size;
   size_t _chunk_size;
   bool _generate_patches;

+ 13 - 11
samples/shader-terrain/main.py

@@ -2,16 +2,15 @@
 
 # Author: tobspr
 #
-# Last Updated: 2016-02-13
+# Last Updated: 2016-04-30
 #
 # This tutorial provides an example of using the ShaderTerrainMesh class
 
-import os, sys, math, random
-
 from direct.showbase.ShowBase import ShowBase
 from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data
 from panda3d.core import SamplerState
 
+
 class ShaderTerrainDemo(ShowBase):
     def __init__(self):
 
@@ -19,14 +18,14 @@ class ShaderTerrainDemo(ShowBase):
         # before the ShowBase is initialized
         load_prc_file_data("", """
             textures-power-2 none
-            window-title Panda3D Shader Terrain Demo
             gl-coordinate-system default
+            window-title Panda3D ShaderTerrainMesh Demo
         """)
 
         # Initialize the showbase
         ShowBase.__init__(self)
 
-        # Increase camera FOV aswell as the far plane
+        # Increase camera FOV as well as the far plane
         self.camLens.set_fov(90)
         self.camLens.set_near_far(0.1, 50000)
 
@@ -35,7 +34,7 @@ class ShaderTerrainDemo(ShowBase):
 
         # Set a heightfield, the heightfield should be a 16-bit png and
         # have a quadratic size of a power of two.
-        self.terrain_node.heightfield_filename = "heightfield.png"
+        self.terrain_node.heightfield = self.loader.loadTexture("heightfield.png")
 
         # Set the target triangle width. For a value of 10.0 for example,
         # the terrain will attempt to make every triangle 10 pixels wide on screen.
@@ -44,24 +43,28 @@ class ShaderTerrainDemo(ShowBase):
         # Generate the terrain
         self.terrain_node.generate()
 
-        # Attach the terrain to the main scene and set its scale
+        # Attach the terrain to the main scene and set its scale. With no scale
+        # set, the terrain ranges from (0, 0, 0) to (1, 1, 1)
         self.terrain = self.render.attach_new_node(self.terrain_node)
         self.terrain.set_scale(1024, 1024, 100)
         self.terrain.set_pos(-512, -512, -70.0)
 
         # Set a shader on the terrain. The ShaderTerrainMesh only works with
-        # an applied shader. You can use the shaders used here in your own shaders
+        # an applied shader. You can use the shaders used here in your own application
         terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain.frag.glsl")
         self.terrain.set_shader(terrain_shader)
         self.terrain.set_shader_input("camera", self.camera)
 
+        # Shortcut to view the wireframe mesh
+        self.accept("f3", self.toggleWireframe)
+
         # Set some texture on the terrain
         grass_tex = self.loader.loadTexture("textures/grass.png")
         grass_tex.set_minfilter(SamplerState.FT_linear_mipmap_linear)
         grass_tex.set_anisotropic_degree(16)
         self.terrain.set_texture(grass_tex)
 
-        # Load some skybox - you can safely ignore this code
+        # Load a skybox - you can safely ignore this code
         skybox = self.loader.loadModel("models/skybox.bam")
         skybox.reparent_to(self.render)
         skybox.set_scale(20000)
@@ -77,5 +80,4 @@ class ShaderTerrainDemo(ShowBase):
         skybox_shader = Shader.load(Shader.SL_GLSL, "skybox.vert.glsl", "skybox.frag.glsl")
         skybox.set_shader(skybox_shader)
 
-demo = ShaderTerrainDemo()
-demo.run()
+ShaderTerrainDemo().run()