Browse Source

add time_delta interfaces

David Rose 14 years ago
parent
commit
a552cfa859

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

@@ -111,6 +111,58 @@ get_terrain() const {
   return _terrain;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::set_time_delta
+//       Access: Published
+//  Description: Specifies an offset that is to be added each frame to
+//               the global clock's frame_time for the purpose of
+//               animating the trees in this particular node.  Also
+//               see set_global_time_delta().
+////////////////////////////////////////////////////////////////////
+INLINE void SpeedTreeNode::
+set_time_delta(double delta) {
+  _time_delta = delta;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::get_time_delta
+//       Access: Published
+//  Description: Returns an offset that is to be added each frame to
+//               the global clock's frame_time for the purpose of
+//               animating the trees in this particular node.  Also
+//               see get_global_time_delta().
+////////////////////////////////////////////////////////////////////
+INLINE double SpeedTreeNode::
+get_time_delta() const {
+  return _time_delta;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::set_global_time_delta
+//       Access: Published, Static
+//  Description: Specifies an offset that is to be added each frame to
+//               the global clock's frame_time for the purpose of
+//               animating the trees in all SpeedTreeNodes.  Also
+//               see set_time_delta().
+////////////////////////////////////////////////////////////////////
+INLINE void SpeedTreeNode::
+set_global_time_delta(double delta) {
+  _global_time_delta = delta;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::get_global_time_delta
+//       Access: Published, Static
+//  Description: Returns an offset that is to be added each frame to
+//               the global clock's frame_time for the purpose of
+//               animating the trees in all SpeedTreeNodes.  Also
+//               see get_time_delta().
+////////////////////////////////////////////////////////////////////
+INLINE double SpeedTreeNode::
+get_global_time_delta() {
+  return _global_time_delta;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SpeedTreeNode::InstanceList::Constructor
 //       Access: Public

+ 146 - 123
panda/src/speedtree/speedTreeNode.cxx

@@ -43,6 +43,7 @@
 #include "dxGraphicsStateGuardian9.h"
 #endif
 
+double SpeedTreeNode::_global_time_delta = 0.0;
 bool SpeedTreeNode::_authorized;
 bool SpeedTreeNode::_done_first_init;
 TypeHandle SpeedTreeNode::_type_handle;
@@ -65,13 +66,14 @@ PStatCollector SpeedTreeNode::_draw_speedtree_terrain_update_pcollector("Draw:Sp
 ////////////////////////////////////////////////////////////////////
 SpeedTreeNode::
 SpeedTreeNode(const string &name) :
-  PandaNode(name)
+  PandaNode(name),
 #ifdef ST_DELETE_FOREST_HACK
   // Early versions of SpeedTree don't destruct unused CForestRender
   // objects correctly.  To avoid crashes, we have to leak these
   // things.
-  , _forest_render(*(new SpeedTree::CForestRender))
+  _forest_render(*(new SpeedTree::CForestRender)),
 #endif
+  _time_delta(0.0)
 {
   init_node();
   // For now, set an infinite bounding volume.  Maybe in the future
@@ -92,14 +94,14 @@ SpeedTreeNode(const string &name) :
       shaders_dir = token_filename.get_dirname();
     } else {
       if (!shaders_dir.is_directory()) {
-	speedtree_cat.warning()
-	  << "speedtree-shaders-dir is set to " << shaders_dir
-	  << ", which doesn't exist.\n";
+        speedtree_cat.warning()
+          << "speedtree-shaders-dir is set to " << shaders_dir
+          << ", which doesn't exist.\n";
       } else {
-	speedtree_cat.warning()
-	  << "speedtree-shaders-dir is set to " << shaders_dir
-	  << ", which exists but doesn't contain " <<  token_filename
-	  << ".\n";
+        speedtree_cat.warning()
+          << "speedtree-shaders-dir is set to " << shaders_dir
+          << ", which exists but doesn't contain " <<  token_filename
+          << ".\n";
       }
     }
   }
@@ -169,7 +171,7 @@ add_tree(const STTree *tree) {
 
     if (!_forest_render.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
       speedtree_cat.warning()
-	<< "Failed to register tree " << tree->get_fullpath() << "\n";
+        << "Failed to register tree " << tree->get_fullpath() << "\n";
       write_error(speedtree_cat.warning());
     }
   }
@@ -225,7 +227,7 @@ remove_all_trees() {
     const STTree *tree = instance_list->get_tree();
     if (!_forest_render.UnregisterTree(tree->get_tree())) {
       speedtree_cat.warning()
-	<< "Failed to unregister tree " << tree->get_fullpath() << "\n";
+        << "Failed to unregister tree " << tree->get_fullpath() << "\n";
       write_error(speedtree_cat.warning());
     }
     delete instance_list;
@@ -313,7 +315,7 @@ void SpeedTreeNode::
 add_instances(const NodePath &root, const TransformState *transform) {
   nassertv(!root.is_empty());
   r_add_instances(root.node(), transform->compose(root.get_transform()),
-		  Thread::get_current_thread());
+                  Thread::get_current_thread());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -361,12 +363,12 @@ add_instances_from(const SpeedTreeNode *other, const TransformState *transform)
       CPT(TransformState) new_trans = transform->compose(other_trans);
 
       if (speedtree_follow_terrain && has_terrain()) {
-	STTransform new_transform = new_trans;
-	new_transform._pos[2] = _terrain->get_height(new_transform._pos[0], new_transform._pos[1]);
-	this_instance_list.add_instance(new_transform);
+        STTransform new_transform = new_trans;
+        new_transform._pos[2] = _terrain->get_height(new_transform._pos[0], new_transform._pos[1]);
+        this_instance_list.add_instance(new_transform);
 
       } else {
-	this_instance_list.add_instance(new_trans.p());
+        this_instance_list.add_instance(new_trans.p());
       }
     }
   }
@@ -387,12 +389,12 @@ add_instances_from(const SpeedTreeNode *other, const TransformState *transform)
 ////////////////////////////////////////////////////////////////////
 void SpeedTreeNode::
 add_random_instances(const STTree *tree, int quantity, 
-		     float x_min, float x_max, 
-		     float y_min, float y_max,
-		     float scale_min, float scale_max,
-		     float height_min, float height_max,
-		     float slope_min, float slope_max,
-		     Randomizer &randomizer) {
+                     float x_min, float x_max, 
+                     float y_min, float y_max,
+                     float scale_min, float scale_max,
+                     float height_min, float height_max,
+                     float slope_min, float slope_max,
+                     Randomizer &randomizer) {
   InstanceList &instance_list = add_tree(tree);
   _needs_repopulate = true;
 
@@ -407,12 +409,12 @@ add_random_instances(const STTree *tree, int quantity,
       // Spin till we find a valid match with terrain.
       int repeat_count = speedtree_max_random_try_count;
       while (!_terrain->placement_is_acceptable(transform._pos[0], transform._pos[1], height_min, height_max, slope_min, slope_max)) {
-	transform._pos[0] = randomizer.random_real(x_max - x_min) + x_min;
-	transform._pos[1] = randomizer.random_real(y_max - y_min) + y_min;
-	if (--repeat_count == 0) {
-	  nassert_raise("Exceeded speedtree-max-random-try-count; bad placement parameters?");
-	  return;
-	}
+        transform._pos[0] = randomizer.random_real(x_max - x_min) + x_min;
+        transform._pos[1] = randomizer.random_real(y_max - y_min) + y_min;
+        if (--repeat_count == 0) {
+          nassert_raise("Exceeded speedtree-max-random-try-count; bad placement parameters?");
+          return;
+        }
       }
       transform._pos[2] = _terrain->get_height(transform._pos[0], transform._pos[1]);
       
@@ -483,7 +485,7 @@ add_from_stf(const Filename &stf_filename, const LoaderOptions &options) {
 ////////////////////////////////////////////////////////////////////
 bool SpeedTreeNode::
 add_from_stf(istream &in, const Filename &pathname, 
-	     const LoaderOptions &options, Loader *loader) {
+             const LoaderOptions &options, Loader *loader) {
   if (loader == NULL) {
     loader = Loader::get_global_ptr();
   }
@@ -514,14 +516,14 @@ add_from_stf(istream &in, const Filename &pathname,
       PT(PandaNode) srt_root = loader->load_sync(srt_filename);
 
       if (srt_root != NULL) {
-	NodePath srt(srt_root);
-	NodePath srt_np = srt.find("**/+SpeedTreeNode");
-	if (!srt_np.is_empty()) {
-	  SpeedTreeNode *srt_node = DCAST(SpeedTreeNode, srt_np.node());
-	  if (srt_node->get_num_trees() >= 1) {
-	    tree = srt_node->get_tree(0);
-	  }
-	}
+        NodePath srt(srt_root);
+        NodePath srt_np = srt.find("**/+SpeedTreeNode");
+        if (!srt_np.is_empty()) {
+          SpeedTreeNode *srt_node = DCAST(SpeedTreeNode, srt_np.node());
+          if (srt_node->get_num_trees() >= 1) {
+            tree = srt_node->get_tree(0);
+          }
+        }
       }
       already_loaded[srt_filename] = tree;
     }
@@ -536,14 +538,14 @@ add_from_stf(istream &in, const Filename &pathname,
       in >> pos[0] >> pos[1] >> pos[2] >> rotate >> scale;
 
       if (!speedtree_5_2_stf) {
-	// 5.1 or earlier stf files also included these additional
-	// values, which we will ignore:
-	float height_min, height_max, slope_min, slope_max;
-	in >> height_min >> height_max >> slope_min >> slope_max;
+        // 5.1 or earlier stf files also included these additional
+        // values, which we will ignore:
+        float height_min, height_max, slope_min, slope_max;
+        in >> height_min >> height_max >> slope_min >> slope_max;
       }
 
       if (tree != NULL) {
-	add_instance(tree, STTransform(pos, rad_2_deg(rotate), scale));
+        add_instance(tree, STTransform(pos, rad_2_deg(rotate), scale));
       }
     }
     in >> os_filename;
@@ -664,19 +666,19 @@ snap_to_terrain() {
     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);
+        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);
+        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);
       }
     }
   }
@@ -746,12 +748,24 @@ reload_config() {
 
   _terrain_render.SetMaxAnisotropy(speedtree_max_anisotropy);
   _terrain_render.SetHint(SpeedTree::CTerrain::HINT_MAX_NUM_VISIBLE_CELLS, 
-			  speedtree_max_num_visible_cells);
+                          speedtree_max_num_visible_cells);
   _visible_terrain.Reserve(speedtree_max_num_visible_cells);
 
   _needs_repopulate = true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::set_wind
+//       Access: Published
+//  Description: Specifies the overall wind strength and direction.
+//               Gusts are controlled internally.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+set_wind(double strength, const LVector3f &direction) {
+  _forest_render.SetGlobalWindStrength(strength);
+  _forest_render.SetGlobalWindDirection(SpeedTree::Vec3(direction[0], direction[1], direction[2]));
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SpeedTreeNode::authorize
 //       Access: Published, Static
@@ -770,10 +784,10 @@ authorize(const string &license) {
       SpeedTree::CCore::Authorize(license.c_str());
     } else {
       if (!speedtree_license.empty()) {
-	SpeedTree::CCore::Authorize(speedtree_license.c_str());
+        SpeedTree::CCore::Authorize(speedtree_license.c_str());
       }
     }
-							     
+                                                             
     _authorized = SpeedTree::CCore::IsAuthorized();
 
     SpeedTree::CCore::SetTextureFlip(true);
@@ -791,13 +805,14 @@ SpeedTreeNode::
 SpeedTreeNode(const SpeedTreeNode &copy) :
   PandaNode(copy),
   _os_shaders_dir(copy._os_shaders_dir),
-  _shadow_infos(copy._shadow_infos)
+  _shadow_infos(copy._shadow_infos),
 #ifdef ST_DELETE_FOREST_HACK
   // Early versions of SpeedTree don't destruct unused CForestRender
   // objects correctly.  To avoid crashes, we have to leak these
   // things.
-  , _forest_render(*(new SpeedTree::CForestRender))
+  _forest_render(*(new SpeedTree::CForestRender)),
 #endif
+  _time_delta(copy._time_delta)
 {
   init_node();
 
@@ -807,7 +822,7 @@ SpeedTreeNode(const SpeedTreeNode &copy) :
   // No way to copy these parameters, so we just re-assign them.
   _terrain_render.SetMaxAnisotropy(speedtree_max_anisotropy);
   _terrain_render.SetHint(SpeedTree::CTerrain::HINT_MAX_NUM_VISIBLE_CELLS, 
-			  speedtree_max_num_visible_cells);
+                          speedtree_max_num_visible_cells);
   _visible_terrain.Reserve(speedtree_max_num_visible_cells);
 
   Trees::const_iterator ti;
@@ -816,7 +831,7 @@ SpeedTreeNode(const SpeedTreeNode &copy) :
     const STTree *tree = instance_list->get_tree();
     if (!_forest_render.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
       speedtree_cat.warning()
-	<< "Failed to register tree " << tree->get_fullpath() << "\n";
+        << "Failed to register tree " << tree->get_fullpath() << "\n";
       write_error(speedtree_cat.warning());
     }
 
@@ -916,8 +931,8 @@ apply_attribs_to_vertices(const AccumulatedAttribs &attribs, int attrib_types,
       STInstances &instances = instance_list->_instances;
       STInstances::iterator sti;
       for (sti = instances.begin(); sti != instances.end(); ++sti) {
-	STTransform orig_transform = *sti;
-	(*sti) = orig_transform * xform;
+        STTransform orig_transform = *sti;
+        (*sti) = orig_transform * xform;
       }
     }
   }
@@ -963,7 +978,7 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   }
 
   ClockObject *clock = ClockObject::get_global_clock();
-  _forest_render.SetGlobalTime(clock->get_frame_time());
+  _forest_render.SetGlobalTime(clock->get_frame_time() + _time_delta + _global_time_delta);
   _forest_render.AdvanceGlobalWind();
   
   // Compute the modelview and camera transforms, to pass to the
@@ -980,9 +995,9 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
     lens->get_projection_mat();
   
   _view.Set(SpeedTree::Vec3(camera_pos[0], camera_pos[1], camera_pos[2]),
-	    SpeedTree::Mat4x4(projection_mat.get_data()),
-	    SpeedTree::Mat4x4(modelview_mat.get_data()),
-	    lens->get_near(), lens->get_far());
+            SpeedTree::Mat4x4(projection_mat.get_data()),
+            SpeedTree::Mat4x4(modelview_mat.get_data()),
+            lens->get_near(), lens->get_far());
 
   // Convert the render state to SpeedTree's input.
   const RenderState *state = data._state;
