Browse Source

SpeedTree: first pass

David Rose 15 years ago
parent
commit
d0db1d1f07

+ 47 - 0
panda/src/speedtree/Sources.pp

@@ -0,0 +1,47 @@
+#define BUILD_DIRECTORY $[HAVE_SPEEDTREE]
+
+#define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
+                   dtoolutil:c dtoolbase:c dtool:m prc:c
+
+#define USE_PACKAGES speedtree $[if $[eq $[SPEEDTREE_API],opengl],gl cg cggl] $[if $[eq $[SPEEDTREE_API],directx9],dx9 cg cgdx9]
+#define BUILDING_DLL BUILDING_PANDASKEL
+
+#begin lib_target
+  #define TARGET pandaspeedtree
+  #define LOCAL_LIBS \
+    display text pgraph gobj linmath putil
+    
+  #define COMBINED_SOURCES $[TARGET]_composite1.cxx 
+
+  #define SOURCES \
+    config_speedtree.h \
+    loaderFileTypeSrt.h \
+    speedtree_api.h \
+    speedTreeNode.h \
+    stTransform.h \
+    stTree.h
+
+  // A generated file
+  #define SOURCES $[SOURCES] speedtree_parameters.h 
+    
+  #define INCLUDED_SOURCES \
+    config_speedtree.cxx \
+    loaderFileTypeSrt.cxx \
+    speedtree_api.cxx \
+    speedTreeNode.cxx \
+    stTransform.cxx \
+    stTree.cxx
+
+  #define INSTALL_HEADERS \
+    speedtree_parameters.h \
+    speedtree_api.h \
+    speedTreeNode.h \
+    stTransform.h \
+    stTree.h
+
+  #define IGATESCAN all
+
+#end lib_target
+
+
+#include $[THISDIRPREFIX]speedtree_parameters.h.pp

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

@@ -0,0 +1,107 @@
+// Filename: config_speedtree.cxx
+// Created by:  drose (30Sep10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "config_speedtree.h"
+#include "speedTreeNode.h"
+#include "stTree.h"
+#include "loaderFileTypeSrt.h"
+#include "loaderFileTypeRegistry.h"
+#include "dconfig.h"
+
+Configure(config_speedtree);
+NotifyCategoryDef(speedtree, "");
+
+ConfigureFn(config_speedtree) {
+  init_libspeedtree();
+}
+
+ConfigVariableString speedtree_license
+("speedtree-license", "", 
+ PRC_DESC("Specify the license string to pass to SpeedTreeNode::authorize() by default."));
+
+ConfigVariableFilename speedtree_shaders_dir
+("speedtree-shaders-dir", Filename(Filename::from_os_specific(SPEEDTREE_BIN_DIR), "Shaders"),
+ PRC_DESC("Specifies the directory in which to locate SpeedTree's system "
+	  "shaders at runtime.  If this is empty, the default is based on "
+	  "SPEEDTREE_BIN_DIR, as provided at compile time."));
+
+ConfigVariableBool speedtree_allow_horizontal_billboards
+("speedtree-allow-horizontal-billboards", true,
+ PRC_DESC("Set this true to allow the use of horizontal billboards in "
+	  "SpeedTree, or false to disallow them.  Documentation on this "
+	  "feature is sparse, but presumably enabling them increases "
+	  "visual quality and also causes a greater performance impact."));
+
+ConfigVariableInt speedtree_max_num_visible_cells
+("speedtree-max-num-visible-cells", 75,
+ PRC_DESC("Specifies the maximum number of cells in a single SpeedTree forest "
+	  "frustum.  This is used internally by SpeedTree's billboard system."));
+
+ConfigVariableInt speedtree_max_billboard_images_by_base
+("speedtree-max-billboard-images-by-base", 20,
+ PRC_DESC("Specifies the maximum number of billboard images used by any single "
+	  "tree."));
+
+ConfigVariableDouble speedtree_cull_cell_size
+("speedtree-cull-cell-size", 1200,
+ PRC_DESC("Specifies the size of a single SpeedTree cull cell, in Panda "
+	  "units.  Increasing this number decreases the number of "
+	  "individual calls that must be made to render geometry, "
+	  "while increasing the number of trees that are rendered "
+	  "per call."));
+
+////////////////////////////////////////////////////////////////////
+//     Function: init_libspeedtree
+//  Description: Initializes the library.  This must be called at
+//               least once before any of the functions or classes in
+//               this library can be used.  Normally it will be
+//               called by the static initializers and need not be
+//               called explicitly, but special cases exist.
+////////////////////////////////////////////////////////////////////
+void
+init_libspeedtree() {
+  static bool initialized = false;
+  if (initialized) {
+    return;
+  }
+  initialized = true;
+
+  SpeedTreeNode::init_type();
+  STTree::init_type();
+  LoaderFileTypeSrt::init_type();
+
+  LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
+  reg->register_type(new LoaderFileTypeSrt);
+}
+
+// We need a SpeedTree custom allocator to integrate with Panda's
+// memory management.
+class STCustomAllocator : public SpeedTree::CAllocator {
+public:
+  void *Alloc(size_t block_size) {
+    return PANDA_MALLOC_ARRAY(block_size);
+  }
+  
+  void Free(void *block) {
+    if (block != NULL) {
+      PANDA_FREE_ARRAY(block);
+    }
+  }
+};
+
+// Hook our custom allocator into SpeedTree.
+#ifndef CPPPARSER
+static STCustomAllocator custom_allocator;
+static SpeedTree::CAllocatorInterface allocator_interface(&custom_allocator);
+#endif // CPPPARSER

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

@@ -0,0 +1,39 @@
+// Filename: config_speedtree.h
+// Created by:  drose (30Sep10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 CONFIG_SPEEDTREE_H
+#define CONFIG_SPEEDTREE_H
+
+#include "pandabase.h"
+#include "notifyCategoryProxy.h"
+#include "configVariableBool.h"
+#include "configVariableDouble.h"
+#include "configVariableString.h"
+#include "configVariableInt.h"
+#include "configVariableFilename.h"
+
+NotifyCategoryDecl(speedtree, EXPCL_PANDASKEL, EXPTP_PANDASKEL);
+
+extern ConfigVariableString speedtree_license;
+extern ConfigVariableFilename speedtree_shaders_dir;
+extern ConfigVariableBool speedtree_allow_horizontal_billboards;
+extern ConfigVariableInt speedtree_max_num_visible_cells;
+extern ConfigVariableInt speedtree_max_billboard_images_by_base;
+extern ConfigVariableDouble speedtree_cull_cell_size;
+
+extern EXPCL_PANDASKEL void init_libspeedtree();
+
+#endif
+
+

+ 85 - 0
panda/src/speedtree/loaderFileTypeSrt.cxx

