David Rose 15 年之前
父節點
當前提交
d93aac8ad8

+ 12 - 6
panda/src/speedtree/Sources.pp

@@ -18,9 +18,11 @@
     loaderFileTypeSrt.h \
     loaderFileTypeStf.h \
     speedtree_api.h \
-    speedTreeNode.h \
-    stTransform.h \
-    stTree.h
+    speedTreeNode.h speedTreeNode.I \
+    stBasicTerrain.h stBasicTerrain.I \
+    stTerrain.h stTerrain.I \
+    stTransform.h stTransform.I \
+    stTree.h stTree.I
 
   // A generated file
   #define SOURCES $[SOURCES] speedtree_parameters.h 
@@ -31,15 +33,19 @@
     loaderFileTypeStf.cxx \
     speedtree_api.cxx \
     speedTreeNode.cxx \
+    stBasicTerrain.cxx \
+    stTerrain.cxx \
     stTransform.cxx \
     stTree.cxx
 
   #define INSTALL_HEADERS \
     speedtree_parameters.h \
     speedtree_api.h \
-    speedTreeNode.h \
-    stTransform.h \
-    stTree.h
+    speedTreeNode.h speedTreeNode.I \
+    stBasicTerrain.h stBasicTerrain.I \
+    stTerrain.h stTerrain.I \
+    stTransform.h stTransform.I \
+    stTree.h stTree.I
 
   #define IGATESCAN all
 

+ 24 - 0
panda/src/speedtree/config_speedtree.cxx

@@ -14,6 +14,8 @@
 
 #include "config_speedtree.h"
 #include "speedTreeNode.h"
+#include "stBasicTerrain.h"
+#include "stTerrain.h"
 #include "stTree.h"
 #include "loaderFileTypeSrt.h"
 #include "loaderFileTypeStf.h"
@@ -147,6 +149,26 @@ ConfigVariableDouble speedtree_sun_fog_bloom
 ("speedtree-sun-fog-bloom", 0.0,
  PRC_DESC("Undocumented SpeedTree parameter."));
 