@@ -1012,17 +1027,17 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
     for (int i = 0; i < la->get_num_on_lights(); ++i) {
       NodePath light = la->get_on_light(i);
       if (!light.is_empty() && light.node()->is_of_type(DirectionalLight::get_class_type())) {
-	// A directional light.
-	DirectionalLight *light_obj = DCAST(DirectionalLight, light.node());
-	if (dlight == NULL || light_obj->get_priority() > dlight->get_priority()) {
-	  // Here's the most important directional light.
-	  dlight = light_obj;
-	  dlight_np = light;
-	}
+        // A directional light.
+        DirectionalLight *light_obj = DCAST(DirectionalLight, light.node());
+        if (dlight == NULL || light_obj->get_priority() > dlight->get_priority()) {
+          // Here's the most important directional light.
+          dlight = light_obj;
+          dlight_np = light;
+        }
       } else if (!light.is_empty() && light.node()->is_of_type(AmbientLight::get_class_type())) {
-	// An ambient light.  We keep the color only.
-	AmbientLight *light_obj = DCAST(AmbientLight, light.node());
-	ambient_color += light_obj->get_color();
+        // An ambient light.  We keep the color only.
+        AmbientLight *light_obj = DCAST(AmbientLight, light.node());
+        ambient_color += light_obj->get_color();
       }
     }
   }