@@ -0,0 +1,85 @@
+// Filename: loaderFileTypeSrt.cxx
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "loaderFileTypeSrt.h"
+#include "speedTreeNode.h"
+#include "stTree.h"
+
+TypeHandle LoaderFileTypeSrt::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeSrt::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+LoaderFileTypeSrt::
+LoaderFileTypeSrt() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeSrt::get_name
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+string LoaderFileTypeSrt::
+get_name() const {
+  return "SpeedTree compiled tree";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeSrt::get_extension
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+string LoaderFileTypeSrt::
+get_extension() const {
+  return "srt";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeSrt::supports_compressed
+//       Access: Published, Virtual
+//  Description: Returns true if this file type can transparently load
+//               compressed files (with a .pz extension), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypeSrt::
+supports_compressed() const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeSrt::load_file
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) LoaderFileTypeSrt::
+load_file(const Filename &path, const LoaderOptions &, 
+          BamCacheRecord *record) const {
+  if (!path.is_regular_file()) {
+    // Quietly fail if the file doesn't exist.  The Loader expects
+    // this.
+    return NULL;
+  }
+
+  PT(STTree) tree = new STTree(path);
+  if (!tree->is_valid()) {
+    return NULL;
+  }
+
+  PT(SpeedTreeNode) st = new SpeedTreeNode(path.get_basename());
+  st->add_instance(tree, STTransform());
+
+  return st.p();
+}

+ 59 - 0
panda/src/speedtree/loaderFileTypeSrt.h

@@ -0,0 +1,59 @@
+// Filename: loaderFileTypeSrt.h
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 LOADERFILETYPESRT_H
+#define LOADERFILETYPESRT_H
+
+#include "pandabase.h"
+
+#include "loaderFileType.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : LoaderFileTypeSrt
+// Description : This defines the Loader interface to read SpeedTree
+//               SRT files, which describe a single tree.  It actually
+//               returns a SpeedTreeNode with just a single tree
+//               within it.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDASKEL LoaderFileTypeSrt : public LoaderFileType {
+public:
+  LoaderFileTypeSrt();
+
+  virtual string get_name() const;
+  virtual string get_extension() const;
+  virtual bool supports_compressed() const;
+
+  virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &options,
+                                  BamCacheRecord *record) const;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    LoaderFileType::init_type();
+    register_type(_type_handle, "LoaderFileTypeSrt",
+                  LoaderFileType::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;
+};
+
+#endif
+

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

@@ -0,0 +1,6 @@
+#include "config_speedtree.cxx"
+#include "loaderFileTypeSrt.cxx"
+#include "speedTreeNode.cxx"
+#include "speedtree_api.cxx"
+#include "stTransform.cxx"
+#include "stTree.cxx"

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