+ConfigVariableDouble speedtree_ambient_color
+("speedtree-ambient-color", "1 1 1",
+ PRC_DESC("Specifies the r g b color of the ambient light on SpeedTree "
+	  "surfaces."));
+
+ConfigVariableDouble speedtree_diffuse_color
+("speedtree-diffuse-color", "1 1 1",
+ PRC_DESC("Specifies the r g b color of the diffuse light on SpeedTree "
+	  "surfaces."));
+
+ConfigVariableDouble speedtree_specular_color
+("speedtree-specular-color", "1 1 1",
+ PRC_DESC("Specifies the r g b color of the specular reflections on SpeedTree "
+	  "surfaces."));
+
+ConfigVariableDouble speedtree_emissive_color
+("speedtree-emissive-color", "0 0 0",
+ PRC_DESC("Specifies the r g b color of the emissive light effect on SpeedTree "
+	  "surfaces."));
+
 ConfigVariableInt speedtree_num_shadow_maps
 ("speedtree-num-shadow-maps", 3,
  PRC_DESC("Specifies the number of shadow maps to use to render SpeedTree "
@@ -216,6 +238,8 @@ init_libspeedtree() {
   initialized = true;
 
   SpeedTreeNode::init_type();
+  STBasicTerrain::init_type();
+  STTerrain::init_type();
   STTree::init_type();
   LoaderFileTypeSrt::init_type();
   LoaderFileTypeStf::init_type();

+ 4 - 0
panda/src/speedtree/config_speedtree.h

@@ -52,6 +52,10 @@ extern ConfigVariableDouble speedtree_sun_color;
 extern ConfigVariableDouble speedtree_sun_size;
 extern ConfigVariableDouble speedtree_sun_spread_exponent;
 extern ConfigVariableDouble speedtree_sun_fog_bloom;
+extern ConfigVariableDouble speedtree_ambient_color;
+extern ConfigVariableDouble speedtree_diffuse_color;
+extern ConfigVariableDouble speedtree_specular_color;
+extern ConfigVariableDouble speedtree_emissive_color;
 extern ConfigVariableInt speedtree_num_shadow_maps;
 extern ConfigVariableInt speedtree_shadow_map_resolution;
 extern ConfigVariableBool speedtree_smooth_shadows;

+ 2 - 0
panda/src/speedtree/pandaspeedtree_composite1.cxx

@@ -3,5 +3,7 @@
 #include "loaderFileTypeStf.cxx"
 #include "speedTreeNode.cxx"
 #include "speedtree_api.cxx"
+#include "stBasicTerrain.cxx"
+#include "stTerrain.cxx"
 #include "stTransform.cxx"
 #include "stTree.cxx"

+ 44 - 0
panda/src/speedtree/speedTreeNode.I

@@ -79,6 +79,38 @@ modify_tree(int n) {
   return (STTree *)instance_list->get_tree();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::clear_terrain
+//       Access: Published
+//  Description: Removes the terrain associated with the node.
+////////////////////////////////////////////////////////////////////
+INLINE void SpeedTreeNode::
+clear_terrain() {
+  set_terrain(NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::has_terrain
+//       Access: Published
+//  Description: Returns true if a valid terrain has been associated
+//               with the node, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool SpeedTreeNode::
+has_terrain() const {
+  return _terrain != (STTerrain *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::get_terrain
+//       Access: Published
+//  Description: Returns the terrain associated with the node, or NULL
+//               if there is no terrain.
+////////////////////////////////////////////////////////////////////
+INLINE STTerrain *SpeedTreeNode::
+get_terrain() const {
+  return _terrain;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SpeedTreeNode::InstanceList::Constructor
 //       Access: Public
@@ -130,6 +162,18 @@ get_instance(int n) const {
   return _instances[n];
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::set_instance
+//       Access: Published
+//  Description: Replaces the transform of the nth instance of this
+//               tree.
+////////////////////////////////////////////////////////////////////
+INLINE void SpeedTreeNode::InstanceList::
+set_instance(int n, const STTransform &transform) {
+  nassertv(n >= 0 && n < (int)_instances.size());
+  _instances[n] = transform;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SpeedTreeNode::InstanceList::add_instance
 //       Access: Published

+ 286 - 53
panda/src/speedtree/speedTreeNode.cxx

@@ -14,6 +14,7 @@
 
 #include "pandabase.h"
 #include "speedTreeNode.h"
+#include "stBasicTerrain.h"
 #include "virtualFileSystem.h"
 #include "config_util.h"
 #include "cullTraverser.h"
@@ -57,7 +58,7 @@ SpeedTreeNode(const string &name) :
   // Early versions of SpeedTree don't destruct unused CForestRender
   // objects correctly.  To avoid crashes, we have to leak these
   // things.
-  , _forest(*(new SpeedTree::CForestRender))
+  , _forest_render(*(new SpeedTree::CForestRender))
 #endif
 {
   init_node();
@@ -94,19 +95,19 @@ SpeedTreeNode(const string &name) :
     }
   }
 
-  string os_shaders_dir = shaders_dir.to_os_specific();
+  _os_shaders_dir = shaders_dir.to_os_specific();
   // Ensure the path ends with a terminal slash; SpeedTree requires this.
 #ifdef WIN32
-  if (!os_shaders_dir.empty() && os_shaders_dir[os_shaders_dir.length() - 1] != '\\') {
-    os_shaders_dir += "\\";
+  if (!_os_shaders_dir.empty() && _os_shaders_dir[_os_shaders_dir.length() - 1] != '\\') {
+    _os_shaders_dir += "\\";
   }
 #else
-  if (!os_shaders_dir.empty() && os_shaders_dir[os_shaders_dir.length() - 1] != '/') {
-    os_shaders_dir += "/";
+  if (!_os_shaders_dir.empty() && _os_shaders_dir[_os_shaders_dir.length() - 1] != '/') {
+    _os_shaders_dir += "/";
   }
 #endif
 
-  render_info.m_strShaderPath = os_shaders_dir.c_str();
+  render_info.m_strShaderPath = _os_shaders_dir.c_str();
   render_info.m_nMaxAnisotropy = speedtree_max_anisotropy;
   render_info.m_bHorizontalBillboards = speedtree_horizontal_billboards;
   render_info.m_fAlphaTestScalar = speedtree_alpha_test_scalar;
@@ -114,7 +115,10 @@ SpeedTreeNode(const string &name) :
   render_info.m_nMaxBillboardImagesByBase = speedtree_max_billboard_images_by_base;
   render_info.m_fVisibility = speedtree_visibility;
   render_info.m_fGlobalLightScalar = speedtree_global_light_scalar;
-  //render_info.m_sLightMaterial = ...
+  render_info.m_sLightMaterial.m_vAmbient = SpeedTree::Vec4(speedtree_ambient_color[0], speedtree_ambient_color[1], speedtree_ambient_color[2], 1.0f);
+  render_info.m_sLightMaterial.m_vDiffuse = SpeedTree::Vec4(speedtree_diffuse_color[0], speedtree_diffuse_color[1], speedtree_diffuse_color[2], 1.0f);
+  render_info.m_sLightMaterial.m_vSpecular = SpeedTree::Vec4(speedtree_specular_color[0], speedtree_specular_color[1], speedtree_specular_color[2], 1.0f);
+  render_info.m_sLightMaterial.m_vEmissive = SpeedTree::Vec4(speedtree_emissive_color[0], speedtree_emissive_color[1], speedtree_emissive_color[2], 1.0f);
   render_info.m_bSpecularLighting = speedtree_specular_lighting;
   render_info.m_bTransmissionLighting = speedtree_transmission_lighting;
   render_info.m_bDetailLayer = speedtree_detail_layer;
@@ -138,7 +142,7 @@ SpeedTreeNode(const string &name) :
   render_info.m_bWindEnabled = speedtree_wind_enabled;
   render_info.m_bFrondRippling = speedtree_frond_rippling;
   
-  _forest.SetRenderInfo(render_info);
+  _forest_render.SetRenderInfo(render_info);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -184,9 +188,9 @@ add_tree(const STTree *tree) {
     bool inserted = result.second;
     nassertr(inserted, *(*ti));
 
-    if (!_forest.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
+    if (!_forest_render.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
       speedtree_cat.warning()
-	<< "Failed to register tree " << tree->get_filename() << "\n";
+	<< "Failed to register tree " << tree->get_fullpath() << "\n";
       speedtree_cat.warning()
 	<< SpeedTree::CCore::GetError() << "\n";
     }
@@ -213,9 +217,9 @@ remove_tree(const STTree *tree) {
     return 0;
   }
 
-  if (!_forest.UnregisterTree(tree->get_tree())) {
+  if (!_forest_render.UnregisterTree(tree->get_tree())) {
     speedtree_cat.warning()
-      << "Failed to unregister tree " << tree->get_filename() << "\n";
+      << "Failed to unregister tree " << tree->get_fullpath() << "\n";
     speedtree_cat.warning()
       << SpeedTree::CCore::GetError() << "\n";
   }
@@ -242,9 +246,9 @@ remove_all_trees() {
   for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
     InstanceList *instance_list = (*ti);
     const STTree *tree = instance_list->get_tree();
-    if (!_forest.UnregisterTree(tree->get_tree())) {
+    if (!_forest_render.UnregisterTree(tree->get_tree())) {
       speedtree_cat.warning()
-	<< "Failed to unregister tree " << tree->get_filename() << "\n";
+	<< "Failed to unregister tree " << tree->get_fullpath() << "\n";
       speedtree_cat.warning()
 	<< SpeedTree::CCore::GetError() << "\n";
     }
@@ -387,25 +391,33 @@ add_instances_from(const SpeedTreeNode *other, const TransformState *transform)
 //               false on failure.
 ////////////////////////////////////////////////////////////////////
 bool SpeedTreeNode::
-add_from_stf(const Filename &pathname, const LoaderOptions &options) {
+add_from_stf(const Filename &stf_filename, const LoaderOptions &options) {
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
 
-  Filename filename = Filename::text_filename(pathname);
-  PT(VirtualFile) file = vfs->get_file(filename);
+  Filename fullpath = Filename::text_filename(stf_filename);
+  vfs->resolve_filename(fullpath, get_model_path());
+
+  if (!vfs->exists(fullpath)) {
+    speedtree_cat.warning()
+      << "Couldn't find " << stf_filename << "\n";
+    return false;
+  }
+
+  PT(VirtualFile) file = vfs->get_file(fullpath);
   if (file == (VirtualFile *)NULL) {
     // No such file.
     speedtree_cat.error()
-      << "Could not find " << pathname << "\n";
+      << "Could not find " << stf_filename << "\n";
     return false;
   }
 
   if (speedtree_cat.is_debug()) {
     speedtree_cat.debug()
-      << "Reading STF file " << filename << "\n";
+      << "Reading STF file " << fullpath << "\n";
   }
 
   istream *in = file->open_read_file(true);
-  bool success = add_from_stf(*in, pathname, options);
+  bool success = add_from_stf(*in, fullpath, options);
   vfs->close_read_file(in);
 
   return success;
@@ -513,6 +525,123 @@ add_from_stf(istream &in, const Filename &pathname,
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::setup_terrain
+//       Access: Published
+//  Description: A convenience function to set up terrain geometry by
+//               reading a terrain.txt file as defined by SpeedTree.
+//               This file names the various map files that define the
+//               terrain, as well as defining parameters size as its
+//               size and color.
+//
+//               This method implicitly creates a STBasicTerrain
+//               object and passes it to set_terrain().
+////////////////////////////////////////////////////////////////////
+bool SpeedTreeNode::
+setup_terrain(const Filename &terrain_file) {
+  PT(STBasicTerrain) terrain = new STBasicTerrain;
+  if (terrain->setup_terrain(terrain_file)) {
+    set_terrain(terrain);
+    return true;
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::set_terrain
+//       Access: Published
+//  Description: Associated a terrain with the node.  If the terrain
+//               has not already been loaded prior to this call,
+//               load_data() will be called immediately.
+//
+//               The terrain will be rendered using SpeedTree
+//               callbacks, and trees may be repositioned with a call
+//               to snap_to_terrain().
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+set_terrain(STTerrain *terrain) {
+  _terrain = NULL;
+  if (terrain == (STTerrain *)NULL) {
+    return;
+  }
+
+  if (!terrain->is_valid()) {
+    // If the terrain was not already loaded, load it immediately.
+    terrain->load_data();
+  }
+
+  nassertv(terrain->is_valid());
+  nassertv(terrain->get_num_splat_layers() == SpeedTree::c_nNumTerrainSplatLayers);
+  _terrain = terrain;
+
+  _terrain_render.SetShaderLoader(_forest_render.GetShaderLoader());
+
+  SpeedTree::STerrainRenderInfo render_info;
+  render_info.m_strShaderPath = _os_shaders_dir.c_str();
+
+  string os_specific = terrain->get_normal_map().to_os_specific();
+  render_info.m_strNormalMap = os_specific.c_str();
+  os_specific = terrain->get_splat_map().to_os_specific();
+  render_info.m_strSplatMap = os_specific.c_str();
+
+  for (int i = 0; i < SpeedTree::c_nNumTerrainSplatLayers; ++i) {
+    os_specific = terrain->get_splat_layer(i).to_os_specific();
+    render_info.m_astrSplatLayers[i] = os_specific.c_str();
+    render_info.m_afSplatTileValues[i] = terrain->get_splat_layer_tiling(i);
+  }
+
+  render_info.m_fNormalMapBlueScale = 1.0f;
+  render_info.m_bShadowsEnabled = false;
+  render_info.m_bZPrePass = false;
+
+  _terrain_render.SetRenderInfo(render_info);
+  _terrain_render.SetMaxAnisotropy(speedtree_max_anisotropy);
+  _terrain_render.SetHint(SpeedTree::CTerrain::HINT_MAX_NUM_VISIBLE_CELLS, 
+			  speedtree_max_num_visible_cells);
+  _visible_terrain.Reserve(speedtree_max_num_visible_cells);
+
+  _terrain_render.SetHeightHints(terrain->get_min_height(), terrain->get_max_height());
+
+  _needs_repopulate = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::snap_to_terrain
+//       Access: Published
+//  Description: Adjusts all the trees in this node so that their Z
+//               position matches the height of the terrain at their
+//               X, Y position.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+snap_to_terrain() {
+  Trees::iterator ti;
+  for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
+    InstanceList *instance_list = (*ti);
+
+    int num_instances = instance_list->get_num_instances();
+    if (_terrain != (STTerrain *)NULL) {
+      for (int i = 0; i < num_instances; ++i) {
+	STTransform trans = instance_list->get_instance(i);
+	LPoint3f pos = trans.get_pos();
+	pos[2] = _terrain->get_height(pos[0], pos[1]);
+	trans.set_pos(pos);
+	instance_list->set_instance(i, trans);
+      }
+    } else {
+      for (int i = 0; i < num_instances; ++i) {
+	STTransform trans = instance_list->get_instance(i);
+	LPoint3f pos = trans.get_pos();
+	pos[2] = 0.0f;
+	trans.set_pos(pos);
+	instance_list->set_instance(i, trans);
+      }
+    }
+  }
+
+  _needs_repopulate = true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SpeedTreeNode::authorize
 //       Access: Published, Static
@@ -555,20 +684,20 @@ SpeedTreeNode(const SpeedTreeNode &copy) :
   // Early versions of SpeedTree don't destruct unused CForestRender
   // objects correctly.  To avoid crashes, we have to leak these
   // things.
-  , _forest(*(new SpeedTree::CForestRender))
+  , _forest_render(*(new SpeedTree::CForestRender))
 #endif
 {
   init_node();
 
-  _forest.SetRenderInfo(copy._forest.GetRenderInfo());
+  _forest_render.SetRenderInfo(copy._forest_render.GetRenderInfo());
 
   Trees::const_iterator ti;
   for (ti = copy._trees.begin(); ti != copy._trees.end(); ++ti) {
     InstanceList *instance_list = (*ti);
     const STTree *tree = instance_list->get_tree();
-    if (!_forest.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
+    if (!_forest_render.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
       speedtree_cat.warning()
-	<< "Failed to register tree " << tree->get_filename() << "\n";
+	<< "Failed to register tree " << tree->get_fullpath() << "\n";
       speedtree_cat.warning()
 	<< SpeedTree::CCore::GetError() << "\n";
     }
@@ -588,7 +717,7 @@ SpeedTreeNode(const SpeedTreeNode &copy) :
 SpeedTreeNode::
 ~SpeedTreeNode() {
   // Help reduce memory waste from ST_DELETE_FOREST_HACK.
-  _forest.ClearInstances();
+  _forest_render.ClearInstances();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -702,8 +831,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   }
 
   ClockObject *clock = ClockObject::get_global_clock();
-  _forest.SetGlobalTime(clock->get_frame_time());
-  _forest.AdvanceGlobalWind();
+  _forest_render.SetGlobalTime(clock->get_frame_time());
+  _forest_render.AdvanceGlobalWind();
   
   // Compute the modelview and camera transforms, to pass to the
   // SpeedTree CView structure.
@@ -733,7 +862,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   if (ta != (TextureAttrib *)NULL) {
     show_textures = !ta->has_all_off();
   }
-  _forest.EnableTexturing(show_textures);
+  _forest_render.EnableTexturing(show_textures);
+  _terrain_render.EnableTexturing(show_textures);
 
   // Check lighting state.  SpeedTree only supports a single
   // directional light; we look for a directional light in the
@@ -748,21 +878,27 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
 
     CPT(TransformState) transform = light.get_transform(trav->get_scene()->get_scene_root().get_parent());
     LVector3f dir = light_obj->get_direction() * transform->get_mat();
-    _forest.SetLightDir(SpeedTree::Vec3(dir[0], dir[1], dir[2]));
+    _light_dir = SpeedTree::Vec3(dir[0], dir[1], dir[2]);
 
   } else {
     // No light.  But there's no way to turn off lighting in
     // SpeedTree.  In lieu of this, we just shine a light from
     // above.
-    _forest.SetLightDir(SpeedTree::Vec3(0.0, 0.0, -1.0));
+    _light_dir = SpeedTree::Vec3(0.0, 0.0, -1.0);
   }
 
+  _forest_render.SetLightDir(_light_dir);
+
   if (!_needs_repopulate) {
     // Don't bother culling now unless we're correctly fully
     // populated.  (Culling won't be accurate unless the forest has
     // been populated, but we have to be in the draw traversal to
     // populate.)
-    _forest.CullAndComputeLOD(_view, _visible_trees);
+    _forest_render.CullAndComputeLOD(_view, _visible_trees);
+
+    if (has_terrain()) {
+      _terrain_render.CullAndComputeLOD(_view, _visible_terrain);
+    }
   }
 
   // Recurse onto the node's children.
@@ -921,9 +1057,10 @@ init_node() {
     return;
   }
 
-  _forest.SetHint(SpeedTree::CForest::HINT_MAX_NUM_VISIBLE_CELLS, speedtree_max_num_visible_cells);
+  _forest_render.SetHint(SpeedTree::CForest::HINT_MAX_NUM_VISIBLE_CELLS, 
+			 speedtree_max_num_visible_cells);
 
-  _forest.SetCullCellSize(speedtree_cull_cell_size);
+  _forest_render.SetCullCellSize(speedtree_cull_cell_size);
 
   _is_valid = true;
 }
@@ -958,7 +1095,7 @@ r_add_instances(PandaNode *node, const TransformState *transform,
 ////////////////////////////////////////////////////////////////////
 void SpeedTreeNode::
 repopulate() {
-  _forest.ClearInstances();
+  _forest_render.ClearInstances();
 
   Trees::iterator ti;
   for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
@@ -973,7 +1110,7 @@ repopulate() {
       continue;
     }
 
-    if (!_forest.AddInstances(tree->get_tree(), &instances[0], instances.size())) {
+    if (!_forest_render.AddInstances(tree->get_tree(), &instances[0], instances.size())) {
       speedtree_cat.warning()
 	<< "Failed to add " << instances.size()
 	<< " instances for " << *tree << "\n";
@@ -982,7 +1119,7 @@ repopulate() {
     }
   }
   
-  _forest.GetPopulationStats(_population_stats);
+  _forest_render.GetPopulationStats(_population_stats);
   print_forest_stats(_population_stats);
 
   // setup billboard caps based on instances-per-cell stats
@@ -1005,13 +1142,59 @@ repopulate() {
     max_instances_by_cell = max(max_instances_by_cell, max_instances);
   }
 
-  _visible_trees.Reserve(_forest.GetBaseTrees(),
-			 _forest.GetBaseTrees().size(), 
+  _visible_trees.Reserve(_forest_render.GetBaseTrees(),
+			 _forest_render.GetBaseTrees().size(), 
 			 speedtree_max_num_visible_cells, 
 			 max_instances_by_cell,
 			 speedtree_horizontal_billboards);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::update_terrain_cells
+//       Access: Private
+//  Description: Called once a frame to load vertex data for
+//               newly-visible terrain cells.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+update_terrain_cells() {
+  nassertv(has_terrain());
+
+  SpeedTree::TTerrainCellArray &cells = _visible_terrain.m_aCellsToUpdate;
+
+  int num_tile_res = _terrain_render.GetMaxTileRes();
+  float cell_size = _terrain_render.GetCellSize();
+
+  // A temporary vertex data object for populating terrain.
+  PT(GeomVertexData) vertex_data = 
+    new GeomVertexData("terrain", _terrain->get_vertex_format(), 
+		       GeomEnums::UH_static);
+  int num_vertices = num_tile_res * num_tile_res;
+  vertex_data->set_num_rows(num_vertices);
+  size_t num_bytes = vertex_data->get_array(0)->get_data_size_bytes();
+
+  int num_cells = (int)cells.size();
+  for (int ci = 0; ci < num_cells; ++ci) {
+    SpeedTree::CTerrainCell *cell = cells[ci];
+    nassertv(cell != NULL && cell->GetVbo() != NULL);
+    int cell_yi = cell->Row();
+    int cell_xi = cell->Col();
+    //cerr << "populating cell " << cell_xi << " " << cell_yi << "\n";
+
+    _terrain->fill_vertices(vertex_data,
+			    cell_xi * cell_size, cell_yi * cell_size,
+			    cell_size, num_tile_res);
+
+    const GeomVertexArrayData *array_data = vertex_data->get_array(0);
+    CPT(GeomVertexArrayDataHandle) handle = array_data->get_handle();
+    const unsigned char *data_pointer = handle->get_read_pointer(true);
+    SpeedTree::CGeometryBuffer *vbo = (SpeedTree::CGeometryBuffer *)cell->GetVbo();
+
+    nassertv(vbo->NumVertices() == num_tile_res * num_tile_res);
+    nassertv(vbo->NumVertices() * vbo->VertexSize() == handle->get_data_size_bytes());
+    vbo->OverwriteVertices(data_pointer, num_vertices, 0);
+  }    
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SpeedTreeNode::validate_api
 //       Access: Private
@@ -1060,15 +1243,42 @@ draw_callback(CallbackData *data) {
   GraphicsStateGuardian *gsg = DCAST(GraphicsStateGuardian, geom_cbdata->get_gsg());
 
   setup_for_render(gsg);
+
+  if (_terrain_render.IsEnabled()) {
+
+    // Is this needed for terrain?
+    _terrain_render.UploadShaderConstants
+      (&_forest_render, _light_dir,
+       _forest_render.GetRenderInfo().m_sLightMaterial);
+
+    // set terrain render states
+    //SetTransparentTextureMode(TRANS_TEXTURE_NOTHING);
+
+    // render actual terrain
+    bool terrain = _terrain_render.Render
+      (&_forest_render, _visible_terrain, SpeedTree::RENDER_PASS_STANDARD,
+       _light_dir, _forest_render.GetRenderInfo().m_sLightMaterial, 
+       &_forest_render.GetRenderStats());
+
+    if (!terrain) {
+      speedtree_cat.warning()
+	<< "Failed to render terrain\n";
+      speedtree_cat.warning()
+	<< SpeedTree::CCore::GetError() << "\n";
+    }
+
+    // restore default forest rendering states
+    //SetTransparentTextureMode(ETextureAlphaRenderMode(m_uiTransparentTextureRenderMode));
+  }
   
   // start the forest render
-  _forest.StartRender();
+  _forest_render.StartRender();
   
-  bool branches = _forest.RenderBranches(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
-  bool fronds = _forest.RenderFronds(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
-  bool leaf_meshes = _forest.RenderLeafMeshes(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
-  bool leaf_cards = _forest.RenderLeafCards(_visible_trees, SpeedTree::RENDER_PASS_STANDARD, _view);
-  bool billboards = _forest.RenderBillboards(_visible_trees, SpeedTree::RENDER_PASS_STANDARD, _view);
+  bool branches = _forest_render.RenderBranches(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
+  bool fronds = _forest_render.RenderFronds(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
+  bool leaf_meshes = _forest_render.RenderLeafMeshes(_visible_trees, SpeedTree::RENDER_PASS_STANDARD);
+  bool leaf_cards = _forest_render.RenderLeafCards(_visible_trees, SpeedTree::RENDER_PASS_STANDARD, _view);
+  bool billboards = _forest_render.RenderBillboards(_visible_trees, SpeedTree::RENDER_PASS_STANDARD, _view);
 
   if (!branches || !fronds || !leaf_meshes || !leaf_cards || !billboards) {
     speedtree_cat.warning()
@@ -1078,7 +1288,7 @@ draw_callback(CallbackData *data) {
       << SpeedTree::CCore::GetError() << "\n";
   }
 
-  _forest.EndRender();
+  _forest_render.EndRender();
 
   // SpeedTree leaves the graphics state indeterminate.  But this
   // doesn't help?
@@ -1167,9 +1377,9 @@ setup_for_render(GraphicsStateGuardian *gsg) {
 #endif
       }
 
-      if (!_forest.InitTreeGraphics((SpeedTree::CTreeRender *)tree->get_tree(), 
-				    max_instances, speedtree_horizontal_billboards,
-				    os_textures_dir.c_str())) {
+      if (!_forest_render.InitTreeGraphics((SpeedTree::CTreeRender *)tree->get_tree(), 
+					   max_instances, speedtree_horizontal_billboards,
+					   os_textures_dir.c_str())) {
 	speedtree_cat.warning()
 	  << "Failed to init tree graphics for " << *tree << "\n";
 	speedtree_cat.warning()
@@ -1178,7 +1388,7 @@ setup_for_render(GraphicsStateGuardian *gsg) {
     }
 
     // Init overall graphics
-    if (!_forest.InitGraphics(false)) {
+    if (!_forest_render.InitGraphics(false)) {
       speedtree_cat.warning()
 	<< "Failed to init graphics\n";
       speedtree_cat.warning()
@@ -1190,16 +1400,39 @@ setup_for_render(GraphicsStateGuardian *gsg) {
     // This call apparently must be made at draw time, not earlier,
     // because it might attempt to create OpenGL index buffers and
     // such.
-    _forest.UpdateTreeCellExtents();
+    _forest_render.UpdateTreeCellExtents();
+
+    if (has_terrain()) {
+      // Now initialize the terrain.
+      static const int speedtree_terrain_num_lods = 5;  // number of LOD stages
+      static const int speedtree_terrain_resolution = 33; // num vertices per edge of grid cell at highest LOD, must be power-of-two-plus-1
+      static const float speedtree_terrain_cell_size = 800.0f;  // spatial size of one edge of grid cell
+      
+      if (!_terrain_render.Init(speedtree_terrain_num_lods, 
+				speedtree_terrain_resolution,
+				speedtree_terrain_cell_size,
+				_terrain->get_st_vertex_format())) {
+	speedtree_cat.warning()
+	  << "Failed to init terrain\n";
+	speedtree_cat.warning()
+	  << SpeedTree::CCore::GetError() << "\n";
+      }
+    }
 
     // If we needed to repopulate, it means we didn't cull in the cull
     // traversal.  Do it now.
-    _forest.CullAndComputeLOD(_view, _visible_trees);
+    _forest_render.CullAndComputeLOD(_view, _visible_trees);
+    if (has_terrain()) {
+      _terrain_render.CullAndComputeLOD(_view, _visible_terrain);
+    }
 
     _needs_repopulate = false;
   }
+  if (has_terrain()) {
+    update_terrain_cells();
+  }
 
-  if (!_forest.UploadViewShaderParameters(_view)) {
+  if (!_forest_render.UploadViewShaderParameters(_view)) {
     speedtree_cat.warning()
       << "Couldn't set view parameters\n";
     speedtree_cat.warning()

+ 22 - 3
panda/src/speedtree/speedTreeNode.h

@@ -20,6 +20,7 @@
 #include "pointerTo.h"
 #include "stTree.h"
 #include "stTransform.h"
+#include "stTerrain.h"
 #include "callbackObject.h"
 #include "loaderOptions.h"
 #include "transformState.h"
@@ -68,6 +69,7 @@ PUBLISHED:
     INLINE int get_num_instances() const;
     INLINE STTransform get_instance(int n) const;
     MAKE_SEQ(get_instances, get_num_instances, get_instance);
+    INLINE void set_instance(int n, const STTransform &transform);
 
     INLINE int add_instance(const STTransform &transform);
     INLINE void remove_instance(int n);
@@ -108,12 +110,20 @@ PUBLISHED:
   void add_instances(const NodePath &root, const TransformState *transform = TransformState::make_identity());
   void add_instances_from(const SpeedTreeNode *other);
   void add_instances_from(const SpeedTreeNode *other, const TransformState *transform);
-  bool add_from_stf(const Filename &pathname, 
+  bool add_from_stf(const Filename &stf_filename, 
 		    const LoaderOptions &options = LoaderOptions());
   bool add_from_stf(istream &in, const Filename &pathname, 
 		    const LoaderOptions &options = LoaderOptions(),
 		    Loader *loader = NULL);
 
+  bool setup_terrain(const Filename &terrain_file);
+  void set_terrain(STTerrain *terrain);
+  INLINE void clear_terrain();
+  INLINE bool has_terrain() const;
+  INLINE STTerrain *get_terrain() const;
+
+  void snap_to_terrain();
+
   static bool authorize(const string &license = "");
 
 public:
@@ -145,6 +155,7 @@ private:
 		       Thread *current_thread);
 
   void repopulate();
+  void update_terrain_cells();
   bool validate_api(GraphicsStateGuardian *gsg);
   void draw_callback(CallbackData *cbdata);
   void setup_for_render(GraphicsStateGuardian *gsg);
@@ -179,14 +190,16 @@ private:
   };
 
 private:
+  string _os_shaders_dir;
+  
   // A list of instances per each unique tree.
   typedef ov_set<InstanceList *, IndirectLess<InstanceList> > Trees;
   Trees _trees;
 
 #ifdef ST_DELETE_FOREST_HACK
-  SpeedTree::CForestRender &_forest;
+  SpeedTree::CForestRender &_forest_render;
 #else
-  SpeedTree::CForestRender _forest;
+  SpeedTree::CForestRender _forest_render;
 #endif  // ST_DELETE_FOREST_HACK
   SpeedTree::CView _view;
   SpeedTree::SForestCullResultsRender _visible_trees;
@@ -194,6 +207,12 @@ private:
   bool _needs_repopulate;
   bool _is_valid;
 
+  PT(STTerrain) _terrain;
+  SpeedTree::CTerrainRender _terrain_render;
+  SpeedTree::STerrainCullResults _visible_terrain;
+
+  SpeedTree::Vec3 _light_dir;
+
   static bool _authorized;
   static bool _done_first_init;
 

+ 184 - 0
panda/src/speedtree/stBasicTerrain.I

@@ -0,0 +1,184 @@
+// Filename: stBasicTerrain.I
+// Created by:  drose (12Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::set_height_map
+//       Access: Published
+//  Description: Specifies the image filename that will define the
+//               height map of the terrain.  This will require a
+//               subsequent call to load_data() to actually read the
+//               data.
+////////////////////////////////////////////////////////////////////
+INLINE void STBasicTerrain::
+set_height_map(const Filename &height_map) {
+  _height_map = height_map;
+  _is_valid = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::get_height_map
+//       Access: Published
+//  Description: Returns the image filename that defines the
+//               height map of the terrain.
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &STBasicTerrain::
+get_height_map() const {
+  return _height_map;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::get_size
+//       Access: Published
+//  Description: Returns the length, in scene graph units, of one edge
+//               of the heightmap as it is manifested by the terrain.
+//               Increasing this number spreads the heightmap out over
+//               a greater area.
+////////////////////////////////////////////////////////////////////
+INLINE float STBasicTerrain::
+get_size() const {
+  return _size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::InterpolationData::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+template<class ValueType>
+STBasicTerrain::InterpolationData<ValueType>::
+InterpolationData() : _width(0), _height(0) 
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::InterpolationData::reset
+//       Access: Public
+//  Description: Resets the array to an empty array of width x height
+//               cells.
+////////////////////////////////////////////////////////////////////
+template<class ValueType>
+void STBasicTerrain::InterpolationData<ValueType>::
+reset(int width, int height) {
+  _width = width;
+  _height = height;
+  _data.clear();
+  _data.insert(_data.begin(), width * height, ValueType());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::InterpolationData::get_nearest_neighbor
+//       Access: Public
+//  Description: Returns the value nearest to (u, v) in the data.
+////////////////////////////////////////////////////////////////////
+template<class ValueType>
+ValueType STBasicTerrain::InterpolationData<ValueType>::
+get_nearest_neighbor(float u, float v) const {
+  int u = int(u * _width + 0.5f);
+  int v = int(v * _height + 0.5f);
+  int index = u + v * _width;
+  nassertr(index >= 0 && index < (int)_data.size(), 0);
+  return _data[index];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::InterpolationData::calc_bilnear_interpolation
+//       Access: Public
+//  Description: Interpolates the value at (u, v) between its four
+//               nearest neighbors.
+////////////////////////////////////////////////////////////////////
+template<class ValueType>
+ValueType STBasicTerrain::InterpolationData<ValueType>::
+calc_bilinear_interpolation(float u, float v) const {
+  u -= cfloor(u);
+  v -= cfloor(v);
+  nassertr((u >= 0.0f) && (u < 1.0f) && 
+	   (v >= 0.0f) && (v < 1.0f), 0);
+  
+  u *= (float)_width;
+  v *= (float)_height;
+  
+  const int lower_x = int(u);
+  const int lower_y = int(v);
+  const int higher_x = (lower_x + 1) % _width;
+  const int higher_y = (lower_y + 1) % _height;
+  
+  const float ratio_x = u - float(lower_x);
+  const float ratio_y = v - float(lower_y);
+  const float inv_ratio_x = 1.0f - ratio_x;
+  const float inv_ratio_y = 1.0f - ratio_y;
+  
+  nassertr(lower_x + lower_y * _width >= 0 && higher_x + higher_y * _width < (int)_data.size(), 0);
+
+  const ValueType &t1 = _data[lower_x + lower_y * _width];
+  const ValueType &t2 = _data[higher_x + lower_y * _width];
+  const ValueType &t3 = _data[lower_x + higher_y * _width];
+  const ValueType &t4 = _data[higher_x + higher_y * _width];
+  
+  return (t1 * inv_ratio_x + t2 * ratio_x) * inv_ratio_y + 
+    (t3 * inv_ratio_x + t4 * ratio_x) * ratio_y;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::InterpolationData::calc_smooth
+//       Access: Public
+//  Description: Approximates the average value at (u, v) over the
+//               indicated radius, assuming a polynomial curve.
+////////////////////////////////////////////////////////////////////
+template<class ValueType>
+ValueType STBasicTerrain::InterpolationData<ValueType>::
+calc_smooth(float u, float v, float radius) const {
+  ValueType retval = 0;
+
+  if (radius <= 0.0f) {
+    retval = calc_bilinear_interpolation(u, v);
+
+  } else {
+    const float test_points[9][2] = {
+      {  0.0f * radius,   0.0f * radius },
+      {  0.8f * radius,   0.0f * radius },
+      { -0.8f * radius,   0.0f * radius },
+      {  0.0f * radius,   0.8f * radius },
+      {  0.0f * radius,  -0.8f * radius },
+      {  0.25f * radius,  0.25f * radius },
+      {  0.25f * radius, -0.25f * radius },
+      { -0.25f * radius,  0.25f * radius },
+      { -0.25f * radius, -0.25f * radius }
+    };
+
+    float total_weight = 0.0f;
+    for (int i = 0; i < 9; ++i) {
+      const float *test_point = test_points[i];
+      float weight = (1.0f - sqrt((test_point[0] * test_point[0]) + (test_point[1] * test_point[1])));
+      total_weight += weight;
+      retval += weight * calc_bilinear_interpolation(u + test_point[0], v + test_point[1]);
+    }   
+    
+    retval /= total_weight;
+  }
+  
+  return retval;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::InterpolationData::is_present
+//       Access: Public
+//  Description: Returns true if the data is present--that is, reset()
+//               was called with non-zero values--or false otherwise.
+////////////////////////////////////////////////////////////////////
+template<class ValueType>
+bool STBasicTerrain::InterpolationData<ValueType>::
+is_present() const {
+  return !_data.empty();
+}

+ 424 - 0
panda/src/speedtree/stBasicTerrain.cxx

@@ -0,0 +1,424 @@
+// Filename: stBasicTerrain.cxx
+// Created by:  drose (12Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "stBasicTerrain.h"
+#include "geomVertexWriter.h"
+#include "pnmImage.h"
+#include "indent.h"
+
+TypeHandle STBasicTerrain::_type_handle;
+
+// VERTEX_ATTRIB_END is defined as a macro that must be evaluated
+// within the SpeedTree namespace.
+namespace SpeedTree {
+  static const SVertexAttribDesc st_attrib_end = VERTEX_ATTRIB_END();
+}
+
+/* Hmm, maybe we want to use this lower-level structure directly
+   instead of the GeomVertexWriter.
+
+namespace SpeedTree {
+  static const SVertexAttribDesc std_vertex_format[] = {
+    { VERTEX_ATTRIB_SEMANTIC_POS, VERTEX_ATTRIB_TYPE_FLOAT, 3 },
+    { VERTEX_ATTRIB_SEMANTIC_TEXCOORD0, VERTEX_ATTRIB_TYPE_FLOAT, 3 },
+    VERTEX_ATTRIB_END( )
+  };
+  static const int std_vertex_format_length = 
+    sizeof(std_vertex_format) / sizeof(std_vertex_format[0]);
+};
+*/
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+STBasicTerrain::
+STBasicTerrain() {
+  clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::Copy Constructor
+//       Access: Published
+//  Description: Not sure whether any derived classes will implement
+//               the copy constructor, but it's defined here at the
+//               base level just in case.
+////////////////////////////////////////////////////////////////////
+STBasicTerrain::
+STBasicTerrain(const STBasicTerrain &copy) :
+  STTerrain(copy),
+  _size(copy._size),
+  _height_scale(copy._height_scale)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::Destructor
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+STBasicTerrain::
+~STBasicTerrain() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::clear
+//       Access: Published, Virtual
+//  Description: Resets the terrain to its initial, unloaded state.
+////////////////////////////////////////////////////////////////////
+void STBasicTerrain::
+clear() {
+  STTerrain::clear();
+
+  _height_map = "";
+  _size = 1.0f;
+  _height_scale = 1.0f;
+
+  CPT(GeomVertexFormat) format = GeomVertexFormat::register_format
+    (new GeomVertexArrayFormat(InternalName::get_vertex(), 3, 
+			       GeomEnums::NT_float32, GeomEnums::C_point,
+			       InternalName::get_texcoord(), 3, 
+			       GeomEnums::NT_float32, GeomEnums::C_texcoord));
+  set_vertex_format(format);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::setup_terrain
+//       Access: Published
+//  Description: Sets up the terrain by reading a terrain.txt file as
+//               defined by SpeedTree.  This file names the various
+//               map files that define the terrain, as well as
+//               defining parameters size as its size and color.
+//
+//               If a relative filename is supplied, the model-path is
+//               searched.  If a directory is named, "terrain.txt" is
+//               implicitly appended.
+////////////////////////////////////////////////////////////////////
+bool STBasicTerrain::
+setup_terrain(const Filename &terrain_filename) {
+  _is_valid = false;
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+  Filename fullpath = Filename::text_filename(terrain_filename);
+  vfs->resolve_filename(fullpath, get_model_path());
+
+  if (!vfs->exists(fullpath)) {
+    speedtree_cat.warning()
+      << "Couldn't find " << terrain_filename << "\n";
+    return false;
+  }
+
+  if (vfs->is_directory(fullpath)) {
+    fullpath = Filename(fullpath, "terrain.txt");
+  }
+
+  istream *in = vfs->open_read_file(fullpath, true);
+  if (in == NULL) {
+    speedtree_cat.warning()
+      << "Couldn't open " << terrain_filename << "\n";
+    return false;
+  }
+    
+  bool success = setup_terrain(*in, fullpath);
+  vfs->close_read_file(in);
+
+  return success;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::setup_terrain
+//       Access: Published
+//  Description: Sets up the terrain by reading a terrain.txt file as
+//               defined by SpeedTree.  This variant on this method
+//               accepts an istream for an already-opened terrain.txt
+//               file.  The filename is provided for reference, to
+//               assist relative file operations.  It should name the
+//               terrain.txt file that has been opened.
+////////////////////////////////////////////////////////////////////
+bool STBasicTerrain::
+setup_terrain(istream &in, const Filename &pathname) {
+  clear();
+
+  Filename dirname = pathname.get_dirname();
+
+  string keyword;
+  in >> keyword;
+  while (in && !in.eof()) {
+    if (keyword == "area") {
+      // area defines the size of the terrain in square kilometers.
+      static const float feet_per_km = 3280.839895013f;
+      float area;
+      in >> area;
+      _size = csqrt(area) * feet_per_km;
+
+    } else if (keyword == "height_scale") {
+      in >> _height_scale;
+
+    } else if (keyword == "normalmap_b_scale") {
+      float normalmap_b_scale;
+      in >> normalmap_b_scale;
+
+    } else if (keyword == "heightmap") {
+      read_quoted_filename(_height_map, in, dirname);
+
+    } else if (keyword == "texture") {
+      SplatLayer splat;
+      read_quoted_filename(splat._filename, in, dirname);
+      in >> splat._tiling;
+      splat._color.set(1.0f, 1.0f, 1.0f, 1.0f);
+      _splat_layers.push_back(splat);
+
+    } else if (keyword == "color") {
+      // color means the overall color of the previous texture.
+      float r, g, b;
+      in >> r >> g >> b;
+      if (!_splat_layers.empty()) {
+	_splat_layers.back()._color.set(r, g, b, 1.0f);
+      }
+
+    } else if (keyword == "ambient" || keyword == "diffuse" || keyword == "specular" || keyword == "emissive") {
+      float r, g, b;
+      in >> r >> g >> b;
+
+    } else if (keyword == "shininess") {
+      float s;
+      in >> s;
+
+    } else {
+      speedtree_cat.error()
+	<< "Invalid token " << keyword << " in " << pathname << "\n";
+      return false;
+    }
+
+    in >> keyword;
+  }
+
+  // Consume any whitespace at the end of the file.
+  in >> ws;
+
+  if (!in.eof()) {
+    // If we didn't read all the way to end-of-file, there was an
+    // error.
+    in.clear();
+    string text;
+    in >> text;
+    speedtree_cat.error()
+      << "Unexpected text in " << pathname << " at \"" << text << "\"\n";
+    return false;
+  }
+
+  // The first two textures are the normal map and splat map,
+  // respectively.
+  if (!_splat_layers.empty()) {
+    _normal_map = _splat_layers[0]._filename;
+    _splat_layers.erase(_splat_layers.begin());
+  }
+  if (!_splat_layers.empty()) {
+    _splat_map = _splat_layers[0]._filename;
+    _splat_layers.erase(_splat_layers.begin());
+  }
+
+  // Now try to load the actual height map data.
+  load_data();
+
+  return _is_valid;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::load_data
+//       Access: Published, Virtual
+//  Description: This will be called at some point after
+//               initialization.  It should be overridden by a derived
+//               class to load up the terrain data from its source and
+//               fill in the data members of this class appropriately,
+//               especially _is_valid.  After this call, if _is_valid
+//               is true, then get_height() etc. will be called to
+//               query the terrain's data.
+////////////////////////////////////////////////////////////////////
+void STBasicTerrain::
+load_data() {
+  _is_valid = false;
+
+  if (!read_height_map()) {
+    return;
+  }
+
+  _is_valid = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::get_height
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this should return
+//               the computed height value at point (x, y) of the
+//               terrain, where x and y are unbounded and may refer to
+//               any 2-d point in space.
+////////////////////////////////////////////////////////////////////
+float STBasicTerrain::
+get_height(float x, float y) const {
+  return _height_data.calc_bilinear_interpolation(x / _size, y / _size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::get_smooth_height
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this should return
+//               the approximate average height value over a circle of
+//               the specified radius, centered at point (x, y) of the
+//               terrain.
+////////////////////////////////////////////////////////////////////
+float STBasicTerrain::
+get_smooth_height(float x, float y, float radius) const {
+  return _height_data.calc_smooth(x / _size, y / _size, radius / _size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::get_slope
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this should return
+//               the directionless slope at point (x, y) of the
+//               terrain, where 0.0 is flat and 1.0 is vertical.  This
+//               is used for determining the legal points to place
+//               trees and grass.
+////////////////////////////////////////////////////////////////////
+float STBasicTerrain::
+get_slope(float x, float y) const {
+  return 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::fill_vertices
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this will be
+//               called occasionally to populate the vertices for a
+//               terrain cell.
+//
+//               It will be passed a GeomVertexData whose format will
+//               match get_vertex_format(), and already allocated with
+//               num_xy * num_xy rows.  This method should fill the
+//               rows of the data with the appropriate vertex data for
+//               the terrain, over the grid described by the corners
+//               (start_x, start_y) up to and including (start_x +
+//               size_x, start_y + size_xy)--a square of the terrain
+//               with num_xy vertices on a size, arranged in row-major
+//               order.
+////////////////////////////////////////////////////////////////////
+void STBasicTerrain::
+fill_vertices(GeomVertexData *data,
+	      float start_x, float start_y,
+	      float size_xy, int num_xy) const {
+  nassertv(data->get_format() == _vertex_format);
+  GeomVertexWriter vertex(data, InternalName::get_vertex());
+  GeomVertexWriter texcoord(data, InternalName::get_texcoord());
+
+  float vertex_scale = 1.0 / (float)(num_xy - 1);
+  float texcoord_scale = 1.0 / _size;
+  for (int xi = 0; xi < num_xy; ++xi) {
+    float xt = xi * vertex_scale;
+    float x = start_x + xt * size_xy;
+    for (int yi = 0; yi < num_xy; ++yi) {
+      float yt = yi * vertex_scale;
+      float y = start_y + yt * size_xy;
+
+      float z = get_height(x, y);
+      
+      vertex.set_data3f(x, y, z);
+      texcoord.set_data3f(x * texcoord_scale, -y * texcoord_scale, 1.0f);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::output
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void STBasicTerrain::
+output(ostream &out) const {
+  Namable::output(out);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::write
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void STBasicTerrain::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << *this << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::read_height_map
+//       Access: Protected
+//  Description: Reads the height map image stored in _height_map, and
+//               stores it in _height_data.  Returns true on success,
+//               false on failure.
+////////////////////////////////////////////////////////////////////
+bool STBasicTerrain::
+read_height_map() {
+  PNMImage image(_height_map);
+  if (!image.is_valid()) {
+    return false;
+  }
+
+  _height_data.reset(image.get_x_size(), image.get_y_size());
+  _min_height = FLT_MAX;
+  _max_height = FLT_MIN;
+
+  float scalar = _size * _height_scale / image.get_num_channels();
+  int pi = 0;
+  for (int yi = image.get_y_size() - 1; yi >= 0; --yi) {
+    for (int xi = 0; xi < image.get_x_size(); ++xi) {
+      Colord rgba = image.get_xel_a(xi, yi);
+      float v = rgba[0] + rgba[1] + rgba[2] + rgba[3];
+      v *= scalar;
+      _height_data._data[pi] = v;
+      ++pi;
+      _min_height = min(_min_height, v);
+      _max_height = max(_max_height, v);
+    }
+  }
+  
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STBasicTerrain::read_quoted_filename
+//       Access: Private, Static
+//  Description: Reads a quoted filename from the input stream, which
+//               is understood to be relative to the indicated
+//               directory.
+////////////////////////////////////////////////////////////////////
+void STBasicTerrain::
+read_quoted_filename(Filename &result, istream &in, const Filename &dirname) {
+  string filename;
+  in >> filename;
+
+  // The terrain.txt file should, in theory, support spaces, but the
+  // SpeedTree reference application doesn't, so we don't bother
+  // either.
+  if (filename.size() >= 2 && filename[0] == '"' && filename[filename.size() - 1] == '"') {
+    filename = filename.substr(1, filename.size() - 2);
+  }
+
+  result = Filename::from_os_specific(filename);
+  if (result.is_local()) {
+    result = Filename(dirname, result);
+  }
+}
+

+ 112 - 0
panda/src/speedtree/stBasicTerrain.h

@@ -0,0 +1,112 @@
+// Filename: stBasicTerrain.h
+// Created by:  drose (12Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef STBASICTERRAIN_H
+#define STBASICTERRAIN_H
+
+#include "pandabase.h"
+#include "stTerrain.h"
+#include "luse.h"
+#include "pvector.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : STBasicTerrain
+// Description : A specific implementation of STTerrain that supports
+//               basic heightmaps loaded from an image file, as
+//               described in a terrain.txt file similar to those
+//               provided with the SpeedTree example application.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDASPEEDTREE STBasicTerrain : public STTerrain {
+PUBLISHED:
+  STBasicTerrain();
+  STBasicTerrain(const STBasicTerrain &copy);
+  virtual ~STBasicTerrain();
+
+  void clear();
+
+  bool setup_terrain(const Filename &terrain_filename);
+  bool setup_terrain(istream &in, const Filename &pathname);
+
+  INLINE void set_height_map(const Filename &height_map);
+  INLINE const Filename &get_height_map() const;
+
+  virtual void load_data();
+
+  INLINE float get_size() const;
+  virtual float get_height(float x, float y) const;
+  virtual float get_smooth_height(float x, float y, float radius) const;
+  virtual float get_slope(float x, float y) const;
+
+  virtual void fill_vertices(GeomVertexData *data,
+			     float start_x, float start_y,
+			     float size_xy, int num_xy) const;
+
+  virtual void output(ostream &out) const;
+  virtual void write(ostream &out, int indent_level = 0) const;
+
+protected:
+  bool read_height_map();
+
+private:
+  static void read_quoted_filename(Filename &result, istream &in, 
+				   const Filename &dirname);
+
+protected:
+  template<class ValueType>
+  class InterpolationData {
+  public:
+    InterpolationData();
+    void reset(int width, int height);
+
+    ValueType get_nearest_neighbor(float u, float v) const;
+    ValueType calc_bilinear_interpolation(float u, float v) const;
+    ValueType calc_smooth(float u, float v, float radius) const;
+    bool is_present() const;
+
+    int _width;
+    int _height;
+    pvector<ValueType> _data;
+  };
+
+protected:
+  Filename _height_map;
+  float _size;
+  float _height_scale;
+
+  InterpolationData<float> _height_data;
+  //InterpolationData<LVector3f> _normal_data;
+  InterpolationData<float> _slope_data;
+  //InterpolationData<unsigned char> _ao_data;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    STTerrain::init_type();
+    register_type(_type_handle, "STBasicTerrain",
+                  STTerrain::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "stBasicTerrain.I"
+
+#endif

+ 138 - 0
panda/src/speedtree/stTerrain.I

@@ -0,0 +1,138 @@
+// Filename: stTerrain.I
+// Created by:  drose (11Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::is_valid
+//       Access: Published
+//  Description: Returns true if the terrain data is well-defined and
+//               ready to use.
+////////////////////////////////////////////////////////////////////
+INLINE bool STTerrain::
+is_valid() const {
+  return _is_valid && !_st_vertex_attribs.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_normal_map
+//       Access: Published
+//  Description: Returns the normal map that should be applied to the
+//               terrain.  This will be loaded and supplied to the
+//               shader.
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &STTerrain::
+get_normal_map() const {
+  return _normal_map;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_splat_map
+//       Access: Published
+//  Description: Returns the splat map that should be applied to the
+//               terrain.  This will be loaded and supplied to the
+//               shader.  Presumably, the shader will use the channels
+//               of this map to determine which of the splat layers
+//               are to be rendered at any given point.
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &STTerrain::
+get_splat_map() const {
+  return _splat_map;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_num_splat_layers
+//       Access: Published
+//  Description: Returns the number of splat layers that are to be
+//               applied to the terrain.  This must be consistent with
+//               c_nNumTerrainSplatLayers in SpeedTree's TerrainRI.h.
+////////////////////////////////////////////////////////////////////
+INLINE int STTerrain::
+get_num_splat_layers() const {
+  return _splat_layers.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_splat_layer
+//       Access: Published
+//  Description: Returns the nth splat layer that is to be applied to
+//               the terrain.
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &STTerrain::
+get_splat_layer(int n) const {
+  nassertr(n >= 0 && n < (int)_splat_layers.size(), _splat_layers[0]._filename);
+  return _splat_layers[n]._filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_splat_layer_tiling
+//       Access: Published
+//  Description: Returns the tiling value of the nth splat layer.
+//               This is an arbitrary UV scale that is applied to each
+//               layer individually, by the terrain shader.
+////////////////////////////////////////////////////////////////////
+INLINE float STTerrain::
+get_splat_layer_tiling(int n) const {
+  nassertr(n >= 0 && n < (int)_splat_layers.size(), 0.0f);
+  return _splat_layers[n]._tiling;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_splat_layer_color
+//       Access: Published
+//  Description: Returns the overall color of the nth splat layer.  This
+//               is used just to match the color of the grass to its
+//               terrain.
+////////////////////////////////////////////////////////////////////
+INLINE const LVecBase4f &STTerrain::
+get_splat_layer_color(int n) const {
+  nassertr(n >= 0 && n < (int)_splat_layers.size(), _splat_layers[0]._color);
+  return _splat_layers[n]._color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_vertex_format
+//       Access: Published
+//  Description: Returns the vertex format of the vertex array that is
+//               supported by this terrain data.  A GeomVertexData of
+//               the requested format will be passed to
+//               fill_vertices().
+////////////////////////////////////////////////////////////////////
+INLINE const GeomVertexFormat *STTerrain::
+get_vertex_format() {
+  return _vertex_format;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_min_height
+//       Access: Published
+//  Description: Returns the smallest height value that might be
+//               returned by get_height().  This is used as a culling
+//               optimization.
+////////////////////////////////////////////////////////////////////
+INLINE float STTerrain::
+get_min_height() const {
+  return _min_height;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_max_height
+//       Access: Published
+//  Description: Returns the largest height value that might be
+//               returned by get_height().  This is used as a culling
+//               optimization.
+////////////////////////////////////////////////////////////////////
+INLINE float STTerrain::
+get_max_height() const {
+  return _max_height;
+}

+ 293 - 0
panda/src/speedtree/stTerrain.cxx

@@ -0,0 +1,293 @@
+// Filename: stTerrain.cxx
+// Created by:  drose (11Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "stTerrain.h"
+#include "indent.h"
+
+TypeHandle STTerrain::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+STTerrain::
+STTerrain() {
+  _is_valid = false;
+  _min_height = 0.0f;
+  _max_height = 1.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::Copy Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+STTerrain::
+STTerrain(const STTerrain &copy) :
+  TypedReferenceCount(copy),
+  Namable(copy),
+  _is_valid(copy._is_valid),
+  _normal_map(copy._normal_map),
+  _splat_layers(copy._splat_layers),
+  _min_height(copy._min_height),
+  _max_height(copy._max_height)
+{
+  set_vertex_format(copy._vertex_format);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::Destructor
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+STTerrain::
+~STTerrain() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::clear
+//       Access: Published, Virtual
+//  Description: Resets the terrain to its initial, unloaded state.
+////////////////////////////////////////////////////////////////////
+void STTerrain::
+clear() {
+  _is_valid = false;
+  _min_height = 0.0f;
+  _max_height = 1.0f;
+
+  _normal_map = "";
+  _splat_map = "";
+  _splat_layers.clear();
+
+  set_vertex_format(NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::load_data
+//       Access: Published, Virtual
+//  Description: This will be called at some point after
+//               initialization.  It should be overridden by a derived
+//               class to load up the terrain data from its source and
+//               fill in the data members of this class appropriately,
+//               especially _is_valid.  After this call, if _is_valid
+//               is true, then get_height() etc. will be called to
+//               query the terrain's data.
+////////////////////////////////////////////////////////////////////
+void STTerrain::
+load_data() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_height
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this should return
+//               the computed height value at point (x, y) of the
+//               terrain, where x and y are unbounded and may refer to
+//               any 2-d point in space.
+////////////////////////////////////////////////////////////////////
+float STTerrain::
+get_height(float x, float y) const {
+  return 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_smooth_height
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this should return
+//               the approximate average height value over a circle of
+//               the specified radius, centered at point (x, y) of the
+//               terrain.
+////////////////////////////////////////////////////////////////////
+float STTerrain::
+get_smooth_height(float x, float y, float radius) const {
+  return get_height(x, y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_slope
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this should return
+//               the directionless slope at point (x, y) of the
+//               terrain, where 0.0 is flat and 1.0 is vertical.  This
+//               is used for determining the legal points to place
+//               trees and grass.
+////////////////////////////////////////////////////////////////////
+float STTerrain::
+get_slope(float x, float y) const {
+  return 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::fill_vertices
+//       Access: Published, Virtual
+//  Description: After load_data() has been called, this will be
+//               called occasionally to populate the vertices for a
+//               terrain cell.
+//
+//               It will be passed a GeomVertexData whose format will
+//               match get_vertex_format(), and already allocated with
+//               num_xy * num_xy rows.  This method should fill the
+//               rows of the data with the appropriate vertex data for
+//               the terrain, over the grid described by the corners
+//               (start_x, start_y) up to and including (start_x +
+//               size_x, start_y + size_xy)--a square of the terrain
+//               with num_xy vertices on a side, arranged in row-major
+//               order.
+////////////////////////////////////////////////////////////////////
+void STTerrain::
+fill_vertices(GeomVertexData *data,
+	      float start_x, float start_y,
+	      float size_xy, int num_xy) const {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::output
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void STTerrain::
+output(ostream &out) const {
+  Namable::output(out);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::write
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void STTerrain::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << *this << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::get_st_vertex_format
+//       Access: Public
+//  Description: Returns a pointer to the SpeedTree array of vertex
+//               attribs that defines the vertex format for SpeedTree.
+////////////////////////////////////////////////////////////////////
+const SpeedTree::SVertexAttribDesc *STTerrain::
+get_st_vertex_format() const {
+  //  return SpeedTree::std_vertex_format;
+  nassertr(!_st_vertex_attribs.empty(), NULL);
+
+  return &_st_vertex_attribs[0];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::set_vertex_format
+//       Access: Protected
+//  Description: Should be called in load_data() by a derived class to
+//               fill in the _vertex_format member.  This will also
+//               compute and store the appropriate value for
+//               _st_vertex_attribs.
+////////////////////////////////////////////////////////////////////
+bool STTerrain::
+set_vertex_format(const GeomVertexFormat *format) {
+  if (format == NULL) {
+    _vertex_format = NULL;
+    _st_vertex_attribs.clear();
+    _is_valid = false;
+    return true;
+  }
+
+  _vertex_format = GeomVertexFormat::register_format(format);
+  if (!convert_vertex_format(_st_vertex_attribs, _vertex_format)) {
+    _is_valid = false;
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::convert_vertex_format
+//       Access: Protected, Static
+//  Description: Populates the indicated st_vertex_attribs vector with
+//               an array of SpeedTree vertex attribute entries that
+//               corresponds to the requested format.  Returns true on
+//               success, or false if the format cannot be represented
+//               in SpeedTree.
+////////////////////////////////////////////////////////////////////
+bool STTerrain::
+convert_vertex_format(STTerrain::VertexAttribs &st_vertex_attribs,
+		      const GeomVertexFormat *format) {
+  st_vertex_attribs.clear();
+
+  if (format->get_num_arrays() != 1) {
+    speedtree_cat.error()
+      << "Cannot represent multi-array vertex format in SpeedTree.\n";
+    return false;
+  }
+
+  const GeomVertexArrayFormat *array = format->get_array(0);
+
+  int num_columns = array->get_num_columns();
+  for (int ci = 0; ci < num_columns; ++ci) {
+    const GeomVertexColumn *column = array->get_column(ci);
+    st_vertex_attribs.push_back(SpeedTree::SVertexAttribDesc());
+    SpeedTree::SVertexAttribDesc &attrib = st_vertex_attribs.back();
+    if (!convert_vertex_column(attrib, column)) {
+      st_vertex_attribs.clear();
+      return false;
+    }
+  }
+
+  st_vertex_attribs.push_back(SpeedTree::st_attrib_end);
+  
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTerrain::convert_vertex_column
+//       Access: Protected, Static
+//  Description: Converts the indicated vertex column definition to
+//               the corresponding SpeedTree::SVertexAttribDesc
+//               format.  Returns true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool STTerrain::
+convert_vertex_column(SpeedTree::SVertexAttribDesc &st_attrib,
+		      const GeomVertexColumn *column) {
+  switch (column->get_numeric_type()) {
+  case GeomEnums::NT_float32:
+    st_attrib.m_eDataType = SpeedTree::VERTEX_ATTRIB_TYPE_FLOAT;
+    break;
+
+  default:
+    speedtree_cat.error()
+      << "Unsupported vertex numeric type for " << *column << "\n";
+    return false;
+  }
+
+  st_attrib.m_uiNumElements = column->get_num_components();
+
+  if (column->get_name() == InternalName::get_vertex()) {
+    st_attrib.m_eSemantic = SpeedTree::VERTEX_ATTRIB_SEMANTIC_POS;
+
+  } else if (column->get_name() == InternalName::get_texcoord()) {
+    st_attrib.m_eSemantic = SpeedTree::VERTEX_ATTRIB_SEMANTIC_TEXCOORD0;
+
+  } else {
+    speedtree_cat.error()
+      << "Unsupported vertex semantic name for " << *column << "\n";
+    return false;
+  }
+
+  nassertr(st_attrib.SizeOfAttrib() == column->get_total_bytes(), false);
+
+  return true;
+}
+

+ 134 - 0
panda/src/speedtree/stTerrain.h

@@ -0,0 +1,134 @@
+// Filename: stTerrain.h
+// Created by:  drose (11Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef STTERRAIN_H
+#define STTERRAIN_H
+
+#include "pandabase.h"
+#include "typedReferenceCount.h"
+#include "namable.h"
+#include "geomVertexData.h"
+#include "speedtree_api.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : STTerrain
+// Description : This is the abstract base class that defines the
+//               interface needed to describe a terrain for rendering
+//               by SpeedTree.  To use it, you must subclass and
+//               override the appropriate virtual methods.  Or,
+//               consider just using STBasicTerrain.
+//
+//               A terrain is defined as a 2-d height function over
+//               all space: get_height(x, y) may be called for any
+//               point in space and it should return a reasonable
+//               value.  A terrain also provides normal maps and splat
+//               maps, as rendered by SpeedTree's Terrain.hlsl shader
+//               file.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDASPEEDTREE STTerrain : public TypedReferenceCount, public Namable {
+protected:  
+  STTerrain();
+  STTerrain(const STTerrain &copy);
+PUBLISHED:
+  virtual ~STTerrain();
+
+  virtual void clear();
+  virtual void load_data()=0;
+
+  INLINE bool is_valid() const;
+
+  INLINE const Filename &get_normal_map() const;
+  INLINE const Filename &get_splat_map() const;
+
+  INLINE int get_num_splat_layers() const;
+  INLINE const Filename &get_splat_layer(int n) const;
+  INLINE float get_splat_layer_tiling(int n) const;
+  INLINE const LVecBase4f &get_splat_layer_color(int n) const;
+
+  INLINE const GeomVertexFormat *get_vertex_format();
+
+  INLINE float get_min_height() const;
+  INLINE float get_max_height() const;
+
+  virtual float get_height(float x, float y) const=0;
+  virtual float get_smooth_height(float x, float y, float radius) const;
+  virtual float get_slope(float x, float y) const;
+
+  virtual void fill_vertices(GeomVertexData *data,
+			     float start_x, float start_y,
+			     float size_xy, int num_xy) const;
+
+  virtual void output(ostream &out) const;
+  virtual void write(ostream &out, int indent_level = 0) const;
+
+public:
+  const SpeedTree::SVertexAttribDesc *get_st_vertex_format() const;
+
+protected:
+  bool set_vertex_format(const GeomVertexFormat *format);
+
+  typedef pvector<SpeedTree::SVertexAttribDesc> VertexAttribs;
+  static bool convert_vertex_format(VertexAttribs &st_vertex_attribs,
+				    const GeomVertexFormat *format);
+  static bool convert_vertex_column(SpeedTree::SVertexAttribDesc &st_attrib,
+				    const GeomVertexColumn *column);
+
+protected:
+  class SplatLayer {
+  public:
+    Filename _filename;
+    float _tiling;
+    LVecBase4f _color;
+  };
+  typedef pvector<SplatLayer> SplatLayers;
+
+protected:
+  bool _is_valid;
+
+  Filename _normal_map;
+  Filename _splat_map;
+  SplatLayers _splat_layers;
+
+  CPT(GeomVertexFormat) _vertex_format;
+  VertexAttribs _st_vertex_attribs;
+
+  float _min_height;
+  float _max_height;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "STTerrain",
+                  TypedReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const STTerrain &terrain) {
+  terrain.output(out);
+  return out;
+}
+
+#include "stTerrain.I"
+
+#endif

+ 0 - 12
panda/src/speedtree/stTree.I

@@ -24,18 +24,6 @@ get_fullpath() const {
   return _fullpath;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: STTree::get_filename
-//       Access: Published
-//  Description: Returns the original filename given for the SRT file
-//               for this tree, before resolving it along the
-//               model-path, as passed to the constructor.
-////////////////////////////////////////////////////////////////////
-INLINE const Filename &STTree::
-get_filename() const {
-  return _filename;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: STTree::is_valid
 //       Access: Published

+ 3 - 11
panda/src/speedtree/stTree.cxx

@@ -25,20 +25,12 @@ TypeHandle STTree::_type_handle;
 //               the read was successful or not.  Note that the
 //               filename must be a fully-qualified pathname; the
 //               STTree constructor does not search the model-path.
-//               (However, the user-specified relative filename may be
-//               specified as an optional second parameter, which is
-//               used for documentary purposes only.)
 ////////////////////////////////////////////////////////////////////
 STTree::
-STTree(const Filename &fullpath, const Filename &filename) :
+STTree(const Filename &fullpath) :
   Namable(fullpath.get_basename_wo_extension()),
-  _fullpath(fullpath),
-  _filename(filename)
+  _fullpath(fullpath)
 {
-  if (_filename.empty()) {
-    _filename = fullpath;
-  }
-
   _is_valid = false;
 
   // Ensure we have a license.
@@ -70,7 +62,7 @@ STTree(const Filename &fullpath, const Filename &filename) :
   }
 
   speedtree_cat.info() 
-    << "Read " << _filename << "\n";
+    << "Read " << _fullpath << "\n";
   _is_valid = true;
 }
 

+ 1 - 3
panda/src/speedtree/stTree.h

@@ -29,13 +29,12 @@ class SpeedTreeNode;
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDASPEEDTREE STTree : public TypedReferenceCount, public Namable {
 PUBLISHED:
-  STTree(const Filename &fullpath, const Filename &filename = Filename());
+  STTree(const Filename &fullpath);
 private:
   STTree(const STTree &copy);
 
 PUBLISHED:
   INLINE const Filename &get_fullpath() const;
-  INLINE const Filename &get_filename() const;
 
   INLINE bool is_valid() const;
 
@@ -47,7 +46,6 @@ public:
 
 private:
   Filename _fullpath;
-  Filename _filename;
   bool _is_valid;
   SpeedTree::CTreeRender _tree;