@@ -1105,9 +1120,9 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
     // SpeedTree to render the forest during the actual draw.
     CullableObject *object = 
       new CullableObject(NULL, data._state,
-			 TransformState::make_identity(),
-			 TransformState::make_identity(),
-			 trav->get_gsg());
+                         TransformState::make_identity(),
+                         TransformState::make_identity(),
+                         trav->get_gsg());
     object->set_draw_callback(new DrawCallback(this));
     trav->get_cull_handler()->record_object(object, trav);
   }
@@ -1171,7 +1186,7 @@ compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
   const SpeedTree::Vec3 &emin = extents.Min();
   const SpeedTree::Vec3 &emax = extents.Max();
   internal_bounds = new BoundingBox(LPoint3f(emin[0], emin[1], emin[2]),
-				    LPoint3f(emax[0], emax[1], emax[2]));
+                                    LPoint3f(emax[0], emax[1], emax[2]));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1277,10 +1292,13 @@ init_node() {
   }
 
   _forest_render.SetHint(SpeedTree::CForest::HINT_MAX_NUM_VISIBLE_CELLS, 
-			 speedtree_max_num_visible_cells);
+                         speedtree_max_num_visible_cells);
 
   _forest_render.SetCullCellSize(speedtree_cull_cell_size);
 
