Browse Source

new egg2pg

David Rose 24 years ago
parent
commit
2268aa2adc

+ 1 - 1
panda/metalibs/pandaegg/Sources.pp

@@ -8,7 +8,7 @@
 #define BUILDING_DLL BUILDING_PANDAEGG
 
 #define COMPONENT_LIBS \
-    egg2sg egg builder
+    egg2pg egg2sg egg builder
 
 #define LOCAL_LIBS putil express
 #define OTHER_LIBS dtoolconfig dtool

+ 151 - 4
panda/src/builder/builder.cxx

@@ -18,12 +18,14 @@
 
 #include "builderFuncs.h"
 #include "builderMisc.h"
-#include <notify.h>
-#include <namedNode.h>
-#include <geomNode.h>
+#include "notify.h"
+#include "namedNode.h"
+#include "geomNode.h"
 #include "pmap.h"
 #include "builder.h"
-#include <renderRelation.h>
+#include "renderRelation.h"
+#include "pandaNode.h"
+#include "qpgeomNode.h"
 
 
 ////////////////////////////////////////////////////////////////////
@@ -92,6 +94,31 @@ public:
   const BuilderBucket *_bucket;
 };
 
+class qpNodeMap : public Namable {
+public:
+  qpNodeMap(PandaNode *node, const BuilderBucket *bucket)
+    : _node(node), _bucket(bucket) { }
+
+  bool operator < (const qpNodeMap &other) const {
+    if (_node != other._node) {
+      return _node < other._node;
+    }
+    if (_bucket->get_name() != other._bucket->get_name()) {
+      return _bucket->get_name() < other._bucket->get_name();
+    }
+    return (_bucket->_state < other._bucket->_state);
+  }
+
+  PandaNode *_node;
+
+  // Although a bucket pointer is stored here in the NodeMap class,
+  // you should not use it except to extract the name and/or the
+  // _trans member.  Remember, this bucket pointer stands for any of
+  // possibly several bucket pointers, all different, except that they
+  // share the same name.
+  const BuilderBucket *_bucket;
+};
+
 
 
 ////////////////////////////////////////////////////////////////////
@@ -215,6 +242,126 @@ build(const string &default_name) {
   return base_geom_node;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Builder::build
+//       Access: Public
+//  Description: Creates Geoms for all the primitives added to all
+//               buckets, and adds them where appropriate to place
+//               them in the scene graph under their respective
+//               parents, and/or returns a single GeomNode that
+//               contains all geometry whose bucket did not reference
+//               a particular scene graph node to parent them to.
+//
+//               If a bucket's _node pointer was a GeomNode, the
+//               geometry will be added directly to that node.  If the
+//               _node pointer was any other kind of node, a GeomNode
+//               will be created and parented to that node, and its
+//               name will be the name of the bucket.  In this case,
+//               the name of the bucket can also be used to different
+//               nodes: if two buckets reference the same node, but
+//               have different names, then two different GeomNodes
+//               are created, one with each name.
+////////////////////////////////////////////////////////////////////
+qpGeomNode *Builder::
+qpbuild(const string &default_name) {
+  typedef pmap<qpNodeMap, qpGeomNode *> GeomNodeMap;
+  GeomNodeMap geom_nodes;
+
+  // First, build all the Geoms and create GeomNodes for them.  Each
+  // unique Node gets its own GeomNode.  If the Node is itself a
+  // GeomNode, that GeomNode is used directly.
+  Buckets::iterator i;
+  for (i = _buckets.begin();
+       i != _buckets.end();
+       ++i) {
+    BuilderBucket *bucket = (*i).get_bucket();
+    PandaNode *node = bucket->_qpnode;
+    //    const string &name = bucket->get_name();
+    qpGeomNode *geom_node = NULL;
+
+    if (node!=NULL && node->is_of_type(qpGeomNode::get_class_type())) {
+      // The node is a GeomNode.  In this case, we simply use that
+      // node.  We can't separate them out by name in this case; we'll
+      // just assign to it the first nonempty name we encounter.
+      geom_node = DCAST(qpGeomNode, node);
+
+      // Since the caller already created this GeomNode and passed it
+      // in, we'll leave it up to the caller to name the node and set
+      // up the state transitions leading into it.
+
+    } else {
+      // The node is not a GeomNode, so look it up in the map.
+      GeomNodeMap::iterator f = geom_nodes.find(qpNodeMap(node, bucket));
+      if (f != geom_nodes.end()) {
+        geom_node = (*f).second;
+
+      } else {
+        // No such node/name combination.  Create a new one.
+        geom_node = bucket->qpmake_geom_node();
+        if (geom_node != NULL) {
+          geom_nodes[qpNodeMap(node, bucket)] = geom_node;
+        }
+      }
+    }
+
+    if (geom_node != NULL) {
+      (*i).build(geom_node);
+    }
+  }
+
+  // Now go through and parent the geom_nodes under their respective
+  // group nodes.  Save out the geom_node associated with a NULL Node;
+  // this one is returned from this function.
+
+  qpGeomNode *base_geom_node = NULL;
+
+  GeomNodeMap::iterator gi;
+
+  for (gi = geom_nodes.begin();
+       gi != geom_nodes.end();
+       ++gi) {
+    const qpNodeMap &nm = (*gi).first;
+    qpGeomNode *geom_node = (*gi).second;
+
+    PandaNode *node = nm._node;
+    const string &name = nm._bucket->get_name();
+    CPT(RenderState) state = nm._bucket->_state;
+
+    // Assign the name to the geom, if it doesn't have one already.
+    if (!geom_node->has_name()) {
+      if (!name.empty()) {
+        geom_node->set_name(name);
+
+      } else if (!default_name.empty()) {
+        geom_node->set_name(default_name);
+      }
+    }
+
+    // Only reparent the geom_node if it has no parent already.
+    int num_parents = geom_node->get_num_parents();
+    if (num_parents == 0) {
+      if (geom_node->get_num_geoms() == 0) {
+        // If there was nothing added, never mind.
+        delete geom_node;
+
+      } else if (node==NULL) {
+        nassertr(base_geom_node == NULL, NULL);
+        base_geom_node = geom_node;
+
+      } else {
+        node->add_child(geom_node);
+        // Now, this is our only opportunity to apply the scene-graph
+        // state specified in the bucket to the node: we have created
+        // our own geom_node for these buckets, and we have parented
+        // it to the scene graph.
+        geom_node->set_state(state);
+      }
+    }
+  }
+
+  return base_geom_node;
+}
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Builder::add_bucket

+ 2 - 0
panda/src/builder/builder.h

@@ -176,6 +176,7 @@
 
 
 class GeomNode;
+class qpGeomNode;
 
 
 ///////////////////////////////////////////////////////////////////
@@ -196,6 +197,7 @@ public:
                                   const BuilderPrimI &prim);
 
   GeomNode *build(const string &default_name = "");
+  qpGeomNode *qpbuild(const string &default_name = "");
 
 protected:
   void add_bucket(const BuilderBucket &bucket);

+ 24 - 2
panda/src/builder/builderBucket.cxx

@@ -21,8 +21,9 @@
 #include "builderBucket.h"
 #include "builderFuncs.h"
 #include "builderMisc.h"
-#include <namedNode.h>
-#include <geomNode.h>
+#include "namedNode.h"
+#include "geomNode.h"
+#include "qpgeomNode.h"
 
 
 BuilderBucket *BuilderBucket::_default_bucket = NULL;
@@ -36,6 +37,7 @@ BuilderBucket *BuilderBucket::_default_bucket = NULL;
 BuilderBucket::
 BuilderBucket() {
   _node = NULL;
+  _qpnode = NULL;
   (*this) = (*get_default_bucket());
 }
 