@@ -0,0 +1,164 @@
+// Filename: speedTreeNode.I
+// Created by:  drose (30Sep10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: SpeedTreeNode::is_valid
+//       Access: Published
+//  Description: Returns true if the node is valid and ready to
+//               render, false otherwise.  Note that this might not
+//               become false until after the first time the node is
+//               rendered.
+////////////////////////////////////////////////////////////////////
+INLINE bool SpeedTreeNode::
+is_valid() const {
+  return _is_valid;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::get_num_trees
+//       Access: Published
+//  Description: Returns the number of unique tree objects that have
+//               been added to the node.  This count does not include
+//               multiple instances of the same tree that appear in
+//               different transforms.
+////////////////////////////////////////////////////////////////////
+INLINE int SpeedTreeNode::
+get_num_trees() const {
+  return (int)_trees.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::get_tree
+//       Access: Published
+//  Description: Returns the STTree pointer for the nth tree.
+//               See get_num_trees().
+////////////////////////////////////////////////////////////////////
+INLINE const STTree *SpeedTreeNode::
+get_tree(int n) const {
+  nassertr(n >= 0 && n < (int)_trees.size(), NULL);
+  InstanceList *instance_list = _trees[n];
+  return instance_list->get_tree();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::get_instance_list
+//       Access: Published
+//  Description: Returns a list of transforms that corresponds to the
+//               instances at which the nth tree appears.
+////////////////////////////////////////////////////////////////////
+INLINE const SpeedTreeNode::InstanceList &SpeedTreeNode::
+get_instance_list(int n) const {
+  nassertr(n >= 0 && n < (int)_trees.size(), *(InstanceList *)NULL);
+  InstanceList *instance_list = _trees[n];
+  return *instance_list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::modify_tree
+//       Access: Published
+//  Description: Returns a modifiable STTree pointer for the nth tree
+//               instance.
+////////////////////////////////////////////////////////////////////
+INLINE STTree *SpeedTreeNode::
+modify_tree(int n) {
+  nassertr(n >= 0 && n < (int)_trees.size(), NULL);
+  InstanceList *instance_list = _trees[n];
+  _needs_repopulate = true;
+  return (STTree *)instance_list->get_tree();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE SpeedTreeNode::InstanceList::
+InstanceList(const STTree *tree) : _tree((STTree *)tree) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::operator <
+//       Access: Public
+//  Description: Used for comparison for ov_set.
+////////////////////////////////////////////////////////////////////
+INLINE bool SpeedTreeNode::InstanceList::
+operator < (const InstanceList &other) const {
+  return _tree < other._tree;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::get_tree
+//       Access: Published
+//  Description: Returns the particular tree this list refers to.
+////////////////////////////////////////////////////////////////////
+INLINE const STTree *SpeedTreeNode::InstanceList::
+get_tree() const {
+  return _tree;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::get_num_instances
+//       Access: Published
+//  Description: Returns the number of instances of this tree.
+////////////////////////////////////////////////////////////////////
+INLINE int SpeedTreeNode::InstanceList::
+get_num_instances() const {
+  return (int)_instances.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::get_instance
+//       Access: Published
+//  Description: Returns the transform of the nth instance of this
+//               tree.
+////////////////////////////////////////////////////////////////////
+INLINE STTransform SpeedTreeNode::InstanceList::
+get_instance(int n) const {
+  nassertr(n >= 0 && n < (int)_instances.size(), STTransform::ident_mat());
+  return _instances[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::add_instance
+//       Access: Published
+//  Description: Adds a new instance of this tree at the indicated
+//               transform.  Returns the index number of the new
+//               instance.
+////////////////////////////////////////////////////////////////////
+INLINE int SpeedTreeNode::InstanceList::
+add_instance(const STTransform &transform) {
+  _instances.push_back(transform);
+  return ((int)_instances.size() - 1);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::remove_instance
+//       Access: Published
+//  Description: Removes the nth instance of this tree.
+////////////////////////////////////////////////////////////////////
+INLINE void SpeedTreeNode::InstanceList::
+remove_instance(int n) {
+  nassertv(n >= 0 && n < (int)_instances.size());
+  _instances.erase(_instances.begin() + n);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::DrawCallback::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE SpeedTreeNode::DrawCallback::
+DrawCallback(SpeedTreeNode *node) : _node(node) {
+}

+ 981 - 0
panda/src/speedtree/speedTreeNode.cxx

@@ -0,0 +1,981 @@
+// Filename: speedTreeNode.cxx
+// Created by:  drose (13Mar09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "pandabase.h"
+#include "speedTreeNode.h"
+#include "virtualFileSystem.h"
+#include "config_util.h"
+#include "cullTraverser.h"
+#include "cullableObject.h"
+#include "cullHandler.h"
+#include "omniBoundingVolume.h"
+#include "boundingSphere.h"
+#include "boundingBox.h"
+#include "clockObject.h"
+#include "geomDrawCallbackData.h"
+#include "graphicsStateGuardian.h"
+#include "textureAttrib.h"
+
+#ifdef SPEEDTREE_OPENGL
+#include "glew/glew.h"
+#endif  // SPEEDTREE_OPENGL
+
+bool SpeedTreeNode::_authorized;
+bool SpeedTreeNode::_done_first_init;
+TypeHandle SpeedTreeNode::_type_handle;
+TypeHandle SpeedTreeNode::DrawCallback::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+SpeedTreeNode::
+SpeedTreeNode(const string &name) :
+  PandaNode(name),
+  _forest(*(new SpeedTree::CForestRender))  // HACK!  SpeedTree doesn't destruct unused CForestRender objects correctly.  Temporarily leaking these things until SpeedTree is fixed.
+{
+  init_node();
+  // For now, set an infinite bounding volume.  Maybe in the future
+  // we'll change this to match whatever set of trees we're holding,
+  // though it probably doesn't really matter too much.
+  //set_internal_bounds(new OmniBoundingVolume);
+  //  set_internal_bounds(new BoundingSphere(LPoint3f::zero(), 10.0f));
+
+  // Intialize the render params.
+  SpeedTree::SForestRenderInfo render_info;
+
+  // First, get the shader directory.
+  if (!speedtree_shaders_dir.get_value().is_directory()) {
+    speedtree_cat.warning()
+      << "speedtree-shaders-dir is set to " << speedtree_shaders_dir
+      << ", which doesn't exist.\n";
+  }
+
+  string shaders_dir = speedtree_shaders_dir.get_value().to_os_specific();
+  // Ensure the path ends with a terminal slash; SpeedTree requires this.
+#ifdef WIN32
+  if (!shaders_dir.empty() && shaders_dir[shaders_dir.length() - 1] != '\\') {
+    shaders_dir += "\\";
+  }
+#else
+  if (!shaders_dir.empty() && shaders_dir[shaders_dir.length() - 1] != '/') {
+    shaders_dir += "/";
+  }
+#endif
+
+  render_info.m_strShaderPath = shaders_dir.c_str();
+  render_info.m_nMaxBillboardImagesByBase = speedtree_max_billboard_images_by_base;
+  render_info.m_nNumShadowMaps = 1;
+  render_info.m_nShadowMapResolution = 0;
+  _forest.SetRenderInfo(render_info);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::count_total_instances
+//       Access: Published
+//  Description: Returns the total number of trees that will be
+//               rendered by this node, counting all instances of all
+//               trees.
+////////////////////////////////////////////////////////////////////
+int SpeedTreeNode::
+count_total_instances() const {
+  int total_instances = 0;
+  Trees::const_iterator ti;
+  for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
+    InstanceList *instance_list = (*ti);
+    total_instances += instance_list->get_num_instances();
+  }
+
+  return total_instances;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::add_tree
+//       Access: Published
+//  Description: Adds a new tree for rendering.  Returns the
+//               InstanceList which can be used to add to the
+//               instances for this tree.  If the tree has previously
+//               been added, returns the existing InstanceList.
+////////////////////////////////////////////////////////////////////
+SpeedTreeNode::InstanceList &SpeedTreeNode::
+add_tree(const STTree *tree) {
+  nassertr(is_valid(), *(InstanceList *)NULL);
+  nassertr(tree->is_valid(), *(InstanceList *)NULL);
+
+  InstanceList ilist(tree);
+  Trees::iterator ti = _trees.find(&ilist);
+  if (ti == _trees.end()) {
+    // This is the first time that this particular tree has been
+    // added.
+    InstanceList *instance_list = new InstanceList(tree);
+    pair<Trees::iterator, bool> result = _trees.insert(instance_list);
+    ti = result.first;
+    bool inserted = result.second;
+    nassertr(inserted, *(*ti));
+
+    if (!_forest.RegisterTree((SpeedTree::CTree *)tree->get_tree())) {
+      speedtree_cat.warning()
+	<< "Failed to register tree " << tree->get_filename() << "\n";
+      speedtree_cat.warning()
+	<< SpeedTree::CCore::GetError() << "\n";
+    }
+  }
+
+  _needs_repopulate = true;
+  mark_internal_bounds_stale();
+  InstanceList *instance_list = (*ti);
+  return *instance_list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::remove_tree
+//       Access: Published
+//  Description: Removes all instances of the indicated tree.  Returns
+//               the number of instances removed.
+////////////////////////////////////////////////////////////////////
+int SpeedTreeNode::
+remove_tree(const STTree *tree) {
+  InstanceList ilist(tree);
+  Trees::iterator ti = _trees.find(&ilist);
+  if (ti == _trees.end()) {
+    // The tree was not already present.
+    return 0;
+  }
+
+  if (!_forest.UnregisterTree(tree->get_tree())) {
+    speedtree_cat.warning()
+      << "Failed to unregister tree " << tree->get_filename() << "\n";
+    speedtree_cat.warning()
+      << SpeedTree::CCore::GetError() << "\n";
+  }
+
+  _needs_repopulate = true;
+  mark_internal_bounds_stale();
+
+  InstanceList *instance_list = (*ti);
+  int num_removed = instance_list->get_num_instances();
+  _trees.erase(ti);
+  delete instance_list;
+
+  return num_removed;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::remove_all_trees
+//       Access: Published
+//  Description: Removes all instances of all trees from the node.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+remove_all_trees() {
+  Trees::iterator ti;
+  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())) {
+      speedtree_cat.warning()
+	<< "Failed to unregister tree " << tree->get_filename() << "\n";
+      speedtree_cat.warning()
+	<< SpeedTree::CCore::GetError() << "\n";
+    }
+    delete instance_list;
+  }
+
+  _trees.clear();
+  _needs_repopulate = true;
+  mark_internal_bounds_stale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::has_instance_list
+//       Access: Published
+//  Description: Returns true if the indicated tree has any instances
+//               within this node, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool SpeedTreeNode::
+has_instance_list(const STTree *tree) const {
+  InstanceList ilist(tree);
+  Trees::const_iterator ti = _trees.find(&ilist);
+  return (ti != _trees.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::get_instance_list
+//       Access: Published
+//  Description: Returns a list of transforms that corresponds to the
+//               instances at which the indicated tree appears.  You
+//               should ensure that has_instance_list() returns true
+//               before calling this method.
+////////////////////////////////////////////////////////////////////
+const SpeedTreeNode::InstanceList &SpeedTreeNode::
+get_instance_list(const STTree *tree) const {
+  InstanceList ilist(tree);
+  Trees::const_iterator ti = _trees.find(&ilist);
+  if (ti == _trees.end()) {
+    // The tree was not already present.
+    static InstanceList empty_list((STTree *)NULL);
+    return empty_list;
+  }
+
+  InstanceList *instance_list = (*ti);
+  return *instance_list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::modify_instance_list
+//       Access: Published
+//  Description: Returns a modifiable list of transforms that
+//               corresponds to the instances of this tree.  This is
+//               equivalent to add_tree().
+////////////////////////////////////////////////////////////////////
+SpeedTreeNode::InstanceList &SpeedTreeNode::
+modify_instance_list(const STTree *tree) {
+  return add_tree(tree);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::add_instance
+//       Access: Published
+//  Description: Adds a new instance of the indicated tree at the
+//               indicated transform.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+add_instance(const STTree *tree, const STTransform &transform) {
+  add_tree(tree).add_instance(transform);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::add_instances
+//       Access: Published
+//  Description: Walks the scene graph beginning at root, looking for
+//               nested SpeedTreeNodes.  For each SpeedTreeNode found,
+//               adds all of the instances defined within that
+//               SpeedTreeNode as instances of this node, after
+//               applying the indicated scene-graph transform.
+////////////////////////////////////////////////////////////////////
+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());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::authorize
+//       Access: Published, Static
+//  Description: Make this call to initialized the SpeedTree API and
+//               verify the license.  If an empty string is passed for
+//               the license, the config variable speedtree-license is
+//               consulted.  Returns true on success, false on
+//               failure.  If this call is not made explicitly, it
+//               will be made implicitly the first time a
+//               SpeedTreeNode is created.
+////////////////////////////////////////////////////////////////////
+bool SpeedTreeNode::
+authorize(const string &license) {
+  if (!_authorized) {
+    if (!license.empty()) {
+      SpeedTree::CCore::Authorize(license.c_str());
+    } else {
+      if (!speedtree_license.empty()) {
+	SpeedTree::CCore::Authorize(speedtree_license.c_str());
+      }
+    }
+							     
+    _authorized = SpeedTree::CCore::IsAuthorized();
+
+    SpeedTree::CCore::SetTextureFlip(true);
+  }
+  
+  return _authorized;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::Copy Constructor
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+SpeedTreeNode::
+SpeedTreeNode(const SpeedTreeNode &copy) :
+  PandaNode(copy),
+  _forest(*(new SpeedTree::CForestRender))  // HACK!  SpeedTree doesn't destruct unused CForestRender objects correctly.  Temporarily leaking these things until SpeedTree is fixed.
+{
+  init_node();
+
+  _forest.SetRenderInfo(copy._forest.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())) {
+      speedtree_cat.warning()
+	<< "Failed to register tree " << tree->get_filename() << "\n";
+      speedtree_cat.warning()
+	<< SpeedTree::CCore::GetError() << "\n";
+    }
+
+    _trees.push_back(new InstanceList(*instance_list));
+  }
+
+  _needs_repopulate = true;
+  mark_internal_bounds_stale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *SpeedTreeNode::
+make_copy() const {
+  return new SpeedTreeNode(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::safe_to_combine
+//       Access: Public, Virtual
+//  Description: Returns true if it is generally safe to combine this
+//               particular kind of PandaNode with other kinds of
+//               PandaNodes of compatible type, adding children or
+//               whatever.  For instance, an LODNode should not be
+//               combined with any other PandaNode, because its set of
+//               children is meaningful.
+////////////////////////////////////////////////////////////////////
+bool SpeedTreeNode::
+safe_to_combine() const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::cull_callback
+//       Access: Public, Virtual
+//  Description: This function will be called during the cull
+//               traversal to perform any additional operations that
+//               should be performed at cull time.  This may include
+//               additional manipulation of render state or additional
+//               visible/invisible decisions, or any other arbitrary
+//               operation.
+//
+//               Note that this function will *not* be called unless
+//               set_cull_callback() is called in the constructor of
+//               the derived class.  It is necessary to call
+//               set_cull_callback() to indicated that we require
+//               cull_callback() to be called.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool SpeedTreeNode::
+cull_callback(CullTraverser *trav, CullTraverserData &data) {
+  GraphicsStateGuardian *gsg = DCAST(GraphicsStateGuardian, trav->get_gsg());
+  nassertr(gsg != (GraphicsStateGuardian *)NULL, true);
+  if (!validate_api(gsg)) {
+    return true;
+  }
+
+  ClockObject *clock = ClockObject::get_global_clock();
+  _forest.SetGlobalTime(clock->get_frame_time());
+  _forest.AdvanceGlobalWind();
+  
+  // Compute the modelview and camera transforms, to pass to the
+  // SpeedTree CView structure.
+  CPT(TransformState) modelview = data.get_modelview_transform(trav);
+  modelview = gsg->get_cs_transform()->compose(modelview);
+  CPT(TransformState) camera_transform = modelview->invert_compose(TransformState::make_identity());
+  const LMatrix4f &modelview_mat = modelview->get_mat();
+  const LPoint3f &camera_pos = camera_transform->get_pos();
+  const Lens *lens = trav->get_scene()->get_lens();
+  
+  LMatrix4f projection_mat =
+    LMatrix4f::convert_mat(gsg->get_internal_coordinate_system(), lens->get_coordinate_system()) *
+    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());
+  
+  if (!_forest.UploadViewShaderParameters(_view)) {
+    speedtree_cat.warning()
+      << "Couldn't set view parameters\n";
+    speedtree_cat.warning()
+      << SpeedTree::CCore::GetError() << "\n";
+  }
+
+  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);
+  }
+  //  cerr << _visible_trees.m_aVisibleCells.size() << " visible cells\n";
+
+  // Recurse onto the node's children.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::is_renderable
+//       Access: Public, Virtual
+//  Description: Returns true if there is some value to visiting this
+//               particular node during the cull traversal for any
+//               camera, false otherwise.  This will be used to
+//               optimize the result of get_net_draw_show_mask(), so
+//               that any subtrees that contain only nodes for which
+//               is_renderable() is false need not be visited.
+////////////////////////////////////////////////////////////////////
+bool SpeedTreeNode::
+is_renderable() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::add_for_draw
+//       Access: Public, Virtual
+//  Description: Adds the node's contents to the CullResult we are
+//               building up during the cull traversal, so that it
+//               will be drawn at render time.  For most nodes other
+//               than GeomNodes, this is a do-nothing operation.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+add_for_draw(CullTraverser *trav, CullTraverserData &data) {
+  if (_is_valid) {
+    // We create a CullableObject that has an explicit draw_callback
+    // into this node, so that we can make the appropriate calls into
+    // 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());
+    object->set_draw_callback(new DrawCallback(this));
+    trav->get_cull_handler()->record_object(object, trav);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::prepare_scene
+//       Access: Published
+//  Description: Walks through the scene graph beginning at this node,
+//               and does whatever initialization is required to
+//               render the scene properly with the indicated GSG.  It
+//               is not strictly necessary to call this, since the GSG
+//               will initialize itself when the scene is rendered,
+//               but this may take some of the overhead away from that
+//               process.
+//
+//               In particular, this will ensure that textures within
+//               the scene are loaded in texture memory, and display
+//               lists are built up from static geometry.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+prepare_scene(GraphicsStateGuardianBase *gsgbase, const RenderState *) {
+  GraphicsStateGuardian *gsg = DCAST(GraphicsStateGuardian, gsgbase);
+  if (validate_api(gsg)) {
+    setup_for_render(gsg);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::compute_internal_bounds
+//       Access: Protected, Virtual
+//  Description: Returns a newly-allocated BoundingVolume that
+//               represents the internal contents of the node.  Should
+//               be overridden by PandaNode classes that contain
+//               something internally.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
+                        int &internal_vertices,
+                        int pipeline_stage,
+                        Thread *current_thread) const {
+  internal_vertices = 0;
+
+  SpeedTree::CExtents extents;
+  Trees::const_iterator ti;
+  for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
+    InstanceList *instance_list = (*ti);
+    const STTree *tree = instance_list->get_tree();
+
+    const STInstances &st_instances = instance_list->_instances;
+    STInstances::const_iterator ii;
+    for (ii = st_instances.begin(); ii != st_instances.end(); ++ii) {
+      SpeedTree::CExtents tree_extents = tree->get_tree()->GetExtents();
+      tree_extents.Rotate((*ii).GetRotationAngle());
+      tree_extents.Scale((*ii).GetScale());
+      tree_extents.Translate((*ii).GetPos());
+      extents.ExpandAround(tree_extents);
+    }
+  }
+
+  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]));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::output
+//       Access: Public, Virtual
+//  Description: Writes a brief description of the node to the
+//               indicated output stream.  This is invoked by the <<
+//               operator.  It may be overridden in derived classes to
+//               include some information relevant to the class.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+output(ostream &out) const {
+  PandaNode::output(out);
+  out
+    << " (" << get_num_trees() << " unique trees with "
+    << count_total_instances() << " total instances)";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::write
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+write(ostream &out, int indent_level) const {
+  PandaNode::write(out, indent_level);
+
+  Trees::const_iterator ti;
+  for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
+    InstanceList *instance_list = (*ti);
+    instance_list->write(out, indent_level + 2);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::init_node
+//       Access: Private
+//  Description: Called from the constructor to initialize some
+//               internal values.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+init_node() {
+  PandaNode::set_cull_callback();
+
+  _is_valid = false;
+  _needs_repopulate = false;
+
+  // Ensure we have a license.
+  if (!authorize()) {
+    speedtree_cat.warning()
+      << "SpeedTree license not available.\n";
+    return;
+  }
+
+  _forest.SetHint(SpeedTree::CForest::HINT_MAX_NUM_VISIBLE_CELLS, speedtree_max_num_visible_cells);
+
+  _forest.SetCullCellSize(speedtree_cull_cell_size);
+
+  _is_valid = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::r_add_instances
+//       Access: Private
+//  Description: The recursive implementation of add_instances().
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+r_add_instances(PandaNode *node, const TransformState *transform,
+		Thread *current_thread) {
+  if (node->is_of_type(SpeedTreeNode::get_class_type()) && node != this) {
+    SpeedTreeNode *other = DCAST(SpeedTreeNode, node);
+
+    int num_trees = other->get_num_trees();
+    for (int ti = 0; ti < num_trees; ++ti) {
+      const InstanceList &other_instance_list = other->get_instance_list(ti);
+      const STTree *tree = other_instance_list.get_tree();
+      InstanceList &this_instance_list = add_tree(tree);
+      
+      int num_instances = other_instance_list.get_num_instances();
+      for (int i = 0; i < num_instances; ++i) {
+	CPT(TransformState) other_trans = other_instance_list.get_instance(i);
+	CPT(TransformState) new_trans = transform->compose(other_trans);
+	this_instance_list.add_instance(new_trans.p());
+      }
+    }
+  }
+
+  Children children = node->get_children(current_thread);
+  for (int i = 0; i < children.get_num_children(); i++) {
+    PandaNode *child = children.get_child(i);
+    CPT(TransformState) child_transform = transform->compose(child->get_transform());
+    r_add_instances(child, child_transform, current_thread);
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::repopulate
+//       Access: Private
+//  Description: Rebuilds the internal structures as necessary for
+//               rendering.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+repopulate() {
+  _forest.ClearInstances();
+
+  Trees::iterator ti;
+  for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
+    InstanceList *instance_list = (*ti);
+    const STTree *tree = instance_list->get_tree();
+    const STInstances &instances = instance_list->_instances;
+    if (instances.empty()) {
+      // There are no instances, so don't bother.  (This shouldn't
+      // happen often, because we remove trees from the SpeedTreeNode
+      // when their instance list goes empty, though it's possible if
+      // the user has explicitly removed all of the instances.)
+      continue;
+    }
+
+    if (!_forest.AddInstances(tree->get_tree(), &instances[0], instances.size())) {
+      speedtree_cat.warning()
+	<< "Failed to add " << instances.size()
+	<< " instances for " << *tree << "\n";
+      speedtree_cat.warning()
+	<< SpeedTree::CCore::GetError() << "\n";
+    }
+  }
+  
+  _forest.GetPopulationStats(_population_stats);
+  print_forest_stats(_population_stats);
+
+  // setup billboard caps based on instances-per-cell stats
+  int max_instances_by_cell = 1;
+  for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
+    InstanceList *instance_list = (*ti);
+    const STTree *tree = instance_list->get_tree();
+    const STInstances &instances = instance_list->_instances;
+    if (instances.empty()) {
+      continue;
+    }
+
+    int max_instances = 1;
+    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_by_cell = max(max_instances_by_cell, max_instances);
+  }
+
+  _visible_trees.Reserve(_forest.GetBaseTrees(),
+			 _forest.GetBaseTrees().size(), 
+			 speedtree_max_num_visible_cells, 
+			 max_instances_by_cell,
+			 speedtree_allow_horizontal_billboards);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::validate_api
+//       Access: Private
+//  Description: Returns true if the indicated GSG shares the
+//               appropriate API for this SpeedTreeNode, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool SpeedTreeNode::
+validate_api(GraphicsStateGuardian *gsg) {
+  GraphicsPipe *pipe = gsg->get_pipe();
+  nassertr(pipe != (GraphicsPipe *)NULL, true);
+
+#if defined(SPEEDTREE_OPENGL)
+  static const string compiled_api = "OpenGL";
+#elif defined(SPEEDTREE_DIRECTX9)
+  static const string compiled_api = "DirectX9";
+#else
+  #error Unexpected graphics API.
+#endif
+
+  if (pipe->get_interface_name() != compiled_api) {
+    ostringstream stream;
+    stream
+      << "SpeedTree is compiled for " << compiled_api
+      << ", cannot render with " << pipe->get_interface_name();
+    nassert_raise(stream.str());
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::draw_callback
+//       Access: Private
+//  Description: Called when the node is visited during the draw
+//               traversal, by virtue of our DrawCallback construct.
+//               This makes the calls into SpeedTree to perform the
+//               actual rendering.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+draw_callback(CallbackData *data) {
+  GeomDrawCallbackData *geom_cbdata;
+  DCAST_INTO_V(geom_cbdata, data);
+
+  // Check the input state.
+  const RenderState *state = geom_cbdata->get_object()->_state;
+  
+  bool show_textures = true;
+  const TextureAttrib *texattrib = DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_slot()));
+  if (texattrib != (TextureAttrib *)NULL) {
+    show_textures = !texattrib->has_all_off();
+  }
+  _forest.EnableTexturing(show_textures);
+
+  GraphicsStateGuardian *gsg = DCAST(GraphicsStateGuardian, geom_cbdata->get_gsg());
+
+  setup_for_render(gsg);
+  
+  // start the forest render
+  _forest.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);
+
+  if (!branches || !fronds || !leaf_meshes || !leaf_cards || !billboards) {
+    speedtree_cat.warning()
+      << "Failed to render forest completely: "
+      << branches << " " << fronds << " " << leaf_meshes << " " << leaf_cards << " " << billboards << "\n";
+    speedtree_cat.warning()
+      << SpeedTree::CCore::GetError() << "\n";
+  }
+
+  _forest.EndRender();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::setup_for_render
+//       Access: Private
+//  Description: Does whatever calls are necessary to set up the
+//               forest for rendering--create vbuffers, load shaders,
+//               and whatnot.  Primarily, this is the calls to
+//               InitTreeGraphics and the like.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+setup_for_render(GraphicsStateGuardian *gsg) {
+  if (!_done_first_init) {
+    // This is the first time we have entered the draw callback since
+    // creating any SpeedTreeNode.  Now we have an opportunity to do
+    // any initial setup that requires a graphics context.
+    
+#ifdef SPEEDTREE_OPENGL
+    // For OpenGL, we have to ensure GLEW has been initialized.
+    // (SpeedTree uses it, though Panda doesn't.)
+    GLenum err = glewInit();
+    if (err != GLEW_OK) {
+      speedtree_cat.error()
+	<< "GLEW initialization failed: %s\n", glewGetErrorString(err);
+      // Can't proceed without GLEW.
+      _is_valid = false;
+      return;
+    }
+
+    // Insist that OpenGL 2.0 is available as the SpeedTree renderer
+    // 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";
+      _is_valid = false;
+      return;
+    }
+#endif  // SPEEDTREE_OPENGL
+
+    _done_first_init = true;
+  }
+
+  if (_needs_repopulate) {
+    repopulate();
+
+    // Now init per-tree graphics
+    Trees::const_iterator ti;
+    for (ti = _trees.begin(); ti != _trees.end(); ++ti) {
+      InstanceList *instance_list = (*ti);
+      const STTree *tree = instance_list->get_tree();
+      const STInstances &instances = instance_list->_instances;
+      if (instances.empty()) {
+	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);
+      }
+      
+      if (!_forest.InitTreeGraphics((SpeedTree::CTreeRender *)tree->get_tree(), 
+				    max_instances, speedtree_allow_horizontal_billboards)) {
+	speedtree_cat.warning()
+	  << "Failed to init tree graphics for " << *tree << "\n";
+	speedtree_cat.warning()
+	  << SpeedTree::CCore::GetError() << "\n";
+      }
+    }
+
+    // Init overall graphics
+    if (!_forest.InitGraphics(false)) {
+      speedtree_cat.warning()
+	<< "Failed to init graphics\n";
+      speedtree_cat.warning()
+	<< SpeedTree::CCore::GetError() << "\n";
+      _is_valid = false;
+      return;
+    }
+
+    // This call apparently must be made at draw time, not earlier,
+    // because it might attempt to create OpenGL index buffers and
+    // such.
+    _forest.UpdateTreeCellExtents();
+
+    // If we needed to repopulate, it means we didn't cull in the cull
+    // traversal.  Do it now.
+    _forest.CullAndComputeLOD(_view, _visible_trees);
+
+    _needs_repopulate = false;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::print_forest_stats
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+print_forest_stats(const SpeedTree::CForest::SPopulationStats &forest_stats) const {
+  fprintf(stderr, "\n                Forest Population Statistics\n");
+  fprintf(stderr, "   ---------------------------------------------------\n");
+  fprintf(stderr, "                    # of tree cull cells: %d\n", forest_stats.m_nNumCells);
+  fprintf(stderr, "                  # of unique base trees: %d\n", forest_stats.m_nNumBaseTrees);
+  fprintf(stderr, "                    total # of instances: %d\n", forest_stats.m_nNumInstances);
+  fprintf(stderr, "         average # of instances per base: %g\n", forest_stats.m_fAverageNumInstancesPerBase);
+  fprintf(stderr, "  max # of billboards/instances per cell: %d\n", forest_stats.m_nMaxNumBillboardsPerCell);
+  fprintf(stderr, "    max # of instances per cell per base:\n");
+  SpeedTree::CMap<const SpeedTree::CTree*, SpeedTree::st_int32>::const_iterator i;
+  for (i = forest_stats.m_mMaxNumInstancesPerCellPerBase.begin( ); i != forest_stats.m_mMaxNumInstancesPerCellPerBase.end( ); ++i) {
+    fprintf(stderr, "        %35s: %4d\n", SpeedTree::CFixedString(i->first->GetFilename( )).NoPath( ).c_str( ), i->second);
+  }
+  fprintf(stderr, "            average # instances per cell: %g\n", forest_stats.m_fAverageInstancesPerCell);
+  fprintf(stderr, "               max # of billboard images: %d\n", forest_stats.m_nMaxNumBillboardImages);
+  fprintf(stderr, "\n");
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               SpeedTreeNode.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  PandaNode::write_datagram(manager, dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type SpeedTreeNode is encountered
+//               in the Bam file.  It should create the SpeedTreeNode
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *SpeedTreeNode::
+make_from_bam(const FactoryParams &params) {
+  SpeedTreeNode *node = new SpeedTreeNode("");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new SpeedTreeNode.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  PandaNode::fillin(scan, manager);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::InstanceList::
+output(ostream &out) const {
+  out << *_tree << ": " << _instances.size() << " instances.";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::InstanceList::write
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::InstanceList::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << *_tree << ": " << _instances.size() << " instances.\n";
+  STInstances::const_iterator ii;
+  for (ii = _instances.begin(); ii != _instances.end(); ++ii) {
+    indent(out, indent_level + 2)
+      << STTransform(*ii) << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SpeedTreeNode::DrawCallback::do_callback
+//       Access: Public, Virtual
+//  Description: This method called when the callback is triggered; it
+//               *replaces* the original function.  To continue
+//               performing the original function, you must call
+//               cbdata->upcall() during the callback.
+////////////////////////////////////////////////////////////////////
+void SpeedTreeNode::DrawCallback::
+do_callback(CallbackData *data) {
+  _node->draw_callback(data);
+}

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

@@ -0,0 +1,198 @@
+// Filename: speedTreeNode.h
+// Created by:  drose (30Sep10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 SPEEDTREENODE_H
+#define SPEEDTREENODE_H
+
+#include "pandabase.h"
+#include "pandaNode.h"
+#include "pointerTo.h"
+#include "stTree.h"
+#include "stTransform.h"
+#include "callbackObject.h"
+
+#include "speedtree_api.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : SpeedTreeNode
+// Description : Interfaces with the SpeedTree library to render
+//               SpeedTree objects like a collection of trees,
+//               terrain, or grass within the Panda3D scene graph.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDASKEL SpeedTreeNode : public PandaNode {
+private:
+  // This definition is required by InstanceList, below.
+  typedef pvector<SpeedTree::CInstance> STInstances;
+
+PUBLISHED:
+  // This nested class keeps a linear list of transforms, for the
+  // purpose of recording instances of a particular STTree.  It is
+  // used below.
+  class InstanceList {
+  public:
+    INLINE InstanceList(const STTree *tree);
+    INLINE bool operator < (const InstanceList &other) const;
+
+  PUBLISHED:
+    INLINE const STTree *get_tree() const;
+
+    INLINE int get_num_instances() const;
+    INLINE STTransform get_instance(int n) const;
+    MAKE_SEQ(get_instances, get_num_instances, get_instance);
+
+    INLINE int add_instance(const STTransform &transform);
+    INLINE void remove_instance(int n);
+
+    void output(ostream &out) const;
+    void write(ostream &out, int indent_level = 0) const;
+
+  private:
+    PT(STTree) _tree;
+    STInstances _instances;
+    friend class SpeedTreeNode;
+  };
+
+PUBLISHED:
+  SpeedTreeNode(const string &name);
+
+  INLINE bool is_valid() const;
+
+  INLINE int get_num_trees() const;
+  INLINE const STTree *get_tree(int n) const;
+  MAKE_SEQ(get_trees, get_num_trees, get_tree);
+  const InstanceList &get_instance_list(int n) const;
+  MAKE_SEQ(get_instance_lists, get_num_trees, get_instance_list);
+  INLINE STTree *modify_tree(int n);
+
+  int count_total_instances() const;
+
+  InstanceList &add_tree(const STTree *tree);
+  int remove_tree(const STTree *tree);
+  void remove_all_trees();
+
+  bool has_instance_list(const STTree *tree) const;
+  const InstanceList &get_instance_list(const STTree *tree) const;
+  InstanceList &modify_instance_list(const STTree *tree);
+
+  void add_instance(const STTree *tree, const STTransform &transform);
+  void add_instances(const NodePath &root, const TransformState *transform = TransformState::make_identity());
+
+  static bool authorize(const string &license = "");
+
+public:
+  SpeedTreeNode(const SpeedTreeNode &copy);
+
+  virtual PandaNode *make_copy() const;
+  virtual bool safe_to_combine() const;
+
+  virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
+  virtual bool is_renderable() const;
+  virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
+
+  void prepare_scene(GraphicsStateGuardianBase *gsgbase, const RenderState *net_state);
+
+  virtual void compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
+                                       int &internal_vertices,
+                                       int pipeline_stage,
+                                       Thread *current_thread) const;
+
+  virtual void output(ostream &out) const;
+  virtual void write(ostream &out, int indent_level) const;
+
+private:
+  void init_node();
+  void r_add_instances(PandaNode *node, const TransformState *transform,
+		       Thread *current_thread);
+
+  void repopulate();
+  bool validate_api(GraphicsStateGuardian *gsg);
+  void draw_callback(CallbackData *cbdata);
+  void setup_for_render(GraphicsStateGuardian *gsg);
+  void print_forest_stats(const SpeedTree::CForest::SPopulationStats &forest_stats) const;
+
+private:
+  class DrawCallback : public CallbackObject {
+  public:
+    ALLOC_DELETED_CHAIN(DrawCallback);
+    INLINE DrawCallback(SpeedTreeNode *node);
+    virtual void do_callback(CallbackData *cbdata);
+
+  private:
+    PT(SpeedTreeNode) _node;
+
+  public:
+    static TypeHandle get_class_type() {
+      return _type_handle;
+    }
+    static void init_type() {
+      CallbackObject::init_type();
+      register_type(_type_handle, "SpeedTreeNode::DrawCallback",
+		    CallbackObject::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;
+  };
+
+private:
+  // A list of instances per each unique tree.
+  typedef ov_set<InstanceList *, IndirectLess<InstanceList> > Trees;
+  Trees _trees;
+
+  SpeedTree::CForestRender &_forest;  // Hack!
+  SpeedTree::CView _view;
+  SpeedTree::SForestCullResultsRender _visible_trees;
+  SpeedTree::CForest::SPopulationStats _population_stats;
+  bool _needs_repopulate;
+  bool _is_valid;
+
+  static bool _authorized;
+  static bool _done_first_init;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PandaNode::init_type();
+    register_type(_type_handle, "SpeedTreeNode",
+                  PandaNode::get_class_type());
+    DrawCallback::init_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;
+
+  friend class SpeedTreeNode::DrawCallback;
+};
+
+#include "speedTreeNode.I"
+
+#endif

+ 15 - 0
panda/src/speedtree/speedtree_api.cxx

@@ -0,0 +1,15 @@
+// Filename: speedtree_api.cxx
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "speedtree_api.h"

+ 27 - 0
panda/src/speedtree/speedtree_api.h

@@ -0,0 +1,27 @@
+// Filename: speedtree_api.h
+// Created by:  drose (05Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 SPEEDTREE_API_H
+#define SPEEDTREE_API_H
+
+// This header file should be included first, to pull in any of the
+// required headers from the SpeedTree API, needed in this directory.
+
+#include "speedtree_parameters.h"
+#include "Core/Core.h"
+#include "Forest/Forest.h"
+#include "Renderers/OpenGL/OpenGLRenderer.h"
+
+#endif  // SPEEDTREE_API_H
+

+ 18 - 0
panda/src/speedtree/speedtree_parameters.h.pp

@@ -0,0 +1,18 @@
+// This file is read and processed by ppremake to generate
+// speedtree_parameters.h, which is #included by speedtree_api.h.
+
+#output speedtree_parameters.h notouch
+/* speedtree_parameters.h.  Generated automatically by $[PPREMAKE] $[PPREMAKE_VERSION] from $[notdir $[THISFILENAME]]. */
+/********************************** DO NOT EDIT ****************************/
+
+/* We need to define the appropriate macro to tell the SpeedTree
+   headers which API we intend to use.  This should be one of
+   SPEEDTREE_OPENGL or SPEEDTREE_DIRECTX9 (or, when Panda supports it,
+   SPEEDTREE_DIRECTX10). */
+# define SPEEDTREE_$[upcase $[SPEEDTREE_API]]
+
+/* The default directory in which to search for SpeedTree's provided
+   shaders and terrain files. */
+# define SPEEDTREE_BIN_DIR "$[SPEEDTREE_BIN_DIR]"
+
+#end speedtree_parameters.h

+ 130 - 0
panda/src/speedtree/stTransform.I

@@ -0,0 +1,130 @@
+// Filename: stTransform.I
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: STTransform::Default Constructor
+//       Access: Published
+//  Description: The default constructor creates an identity transform.
+////////////////////////////////////////////////////////////////////
+INLINE STTransform::
+STTransform() :
+  _pos(0.0f, 0.0f, 0.0f),
+  _rotate(0.0f),
+  _scale(1.0f)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::Constructor
+//       Access: Published
+//  Description: Construct a transform with componentwise inputs.
+////////////////////////////////////////////////////////////////////
+INLINE STTransform::
+STTransform(const LPoint3f &pos, float rotate, float scale) :
+  _pos(pos),
+  _rotate(rotate),
+  _scale(scale)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::Constructor
+//       Access: Published
+//  Description: Construct a transform with componentwise inputs.
+////////////////////////////////////////////////////////////////////
+INLINE STTransform::
+STTransform(float x, float y, float z, float rotate, float scale) :
+  _pos(x, y, z),
+  _rotate(rotate),
+  _scale(scale)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::Copy Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE STTransform::
+STTransform(const STTransform &copy) :
+  _pos(copy._pos),
+  _rotate(copy._rotate),
+  _scale(copy._scale)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::Copy Assignment Operator
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void STTransform::
+operator = (const STTransform &copy) {
+  _pos = copy._pos;
+  _rotate = copy._rotate;
+  _scale = copy._scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::CInstance constructor
+//       Access: Public
+//  Description: This is used internally to construct an STTransform
+//               from a SpeedTree::CInstance object.
+////////////////////////////////////////////////////////////////////
+INLINE STTransform::
+STTransform(const SpeedTree::CInstance &instance) {
+  const SpeedTree::Vec3 &pos = instance.GetPos();
+  _pos.set(pos[0], pos[1], pos[2]);
+  _rotate = rad_2_deg(instance.GetRotationAngle());
+  _scale = instance.GetScale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::CInstance operator
+//       Access: Public
+//  Description: This is used internally to convert an STTransform
+//               into a SpeedTree::CInstance object.
+////////////////////////////////////////////////////////////////////
+INLINE STTransform::
+operator SpeedTree::CInstance () const {
+  SpeedTree::CInstance instance;
+  instance.SetPos(SpeedTree::Vec3(_pos[0], _pos[1], _pos[2]));
+  instance.SetRotation(deg_2_rad(_rotate));
+  instance.SetScale(_scale);
+  return instance;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::TransformState operator
+//       Access: Public
+//  Description: This is used internally to convert an STTransform
+//               into a TransformState pointer.
+////////////////////////////////////////////////////////////////////
+INLINE STTransform::
+operator CPT(TransformState) () const {
+  return TransformState::make_pos_hpr_scale(_pos, 
+					    LVecBase3f(_rotate, 0.0f, 0.0f),
+					    LVecBase3f(_scale, _scale, _scale));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::ident_mat
+//       Access: Published, Static
+//  Description: Returns a global identity transform object.
+////////////////////////////////////////////////////////////////////
+INLINE const STTransform &STTransform::
+ident_mat() {
+  return _ident_mat;
+}

+ 54 - 0
panda/src/speedtree/stTransform.cxx

@@ -0,0 +1,54 @@
+// Filename: stTransform.cxx
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "stTransform.h"
+
+STTransform STTransform::_ident_mat;
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::Constructor
+//       Access: Published
+//  Description: This constructor accepts a Panda TransformState, for
+//               instance as extracted from the scene graph.
+////////////////////////////////////////////////////////////////////
+STTransform::
+STTransform(const TransformState *trans) {
+#ifndef NDEBUG
+  // Ensure these are initialized to reasonable values in case we fail
+  // an assertion below.
+  _pos.set(0.0f, 0.0f, 0.0f);
+  _rotate = 0.0f;
+  _scale = 1.0f;
+#endif
+
+  nassertv(trans->has_components());
+  _pos = trans->get_pos();
+
+  const LVecBase3f &hpr = trans->get_hpr();
+  nassertv(IS_NEARLY_ZERO(hpr[1]) && IS_NEARLY_ZERO(hpr[2]));
+  _rotate = hpr[0];
+
+  nassertv(trans->has_uniform_scale());
+  _scale = trans->get_uniform_scale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTransform::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void STTransform::
+output(ostream &out) const {
+  out << "STTransform(" << _pos << ", " << _rotate << ", " << _scale << ")";
+}

+ 67 - 0
panda/src/speedtree/stTransform.h

@@ -0,0 +1,67 @@
+// Filename: stTransform.h
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 STTRANSFORM_H
+#define STTRANSFORM_H
+
+#include "pandabase.h"
+#include "transformState.h"
+#include "speedtree_api.h"
+#include "deg_2_rad.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : STTransform
+// Description : Represents a transform that may be applied to a
+//               particular instance of a tree when added to the
+//               SpeedTreeNode.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDASKEL STTransform {
+PUBLISHED:
+  INLINE STTransform();
+  STTransform(const TransformState *trans);
+  INLINE STTransform(const LPoint3f &pos, float rotate = 0.0f, float scale = 1.0f);
+  INLINE STTransform(float x, float y, float z, float rotate, float scale);
+  INLINE STTransform(const STTransform &copy);
+  INLINE void operator = (const STTransform &copy);
+
+public:
+  INLINE STTransform(const SpeedTree::CInstance &instance);
+  INLINE operator SpeedTree::CInstance () const;
+  INLINE operator CPT(TransformState) () const;
+
+PUBLISHED:
+  INLINE static const STTransform &ident_mat();
+
+  INLINE const LPoint3f &get_pos() const;
+  INLINE float get_rotate() const;
+  INLINE float get_scale() const;
+
+  void output(ostream &out) const;
+
+private:
+  LPoint3f _pos;
+  float _rotate;
+  float _scale;
+
+  static STTransform _ident_mat;
+};
+
+INLINE ostream &operator << (ostream &out, const STTransform &transform) {
+  transform.output(out);
+  return out;
+}
+
+#include "stTransform.I"
+
+#endif

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

@@ -0,0 +1,70 @@
+// Filename: stTree.I
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: STTree::get_fullpath
+//       Access: Published
+//  Description: Returns the full pathname to the SRT file that was
+//               loaded for this tree, as passed to the constructor.
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &STTree::
+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
+//  Description: Returns true if the tree was successfully loaded and
+//               is ready to be used, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool STTree::
+is_valid() const {
+  return _is_valid;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTree::get_tree
+//       Access: Public
+//  Description: Returns a const pointer to the internal SpeedTree
+//               object.
+////////////////////////////////////////////////////////////////////
+INLINE const SpeedTree::CTreeRender *STTree::
+get_tree() const {
+  return &_tree;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTree::modify_tree
+//       Access: Public
+//  Description: Returns a modifiable pointer to the internal SpeedTree
+//               object.
+////////////////////////////////////////////////////////////////////
+INLINE SpeedTree::CTreeRender *STTree::
+modify_tree() {
+  return &_tree;
+}

+ 100 - 0
panda/src/speedtree/stTree.cxx

@@ -0,0 +1,100 @@
+// Filename: stTree.cxx
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "stTree.h"
+#include "speedTreeNode.h"
+
+TypeHandle STTree::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTree::Constructor
+//       Access: Published
+//  Description: The constructor reads the indicated SRT file
+//               immediately.  Check is_valid() to determine whether
+//               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) :
+  Namable(fullpath.get_basename_wo_extension()),
+  _fullpath(fullpath),
+  _filename(filename)
+{
+  if (_filename.empty()) {
+    _filename = fullpath;
+  }
+
+  _is_valid = false;
+
+  // Ensure we have a license.
+  if (!SpeedTreeNode::authorize()) {
+    speedtree_cat.warning()
+      << "SpeedTree license not available.\n";
+    return;
+  }
+
+  // Can't use VFS, due to SpeedTree's insistence on using fopen() to
+  // load dds textures and such.  So we go ahead and use the low-level
+  // Filename interface directly.
+  /*
+  Filename tree_filename = filename;
+  if (!tree_filename.resolve_filename(get_model_path(), "srt")) {
+    speedtree_cat.warning()
+      << "Couldn't find: " << filename << "\n";
+    return false;
+  }
+  */
+
+  string os_fullpath = _fullpath.to_os_specific();
+  if (!_tree.LoadTree(os_fullpath.c_str())) {
+    speedtree_cat.warning()
+      << "Couldn't read: " << _fullpath << "\n";
+    speedtree_cat.warning()
+      << SpeedTree::CCore::GetError() << "\n";
+    return;
+  }
+
+  speedtree_cat.info() 
+    << "Read " << _filename << "\n";
+  _is_valid = true;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTree::Copy Constructor
+//       Access: Private
+//  Description: An STTree copy constructor is not supported.
+////////////////////////////////////////////////////////////////////
+STTree::
+STTree(const STTree &copy) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: STTree::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void STTree::
+output(ostream &out) const {
+  if (!is_valid()) {
+    out << "(invalid STTree)";
+  } else {
+    out << "STTree(" << get_name() << ")";
+  }
+}

+ 79 - 0
panda/src/speedtree/stTree.h

@@ -0,0 +1,79 @@
+// Filename: stTree.h
+// Created by:  drose (06Oct10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 STTREE_H
+#define STTREE_H
+
+#include "pandabase.h"
+#include "typedReferenceCount.h"
+#include "namable.h"
+#include "speedtree_api.h"
+
+class SpeedTreeNode;
+
+////////////////////////////////////////////////////////////////////
+//       Class : STTree
+// Description : Encapsulates a single tree model in the SpeedTree
+//               library, as loaded from an SRT file.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDASKEL STTree : public TypedReferenceCount, public Namable {
+PUBLISHED:
+  STTree(const Filename &fullpath, const Filename &filename = Filename());
+private:
+  STTree(const STTree &copy);
+
+PUBLISHED:
+  INLINE const Filename &get_fullpath() const;
+  INLINE const Filename &get_filename() const;
+
+  INLINE bool is_valid() const;
+
+  virtual void output(ostream &out) const;
+
+public:
+  INLINE const SpeedTree::CTreeRender *get_tree() const;
+  INLINE SpeedTree::CTreeRender *modify_tree();
+
+private:
+  Filename _fullpath;
+  Filename _filename;
+  bool _is_valid;
+  SpeedTree::CTreeRender _tree;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "STTree",
+                  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 STTree &tree) {
+  tree.output(out);
+  return out;
+}
+
+#include "stTree.I"
+
+#endif