+  // Doesn't appear to be necessary to call this explicitly.
+  //_forest_render.EnableWind(true);
+
   _is_valid = true;
 }
 
@@ -1291,7 +1309,7 @@ init_node() {
 ////////////////////////////////////////////////////////////////////
 void SpeedTreeNode::
 r_add_instances(PandaNode *node, const TransformState *transform,
-		Thread *current_thread) {
+                Thread *current_thread) {
   if (node->is_of_type(SpeedTreeNode::get_class_type()) && node != this) {
     SpeedTreeNode *other = DCAST(SpeedTreeNode, node);
     add_instances_from(other, transform);
@@ -1331,8 +1349,8 @@ repopulate() {
 
     if (!_forest_render.AddInstances(tree->get_tree(), &instances[0], instances.size())) {
       speedtree_cat.warning()
-	<< "Failed to add " << instances.size()
-	<< " instances for " << *tree << "\n";
+        << "Failed to add " << instances.size()
+        << " instances for " << *tree << "\n";
       write_error(speedtree_cat.warning());
     }
   }
@@ -1361,10 +1379,10 @@ repopulate() {
   }
 
   _visible_trees.Reserve(_forest_render.GetBaseTrees(),
-			 _forest_render.GetBaseTrees().size(), 
-			 speedtree_max_num_visible_cells, 
-			 max_instances_by_cell,
-			 speedtree_horizontal_billboards);
+                         _forest_render.GetBaseTrees().size(), 
+                         speedtree_max_num_visible_cells, 
+                         max_instances_by_cell,
+                         speedtree_horizontal_billboards);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1385,7 +1403,7 @@ update_terrain_cells() {
   // A temporary vertex data object for populating terrain.
   PT(GeomVertexData) vertex_data = 
     new GeomVertexData("terrain", _terrain->get_vertex_format(), 
-		       GeomEnums::UH_static);
+                       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();
@@ -1399,8 +1417,8 @@ update_terrain_cells() {
     //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);
+                            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();
@@ -1502,7 +1520,7 @@ draw_callback(CallbackData *data) {
 
     if (!terrain) {
       speedtree_cat.warning()
-	<< "Failed to render terrain\n";
+        << "Failed to render terrain\n";
       write_error(speedtree_cat.warning());
       
       // Clear the terrain so we don't keep spamming error messages.
@@ -1525,11 +1543,16 @@ draw_callback(CallbackData *data) {
     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);
+
+    // Sometimes billboards comes back false, particularly if wind is
+    // disabled; but the billboards appear to have been rendered
+    // successfully.  Weird.  Just removing this test from the
+    // condition.
     
-    if (!branches || !fronds || !leaf_meshes || !leaf_cards || !billboards) {
+    if (!branches || !fronds || !leaf_meshes || !leaf_cards /* || !billboards */) {
       speedtree_cat.warning()
-	<< "Failed to render forest completely: "
-	<< branches << " " << fronds << " " << leaf_meshes << " " << leaf_cards << " " << billboards << "\n";
+        << "Failed to render forest completely: "
+        << branches << " " << fronds << " " << leaf_meshes << " " << leaf_cards << " " << billboards << "\n";
       write_error(speedtree_cat.warning());
     }
   }
@@ -1624,7 +1647,7 @@ setup_for_render(GraphicsStateGuardian *gsg) {
     GLenum err = glewInit();
     if (err != GLEW_OK) {
       speedtree_cat.error()
-	<< "GLEW initialization failed: %s\n", glewGetErrorString(err);
+        << "GLEW initialization failed: %s\n", glewGetErrorString(err);
       // Can't proceed without GLEW.
       _is_valid = false;
       return;
@@ -1634,7 +1657,7 @@ setup_for_render(GraphicsStateGuardian *gsg) {
     // requires it.
     if (!GLEW_VERSION_2_0) {
       speedtree_cat.error()
-	<< "The SpeedTree OpenGL implementation requires OpenGL 2.0 or better to run; this system has version " << glGetString(GL_VERSION) << "\n";
+        << "The SpeedTree OpenGL implementation requires OpenGL 2.0 or better to run; this system has version " << glGetString(GL_VERSION) << "\n";
       _is_valid = false;
       return;
     }
@@ -1659,47 +1682,47 @@ setup_for_render(GraphicsStateGuardian *gsg) {
       const STTree *tree = instance_list->get_tree();
       const STInstances &instances = instance_list->_instances;
       if (instances.empty()) {
-	continue;
+        continue;
       }
       
       int max_instances = 2;
       SpeedTree::CMap<const SpeedTree::CTree*, SpeedTree::st_int32>::const_iterator si;
       si = _population_stats.m_mMaxNumInstancesPerCellPerBase.find(tree->get_tree());
       if (si != _population_stats.m_mMaxNumInstancesPerCellPerBase.end()) {
-	max_instances = max(max_instances, (int)si->second);
+        max_instances = max(max_instances, (int)si->second);
       }
 
       // Get the speedtree-textures-dir to pass for initialization.
       string os_textures_dir;
       if (!speedtree_textures_dir.empty()) {
-	os_textures_dir = speedtree_textures_dir.get_value().to_os_specific();
-	// Ensure the path ends with a terminal slash; SpeedTree requires this.
+        os_textures_dir = speedtree_textures_dir.get_value().to_os_specific();
+        // Ensure the path ends with a terminal slash; SpeedTree requires this.
 #if defined(WIN32) || defined(WIN64)
-	if (!os_textures_dir.empty() && os_textures_dir[os_textures_dir.length() - 1] != '\\') {
-	  os_textures_dir += "\\";
-	}
+        if (!os_textures_dir.empty() && os_textures_dir[os_textures_dir.length() - 1] != '\\') {
+          os_textures_dir += "\\";
+        }
 #else
-	if (!os_textures_dir.empty() && os_textures_dir[os_textures_dir.length() - 1] != '/') {
-	  os_textures_dir += "/";
-	}
+        if (!os_textures_dir.empty() && os_textures_dir[os_textures_dir.length() - 1] != '/') {
+          os_textures_dir += "/";
+        }
 #endif
       }
 
       if (!_forest_render.InitTreeGraphics((SpeedTree::CTreeRender *)tree->get_tree(), 
-					   max_instances, speedtree_horizontal_billboards,
-					   os_textures_dir.c_str())) {
-	if (speedtree_cat.is_debug()) {
-	  speedtree_cat.debug()
-	    << "Failed to init tree graphics for " << *tree << "\n";
-	  write_error(speedtree_cat.debug());
-	}
+                                           max_instances, speedtree_horizontal_billboards,
+                                           os_textures_dir.c_str())) {
+        if (speedtree_cat.is_debug()) {
+          speedtree_cat.debug()
+            << "Failed to init tree graphics for " << *tree << "\n";
+          write_error(speedtree_cat.debug());
+        }
       }
     }
 
     // Init overall graphics
     if (!_forest_render.InitGraphics(false)) {
       speedtree_cat.warning()
-	<< "Failed to init graphics\n";
+        << "Failed to init graphics\n";
       write_error(speedtree_cat.warning());
       _is_valid = false;
       return;
@@ -1713,12 +1736,12 @@ setup_for_render(GraphicsStateGuardian *gsg) {
     if (has_terrain()) {
       // Now initialize the terrain.
       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";
-	write_error(speedtree_cat.warning());
+                                speedtree_terrain_resolution,
+                                speedtree_terrain_cell_size,
+                                _terrain->get_st_vertex_format())) {
+        speedtree_cat.warning()
+          << "Failed to init terrain\n";
+        write_error(speedtree_cat.warning());
       }
     }
 
@@ -1757,8 +1780,8 @@ cull_forest() {
       SpeedTree::SForestCullResultsRender &light_cull = _shadow_infos[smi]._light_cull;
 
       _forest_render.ComputeLightView
-	(_forest_render.GetLightDir(), _view.GetFrustumPoints(), smi, 
-	 light_view, 0.0f);
+        (_forest_render.GetLightDir(), _view.GetFrustumPoints(), smi, 
+         light_view, 0.0f);
       
       light_view.SetLodRefPoint(_view.GetCameraPos());
       _forest_render.CullAndComputeLOD(light_view, light_cull, false);
@@ -1949,7 +1972,7 @@ fillin(DatagramIterator &scan, BamReader *manager) {
     if (!srt_np.is_empty()) {
       SpeedTreeNode *srt_node = DCAST(SpeedTreeNode, srt_np.node());
       if (srt_node->get_num_trees() >= 1) {
-	_tree = (STTree *)srt_node->get_tree(0);
+        _tree = (STTree *)srt_node->get_tree(0);
       }
     }
   }

+ 10 - 0
panda/src/speedtree/speedTreeNode.h

@@ -140,6 +140,13 @@ PUBLISHED:
 
   void reload_config();
 
+  void set_wind(double strength, const LVector3f &direction);
+
+  INLINE void set_time_delta(double delta);
+  INLINE double get_time_delta() const;
+  INLINE static void set_global_time_delta(double delta);
+  INLINE static double get_global_time_delta();
+
   static bool authorize(const string &license = "");
 
 public:
@@ -248,6 +255,9 @@ private:
   typedef pvector<ShadowInfo> ShadowInfos;
   ShadowInfos _shadow_infos;
 
+  double _time_delta;
+  static double _global_time_delta;
+
   static bool _authorized;
   static bool _done_first_init;