@@ -48,6 +50,7 @@ BuilderBucket() {
 BuilderBucket::
 BuilderBucket(const BuilderBucket &copy) {
   _node = NULL;
+  _qpnode = NULL;
   (*this) = copy;
 }
 
@@ -69,10 +72,12 @@ operator = (const BuilderBucket &copy) {
   set_colors(copy._colors);
 
   _node = copy._node;
+  _qpnode = copy._qpnode;
   _drawBin = copy._drawBin;
   _drawOrder = copy._drawOrder;
 
   _trans = copy._trans;
+  _state = copy._state;
 
   return *this;
 }
@@ -117,6 +122,20 @@ make_geom_node() {
   return new GeomNode;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderBucket::qpmake_geom_node
+//       Access: Public, Virtual
+//  Description: Called by the builder when it is time to create a new
+//               GeomNode.  This function should allocate and return a
+//               new GeomNode suitable for adding geometry to.  You
+//               may redefine it to return a subclass of GeomNode, or
+//               to do some initialization to the node.
+////////////////////////////////////////////////////////////////////
+qpGeomNode *BuilderBucket::
+qpmake_geom_node() {
+  return new qpGeomNode("");
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BuilderBucket::done_geom
 //       Access: Public, Virtual
@@ -231,6 +250,7 @@ output(ostream &out) const {
 BuilderBucket::
 BuilderBucket(int) {
   _node = NULL;
+  _qpnode = NULL;
 
   _drawBin = -1;
   _drawOrder = 0;
@@ -251,4 +271,6 @@ BuilderBucket(int) {
   _consider_fans = true;
   _max_tfan_angle = 40.0;
   _min_tfan_tris = 0;
+
+  _state = RenderState::make_empty();
 }

+ 16 - 10
panda/src/builder/builderBucket.h

@@ -19,25 +19,28 @@
 #ifndef BUILDERBUCKET_H
 #define BUILDERBUCKET_H
 
-#include <pandabase.h>
+#include "pandabase.h"
 
 #include "builderProperties.h"
 
-#include <namable.h>
-#include <pointerToArray.h>
-#include <luse.h>
-#include <nodeTransitions.h>
-#include <pta_Vertexf.h>
-#include <pta_Normalf.h>
-#include <pta_Colorf.h>
-#include <pta_TexCoordf.h>
+#include "namable.h"
+#include "pointerToArray.h"
+#include "luse.h"
+#include "nodeTransitions.h"
+#include "pta_Vertexf.h"
+#include "pta_Normalf.h"
+#include "pta_Colorf.h"
+#include "pta_TexCoordf.h"
+#include "renderState.h"
 
-#include <stdlib.h>
+#include "stdlib.h"
 
 
 class NamedNode;
 class Geom;
 class GeomNode;
+class PandaNode;
+class qpGeomNode;
 
 
 ///////////////////////////////////////////////////////////////////
@@ -67,6 +70,7 @@ public:
 
   virtual BuilderBucket *make_copy() const;
   virtual GeomNode *make_geom_node();
+  virtual qpGeomNode *qpmake_geom_node();
   virtual Geom *done_geom(Geom *geom);
 
   virtual bool operator < (const BuilderBucket &other) const;
@@ -88,11 +92,13 @@ public:
   virtual void output(ostream &out) const;
 
   NamedNode *_node;
+  PandaNode *_qpnode;
 
   short _drawBin;
   unsigned int _drawOrder;
 
   NodeTransitions _trans;
+  CPT(RenderState) _state;
 
 protected:
   PTA_Vertexf _coords;

+ 54 - 1
panda/src/builder/builderBucketNode.cxx

@@ -17,9 +17,11 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "builderFuncs.h"
-#include <geomNode.h>
 #include "builderBucketNode.h"
 
+#include "geomNode.h"
+#include "qpgeomNode.h"
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BuilderBucketNode::add_prim
 //       Access: Public
@@ -98,3 +100,54 @@ build(GeomNode *geom_node) const {
   return count;
 }
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderBucketNode::build
+//       Access: Public
+//  Description: Builds all the geometry assigned to this particular
+//               bucket, and assigns it to the indicated GeomNode.
+//               Returns the number of Geoms created.
+////////////////////////////////////////////////////////////////////
+int BuilderBucketNode::
+build(qpGeomNode *geom_node) const {
+  int count = 0;
+
+  {
+    // First, the nonindexed.
+    Prims::const_iterator pi, last_pi;
+    last_pi = _prims.begin();
+
+    for (pi = _prims.begin();
+         pi != _prims.end();
+         ++pi) {
+      if ((*last_pi) < (*pi)) {
+        count += mesh_and_build(last_pi, pi, *_bucket, geom_node,
+                                (BuilderPrim *)0);
+        last_pi = pi;
+      }
+    }
+    count += mesh_and_build(last_pi, pi, *_bucket, geom_node,
+                            (BuilderPrim *)0);
+  }
+
+  {
+    // Then, the indexed.
+    IPrims::const_iterator pi, last_pi;
+    last_pi = _iprims.begin();
+
+    for (pi = _iprims.begin();
+         pi != _iprims.end();
+         ++pi) {
+      if ((*last_pi) < (*pi)) {
+        count += mesh_and_build(last_pi, pi, *_bucket, geom_node,
+                                (BuilderPrimI *)0);
+        last_pi = pi;
+      }
+    }
+    count += mesh_and_build(last_pi, pi, *_bucket, geom_node,
+                            (BuilderPrimI *)0);
+  }
+
+  return count;
+}
+

+ 2 - 0
panda/src/builder/builderBucketNode.h

@@ -27,6 +27,7 @@
 #include "pset.h"
 
 class GeomNode;
+class qpGeomNode;
 
 
 ///////////////////////////////////////////////////////////////////
@@ -57,6 +58,7 @@ public:
   INLINE bool operator != (const BuilderBucketNode &other) const;
 
   int build(GeomNode *geom_node) const;
+  int build(qpGeomNode *geom_node) const;
 
 protected:
   typedef pmultiset<BuilderPrim, less<BuilderPrim> > Prims;

+ 3 - 4
panda/src/builder/builderFuncs.I

@@ -23,7 +23,6 @@
 
 #include <geom.h>
 #include <geomprimitives.h>
-#include <geomNode.h>
 
 #include <algorithm>
 
@@ -485,7 +484,7 @@ expand(const PrimType &prim, BuilderBucket &bucket, OutputIterator result) {
 //               creates corresponding geometry for them in the
 //               indicated GeomNode.
 ////////////////////////////////////////////////////////////////////
-template<class InputIterator, class PrimType>
+template<class InputIterator, class PrimType, class GeomNode>
 static int
 build_geoms(InputIterator first, InputIterator last,
             BuilderBucket &bucket, GeomNode *geom_node,
@@ -796,7 +795,7 @@ public:
 //               to infer the PrimType (BuilderPrim or BuilderPrimI)
 //               from the iterator's value type, and template on that.
 ////////////////////////////////////////////////////////////////////
-template<class InputIterator, class PrimType>
+template<class InputIterator, class PrimType, class GeomNode>
 static int
 __mesh_and_build(InputIterator first, InputIterator last,
                  BuilderBucket &bucket, GeomNode *geom_node,
@@ -879,7 +878,7 @@ __mesh_and_build(InputIterator first, InputIterator last,
 //               runs them through the mesher if specified by the
 //               bucket, and builds them into the indicated GeomNode.
 ////////////////////////////////////////////////////////////////////
-template<class InputIterator, class value_type>
+template<class InputIterator, class value_type, class GeomNode>
 int
 mesh_and_build(InputIterator first, InputIterator last,
                BuilderBucket &bucket, GeomNode *geom_node,

+ 1 - 2
panda/src/builder/builderFuncs.h

@@ -25,7 +25,6 @@
 #include <string>
 
 class BuilderBucket;
-class GeomNode;
 
 
 ////////////////////////////////////////////////////////////////////
@@ -57,7 +56,7 @@ expand(const PrimType &prim, BuilderBucket &bucket,
 //               runs them through the mesher if specified by the
 //               bucket, and builds them into the indicated GeomNode.
 ////////////////////////////////////////////////////////////////////
-template<class InputIterator>
+template<class InputIterator, class GeomNode>
 int
 mesh_and_build(InputIterator first, InputIterator last,
                BuilderBucket &bucket, GeomNode *geom_node);

+ 8 - 0
panda/src/builder/builderNormalVisualizer.cxx

@@ -18,6 +18,8 @@
 
 #include "builderFuncs.h"
 #include "builderNormalVisualizer.h"
+#include "geomNode.h"
+#include "qpgeomNode.h"
 
 #ifdef SUPPORT_SHOW_NORMALS
 
@@ -61,6 +63,12 @@ show_normals(GeomNode *node) {
   mesh_and_build(_lines.begin(), _lines.end(), _bucket, node, (BuilderPrim *)0);
 }
 
+void BuilderNormalVisualizer::
+show_normals(qpGeomNode *node) {
+  // Ok, now we've got a bunch of normals saved up; create some geometry.
+  mesh_and_build(_lines.begin(), _lines.end(), _bucket, node, (BuilderPrim *)0);
+}
+
 void BuilderNormalVisualizer::
 add_normal(const BuilderV &center, const BuilderN &normal) {
   BuilderV to = center + normal * _bucket._normal_scale;

+ 1 - 0
panda/src/builder/builderNormalVisualizer.h

@@ -48,6 +48,7 @@ public:
   void add_prim(const BuilderPrimI &prim);
 
   void show_normals(GeomNode *node);
+  void show_normals(qpGeomNode *node);
 
 private:
   void add_normal(const BuilderV &center, const BuilderN &normal);

+ 5 - 0
panda/src/display/graphicsEngine.cxx

@@ -20,6 +20,7 @@
 #include "pipeline.h"
 #include "drawCullHandler.h"
 #include "qpcullTraverser.h"
+#include "clockObject.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsEngine::Constructor
@@ -77,6 +78,10 @@ remove_window(GraphicsWindow *window) {
 void GraphicsEngine::
 render_frame() {
   cull_and_draw_together();
+
+  // **** This doesn't belong here; it really belongs in the Pipeline,
+  // but here it is for now.
+  ClockObject::get_global_clock()->tick();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 107 - 0
panda/src/egg2pg/config_egg2pg.cxx

@@ -0,0 +1,107 @@
+// Filename: config_egg2pg.cxx
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "config_egg2pg.h"
+
+#include "dconfig.h"
+#include "get_config_path.h"
+
+ConfigureDef(config_egg2pg);
+NotifyCategoryDef(egg2pg, "");
+
+bool egg_mesh = config_egg2pg.GetBool("egg-mesh", true);
+bool egg_retesselate_coplanar = config_egg2pg.GetBool("egg-retesselate-coplanar", true);
+bool egg_unroll_fans = config_egg2pg.GetBool("egg-unroll-fans", true);
+bool egg_show_tstrips = config_egg2pg.GetBool("egg-show-tstrips", false);
+bool egg_show_qsheets = config_egg2pg.GetBool("egg-show-qsheets", false);
+bool egg_show_quads = config_egg2pg.GetBool("egg-show-quads", false);
+bool egg_false_color = (egg_show_tstrips | egg_show_qsheets | egg_show_quads);
+bool egg_show_normals = config_egg2pg.GetBool("egg-show-normals", false);
+double egg_normal_scale = config_egg2pg.GetDouble("egg-normal-scale", 1.0);
+bool egg_subdivide_polys = config_egg2pg.GetBool("egg-subdivide-polys", true);
+bool egg_consider_fans = config_egg2pg.GetBool("egg-consider-fans", true);
+double egg_max_tfan_angle = config_egg2pg.GetDouble("egg-max-tfan-angle", 40.0);
+int egg_min_tfan_tris = config_egg2pg.GetInt("egg-min-tfan-tris", 4);
+double egg_coplanar_threshold = config_egg2pg.GetDouble("egg-coplanar-threshold", 0.01);
+bool egg_ignore_mipmaps = config_egg2pg.GetBool("egg-ignore-mipmaps", false);
+bool egg_ignore_filters = config_egg2pg.GetBool("egg-ignore-filters", false);
+bool egg_ignore_clamp = config_egg2pg.GetBool("egg-ignore-clamp", false);
+bool egg_always_decal_textures = config_egg2pg.GetBool("egg-always-decal-textures", false);
+bool egg_ignore_decals = config_egg2pg.GetBool("egg-ignore-decals", false);
+bool egg_flatten = config_egg2pg.GetBool("egg-flatten", true);
+
+// It is almost always a bad idea to set this true.
+bool egg_flatten_siblings = config_egg2pg.GetBool("egg-flatten-siblings", false);
+
+bool egg_show_collision_solids = config_egg2pg.GetBool("egg-show-collision-solids", false);
+
+// When this is true, keep texture pathnames exactly the same as they
+// appeared in the egg file, in particular leaving them as relative
+// paths, rather than letting them reflect the full path at which they
+// were found.  This is particularly useful when generating bam files.
+// However, if the same texture is named by two different relative
+// paths, these will still be collapsed into one texture (using one of
+// the relative paths, chosen arbitrarily).
+bool egg_keep_texture_pathnames = config_egg2pg.GetBool("egg-keep-texture-pathnames", false);
+
+// When this is true, a <NurbsCurve> entry appearing in an egg file
+// will load a ClassicNurbsCurve object instead of the default, a
+// NurbsCurve object.  This only makes a difference when the NURBS++
+// library is available, in which case the default, NurbsCurve, is
+// actually a NurbsPPCurve object.
+bool egg_load_classic_nurbs_curves = config_egg2pg.GetBool("egg-load-classic-nurbs-curves", false);
+
+// When this is true, certain kinds of recoverable errors (not syntax
+// errors) in an egg file will be allowed and ignored when an egg file
+// is loaded.  When it is false, only perfectly pristine egg files may
+// be loaded.
+bool egg_accept_errors = config_egg2pg.GetBool("egg-accept-errors", true);
+
+CoordinateSystem egg_coordinate_system;
+
+ConfigureFn(config_egg2pg) {
+  init_libegg2pg();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: init_libegg2pg
+//  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_libegg2pg() {
+  static bool initialized = false;
+  if (initialized) {
+    return;
+  }
+  initialized = true;
+
+  string csstr = config_egg2pg.GetString("egg-coordinate-system", "default");
+  CoordinateSystem cs = parse_coordinate_system_string(csstr);
+
+  if (cs == CS_invalid) {
+    egg2pg_cat.error()
+      << "Unexpected egg-coordinate-system string: " << csstr << "\n";
+    cs = CS_default;
+  }
+  egg_coordinate_system = (cs == CS_default) ?
+    default_coordinate_system : cs;
+}

+ 60 - 0
panda/src/egg2pg/config_egg2pg.h

@@ -0,0 +1,60 @@
+// Filename: config_egg2pg.h
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef CONFIG_EGG2PG_H
+#define CONFIG_EGG2PG_H
+
+#include "pandabase.h"
+
+#include "coordinateSystem.h"
+#include "notifyCategoryProxy.h"
+#include "dconfig.h"
+
+ConfigureDecl(config_egg2pg, EXPCL_PANDAEGG, EXPTP_PANDAEGG);
+NotifyCategoryDecl(egg2pg, EXPCL_PANDAEGG, EXPTP_PANDAEGG);
+
+extern EXPCL_PANDAEGG bool egg_mesh;
+extern EXPCL_PANDAEGG bool egg_retesselate_coplanar;
+extern EXPCL_PANDAEGG bool egg_unroll_fans;
+extern EXPCL_PANDAEGG bool egg_show_tstrips;
+extern EXPCL_PANDAEGG bool egg_show_qsheets;
+extern EXPCL_PANDAEGG bool egg_show_quads;
+extern EXPCL_PANDAEGG bool egg_false_color;
+extern EXPCL_PANDAEGG bool egg_show_normals;
+extern EXPCL_PANDAEGG double egg_normal_scale;
+extern EXPCL_PANDAEGG bool egg_subdivide_polys;
+extern EXPCL_PANDAEGG bool egg_consider_fans;
+extern EXPCL_PANDAEGG double egg_max_tfan_angle;
+extern EXPCL_PANDAEGG int egg_min_tfan_tris;
+extern EXPCL_PANDAEGG double egg_coplanar_threshold;
+extern EXPCL_PANDAEGG CoordinateSystem egg_coordinate_system;
+extern EXPCL_PANDAEGG bool egg_ignore_mipmaps;
+extern EXPCL_PANDAEGG bool egg_ignore_filters;
+extern EXPCL_PANDAEGG bool egg_ignore_clamp;
+extern EXPCL_PANDAEGG bool egg_always_decal_textures;
+extern EXPCL_PANDAEGG bool egg_ignore_decals;
+extern EXPCL_PANDAEGG bool egg_flatten;
+extern EXPCL_PANDAEGG bool egg_flatten_siblings;
+extern EXPCL_PANDAEGG bool egg_show_collision_solids;
+extern EXPCL_PANDAEGG bool egg_keep_texture_pathnames;
+extern EXPCL_PANDAEGG bool egg_load_classic_nurbs_curves;
+extern EXPCL_PANDAEGG bool egg_accept_errors;
+
+extern EXPCL_PANDAEGG void init_libegg2pg();
+
+#endif

+ 2 - 0
panda/src/egg2pg/egg2sg_composite1.cxx

@@ -0,0 +1,2 @@
+#include "config_egg2pg.cxx"
+#include "qpeggLoader.cxx"

+ 2026 - 0
panda/src/egg2pg/qpeggLoader.cxx

@@ -0,0 +1,2026 @@
+// Filename: qpqpEggLoader.cxx
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pandabase.h"
+
+#include "qpeggLoader.h"
+#include "config_egg2pg.h"
+#include "renderState.h"
+#include "transformState.h"
+#include "textureAttrib.h"
+#include "texturePool.h"
+#include "qpgeomNode.h"
+#include "string_utils.h"
+#include "eggPrimitive.h"
+#include "eggPoint.h"
+#include "eggTextureCollection.h"
+#include "eggNurbsCurve.h"
+#include "eggGroupNode.h"
+#include "eggGroup.h"
+#include "eggPolygon.h"
+#include "eggBin.h"
+#include "eggTable.h"
+
+#include <ctype.h>
+#include <algorithm>
+
+/*
+// This class is used in make_node(EggBin *) to sort LOD instances in
+// order by switching distance.
+class LODInstance {
+public:
+  LODInstance(EggNode *egg_node, RenderRelation *arc);
+  bool operator < (const LODInstance &other) const {
+    return _d->_switch_in < other._d->_switch_in;
+  }
+
+  RenderRelation *_arc;
+  const EggSwitchConditionDistance *_d;
+};
+
+LODInstance::
+LODInstance(EggNode *egg_node, RenderRelation *arc) {
+  assert(arc != NULL);
+  _arc = arc;
+
+  // We expect this egg node to be an EggGroup with an LOD
+  // specification.  That's what the EggBinner collected together,
+  // after all.
+  EggGroup *egg_group = DCAST(EggGroup, egg_node);
+  assert(egg_group->has_lod());
+  const EggSwitchCondition &sw = egg_group->get_lod();
+
+  // For now, this is the only kind of switch condition there is.
+  _d = DCAST(EggSwitchConditionDistance, &sw);
+}
+*/
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpEggLoader::
+qpEggLoader() {
+  // We need to enforce whatever coordinate system the user asked for.
+  _data.set_coordinate_system(egg_coordinate_system);
+  _error = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpEggLoader::
+qpEggLoader(const EggData &data) :
+  _data(data)
+{
+  _error = false;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::build_graph
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+build_graph() {
+  //  _deferred_arcs.clear();
+
+  /*
+  // First, bin up the LOD nodes.
+  EggBinner binner;
+  binner.make_bins(&_data);
+  */
+
+  // Then load up all of the textures.
+  load_textures();
+
+  // Now build up the scene graph.
+  _root = new PandaNode(_data.get_egg_filename().get_basename());
+  make_node(&_data, _root);
+  _builder.qpbuild();
+
+  /*
+  reset_directs();
+  reparent_decals();
+
+  apply_deferred_arcs(_root);
+  */
+}
+
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::reparent_decals
+//       Access: Public
+//  Description: For each node representing a decal base geometry
+//               (i.e. a node corresponding to an EggGroup with the
+//               decal flag set), move all of its nested geometry
+//               directly below the GeomNode representing the group.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+reparent_decals() {
+  Decals::const_iterator di;
+  for (di = _decals.begin(); di != _decals.end(); ++di) {
+    RenderRelation *arc = (*di);
+    nassertv(arc != (RenderRelation *)NULL);
+    PandaNode *node = DCAST(PandaNode, arc->get_child());
+    nassertv(node != (PandaNode *)NULL);
+
+    // First, search for the GeomNode.
+    GeomNode *geom = NULL;
+    int num_children =
+      node->get_num_children(RenderRelation::get_class_type());
+    for (int i = 0; i < num_children; i++) {
+      NodeRelation *child_arc =
+        node->get_child(RenderRelation::get_class_type(), i);
+      nassertv(child_arc != (NodeRelation *)NULL);
+      Node *child = child_arc->get_child();
+      nassertv(child != (Node *)NULL);
+
+      if (child->is_of_type(GeomNode::get_class_type())) {
+        if (geom != (GeomNode *)NULL) {
+          // Oops, too many GeomNodes.
+          egg2pg_cat.error()
+            << "Decal onto " << node->get_name()
+            << " uses base geometry with multiple states.\n";
+          _error = true;
+        }
+        DCAST_INTO_V(geom, child);
+      }
+    }
+
+    if (geom == (GeomNode *)NULL) {
+      // No children were GeomNodes.
+      egg2pg_cat.error()
+        << "Ignoring decal onto " << node->get_name()
+        << "; no geometry within group.\n";
+      _error = true;
+    } else {
+      // Now reparent all of the non-GeomNodes to this node.  We have
+      // to be careful so we don't get lost as we self-modify this
+      // list.
+      int i = 0;
+      while (i < num_children) {
+        NodeRelation *child_arc =
+          node->get_child(RenderRelation::get_class_type(), i);
+        nassertv(child_arc != (NodeRelation *)NULL);
+        Node *child = child_arc->get_child();
+        nassertv(child != (Node *)NULL);
+
+        if (child->is_of_type(GeomNode::get_class_type())) {
+          i++;
+        } else {
+          child_arc->change_parent(geom);
+          num_children--;
+        }
+      }
+    }
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::reset_directs
+//       Access: Public
+//  Description: This applies to all of the nodes marked with the
+//               "render" flag, i.e. direct rendering of a subgraph,
+//               in depth-first order, as opposed to state-sorting
+//               within the subgraph.  For each such node, it moves
+//               all the transitions from the first GeomNode under
+//               that node up to the node itself, just so we'll be
+//               able to state-sort at least the tops of the
+//               subgraphs.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+reset_directs() {
+  Directs::const_iterator di;
+  for (di = _directs.begin(); di != _directs.end(); ++di) {
+    RenderRelation *arc = (*di);
+    nassertv(arc != (RenderRelation *)NULL);
+    PandaNode *node = DCAST(PandaNode, arc->get_child());
+    nassertv(node != (PandaNode *)NULL);
+
+    // First, search for the first GeomNode.
+    GeomNode *geom = NULL;
+    NodeRelation *child_arc = NULL;
+
+    int num_children =
+      node->get_num_children(RenderRelation::get_class_type());
+    for (int i = 0; i < num_children && geom == (GeomNode *)NULL; i++) {
+      child_arc = node->get_child(RenderRelation::get_class_type(), i);
+      nassertv(child_arc != (NodeRelation *)NULL);
+      Node *child = child_arc->get_child();
+      nassertv(child != (Node *)NULL);
+
+      if (child->is_of_type(GeomNode::get_class_type())) {
+        DCAST_INTO_V(geom, child);
+      }
+    }
+
+    if (geom != (GeomNode *)NULL) {
+      // Now copy all of the GeomNode's transitions up to its parent.
+      nassertv(child_arc != (NodeRelation *)NULL);
+      arc->copy_transitions_from(child_arc);
+    }
+  }
+}
+*/
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_nonindexed_primitive
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+make_nonindexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
+                          const LMatrix4d *transform) {
+  BuilderBucket bucket;
+  setup_bucket(bucket, parent, egg_prim);
+
+  LMatrix4d mat;
+
+  if (transform != NULL) {
+    mat = (*transform);
+  } else {
+    mat = egg_prim->get_vertex_to_node();
+  }
+
+  BuilderPrim bprim;
+  bprim.set_type(BPT_poly);
+  if (egg_prim->is_of_type(EggPoint::get_class_type())) {
+    bprim.set_type(BPT_point);
+  }
+
+  if (egg_prim->has_normal()) {
+    Normald norm = egg_prim->get_normal() * mat;
+    norm.normalize();
+    bprim.set_normal(LCAST(float, norm));
+  }
+  if (egg_prim->has_color() && !egg_false_color) {
+    bprim.set_color(egg_prim->get_color());
+  }
+
+  bool has_vert_color = true;
+  EggPrimitive::const_iterator vi;
+  for (vi = egg_prim->begin(); vi != egg_prim->end(); ++vi) {
+    EggVertex *egg_vert = *vi;
+    BuilderVertex bvert(LCAST(float, egg_vert->get_pos3() * mat));
+
+    if (egg_vert->has_normal()) {
+      Normald norm = egg_vert->get_normal() * mat;
+      norm.normalize();
+      bvert.set_normal(LCAST(float, norm));
+    }
+    if (egg_vert->has_color() && !egg_false_color) {
+      bvert.set_color(egg_vert->get_color());
+    } else {
+      // If any vertex doesn't have a color, we can't use any of the
+      // vertex colors.
+      has_vert_color = false;
+    }
+    if (egg_vert->has_uv()) {
+      TexCoordd uv = egg_vert->get_uv();
+      if (egg_prim->has_texture() &&
+          egg_prim->get_texture()->has_transform()) {
+        // If we have a texture matrix, apply it.
+        uv = uv * egg_prim->get_texture()->get_transform();
+      }
+      bvert.set_texcoord(LCAST(float, uv));
+    }
+
+    bprim.add_vertex(bvert);
+  }
+
+  // Finally, if the primitive didn't have a color, and it didn't have
+  // vertex color, make it white.
+  if (!egg_prim->has_color() && !has_vert_color && !egg_false_color) {
+    bprim.set_color(Colorf(1.0, 1.0, 1.0, 1.0));
+  }
+
+  _builder.add_prim(bucket, bprim);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_indexed_primitive
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+make_indexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
+                       const LMatrix4d *transform,
+                       ComputedVerticesMaker &_comp_verts_maker) {
+  /*
+  BuilderBucket bucket;
+  setup_bucket(bucket, parent, egg_prim);
+
+  bucket.set_coords(_comp_verts_maker._coords);
+  bucket.set_normals(_comp_verts_maker._norms);
+  bucket.set_texcoords(_comp_verts_maker._texcoords);
+  bucket.set_colors(_comp_verts_maker._colors);
+
+  LMatrix4d mat;
+
+  if (transform != NULL) {
+    mat = (*transform);
+  } else {
+    mat = egg_prim->get_vertex_to_node();
+  }
+
+  BuilderPrimI bprim;
+  bprim.set_type(BPT_poly);
+  if (egg_prim->is_of_type(EggPoint::get_class_type())) {
+    bprim.set_type(BPT_point);
+  }
+
+  if (egg_prim->has_normal()) {
+    // Define the transform space of the polygon normal.  This will be
+    // the average of all the vertex transform spaces.
+    _comp_verts_maker.begin_new_space();
+    EggPrimitive::const_iterator vi;
+    for (vi = egg_prim->begin(); vi != egg_prim->end(); ++vi) {
+      EggVertex *egg_vert = *vi;
+      _comp_verts_maker.add_vertex_joints(egg_vert, egg_prim);
+    }
+    _comp_verts_maker.mark_space();
+
+    int nindex =
+      _comp_verts_maker.add_normal(egg_prim->get_normal(),
+                                   egg_prim->_dnormals, mat);
+
+    bprim.set_normal(nindex);
+  }
+
+  if (egg_prim->has_color() && !egg_false_color) {
+    int cindex =
+      _comp_verts_maker.add_color(egg_prim->get_color(),
+                                  egg_prim->_drgbas);
+    bprim.set_color(cindex);
+  }
+
+  bool has_vert_color = true;
+  EggPrimitive::const_iterator vi;
+  for (vi = egg_prim->begin(); vi != egg_prim->end(); ++vi) {
+    EggVertex *egg_vert = *vi;
+
+    // Set up the ComputedVerticesMaker for the coordinate space of
+    // the vertex.
+    _comp_verts_maker.begin_new_space();
+    _comp_verts_maker.add_vertex_joints(egg_vert, egg_prim);
+    _comp_verts_maker.mark_space();
+
+    int vindex =
+      _comp_verts_maker.add_vertex(egg_vert->get_pos3(),
+                                   egg_vert->_dxyzs, mat);
+    BuilderVertexI bvert(vindex);
+
+    if (egg_vert->has_normal()) {
+      int nindex =
+        _comp_verts_maker.add_normal(egg_vert->get_normal(),
+                                     egg_vert->_dnormals,
+                                     mat);
+      bvert.set_normal(nindex);
+    }
+
+    if (egg_vert->has_color() && !egg_false_color) {
+      int cindex =
+        _comp_verts_maker.add_color(egg_vert->get_color(),
+                                    egg_vert->_drgbas);
+      bvert.set_color(cindex);
+    } else {
+      // If any vertex doesn't have a color, we can't use any of the
+      // vertex colors.
+      has_vert_color = false;
+    }
+
+    if (egg_vert->has_uv()) {
+      TexCoordd uv = egg_vert->get_uv();
+      LMatrix3d mat;
+
+      if (egg_prim->has_texture() &&
+          egg_prim->get_texture()->has_transform()) {
+        // If we have a texture matrix, apply it.
+        mat = egg_prim->get_texture()->get_transform();
+      } else {
+        mat = LMatrix3d::ident_mat();
+      }
+
+      int tindex =
+        _comp_verts_maker.add_texcoord(uv, egg_vert->_duvs, mat);
+      bvert.set_texcoord(tindex);
+    }
+
+    bprim.add_vertex(bvert);
+  }
+
+  // Finally, if the primitive didn't have a color, and it didn't have
+  // vertex color, make it white.
+  if (!egg_prim->has_color() && !has_vert_color && !egg_false_color) {
+    int cindex =
+      _comp_verts_maker.add_color(Colorf(1.0, 1.0, 1.0, 1.0),
+                                  EggMorphColorList());
+    bprim.set_color(cindex);
+  }
+
+  _builder.add_prim(bucket, bprim);
+  */
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::load_textures
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+load_textures() {
+  // First, collect all the textures that are referenced.
+  EggTextureCollection tc;
+  tc.find_used_textures(&_data);
+
+  // Collapse the textures down by filename only.  Should we also
+  // differentiate by attributes?  Maybe.
+  EggTextureCollection::TextureReplacement replace;
+  tc.collapse_equivalent_textures(EggTexture::E_complete_filename,
+                                  replace);
+
+  EggTextureCollection::iterator ti;
+  for (ti = tc.begin(); ti != tc.end(); ++ti) {
+    PT(EggTexture) egg_tex = (*ti);
+
+    TextureDef def;
+    if (load_texture(def, egg_tex)) {
+      // Now associate the pointers, so we'll be able to look up the
+      // Texture pointer given an EggTexture pointer, later.
+      _textures[egg_tex] = def;
+    }
+  }
+
+  // Finally, associate all of the removed texture references back to
+  // the same pointers as the others.
+  EggTextureCollection::TextureReplacement::const_iterator ri;
+  for (ri = replace.begin(); ri != replace.end(); ++ri) {
+    PT(EggTexture) orig = (*ri).first;
+    PT(EggTexture) repl = (*ri).second;
+
+    _textures[orig] = _textures[repl];
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::load_texture
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool qpEggLoader::
+load_texture(TextureDef &def, const EggTexture *egg_tex) {
+  Texture *tex;
+  if (egg_tex->has_alpha_file()) {
+    tex = TexturePool::load_texture(egg_tex->get_filename(),
+                                    egg_tex->get_alpha_file());
+  } else {
+    tex = TexturePool::load_texture(egg_tex->get_filename());
+  }
+  if (tex == (Texture *)NULL) {
+    return false;
+  }
+
+  if (egg_keep_texture_pathnames) {
+    tex->set_name(egg_tex->get_filename());
+    if (egg_tex->has_alpha_file()) {
+      tex->set_alpha_name(egg_tex->get_alpha_file());
+    } else {
+      tex->clear_alpha_name();
+    }
+  }
+
+  /*
+  PT(TextureApplyTransition) apply =
+    new TextureApplyTransition(TextureApplyProperty::M_modulate);
+  */
+
+  apply_texture_attributes(tex, egg_tex);
+  //  apply_texture_apply_attributes(apply, egg_tex);
+
+  def._texture = TextureAttrib::make(tex);
+  //  def._apply = *(_texture_applies.insert(apply).first);
+
+  return true;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::apply_texture_attributes
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
+  tex->set_name(egg_tex->get_filename().get_fullpath());
+
+  switch (egg_tex->determine_wrap_u()) {
+  case EggTexture::WM_repeat:
+    tex->set_wrapu(Texture::WM_repeat);
+    break;
+
+  case EggTexture::WM_clamp:
+    if (egg_ignore_clamp) {
+      egg2pg_cat.warning()
+        << "Ignoring clamp request\n";
+      tex->set_wrapu(Texture::WM_repeat);
+    } else {
+      tex->set_wrapu(Texture::WM_clamp);
+    }
+    break;
+
+  case EggTexture::WM_unspecified:
+    break;
+
+  default:
+    egg2pg_cat.warning()
+      << "Unexpected texture wrap flag: "
+      << (int)egg_tex->determine_wrap_u() << "\n";
+  }
+
+  switch (egg_tex->determine_wrap_v()) {
+  case EggTexture::WM_repeat:
+    tex->set_wrapv(Texture::WM_repeat);
+    break;
+
+  case EggTexture::WM_clamp:
+    if (egg_ignore_clamp) {
+      egg2pg_cat.warning()
+        << "Ignoring clamp request\n";
+      tex->set_wrapv(Texture::WM_repeat);
+    } else {
+      tex->set_wrapv(Texture::WM_clamp);
+    }
+    break;
+
+  case EggTexture::WM_unspecified:
+    break;
+
+  default:
+    egg2pg_cat.warning()
+      << "Unexpected texture wrap flag: "
+      << (int)egg_tex->determine_wrap_v() << "\n";
+  }
+
+  switch (egg_tex->get_minfilter()) {
+  case EggTexture::FT_nearest:
+    tex->set_minfilter(Texture::FT_nearest);
+    break;
+
+  case EggTexture::FT_linear:
+    if (egg_ignore_filters) {
+      egg2pg_cat.warning()
+        << "Ignoring minfilter request\n";
+      tex->set_minfilter(Texture::FT_nearest);
+    } else {
+      tex->set_minfilter(Texture::FT_linear);
+    }
+    break;
+
+  case EggTexture::FT_nearest_mipmap_nearest:
+    if (egg_ignore_filters) {
+      egg2pg_cat.warning()
+        << "Ignoring minfilter request\n";
+      tex->set_minfilter(Texture::FT_nearest);
+    } else if (egg_ignore_mipmaps) {
+      egg2pg_cat.warning()
+        << "Ignoring mipmap request\n";
+      tex->set_minfilter(Texture::FT_nearest);
+    } else {
+      tex->set_minfilter(Texture::FT_nearest_mipmap_nearest);
+    }
+    break;
+
+  case EggTexture::FT_linear_mipmap_nearest:
+    if (egg_ignore_filters) {
+      egg2pg_cat.warning()
+        << "Ignoring minfilter request\n";
+      tex->set_minfilter(Texture::FT_nearest);
+    } else if (egg_ignore_mipmaps) {
+      egg2pg_cat.warning()
+        << "Ignoring mipmap request\n";
+      tex->set_minfilter(Texture::FT_linear);
+    } else {
+      tex->set_minfilter(Texture::FT_linear_mipmap_nearest);
+    }
+    break;
+
+  case EggTexture::FT_nearest_mipmap_linear:
+    if (egg_ignore_filters) {
+      egg2pg_cat.warning()
+        << "Ignoring minfilter request\n";
+      tex->set_minfilter(Texture::FT_nearest);
+    } else if (egg_ignore_mipmaps) {
+      egg2pg_cat.warning()
+        << "Ignoring mipmap request\n";
+      tex->set_minfilter(Texture::FT_nearest);
+    } else {
+      tex->set_minfilter(Texture::FT_nearest_mipmap_linear);
+    }
+    break;
+
+  case EggTexture::FT_linear_mipmap_linear:
+    if (egg_ignore_filters) {
+      egg2pg_cat.warning()
+        << "Ignoring minfilter request\n";
+      tex->set_minfilter(Texture::FT_nearest);
+    } else if (egg_ignore_mipmaps) {
+      egg2pg_cat.warning()
+        << "Ignoring mipmap request\n";
+      tex->set_minfilter(Texture::FT_linear);
+    } else {
+      tex->set_minfilter(Texture::FT_linear_mipmap_linear);
+    }
+    break;
+
+  case EggTexture::FT_unspecified:
+    // Default is bilinear, unless egg_ignore_filters is specified.
+    if (egg_ignore_filters) {
+      tex->set_minfilter(Texture::FT_nearest);
+    } else {
+      tex->set_minfilter(Texture::FT_linear);
+    }
+  }
+
+  switch (egg_tex->get_magfilter()) {
+  case EggTexture::FT_nearest:
+  case EggTexture::FT_nearest_mipmap_nearest:
+  case EggTexture::FT_nearest_mipmap_linear:
+    tex->set_magfilter(Texture::FT_nearest);
+    break;
+
+  case EggTexture::FT_linear:
+  case EggTexture::FT_linear_mipmap_nearest:
+  case EggTexture::FT_linear_mipmap_linear:
+    if (egg_ignore_filters) {
+      egg2pg_cat.warning()
+        << "Ignoring magfilter request\n";
+      tex->set_magfilter(Texture::FT_nearest);
+    } else {
+      tex->set_magfilter(Texture::FT_linear);
+    }
+    break;
+
+  case EggTexture::FT_unspecified:
+    // Default is bilinear, unless egg_ignore_filters is specified.
+    if (egg_ignore_filters) {
+      tex->set_magfilter(Texture::FT_nearest);
+    } else {
+      tex->set_magfilter(Texture::FT_linear);
+    }
+  }
+
+  if (egg_tex->has_anisotropic_degree()) {
+    tex->set_anisotropic_degree(egg_tex->get_anisotropic_degree());
+  }
+
+  if (tex->_pbuffer->get_num_components() == 1) {
+    switch (egg_tex->get_format()) {
+    case EggTexture::F_red:
+      tex->_pbuffer->set_format(PixelBuffer::F_red);
+      break;
+    case EggTexture::F_green:
+      tex->_pbuffer->set_format(PixelBuffer::F_green);
+      break;
+    case EggTexture::F_blue:
+      tex->_pbuffer->set_format(PixelBuffer::F_blue);
+      break;
+    case EggTexture::F_alpha:
+      tex->_pbuffer->set_format(PixelBuffer::F_alpha);
+      break;
+    case EggTexture::F_luminance:
+      tex->_pbuffer->set_format(PixelBuffer::F_luminance);
+      break;
+
+    case EggTexture::F_unspecified:
+      break;
+
+    default:
+      egg2pg_cat.warning()
+        << "Ignoring inappropriate format " << egg_tex->get_format()
+        << " for 1-component texture " << egg_tex->get_name() << "\n";
+    }
+
+  } else if (tex->_pbuffer->get_num_components() == 2) {
+    switch (egg_tex->get_format()) {
+    case EggTexture::F_luminance_alpha:
+      tex->_pbuffer->set_format(PixelBuffer::F_luminance_alpha);
+      break;
+
+    case EggTexture::F_luminance_alphamask:
+      tex->_pbuffer->set_format(PixelBuffer::F_luminance_alphamask);
+      break;
+
+    case EggTexture::F_unspecified:
+      break;
+
+    default:
+      egg2pg_cat.warning()
+        << "Ignoring inappropriate format " << egg_tex->get_format()
+        << " for 2-component texture " << egg_tex->get_name() << "\n";
+    }
+
+  } else if (tex->_pbuffer->get_num_components() == 3) {
+    switch (egg_tex->get_format()) {
+    case EggTexture::F_rgb:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgb);
+      break;
+    case EggTexture::F_rgb12:
+      if (tex->_pbuffer->get_component_width() >= 2) {
+        // Only do this if the component width supports it.
+        tex->_pbuffer->set_format(PixelBuffer::F_rgb12);
+      } else {
+        egg2pg_cat.warning()
+          << "Ignoring inappropriate format " << egg_tex->get_format()
+          << " for 8-bit texture " << egg_tex->get_name() << "\n";
+      }
+      break;
+    case EggTexture::F_rgb8:
+    case EggTexture::F_rgba8:
+      // We'll quietly accept RGBA8 for a 3-component texture, since
+      // flt2egg generates these for 3-component as well as for
+      // 4-component textures.
+      tex->_pbuffer->set_format(PixelBuffer::F_rgb8);
+      break;
+    case EggTexture::F_rgb5:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgb5);
+      break;
+    case EggTexture::F_rgb332:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgb332);
+      break;
+
+    case EggTexture::F_unspecified:
+      break;
+
+    default:
+      egg2pg_cat.warning()
+        << "Ignoring inappropriate format " << egg_tex->get_format()
+        << " for 3-component texture " << egg_tex->get_name() << "\n";
+    }
+
+  } else if (tex->_pbuffer->get_num_components() == 4) {
+    switch (egg_tex->get_format()) {
+    case EggTexture::F_rgba:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgba);
+      break;
+    case EggTexture::F_rgbm:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgbm);
+      break;
+    case EggTexture::F_rgba12:
+      if (tex->_pbuffer->get_component_width() >= 2) {
+        // Only do this if the component width supports it.
+        tex->_pbuffer->set_format(PixelBuffer::F_rgba12);
+      } else {
+        egg2pg_cat.warning()
+          << "Ignoring inappropriate format " << egg_tex->get_format()
+          << " for 8-bit texture " << egg_tex->get_name() << "\n";
+      }
+      break;
+    case EggTexture::F_rgba8:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgba8);
+      break;
+    case EggTexture::F_rgba4:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgba4);
+      break;
+    case EggTexture::F_rgba5:
+      tex->_pbuffer->set_format(PixelBuffer::F_rgba5);
+      break;
+
+    case EggTexture::F_unspecified:
+      break;
+
+    default:
+      egg2pg_cat.warning()
+        << "Ignoring inappropriate format " << egg_tex->get_format()
+        << " for 4-component texture " << egg_tex->get_name() << "\n";
+    }
+  }
+}
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::apply_texture_apply_attributes
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+apply_texture_apply_attributes(TextureApplyTransition *apply,
+                               const EggTexture *egg_tex) {
+  if (egg_always_decal_textures) {
+    apply->set_mode(TextureApplyProperty::M_decal);
+
+  } else {
+    switch (egg_tex->get_env_type()) {
+    case EggTexture::ET_modulate:
+      apply->set_mode(TextureApplyProperty::M_modulate);
+      break;
+
+    case EggTexture::ET_decal:
+      apply->set_mode(TextureApplyProperty::M_decal);
+      break;
+
+    case EggTexture::ET_unspecified:
+      break;
+
+    default:
+      egg2pg_cat.warning()
+        << "Invalid texture environment "
+        << (int)egg_tex->get_env_type() << "\n";
+    }
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::get_material_transition
+//       Access: Private
+//  Description: Returns a transition suitable for enabling the
+//               material indicated by the given EggMaterial, and with
+//               the indicated backface flag.
+////////////////////////////////////////////////////////////////////
+MaterialTransition *qpEggLoader::
+get_material_transition(const EggMaterial *egg_mat, bool bface) {
+  Materials &materials = bface ? _materials_bface : _materials;
+
+  // First, check whether we've seen this material before.
+  Materials::const_iterator mi;
+  mi = materials.find(egg_mat);
+  if (mi != materials.end()) {
+    return (*mi).second;
+  }
+
+  // Ok, this is the first time we've seen this particular
+  // EggMaterial.  Create a new Material that matches it.
+  PT(Material) mat = new Material;
+  if (egg_mat->has_diff()) {
+    mat->set_diffuse(egg_mat->get_diff());
+    // By default, ambient is the same as diffuse, if diffuse is
+    // specified but ambient is not.
+    mat->set_ambient(egg_mat->get_diff());
+  }
+  if (egg_mat->has_amb()) {
+    mat->set_ambient(egg_mat->get_amb());
+  }
+  if (egg_mat->has_emit()) {
+    mat->set_emission(egg_mat->get_emit());
+  }
+  if (egg_mat->has_spec()) {
+    mat->set_specular(egg_mat->get_spec());
+  }
+  if (egg_mat->has_shininess()) {
+    mat->set_shininess(egg_mat->get_shininess());
+  }
+  if (egg_mat->has_local()) {
+    mat->set_local(egg_mat->get_local());
+  }
+
+  mat->set_twoside(bface);
+
+  // Now get a global Material pointer, shared with other models.
+  const Material *shared_mat = MaterialPool::get_material(mat);
+
+  // And create a MaterialTransition for this Material.
+  PT(MaterialTransition) mt = new MaterialTransition(shared_mat);
+  materials.insert(Materials::value_type(egg_mat, mt));
+
+  return mt;
+}
+*/
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::setup_bucket
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+setup_bucket(BuilderBucket &bucket, PandaNode *parent,
+             EggPrimitive *egg_prim) {
+  bucket._qpnode = parent;
+  bucket._mesh = egg_mesh;
+  bucket._retesselate_coplanar = egg_retesselate_coplanar;
+  bucket._unroll_fans = egg_unroll_fans;
+  bucket._show_tstrips = egg_show_tstrips;
+  bucket._show_qsheets = egg_show_qsheets;
+  bucket._show_quads = egg_show_quads;
+  bucket._show_normals = egg_show_normals;
+  bucket._normal_scale = egg_normal_scale;
+  bucket._subdivide_polys = egg_subdivide_polys;
+  bucket._consider_fans = egg_consider_fans;
+  bucket._max_tfan_angle = egg_max_tfan_angle;
+  bucket._min_tfan_tris = egg_min_tfan_tris;
+  bucket._coplanar_threshold = egg_coplanar_threshold;
+
+  // If a primitive has a name that does not begin with a digit, it
+  // should be used to group primitives together--i.e. each primitive
+  // with the same name gets placed into the same GeomNode.  However,
+  // if a prim's name begins with a digit, just ignore it.
+  if (egg_prim->has_name() && !isdigit(egg_prim->get_name()[0])) {
+    bucket.set_name(egg_prim->get_name());
+  }
+
+  // Assign the appropriate properties to the bucket.
+
+  // The various EggRenderMode properties can be defined directly at
+  // the primitive, at a group above the primitive, or an a texture
+  // applied to the primitive.  The EggNode::determine_*() functions
+  // can find the right pointer to the level at which this is actually
+  // defined for a given primitive.
+  EggRenderMode::AlphaMode am = EggRenderMode::AM_unspecified;
+  EggRenderMode::DepthWriteMode dwm = EggRenderMode::DWM_unspecified;
+  EggRenderMode::DepthTestMode dtm = EggRenderMode::DTM_unspecified;
+  bool implicit_alpha = false;
+  bool has_draw_order = false;
+  int draw_order = 0;
+  bool has_bin = false;
+  string bin;
+
+  EggRenderMode *render_mode;
+  render_mode = egg_prim->determine_alpha_mode();
+  if (render_mode != (EggRenderMode *)NULL) {
+    am = render_mode->get_alpha_mode();
+  }
+  render_mode = egg_prim->determine_depth_write_mode();
+  if (render_mode != (EggRenderMode *)NULL) {
+    dwm = render_mode->get_depth_write_mode();
+  }
+  render_mode = egg_prim->determine_depth_test_mode();
+  if (render_mode != (EggRenderMode *)NULL) {
+    dtm = render_mode->get_depth_test_mode();
+  }
+  render_mode = egg_prim->determine_draw_order();
+  if (render_mode != (EggRenderMode *)NULL) {
+    has_draw_order = true;
+    draw_order = render_mode->get_draw_order();
+  }
+  render_mode = egg_prim->determine_bin();
+  if (render_mode != (EggRenderMode *)NULL) {
+    has_bin = true;
+    bin = render_mode->get_bin();
+  }
+
+  bucket._state = bucket._state->add(TextureAttrib::make_off());
+  if (egg_prim->has_texture()) {
+    PT(EggTexture) egg_tex = egg_prim->get_texture();
+
+    const TextureDef &def = _textures[egg_tex];
+    if (def._texture != (const RenderAttrib *)NULL) {
+      bucket._state = bucket._state->add(def._texture);
+      //      bucket._trans.set_transition(def._apply);
+
+      // If neither the primitive nor the texture specified an alpha
+      // mode, assume it should be alpha'ed if the texture has an
+      // alpha channel.
+      if (am == EggRenderMode::AM_unspecified) {
+        const TextureAttrib *tex_attrib = DCAST(TextureAttrib, def._texture);
+        Texture *tex = tex_attrib->get_texture();
+        nassertv(tex != (Texture *)NULL);
+        int num_components = tex->_pbuffer->get_num_components();
+        if (egg_tex->has_alpha_channel(num_components)) {
+          implicit_alpha = true;
+        }
+      }
+    }
+  }
+
+  /*
+  if (egg_prim->has_material()) {
+    MaterialTransition *mt = get_material_transition(egg_prim->get_material(),
+                                                     egg_prim->get_bface_flag());
+    bucket._trans.set_transition(mt);
+  }
+  */
+
+
+  // Also check the color of the primitive to see if we should assume
+  // alpha based on the alpha values specified in the egg file.
+  if (am == EggRenderMode::AM_unspecified) {
+    if (egg_prim->has_color()) {
+      if (egg_prim->get_color()[3] != 1.0) {
+        implicit_alpha = true;
+      }
+    }
+    EggPrimitive::const_iterator vi;
+    for (vi = egg_prim->begin();
+         !implicit_alpha && vi != egg_prim->end();
+         ++vi) {
+      if ((*vi)->has_color()) {
+        if ((*vi)->get_color()[3] != 1.0) {
+          implicit_alpha = true;
+        }
+      }
+    }
+
+    if (implicit_alpha) {
+      am = EggRenderMode::AM_on;
+    }
+  }
+
+  /*
+  switch (am) {
+  case EggRenderMode::AM_on:
+  case EggRenderMode::AM_blend:
+    bucket._trans.set_transition(new TransparencyTransition(TransparencyProperty::M_alpha));
+    break;
+
+  case EggRenderMode::AM_blend_no_occlude:
+    bucket._trans.set_transition(new TransparencyTransition(TransparencyProperty::M_alpha));
+    bucket._trans.set_transition(new DepthWriteTransition(DepthWriteTransition::off()));
+    break;
+
+  case EggRenderMode::AM_ms:
+    bucket._trans.set_transition(new TransparencyTransition(TransparencyProperty::M_multisample));
+    break;
+
+  case EggRenderMode::AM_ms_mask:
+    bucket._trans.set_transition(new TransparencyTransition(TransparencyProperty::M_multisample_mask));
+    break;
+
+  default:
+    //    bucket._trans.set_transition(new TransparencyTransition(TransparencyProperty::M_none));
+    break;
+  }
+  */
+
+  /*
+  switch (dwm) {
+  case EggRenderMode::DWM_on:
+    bucket._trans.set_transition(new DepthWriteTransition);
+    break;
+
+  case EggRenderMode::DWM_off:
+    bucket._trans.set_transition(new DepthWriteTransition(DepthWriteTransition::off()));
+    break;
+
+  default:
+    break;
+  }
+  */
+
+  /*
+  switch (dtm) {
+  case EggRenderMode::DTM_on:
+    bucket._trans.set_transition(new DepthTestTransition(DepthTestProperty::M_less));
+    break;
+
+  case EggRenderMode::DTM_off:
+    bucket._trans.set_transition(new DepthTestTransition(DepthTestProperty::M_none));
+    break;
+
+  default:
+    break;
+  }
+  */
+
+  /*
+  if (has_bin) {
+    bucket._trans.set_transition(new GeomBinTransition(bin, draw_order));
+
+  } else if (has_draw_order) {
+    bucket._trans.set_transition(new GeomBinTransition("fixed", draw_order));
+  }
+  */
+
+  /*
+  if (egg_prim->get_bface_flag()) {
+    // The primitive is marked with backface culling disabled--we want
+    // to see both sides.
+    bucket._trans.set_transition(new CullFaceTransition(CullFaceProperty::M_cull_none));
+
+  } else {
+    bucket._trans.set_transition(new CullFaceTransition(CullFaceProperty::M_cull_clockwise));
+  }
+  */
+}
+
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_node
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+make_node(EggNode *egg_node, PandaNode *parent) {
+  if (egg_node->is_of_type(EggNurbsCurve::get_class_type())) {
+    return make_node(DCAST(EggNurbsCurve, egg_node), parent);
+  } else if (egg_node->is_of_type(EggPrimitive::get_class_type())) {
+    return make_node(DCAST(EggPrimitive, egg_node), parent);
+  } else if (egg_node->is_of_type(EggBin::get_class_type())) {
+    return make_node(DCAST(EggBin, egg_node), parent);
+  } else if (egg_node->is_of_type(EggGroup::get_class_type())) {
+    return make_node(DCAST(EggGroup, egg_node), parent);
+  } else if (egg_node->is_of_type(EggTable::get_class_type())) {
+    return make_node(DCAST(EggTable, egg_node), parent);
+  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
+    return make_node(DCAST(EggGroupNode, egg_node), parent);
+  }
+
+  return (PandaNode *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_node (EggNurbsCurve)
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+make_node(EggNurbsCurve *egg_curve, PandaNode *parent) {
+  return (PandaNode *)NULL;
+  /*
+  assert(parent != NULL);
+  assert(!parent->is_of_type(GeomNode::get_class_type()));
+
+  PT(ParametricCurve) curve;
+
+  if (egg_load_classic_nurbs_curves) {
+    curve = new ClassicNurbsCurve;
+  } else {
+    curve = new NurbsCurve;
+  }
+
+  NurbsCurveInterface *nurbs = curve->get_nurbs_interface();
+  nassertr(nurbs != (NurbsCurveInterface *)NULL, (PandaNode *)NULL);
+
+  if (egg_curve->get_order() < 1 || egg_curve->get_order() > 4) {
+    egg2pg_cat.error()
+      << "Invalid NURBSCurve order for " << egg_curve->get_name() << ": "
+      << egg_curve->get_order() << "\n";
+    _error = true;
+    return (PandaNode *)NULL;
+  }
+
+  nurbs->set_order(egg_curve->get_order());
+
+  EggPrimitive::const_iterator pi;
+  for (pi = egg_curve->begin(); pi != egg_curve->end(); ++pi) {
+    nurbs->append_cv(LCAST(float, (*pi)->get_pos4()));
+  }
+
+  int num_knots = egg_curve->get_num_knots();
+  if (num_knots != nurbs->get_num_knots()) {
+    egg2pg_cat.error()
+      << "Invalid NURBSCurve number of knots for "
+      << egg_curve->get_name() << ": got " << num_knots
+      << " knots, expected " << nurbs->get_num_knots() << "\n";
+    _error = true;
+    return (PandaNode *)NULL;
+  }
+
+  for (int i = 0; i < num_knots; i++) {
+    nurbs->set_knot(i, egg_curve->get_knot(i));
+  }
+
+  switch (egg_curve->get_curve_type()) {
+  case EggCurve::CT_xyz:
+    curve->set_curve_type(PCT_XYZ);
+    break;
+
+  case EggCurve::CT_hpr:
+    curve->set_curve_type(PCT_HPR);
+    break;
+
+  case EggCurve::CT_t:
+    curve->set_curve_type(PCT_T);
+    break;
+
+  default:
+    break;
+  }
+  curve->set_name(egg_curve->get_name());
+
+  if (!curve->recompute()) {
+    egg2pg_cat.error()
+      << "Invalid NURBSCurve " << egg_curve->get_name() << "\n";
+    _error = true;
+    return (PandaNode *)NULL;
+  }
+
+  return new PandaNode(parent, curve);
+  */
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_node (EggPrimitive)
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+make_node(EggPrimitive *egg_prim, PandaNode *parent) {
+  assert(parent != NULL);
+  assert(!parent->is_of_type(qpGeomNode::get_class_type()));
+
+  if (egg_prim->cleanup()) {
+    /*
+    if (parent->is_of_type(SwitchNode::get_class_type())) {
+      // If we're putting a primitive under a SwitchNode of some kind,
+      // its exact position within the group is relevant, so we need
+      // to create a placeholder now.
+      PandaNode *group = new PandaNode(egg_prim->get_name());
+      parent->add_child(group);
+      make_nonindexed_primitive(egg_prim, group);
+      return group;
+    }
+    */
+
+    // Otherwise, we don't really care what the position of this
+    // primitive is within its parent's list of children, and in fact
+    // we want to allow it to be combined with other polygons added to
+    // the same parent.
+    make_nonindexed_primitive(egg_prim, parent);
+  }
+  return (PandaNode *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_node (EggBin)
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+make_node(EggBin *egg_bin, PandaNode *parent) {
+  return (PandaNode *)NULL;
+  /*
+  // Presently, an EggBin can only mean an LOD node (i.e. a parent of
+  // one or more EggGroups with LOD specifications).  Later it might
+  // mean other things as well.
+
+  assert((EggBinner::BinNumber)egg_bin->get_bin_number() == EggBinner::BN_lod);
+  LODNode *lod_node = new LODNode;
+  lod_node->set_name(egg_bin->get_name());
+
+  pvector<LODInstance> instances;
+
+  EggGroup::const_iterator ci;
+  for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
+    PandaNode *arc = make_node(*ci, lod_node);
+    assert(arc != (PandaNode *)NULL);
+    LODInstance instance(*ci, arc);
+    instances.push_back(instance);
+  }
+
+  // Now that we've created all of our children, put them in the
+  // proper order and tell the LOD node about them.
+  sort(instances.begin(), instances.end());
+
+  if (!instances.empty()) {
+    // Set up the LOD node's center.  All of the children should have
+    // the same center, because that's how we binned them.
+    lod_node->_lod._center = LCAST(float, instances[0]._d->_center);
+  }
+
+  for (size_t i = 0; i < instances.size(); i++) {
+    // Put the children in the proper order within the scene graph.
+    const LODInstance &instance = instances[i];
+
+    // All of the children should have the same center, because that's
+    // how we binned them.
+    assert(lod_node->_lod._center.almost_equal
+           (LCAST(float, instance._d->_center), 0.01));
+
+    instance._arc->set_sort(i);
+
+    // Tell the LOD node about this child's switching distances.
+    lod_node->add_switch(instance._d->_switch_in, instance._d->_switch_out);
+  }
+
+  return create_group_arc(egg_bin, parent, lod_node);
+  */
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_node (EggGroup)
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+make_node(EggGroup *egg_group, PandaNode *parent) {
+  PandaNode *node = NULL;
+
+  if (egg_group->has_objecttype()) {
+    // We'll allow recursive expansion of ObjectType strings--but we
+    // don't want to get caught in a cycle.  Keep track of the strings
+    // we've expanded so far.
+    pset<string> expanded;
+    pvector<string> expanded_history;
+
+    while (egg_group->has_objecttype()) {
+      string objecttype = egg_group->get_objecttype();
+      if (!expanded.insert(objecttype).second) {
+        egg2pg_cat.error()
+          << "Cycle in ObjectType expansions:\n";
+        copy(expanded_history.begin(), expanded_history.end(),
+             ostream_iterator<string>(egg2pg_cat.error(false), " -> "));
+        egg2pg_cat.error(false) << objecttype << "\n";
+        _error = true;
+        break;
+      }
+      expanded_history.push_back(objecttype);
+
+      // Now clear the group's ObjectType flag.  We'll only loop back
+      // here again if we end up setting this during the ObjectType
+      // expansion; e.g. the expansion string itself contains an
+      // <ObjectType> reference.
+      egg_group->clear_objecttype();
+
+      // Now try to find the egg syntax that the given objecttype is
+      // shorthand for.  First, look in the config file.
+
+      string egg_syntax =
+        config_egg2pg.GetString("egg-object-type-" + objecttype, "none");
+
+      if (egg_syntax == "none") {
+        // It wasn't defined in a config file.  Maybe it's built in?
+
+        if (cmp_nocase_uh(objecttype, "barrier") == 0) {
+          egg_syntax = "<Collide> { Polyset descend }";
+
+        } else if (cmp_nocase_uh(objecttype, "solidpoly") == 0) {
+          egg_syntax = "<Collide> { Polyset descend solid }";
+
+        } else if (cmp_nocase_uh(objecttype, "turnstile") == 0) {
+          egg_syntax = "<Collide> { Polyset descend turnstile }";
+
+        } else if (cmp_nocase_uh(objecttype, "sphere") == 0) {
+          egg_syntax = "<Collide> { Sphere descend }";
+
+        } else if (cmp_nocase_uh(objecttype, "trigger") == 0) {
+          egg_syntax = "<Collide> { Polyset descend intangible }";
+
+        } else if (cmp_nocase_uh(objecttype, "trigger_sphere") == 0) {
+          egg_syntax = "<Collide> { Sphere descend intangible }";
+
+        } else if (cmp_nocase_uh(objecttype, "eye_trigger") == 0) {
+          egg_syntax = "<Collide> { Polyset descend intangible center }";
+
+        } else if (cmp_nocase_uh(objecttype, "bubble") == 0) {
+          egg_syntax = "<Collide> { Sphere keep descend }";
+
+        } else if (cmp_nocase_uh(objecttype, "ghost") == 0) {
+          egg_syntax = "<Scalar> collide-mask { 0 }";
+
+        } else if (cmp_nocase_uh(objecttype, "backstage") == 0) {
+          // Ignore "backstage" geometry.
+          return NULL;
+
+        } else {
+          egg2pg_cat.error()
+            << "Unknown ObjectType " << objecttype << "\n";
+          _error = true;
+          break;
+        }
+      }
+
+      if (!egg_syntax.empty()) {
+        if (!egg_group->parse_egg(egg_syntax)) {
+          egg2pg_cat.error()
+            << "Error while parsing definition for ObjectType "
+            << objecttype << "\n";
+          _error = true;
+        }
+      }
+    }
+  }
+
+  if (egg_group->get_dart_type() != EggGroup::DT_none) {
+    /*
+    // A group with the <Dart> flag set means to create a character.
+    CharacterMaker char_maker(egg_group, *this);
+    node = char_maker.make_node();
+    */
+
+  } else if (egg_group->get_cs_type() != EggGroup::CST_none &&
+             egg_group->get_cs_type() != EggGroup::CST_geode) {
+    /*
+    // A collision group: create collision geometry.
+    node = new CollisionNode;
+    node->set_name(egg_group->get_name());
+
+    make_collision_solids(egg_group, egg_group, (CollisionNode *)node);
+    if ((egg_group->get_collide_flags() & EggGroup::CF_keep) != 0) {
+      // If we also specified to keep the geometry, continue the
+      // traversal.
+      EggGroup::const_iterator ci;
+      for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+        make_node(*ci, parent);
+      }
+    }
+
+    PandaNode *arc = create_group_arc(egg_group, parent, node);
+
+    if (!egg_show_collision_solids) {
+      arc->set_transition(new PruneTransition());
+    }
+    return arc;
+    */
+
+  } else if (egg_group->get_switch_flag() &&
+             egg_group->get_switch_fps() != 0.0) {
+    /*
+    // Create a sequence node.
+    node = new SequenceNode(1.0 / egg_group->get_switch_fps());
+    node->set_name(egg_group->get_name());
+
+    EggGroup::const_iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      make_node(*ci, node);
+    }
+    */
+
+  } else if (egg_group->get_model_flag() || egg_group->get_dcs_flag()) {
+    /*
+    // A model or DCS flag; create a model node.
+    node = new ModelNode;
+    node->set_name(egg_group->get_name());
+
+    DCAST(ModelNode, node)->set_preserve_transform(egg_group->get_dcs_flag());
+
+    EggGroup::const_iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      make_node(*ci, node);
+    }
+    */
+
+  } else {
+    // A normal group; just create a normal node, and traverse.
+    node = new PandaNode(egg_group->get_name());
+
+    EggGroup::const_iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      make_node(*ci, node);
+    }
+  }
+
+  if (node == (PandaNode *)NULL) {
+    return NULL;
+  }
+  return create_group_arc(egg_group, parent, node);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::create_group_arc
+//       Access: Private
+//  Description: Creates the arc parenting a new group to the scene
+//               graph, and applies any relevant transitions to the
+//               arc according to the EggGroup node that inspired the
+//               group.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+create_group_arc(EggGroup *egg_group, PandaNode *parent, PandaNode *node) {
+  parent->add_child(node);
+
+  // If the group had a transform, apply it to the arc.
+  if (egg_group->has_transform()) {
+    LMatrix4f matf = LCAST(float, egg_group->get_transform());
+    node->set_transform(TransformState::make_mat(matf));
+  }
+
+  /*
+  // If the group has a billboard flag, apply that.
+  switch (egg_group->get_billboard_type()) {
+  case EggGroup::BT_point_camera_relative:
+    arc->set_transition(new BillboardTransition(BillboardTransition::point_eye()));
+    break;
+
+  case EggGroup::BT_point_world_relative:
+    arc->set_transition(new BillboardTransition(BillboardTransition::point_world()));
+    break;
+
+  case EggGroup::BT_axis:
+    arc->set_transition(new BillboardTransition(BillboardTransition::axis()));
+    break;
+
+  case EggGroup::BT_none:
+    break;
+  }
+  */
+
+  /*
+  if (egg_group->get_decal_flag()) {
+    if (egg_ignore_decals) {
+      egg2pg_cat.error()
+        << "Ignoring decal flag on " << egg_group->get_name() << "\n";
+      _error = true;
+    }
+
+    // If the group has the "decal" flag set, it means that all of the
+    // descendant groups will be decaled onto the geometry within
+    // this group.  This means we'll need to reparent things a bit
+    // afterward.
+    _decals.insert(arc);
+
+    // We'll also set up the DecalTransition now.
+    arc->set_transition(new DecalTransition);
+  }
+  */
+
+  /*
+  if (egg_group->get_direct_flag()) {
+    // If the group has the "direct" flag set, it means that
+    // everything at this node and below should be rendered in direct
+    // mode, i.e. in depth-first tree order, without state-sorting.
+
+    arc->set_transition(new DirectRenderTransition);
+
+    // We'll also want to set up the transitions on this arc to
+    // reflect the geometry at the top of the tree below this node, so
+    // we get good state-sorting behavior.  We'll have to do this
+    // later.
+    _directs.insert(arc);
+  }
+  */
+
+  /*
+  // If the group specified some property that should propagate down
+  // to the leaves, we have to remember this arc and apply the
+  // property later, after we've created the actual geometry.
+  DeferredArcProperty def;
+  if (egg_group->has_collide_mask()) {
+    def._from_collide_mask = egg_group->get_collide_mask();
+    def._into_collide_mask = egg_group->get_collide_mask();
+    def._flags |=
+      DeferredArcProperty::F_has_from_collide_mask |
+      DeferredArcProperty::F_has_into_collide_mask;
+  }
+  if (egg_group->has_from_collide_mask()) {
+    def._from_collide_mask = egg_group->get_from_collide_mask();
+    def._flags |= DeferredArcProperty::F_has_from_collide_mask;
+  }
+  if (egg_group->has_into_collide_mask()) {
+    def._into_collide_mask = egg_group->get_into_collide_mask();
+    def._flags |= DeferredArcProperty::F_has_into_collide_mask;
+  }
+
+  if (def._flags != 0) {
+    _deferred_arcs[arc] = def;
+  }
+  */
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_node (EggTable)
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+make_node(EggTable *egg_table, PandaNode *parent) {
+  return (PandaNode *)NULL;
+  /*
+  if (egg_table->get_table_type() != EggTable::TT_bundle) {
+    // We only do anything with bundles.  Isolated tables are treated
+    // as ordinary groups.
+    return make_node(DCAST(EggGroupNode, egg_table), parent);
+  }
+
+  // It's an actual bundle, so make an AnimBundle from it and its
+  // descendants.
+  AnimBundleMaker bundle_maker(egg_table);
+  AnimBundleNode *node = bundle_maker.make_node();
+  return new PandaNode(parent, node);
+  */
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_node (EggGroupNode)
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode *qpEggLoader::
+make_node(EggGroupNode *egg_group, PandaNode *parent) {
+  PandaNode *node = new PandaNode(egg_group->get_name());
+
+  EggGroupNode::const_iterator ci;
+  for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+    make_node(*ci, node);
+  }
+
+  parent->add_child(node);
+  return node;
+}
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_collision_solids
+//       Access: Private
+//  Description: Creates CollisionSolids corresponding to the
+//               collision geometry indicated at the given node and
+//               below.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+make_collision_solids(EggGroup *start_group, EggGroup *egg_group,
+                      CollisionNode *cnode) {
+  if (egg_group->get_cs_type() != EggGroup::CST_none) {
+    start_group = egg_group;
+  }
+
+  switch (start_group->get_cs_type()) {
+  case EggGroup::CST_none:
+  case EggGroup::CST_geode:
+    // No collision flags; do nothing.  Don't even traverse further.
+    return;
+
+  case EggGroup::CST_inverse_sphere:
+    // These aren't presently supported.
+    egg2pg_cat.error()
+      << "Not presently supported: <Collide> { "
+      << egg_group->get_cs_type() << " }\n";
+    _error = true;
+    break;
+
+  case EggGroup::CST_plane:
+    make_collision_plane(egg_group, cnode, start_group->get_collide_flags());
+    break;
+
+  case EggGroup::CST_polygon:
+    make_collision_polygon(egg_group, cnode, start_group->get_collide_flags());
+    break;
+
+  case EggGroup::CST_polyset:
+    make_collision_polyset(egg_group, cnode, start_group->get_collide_flags());
+    break;
+
+  case EggGroup::CST_sphere:
+    make_collision_sphere(egg_group, cnode, start_group->get_collide_flags());
+    break;
+  }
+
+  if ((start_group->get_collide_flags() & EggGroup::CF_descend) != 0) {
+    // Now pick up everything below.
+    EggGroup::const_iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      if ((*ci)->is_of_type(EggGroup::get_class_type())) {
+        make_collision_solids(start_group, DCAST(EggGroup, *ci), cnode);
+      }
+    }
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_collision_plane
+//       Access: Private
+//  Description: Creates a single CollisionPlane corresponding
+//               to the first polygon associated with this group.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+make_collision_plane(EggGroup *egg_group, CollisionNode *cnode,
+                     EggGroup::CollideFlags flags) {
+  EggGroup *geom_group = find_collision_geometry(egg_group);
+  if (geom_group != (EggGroup *)NULL) {
+    EggGroup::const_iterator ci;
+    for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
+      if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
+        CollisionPlane *csplane =
+          create_collision_plane(DCAST(EggPolygon, *ci), egg_group);
+        if (csplane != (CollisionPlane *)NULL) {
+          apply_collision_flags(csplane, flags);
+          cnode->add_solid(csplane);
+          return;
+        }
+      }
+    }
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_collision_polygon
+//       Access: Private
+//  Description: Creates a single CollisionPolygon corresponding
+//               to the first polygon associated with this group.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+make_collision_polygon(EggGroup *egg_group, CollisionNode *cnode,
+                       EggGroup::CollideFlags flags) {
+
+  EggGroup *geom_group = find_collision_geometry(egg_group);
+  if (geom_group != (EggGroup *)NULL) {
+    EggGroup::const_iterator ci;
+    for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
+      if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
+        create_collision_polygons(cnode, DCAST(EggPolygon, *ci),
+                                  egg_group, flags);
+      }
+    }
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_collision_polyset
+//       Access: Private
+//  Description: Creates a series of CollisionPolygons corresponding
+//               to the polygons associated with this group.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+make_collision_polyset(EggGroup *egg_group, CollisionNode *cnode,
+                       EggGroup::CollideFlags flags) {
+  EggGroup *geom_group = find_collision_geometry(egg_group);
+  if (geom_group != (EggGroup *)NULL) {
+    EggGroup::const_iterator ci;
+    for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
+      if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
+        create_collision_polygons(cnode, DCAST(EggPolygon, *ci),
+                                  egg_group, flags);
+      }
+    }
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::make_collision_sphere
+//       Access: Private
+//  Description: Creates a single CollisionSphere corresponding
+//               to the polygons associated with this group.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+make_collision_sphere(EggGroup *egg_group, CollisionNode *cnode,
+                      EggGroup::CollideFlags flags) {
+  EggGroup *geom_group = find_collision_geometry(egg_group);
+  if (geom_group != (EggGroup *)NULL) {
+    // Collect all of the vertices.
+    pset<EggVertex *> vertices;
+
+    EggGroup::const_iterator ci;
+    for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) {
+      if ((*ci)->is_of_type(EggPrimitive::get_class_type())) {
+        EggPrimitive *prim = DCAST(EggPrimitive, *ci);
+        EggPrimitive::const_iterator pi;
+        for (pi = prim->begin(); pi != prim->end(); ++pi) {
+          vertices.insert(*pi);
+        }
+      }
+    }
+
+    // Now average together all of the vertices to get a center.
+    int num_vertices = 0;
+    LPoint3d center(0.0, 0.0, 0.0);
+    pset<EggVertex *>::const_iterator vi;
+
+    for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
+      EggVertex *vtx = (*vi);
+      if (vtx->get_num_dimensions() == 3) {
+        center += vtx->get_pos3();
+        num_vertices++;
+
+      } else if (vtx->get_num_dimensions() == 4) {
+        LPoint4d p4 = vtx->get_pos4();
+        if (p4[3] != 0.0) {
+          center += LPoint3d(p4[0], p4[1], p4[2]) / p4[3];
+          num_vertices++;
+        }
+      }
+    }
+
+    if (num_vertices > 0) {
+      center /= (double)num_vertices;
+
+      // And the furthest vertex determines the radius.
+      double radius2 = 0.0;
+      for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
+        EggVertex *vtx = (*vi);
+        if (vtx->get_num_dimensions() == 3) {
+          LVector3d v = vtx->get_pos3() - center;
+          radius2 = max(radius2, v.length_squared());
+
+        } else if (vtx->get_num_dimensions() == 4) {
+          LPoint4d p = vtx->get_pos4();
+          if (p[3] != 0.0) {
+            LVector3d v = LPoint3d(p[0], p[1], p[2]) / p[3] - center;
+            radius2 = max(radius2, v.length_squared());
+          }
+        }
+      }
+
+      float radius = sqrtf(radius2);
+      CollisionSphere *cssphere =
+        new CollisionSphere(LCAST(float, center), radius);
+      apply_collision_flags(cssphere, flags);
+      cnode->add_solid(cssphere);
+    }
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::apply_collision_flags
+//       Access: Private
+//  Description: Does funny stuff to the CollisionSolid as
+//               appropriate, based on the settings of the given
+//               CollideFlags.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+apply_collision_flags(CollisionSolid *solid,
+                      EggGroup::CollideFlags flags) {
+  if ((flags & EggGroup::CF_intangible) != 0) {
+    solid->set_tangible(false);
+  }
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::find_collision_geometry
+//       Access: Private
+//  Description: Looks for the node, at or below the indicated node,
+//               that contains the associated collision geometry.
+////////////////////////////////////////////////////////////////////
+EggGroup *qpEggLoader::
+find_collision_geometry(EggGroup *egg_group) {
+  if ((egg_group->get_collide_flags() & EggGroup::CF_descend) != 0) {
+    // If we have the "descend" instruction, we'll get to it when we
+    // get to it.  Don't worry about it now.
+    return egg_group;
+  }
+
+  // Does this group have any polygons?
+  EggGroup::const_iterator ci;
+  for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+    if ((*ci)->is_of_type(EggPolygon::get_class_type())) {
+      // Yes!  Use this group.
+      return egg_group;
+    }
+  }
+
+  // Well, the group had no polygons; look for a child group that has
+  // the same collision type.
+  for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+    if ((*ci)->is_of_type(EggGroup::get_class_type())) {
+      EggGroup *child_group = DCAST(EggGroup, *ci);
+      if (child_group->get_cs_type() == egg_group->get_cs_type()) {
+        return child_group;
+      }
+    }
+  }
+
+  // We got nothing.
+  return NULL;
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::create_collision_plane
+//       Access: Private
+//  Description: Creates a single CollisionPlane from the indicated
+//               EggPolygon.
+////////////////////////////////////////////////////////////////////
+CollisionPlane *qpEggLoader::
+create_collision_plane(EggPolygon *egg_poly, EggGroup *parent_group) {
+  if (!egg_poly->cleanup()) {
+    egg2pg_cat.error()
+      << "Degenerate collision plane in " << parent_group->get_name()
+      << "\n";
+    _error = true;
+    return NULL;
+  }
+
+  pvector<Vertexf> vertices;
+  if (!egg_poly->empty()) {
+    EggPolygon::const_iterator vi;
+    vi = egg_poly->begin();
+
+    Vertexd vert = (*vi)->get_pos3();
+    vertices.push_back(LCAST(float, vert));
+
+    Vertexd last_vert = vert;
+    ++vi;
+    while (vi != egg_poly->end()) {
+      vert = (*vi)->get_pos3();
+      if (!vert.almost_equal(last_vert)) {
+        vertices.push_back(LCAST(float, vert));
+      }
+
+      last_vert = vert;
+      ++vi;
+    }
+  }
+
+  if (vertices.size() < 3) {
+    return NULL;
+  }
+  Planef plane(vertices[0], vertices[1], vertices[2]);
+  return new CollisionPlane(plane);
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::create_collision_polygons
+//       Access: Private
+//  Description: Creates one or more CollisionPolygons from the
+//               indicated EggPolygon, and adds them to the indicated
+//               CollisionNode.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
+                          EggGroup *parent_group,
+                          EggGroup::CollideFlags flags) {
+
+  PT(EggGroup) group = new EggGroup;
+
+  if (!egg_poly->triangulate_into(group, false)) {
+    egg2pg_cat.error()
+      << "Degenerate collision polygon in " << parent_group->get_name()
+      << "\n";
+    _error = true;
+    return;
+  }
+
+  if (group->size() != 1) {
+    egg2pg_cat.error()
+      << "Concave collision polygon in " << parent_group->get_name()
+      << "\n";
+    _error = true;
+  }
+
+  EggGroup::iterator ci;
+  for (ci = group->begin(); ci != group->end(); ++ci) {
+    EggPolygon *poly = DCAST(EggPolygon, *ci);
+
+    pvector<Vertexf> vertices;
+    if (!poly->empty()) {
+      EggPolygon::const_iterator vi;
+      vi = poly->begin();
+
+      Vertexd vert = (*vi)->get_pos3();
+      vertices.push_back(LCAST(float, vert));
+
+      Vertexd last_vert = vert;
+      ++vi;
+      while (vi != poly->end()) {
+        vert = (*vi)->get_pos3();
+        if (!vert.almost_equal(last_vert)) {
+          vertices.push_back(LCAST(float, vert));
+        }
+
+        last_vert = vert;
+        ++vi;
+      }
+    }
+
+    if (vertices.size() >= 3) {
+      const Vertexf *vertices_begin = &vertices[0];
+      const Vertexf *vertices_end = vertices_begin + vertices.size();
+      CollisionPolygon *cspoly =
+        new CollisionPolygon(vertices_begin, vertices_end);
+      apply_collision_flags(cspoly, flags);
+      cnode->add_solid(cspoly);
+    }
+  }
+}
+*/
+
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpEggLoader::apply_deferred_arcs
+//       Access: Private
+//  Description: Walks back over the tree and applies the
+//               DeferredArcProperties that were saved up along the
+//               way.
+////////////////////////////////////////////////////////////////////
+void qpEggLoader::
+apply_deferred_arcs(Node *root) {
+  DeferredArcTraverser trav(_deferred_arcs);
+
+  df_traverse(root, trav, NullTransitionWrapper(), DeferredArcProperty(),
+              PandaNode::get_class_type());
+}
+*/

+ 159 - 0
panda/src/egg2pg/qpeggLoader.h

@@ -0,0 +1,159 @@
+// Filename: qpeggLoader.h
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpEGGLOADER_H
+#define qpEGGLOADER_H
+
+#include "pandabase.h"
+
+#include "eggData.h"
+#include "eggTexture.h"
+#include "pt_EggTexture.h"
+#include "eggGroup.h"
+#include "eggMaterial.h"
+#include "pt_EggMaterial.h"
+#include "texture.h"
+#include "pandaNode.h"
+#include "pointerTo.h"
+#include "builder.h"
+#include "lmatrix.h"
+#include "indirectCompareTo.h"
+#include "textureAttrib.h"
+
+class EggNode;
+class EggBin;
+class EggTable;
+class EggNurbsCurve;
+class EggPrimitive;
+class EggPolygon;
+class EggMaterial;
+class ComputedVerticesMaker;
+class RenderRelation;
+class CollisionSolid;
+class CollisionNode;
+class CollisionPlane;
+class CollisionPolygon;
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpEggLoader
+// Description : Converts an egg data structure, possibly read from an
+//               egg file but not necessarily, into a scene graph
+//               suitable for rendering.
+//
+//               This class isn't exported from this package.
+////////////////////////////////////////////////////////////////////
+class qpEggLoader {
+public:
+  qpEggLoader();
+  qpEggLoader(const EggData &data);
+
+  void build_graph();
+  void reparent_decals();
+  void reset_directs();
+
+  void make_nonindexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
+                                 const LMatrix4d *transform = NULL);
+
+  void make_indexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
+                              const LMatrix4d *transform,
+                              ComputedVerticesMaker &_comp_verts_maker);
+
+private:
+  class TextureDef {
+  public:
+    CPT(RenderAttrib) _texture;
+    //    PT(TextureApplyTransition) _apply;
+  };
+
+  void load_textures();
+  bool load_texture(TextureDef &def, const EggTexture *egg_tex);
+  void apply_texture_attributes(Texture *tex, const EggTexture *egg_tex);
+  void apply_texture_apply_attributes(TextureApplyTransition *apply,
+                                      const EggTexture *egg_tex);
+
+  /*
+  MaterialTransition *get_material_transition(const EggMaterial *egg_mat,
+                                              bool bface);
+  */
+
+  void setup_bucket(BuilderBucket &bucket, PandaNode *parent,
+                    EggPrimitive *egg_prim);
+
+  PandaNode *make_node(EggNode *egg_node, PandaNode *parent);
+  PandaNode *make_node(EggNurbsCurve *egg_curve, PandaNode *parent);
+  PandaNode *make_node(EggPrimitive *egg_prim, PandaNode *parent);
+  PandaNode *make_node(EggBin *egg_bin, PandaNode *parent);
+  PandaNode *make_node(EggGroup *egg_group, PandaNode *parent);
+  PandaNode *create_group_arc(EggGroup *egg_group, PandaNode *parent,
+                                   PandaNode *node);
+  PandaNode *make_node(EggTable *egg_table, PandaNode *parent);
+  PandaNode *make_node(EggGroupNode *egg_group, PandaNode *parent);
+
+  /*
+  void make_collision_solids(EggGroup *start_group, EggGroup *egg_group,
+                             CollisionNode *cnode);
+  void make_collision_plane(EggGroup *egg_group, CollisionNode *cnode,
+                            EggGroup::CollideFlags flags);
+  void make_collision_polygon(EggGroup *egg_group, CollisionNode *cnode,
+                              EggGroup::CollideFlags flags);
+  void make_collision_polyset(EggGroup *egg_group, CollisionNode *cnode,
+                              EggGroup::CollideFlags flags);
+  void make_collision_sphere(EggGroup *egg_group, CollisionNode *cnode,
+                             EggGroup::CollideFlags flags);
+  void apply_collision_flags(CollisionSolid *solid,
+                             EggGroup::CollideFlags flags);
+  EggGroup *find_collision_geometry(EggGroup *egg_group);
+  CollisionPlane *create_collision_plane(EggPolygon *egg_poly,
+                                         EggGroup *parent_group);
+  void create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
+                                 EggGroup *parent_group,
+                                 EggGroup::CollideFlags flags);
+
+  void apply_deferred_arcs(PandaNode *root);
+  */
+
+
+  Builder _builder;
+
+  typedef pmap<PT_EggTexture, TextureDef> Textures;
+  Textures _textures;
+
+  /*
+  typedef pmap<CPT_EggMaterial, PT(MaterialTransition) > Materials;
+  Materials _materials;
+  Materials _materials_bface;
+  */
+
+  /*
+  typedef pset<PandaNode *> Decals;
+  Decals _decals;
+
+  typedef pset<PandaNode *> Directs;
+  Directs _directs;
+
+  DeferredArcs _deferred_arcs;
+  */
+
+public:
+  PT(PandaNode) _root;
+  EggData _data;
+  bool _error;
+};
+
+
+#endif

+ 112 - 0
panda/src/egg2pg/qpload_egg_file.cxx

@@ -0,0 +1,112 @@
+// Filename: qpload_egg_file.cxx
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qpload_egg_file.h"
+#include "qpeggLoader.h"
+#include "config_egg2pg.h"
+
+/*
+#include "sceneGraphReducer.h"
+#include "renderRelation.h"
+*/
+
+static PT(PandaNode)
+load_from_loader(qpEggLoader &loader) {
+  loader._data.resolve_externals();
+
+  loader.build_graph();
+
+  if (loader._error && !egg_accept_errors) {
+    egg2pg_cat.error()
+      << "Errors in egg file.\n";
+    return NULL;
+  }
+
+  /*
+  if (loader._root != (NamedNode *)NULL && egg_flatten) {
+    SceneGraphReducer gr(RenderRelation::get_class_type());
+    int num_reduced = gr.flatten(loader._root, egg_flatten_siblings);
+    egg2pg_cat.info() << "Flattened " << num_reduced << " arcs.\n";
+  }
+  */
+
+  return loader._root;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: load_egg_file
+//  Description: A convenience function.  Loads up the indicated egg
+//               file, and returns the root of a scene graph.  Returns
+//               NULL if the file cannot be read for some reason.
+//               Does not search along the egg path for the filename
+//               first; use EggData::resolve_egg_filename() if this is
+//               required.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode)
+qpload_egg_file(const string &filename, CoordinateSystem cs) {
+  Filename egg_filename = Filename::text_filename(filename);
+  if (!egg_filename.exists()) {
+    egg2pg_cat.error()
+      << "Could not find " << egg_filename << "\n";
+    return NULL;
+  }
+
+  egg2pg_cat.info()
+    << "Reading " << egg_filename << "\n";
+
+  ifstream file;
+  if (!egg_filename.open_read(file)) {
+    egg2pg_cat.error()
+      << "Could not open " << egg_filename << " for reading.\n";
+    return NULL;
+  }
+
+  qpEggLoader loader;
+  loader._data.set_egg_filename(egg_filename);
+  if (cs != CS_default) {
+    loader._data.set_coordinate_system(cs);
+  }
+
+  if (!loader._data.read(file)) {
+    egg2pg_cat.error()
+      << "Error reading " << egg_filename << "\n";
+    return NULL;
+  }
+
+  return load_from_loader(loader);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: load_egg_data
+//  Description: Another convenience function; works like
+//               load_egg_file() but starts from an already-filled
+//               EggData structure.  The structure is destroyed in the
+//               loading.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode)
+qpload_egg_data(EggData &data) {
+  // We temporarily shuttle the children to a holding node so we can
+  // copy them into the EggLoader's structure without it complaining.
+  EggGroupNode children_holder;
+  children_holder.steal_children(data);
+
+  qpEggLoader loader(data);
+  loader._data.steal_children(children_holder);
+
+  return load_from_loader(loader);
+}

+ 52 - 0
panda/src/egg2pg/qpload_egg_file.h

@@ -0,0 +1,52 @@
+// Filename: qpload_egg_file.h
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpLOAD_EGG_FILE_H
+#define qpLOAD_EGG_FILE_H
+
+#include "pandabase.h"
+
+#include "pandaNode.h"
+#include "coordinateSystem.h"
+
+class EggData;
+
+////////////////////////////////////////////////////////////////////
+//     Function: load_egg_file
+//  Description: A convenience function; the primary interface to this
+//               package.  Loads up the indicated egg file, and
+//               returns the root of a scene graph.  Returns NULL if
+//               the file cannot be read for some reason.
+//
+//               Also see the EggLoader class, which can exercise a
+//               bit more manual control over the loading process.
+////////////////////////////////////////////////////////////////////
+EXPCL_PANDAEGG PT(PandaNode)
+qpload_egg_file(const string &filename, CoordinateSystem cs = CS_default);
+
+////////////////////////////////////////////////////////////////////
+//     Function: load_egg_data
+//  Description: Another convenience function; works like
+//               load_egg_file() but starts from an already-filled
+//               EggData structure.  The structure is destroyed in the
+//               loading.
+////////////////////////////////////////////////////////////////////
+EXPCL_PANDAEGG PT(PandaNode)
+qpload_egg_data(EggData &data);
+
+#endif

+ 1 - 1
panda/src/egg2sg/Sources.pp

@@ -6,7 +6,7 @@
 #begin lib_target
   #define TARGET egg2sg
   #define LOCAL_LIBS \
-    parametrics cull collide egg builder loader chan char switchnode
+    parametrics cull collide egg2pg egg builder loader chan char switchnode
 
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 
 

+ 0 - 61
panda/src/egg2sg/config_egg2sg.cxx

@@ -26,56 +26,6 @@
 ConfigureDef(config_egg2sg);
 NotifyCategoryDef(egg2sg, "");
 
-bool egg_mesh = config_egg2sg.GetBool("egg-mesh", true);
-bool egg_retesselate_coplanar = config_egg2sg.GetBool("egg-retesselate-coplanar", true);
-bool egg_unroll_fans = config_egg2sg.GetBool("egg-unroll-fans", true);
-bool egg_show_tstrips = config_egg2sg.GetBool("egg-show-tstrips", false);
-bool egg_show_qsheets = config_egg2sg.GetBool("egg-show-qsheets", false);
-bool egg_show_quads = config_egg2sg.GetBool("egg-show-quads", false);
-bool egg_false_color = (egg_show_tstrips | egg_show_qsheets | egg_show_quads);
-bool egg_show_normals = config_egg2sg.GetBool("egg-show-normals", false);
-double egg_normal_scale = config_egg2sg.GetDouble("egg-normal-scale", 1.0);
-bool egg_subdivide_polys = config_egg2sg.GetBool("egg-subdivide-polys", true);
-bool egg_consider_fans = config_egg2sg.GetBool("egg-consider-fans", true);
-double egg_max_tfan_angle = config_egg2sg.GetDouble("egg-max-tfan-angle", 40.0);
-int egg_min_tfan_tris = config_egg2sg.GetInt("egg-min-tfan-tris", 4);
-double egg_coplanar_threshold = config_egg2sg.GetDouble("egg-coplanar-threshold", 0.01);
-bool egg_ignore_mipmaps = config_egg2sg.GetBool("egg-ignore-mipmaps", false);
-bool egg_ignore_filters = config_egg2sg.GetBool("egg-ignore-filters", false);
-bool egg_ignore_clamp = config_egg2sg.GetBool("egg-ignore-clamp", false);
-bool egg_always_decal_textures = config_egg2sg.GetBool("egg-always-decal-textures", false);
-bool egg_ignore_decals = config_egg2sg.GetBool("egg-ignore-decals", false);
-bool egg_flatten = config_egg2sg.GetBool("egg-flatten", true);
-
-// It is almost always a bad idea to set this true.
-bool egg_flatten_siblings = config_egg2sg.GetBool("egg-flatten-siblings", false);
-
-bool egg_show_collision_solids = config_egg2sg.GetBool("egg-show-collision-solids", false);
-
-// When this is true, keep texture pathnames exactly the same as they
-// appeared in the egg file, in particular leaving them as relative
-// paths, rather than letting them reflect the full path at which they
-// were found.  This is particularly useful when generating bam files.
-// However, if the same texture is named by two different relative
-// paths, these will still be collapsed into one texture (using one of
-// the relative paths, chosen arbitrarily).
-bool egg_keep_texture_pathnames = config_egg2sg.GetBool("egg-keep-texture-pathnames", false);
-
-// When this is true, a <NurbsCurve> entry appearing in an egg file
-// will load a ClassicNurbsCurve object instead of the default, a
-// NurbsCurve object.  This only makes a difference when the NURBS++
-// library is available, in which case the default, NurbsCurve, is
-// actually a NurbsPPCurve object.
-bool egg_load_classic_nurbs_curves = config_egg2sg.GetBool("egg-load-classic-nurbs-curves", false);
-
-// When this is true, certain kinds of recoverable errors (not syntax
-// errors) in an egg file will be allowed and ignored when an egg file
-// is loaded.  When it is false, only perfectly pristine egg files may
-// be loaded.
-bool egg_accept_errors = config_egg2sg.GetBool("egg-accept-errors", true);
-
-CoordinateSystem egg_coordinate_system;
-
 ConfigureFn(config_egg2sg) {
   init_libegg2sg();
 }
@@ -98,17 +48,6 @@ init_libegg2sg() {
 
   LoaderFileTypeEgg::init_type();
 
-  string csstr = config_egg2sg.GetString("egg-coordinate-system", "default");
-  CoordinateSystem cs = parse_coordinate_system_string(csstr);
-
-  if (cs == CS_invalid) {
-    egg2sg_cat.error()
-      << "Unexpected egg-coordinate-system string: " << csstr << "\n";
-    cs = CS_default;
-  }
-  egg_coordinate_system = (cs == CS_default) ?
-    default_coordinate_system : cs;
-
   LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_ptr();
 
   reg->register_type(new LoaderFileTypeEgg);

+ 2 - 27
panda/src/egg2sg/config_egg2sg.h

@@ -26,36 +26,11 @@
 #include <notifyCategoryProxy.h>
 #include <dconfig.h>
 
+#include "config_egg2pg.h" // temp to declare the global consts
+
 ConfigureDecl(config_egg2sg, EXPCL_PANDAEGG, EXPTP_PANDAEGG);
 NotifyCategoryDecl(egg2sg, EXPCL_PANDAEGG, EXPTP_PANDAEGG);
 
-extern EXPCL_PANDAEGG bool egg_mesh;
-extern EXPCL_PANDAEGG bool egg_retesselate_coplanar;
-extern EXPCL_PANDAEGG bool egg_unroll_fans;
-extern EXPCL_PANDAEGG bool egg_show_tstrips;
-extern EXPCL_PANDAEGG bool egg_show_qsheets;
-extern EXPCL_PANDAEGG bool egg_show_quads;
-extern EXPCL_PANDAEGG bool egg_false_color;
-extern EXPCL_PANDAEGG bool egg_show_normals;
-extern EXPCL_PANDAEGG double egg_normal_scale;
-extern EXPCL_PANDAEGG bool egg_subdivide_polys;
-extern EXPCL_PANDAEGG bool egg_consider_fans;
-extern EXPCL_PANDAEGG double egg_max_tfan_angle;
-extern EXPCL_PANDAEGG int egg_min_tfan_tris;
-extern EXPCL_PANDAEGG double egg_coplanar_threshold;
-extern EXPCL_PANDAEGG CoordinateSystem egg_coordinate_system;
-extern EXPCL_PANDAEGG bool egg_ignore_mipmaps;
-extern EXPCL_PANDAEGG bool egg_ignore_filters;
-extern EXPCL_PANDAEGG bool egg_ignore_clamp;
-extern EXPCL_PANDAEGG bool egg_always_decal_textures;
-extern EXPCL_PANDAEGG bool egg_ignore_decals;
-extern EXPCL_PANDAEGG bool egg_flatten;
-extern EXPCL_PANDAEGG bool egg_flatten_siblings;
-extern EXPCL_PANDAEGG bool egg_show_collision_solids;
-extern EXPCL_PANDAEGG bool egg_keep_texture_pathnames;
-extern EXPCL_PANDAEGG bool egg_load_classic_nurbs_curves;
-extern EXPCL_PANDAEGG bool egg_accept_errors;
-
 extern EXPCL_PANDAEGG void init_libegg2sg();
 
 #endif

+ 22 - 0
panda/src/pgraph/pandaNode.I

@@ -369,6 +369,28 @@ clear_transform() {
   cdata->_transform = TransformState::make_identity();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::ls
+//       Access: Published
+//  Description: Lists all the nodes at and below the current path
+//               hierarchically.
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::
+ls() const {
+  ls(nout);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::ls
+//       Access: Published
+//  Description: Lists all the nodes at and below the current path
+//               hierarchically.
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::
+ls(ostream &out, int indent_level) const {
+  r_list_descendants(out, indent_level);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_children
 //       Access: Public

+ 25 - 1
panda/src/pgraph/pandaNode.cxx

@@ -349,7 +349,7 @@ write(ostream &out, int indent_level) const {
     out << " T";
   }
   if (!cdata->_state->is_empty()) {
-    out << " (" << *cdata->_state << ")";
+    out << " " << *cdata->_state;
   }
   out << "\n";
 }
@@ -650,6 +650,30 @@ fix_chain_lengths() {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::r_list_descendants
+//       Access: Private
+//  Description: The recursive implementation of ls().
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+r_list_descendants(ostream &out, int indent_level) const {
+  write(out, indent_level);
+
+  CDReader cdata(_cycler);
+  Down::const_iterator di;
+  for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
+    (*di).get_child()->r_list_descendants(out, indent_level + 2);
+  }
+
+  // Also report the number of stashed nodes at this level.
+  /*
+  int num_stashed = get_num_stashed();
+  if (num_stashed != 0) {
+    indent(out, indent_level) << "(" << num_stashed << " stashed)\n";
+  }
+  */
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::register_with_read_factory
 //       Access: Public, Static

+ 5 - 0
panda/src/pgraph/pandaNode.h

@@ -35,6 +35,7 @@
 #include "luse.h"
 #include "ordered_vector.h"
 #include "pointerTo.h"
+#include "notify.h"
 
 class NodeChainComponent;
 
@@ -90,6 +91,9 @@ PUBLISHED:
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;
 
+  INLINE void ls() const;
+  INLINE void ls(ostream &out, int indent_level = 0) const;
+
 public:
   virtual bool is_geom_node() const;
 
@@ -107,6 +111,7 @@ private:
   PT(NodeChainComponent) get_generic_component();
   void delete_component(NodeChainComponent *component);
   void fix_chain_lengths();
+  void r_list_descendants(ostream &out, int indent_level) const;
 
 private:
   class EXPCL_PANDA DownConnection {

+ 2 - 2
panda/src/pgraph/qpgeomNode.I

@@ -79,7 +79,7 @@ get_geom_state(int n) const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomNode::set_state
+//     Function: qpGeomNode::set_geom_state
 //       Access: Public
 //  Description: Changes the RenderState associated with the nth geom
 //               of the node.  This is just the RenderState directly
@@ -89,7 +89,7 @@ get_geom_state(int n) const {
 //               above this GeomNode.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpGeomNode::
-set_state(int n, const RenderState *state) {
+set_geom_state(int n, const RenderState *state) {
   CDWriter cdata(_cycler);
   nassertv(n >= 0 && n < (int)cdata->_geoms.size());
   cdata->_geoms[n]._state = state;

+ 19 - 18
panda/src/pgraph/qpgeomNode.cxx

@@ -96,6 +96,24 @@ qpGeomNode::
 ~qpGeomNode() {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomNode::write_geoms
+//       Access: Published
+//  Description: Writes a short description of all the Geoms in the
+//               node.
+////////////////////////////////////////////////////////////////////
+void qpGeomNode::
+write_geoms(ostream &out, int indent_level) const {
+  CDReader cdata(_cycler);
+  write(out, indent_level);
+  Geoms::const_iterator gi;
+  for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
+    const GeomEntry &entry = (*gi);
+    indent(out, indent_level + 2) 
+      << *entry._geom << " (" << *entry._state << ")\n";
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomNode::write_verbose
 //       Access: Published
@@ -105,7 +123,7 @@ qpGeomNode::
 void qpGeomNode::
 write_verbose(ostream &out, int indent_level) const {
   CDReader cdata(_cycler);
-  PandaNode::write(out, indent_level);
+  write(out, indent_level);
   Geoms::const_iterator gi;
   for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
     const GeomEntry &entry = (*gi);
@@ -126,23 +144,6 @@ output(ostream &out) const {
   out << " (" << get_num_geoms() << " geoms)";
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomNode::write
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void qpGeomNode::
-write(ostream &out, int indent_level) const {
-  CDReader cdata(_cycler);
-  PandaNode::write(out, indent_level);
-  Geoms::const_iterator gi;
-  for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
-    const GeomEntry &entry = (*gi);
-    indent(out, indent_level + 2) 
-      << *entry._geom << " (" << *entry._state << ")\n";
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomNode::is_geom_node
 //       Access: Public, Virtual

+ 3 - 3
panda/src/pgraph/qpgeomNode.h

@@ -48,17 +48,17 @@ PUBLISHED:
   INLINE int get_num_geoms() const;
   INLINE Geom *get_geom(int n) const;
   INLINE const RenderState *get_geom_state(int n) const;
-  INLINE void set_state(int n, const RenderState *state);
+  INLINE void set_geom_state(int n, const RenderState *state);
 
-  INLINE int add_geom(Geom *geom, const RenderState *state);
+  INLINE int add_geom(Geom *geom, const RenderState *state = RenderState::make_empty());
   INLINE void remove_geom(int n);
   INLINE void remove_all_geoms();
 
+  void write_geoms(ostream &out, int indent_level) const;
   void write_verbose(ostream &out, int indent_level) const;
 
 public:
   virtual void output(ostream &out) const;
-  virtual void write(ostream &out, int indent_level) const;
 
   virtual bool is_geom_node() const;
 

+ 8 - 0
panda/src/pgraph/renderState.cxx

@@ -423,6 +423,14 @@ add(const RenderAttrib *attrib, int override) const {
   *result = new_attribute;
   ++result;
 
+  if (ai != _attributes.end() && !(new_attribute < (*ai))) {
+    // At this point we know:
+    // !((*ai) < new_attribute) && !(new_attribute < (*ai))
+    // which means (*ai) == new_attribute--so we should leave it out,
+    // to avoid duplicating attributes in the set.
+    ++ai;
+  }
+
   while (ai != _attributes.end()) {
     *result = *ai;
     ++ai;

+ 1 - 1
panda/src/testbed/Sources.pp

@@ -32,7 +32,7 @@
   #define SOURCES \
     pview.cxx
 
-  #define LOCAL_LIBS pgraph $[LOCAL_LIBS]
+  #define LOCAL_LIBS egg2pg pgraph $[LOCAL_LIBS]
 #end test_bin_target
 
 #begin test_bin_target

+ 59 - 14
panda/src/testbed/pview.cxx

@@ -33,6 +33,10 @@
 #include "texture.h"
 #include "texturePool.h"
 
+// To load egg files directly.
+#include "qpload_egg_file.h"
+#include "eggData.h"
+
 // These are in support of legacy data graph operations.
 #include "namedNode.h"
 #include "mouse.h"
@@ -59,6 +63,28 @@ static const int win_height = config_pview.GetInt("win-height", 480);
 // As long as this is true, the main loop will continue running.
 bool run_flag = true;
 
+// These are used by report_frame_rate().
+static double start_time = 0.0;
+static int start_frame_count = 0;
+
+void 
+report_frame_rate() {
+  double now = ClockObject::get_global_clock()->get_frame_time();
+  double delta = now - start_time;
+  
+  int frame_count = ClockObject::get_global_clock()->get_frame_count();
+  int num_frames = frame_count - start_frame_count;
+  if (num_frames > 0) {
+    nout << endl << num_frames << " frames in " << delta << " seconds" << endl;
+    double x = ((double)num_frames) / delta;
+    nout << x << " fps average (" << 1000.0 / x << "ms)" << endl;
+
+    // Reset the frame rate counter for the next press of 'f'.
+    start_time = now;
+    start_frame_count = frame_count;
+  }
+}
+
 PT(GraphicsPipe)
 make_pipe() {
   // We use the GraphicsPipe factory to make us a renderable pipe
@@ -166,22 +192,28 @@ make_default_geometry(PandaNode *parent) {
 
 void
 get_models(PandaNode *parent, int argc, char *argv[]) {
-  make_default_geometry(parent);
-  /*
-  Loader loader;
-
-  for (int i = 1; i < argc; i++) {
-    Filename filename = argv[i];
-
-    cerr << "Loading " << filename << "\n";
-    PT_Node node = loader.load_sync(filename);
-    if (node == (Node *)NULL) {
-      cerr << "Unable to load " << filename << "\n";
-    } else {
-      new RenderRelation(parent, node);
+  if (argc < 2) {
+    // In the absence of any models on the command line, load up a
+    // default triangle so we at least have something to look at.
+    make_default_geometry(parent);
+
+  } else {
+
+    for (int i = 1; i < argc; i++) {
+      Filename filename = argv[i];
+      
+      cerr << "Loading " << filename << "\n";
+      EggData::resolve_egg_filename(filename);
+      PT(PandaNode) node = qpload_egg_file(filename);
+      if (node == (PandaNode *)NULL) {
+        cerr << "Unable to load " << filename << "\n";
+
+      } else {
+        node->ls();
+        parent->add_child(node);
+      }
     }
   }
-  */
 }
 
 NamedNode * 
@@ -218,6 +250,11 @@ event_esc(CPT_Event) {
   run_flag = false;
 }
 
+void
+event_f(CPT_Event) {
+  report_frame_rate();
+}
+
 int
 main(int argc, char *argv[]) {
   // First, we need a GraphicsPipe, before we can open a window.
@@ -245,11 +282,18 @@ main(int argc, char *argv[]) {
   EventHandler event_handler(EventQueue::get_global_event_queue());
   event_handler.add_hook("escape", event_esc);
   event_handler.add_hook("q", event_esc);
+  event_handler.add_hook("f", event_f);
 
 
   // Put something in the scene graph to look at.
   get_models(render, argc, argv);
 
+  // Tick the clock once so we won't count the time spent loading up
+  // files, above, in our frame rate average.
+  ClockObject::get_global_clock()->tick();
+  start_time = ClockObject::get_global_clock()->get_frame_time();
+  start_frame_count = ClockObject::get_global_clock()->get_frame_count();
+
 
   // This is our main update loop.  Loop here until someone
   // (e.g. event_esc) sets run_flag to false.
@@ -261,6 +305,7 @@ main(int argc, char *argv[]) {
     engine->render_frame();
   } 
 
+  report_frame_rate();
   delete engine;
   return (0);
 }