2
0
Эх сурвалжийг харах

loader.saveModel(), EggToObjConverter, etc.

David Rose 13 жил өмнө
parent
commit
3af8da3bcd
40 өөрчлөгдсөн 2869 нэмэгдсэн , 1535 устгасан
  1. 72 0
      direct/src/showbase/Loader.py
  2. 6 2
      panda/src/egg2pg/Sources.pp
  3. 25 0
      panda/src/egg2pg/eggSaver.I
  4. 1132 0
      panda/src/egg2pg/eggSaver.cxx
  5. 110 0
      panda/src/egg2pg/eggSaver.h
  6. 36 0
      panda/src/egg2pg/loaderFileTypeEgg.cxx
  7. 5 0
      panda/src/egg2pg/loaderFileTypeEgg.h
  8. 1 0
      panda/src/egg2pg/p3egg2pg_composite1.cxx
  9. 2 1
      panda/src/egg2pg/p3egg2pg_composite2.cxx
  10. 52 0
      panda/src/egg2pg/save_egg_file.cxx
  11. 44 0
      panda/src/egg2pg/save_egg_file.h
  12. 3 0
      panda/src/pgraph/Sources.pp
  13. 2 0
      panda/src/pgraph/config_pgraph.cxx
  14. 44 5
      panda/src/pgraph/loader.I
  15. 117 0
      panda/src/pgraph/loader.cxx
  16. 12 0
      panda/src/pgraph/loader.h
  17. 38 1
      panda/src/pgraph/loaderFileType.cxx
  18. 5 0
      panda/src/pgraph/loaderFileType.h
  19. 47 0
      panda/src/pgraph/loaderFileTypeBam.cxx
  20. 5 0
      panda/src/pgraph/loaderFileTypeBam.h
  21. 82 0
      panda/src/pgraph/modelSaveRequest.I
  22. 58 0
      panda/src/pgraph/modelSaveRequest.cxx
  23. 83 0
      panda/src/pgraph/modelSaveRequest.h
  24. 1 0
      panda/src/pgraph/p3pgraph_composite3.cxx
  25. 3 1092
      pandatool/src/bam/bamToEgg.cxx
  26. 0 67
      pandatool/src/bam/bamToEgg.h
  27. 5 2
      pandatool/src/converter/Sources.pp
  28. 81 0
      pandatool/src/converter/eggToSomethingConverter.I
  29. 85 0
      pandatool/src/converter/eggToSomethingConverter.cxx
  30. 77 0
      pandatool/src/converter/eggToSomethingConverter.h
  31. 4 2
      pandatool/src/objegg/Sources.pp
  32. 445 0
      pandatool/src/objegg/eggToObjConverter.cxx
  33. 79 0
      pandatool/src/objegg/eggToObjConverter.h
  34. 2 2
      pandatool/src/objegg/objToEggConverter.h
  35. 1 1
      pandatool/src/objprogs/Sources.pp
  36. 3 306
      pandatool/src/objprogs/eggToObj.cxx
  37. 1 35
      pandatool/src/objprogs/eggToObj.h
  38. 4 2
      pandatool/src/ptloader/config_ptloader.cxx
  39. 87 15
      pandatool/src/ptloader/loaderFileTypePandatool.cxx
  40. 10 2
      pandatool/src/ptloader/loaderFileTypePandatool.h

+ 72 - 0
direct/src/showbase/Loader.py

@@ -289,6 +289,75 @@ class Loader(DirectObject):
         assert Loader.notify.debug("Unloading model: %s" % (modelNode.getFullpath()))
         ModelPool.releaseModel(modelNode)
 
+    def saveModel(self, modelPath, node, loaderOptions = None, 
+                  callback = None, extraArgs = [], priority = None):
+        """ Saves the model (a NodePath or PandaNode) to the indicated
+        filename path.  Returns true on success, false on failure.  If
+        a callback is used, the model is saved asynchronously, and the
+        true/false status is passed to the callback function. """
+
+        if loaderOptions == None:
+            loaderOptions = LoaderOptions()
+        else:
+            loaderOptions = LoaderOptions(loaderOptions)
+
+        if isinstance(modelPath, types.StringTypes) or \
+           isinstance(modelPath, Filename):
+            # We were given a single model pathname.
+            modelList = [modelPath]
+            nodeList = [node]
+            if phaseChecker:
+                phaseChecker(modelPath, loaderOptions)
+
+            gotList = False
+        else:
+            # Assume we were given a list of model pathnames.
+            modelList = modelPath
+            nodeList = node
+            gotList = True
+
+        assert(len(modelList) == len(nodeList))
+
+        # Make sure we have PandaNodes, not NodePaths.
+        for i in range(len(nodeList)):
+            if isinstance(nodeList[i], NodePath):
+                nodeList[i] = nodeList[i].node()
+
+        # From here on, we deal with a list of (filename, node) pairs.
+        modelList = zip(modelList, nodeList)
+
+        if callback is None:
+            # We got no callback, so it's a synchronous save.
+
+            result = []
+            for modelPath, node in modelList:
+                thisResult = self.loader.saveSync(Filename(modelPath), loaderOptions, node)
+                result.append(thisResult)
+
+            if gotList:
+                return result
+            else:
+                return result[0]
+
+        else:
+            # We got a callback, so we want an asynchronous (threaded)
+            # save.  We'll return immediately, but when all of the
+            # requested models have been saved, we'll invoke the
+            # callback (passing it the models on the parameter list).
+            
+            cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
+            i=0
+            for modelPath, node in modelList:
+                request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node)
+                if priority is not None:
+                    request.setPriority(priority)
+                request.setDoneEvent(self.hook)
+                request.setPythonObject((cb, i))
+                i+=1
+                self.loader.saveAsync(request)
+                cb.requests[request] = True
+            return cb
+            
 
     # font loading funcs
     def loadFont(self, modelPath,
@@ -875,4 +944,7 @@ class Loader(DirectObject):
         elif hasattr(request, "getSound"):
             object = request.getSound()
 
+        elif hasattr(request, "getSuccess"):
+            object = request.getSuccess()
+
         cb.gotObject(i, object)

+ 6 - 2
panda/src/egg2pg/Sources.pp

@@ -20,8 +20,10 @@
     eggBinner.h \
     eggLoader.h eggLoader.I \
     eggRenderState.h eggRenderState.I \
+    eggSaver.h eggSaver.I \
     egg_parametrics.h \
     load_egg_file.h \
+    save_egg_file.h \
     loaderFileTypeEgg.h
 
   #define INCLUDED_SOURCES \
@@ -32,8 +34,10 @@
     eggBinner.cxx \
     eggLoader.cxx \
     eggRenderState.cxx \
+    eggSaver.cxx \
     egg_parametrics.cxx \
     load_egg_file.cxx \
+    save_egg_file.cxx \
     loaderFileTypeEgg.cxx
 
   #if $[DONT_COMBINE_PGRAPH]    
@@ -43,8 +47,8 @@
   #endif
 
   #define INSTALL_HEADERS \
-    egg_parametrics.h load_egg_file.h config_egg2pg.h
+    egg_parametrics.h load_egg_file.h save_egg_file.h config_egg2pg.h
 
-  #define IGATESCAN load_egg_file.h 
+  #define IGATESCAN load_egg_file.h save_egg_file.h
 
 #end lib_target

+ 25 - 0
panda/src/egg2pg/eggSaver.I

@@ -0,0 +1,25 @@
+// Filename: eggSaver.I
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::get_egg_data
+//       Access: Published
+//  Description: Returns the EggData populated within this class.
+////////////////////////////////////////////////////////////////////
+INLINE EggData *EggSaver::
+get_egg_data() const {
+  return _data;
+}
+

+ 1132 - 0
panda/src/egg2pg/eggSaver.cxx

@@ -0,0 +1,1132 @@
+// Filename: eggSaver.cxx
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "eggSaver.h"
+
+#include "pandaNode.h"
+#include "workingNodePath.h"
+#include "nodePath.h"
+#include "billboardEffect.h"
+#include "renderEffects.h"
+#include "transformState.h"
+#include "colorScaleAttrib.h"
+#include "colorAttrib.h"
+#include "textureAttrib.h"
+#include "cullFaceAttrib.h"
+#include "transparencyAttrib.h"
+#include "depthWriteAttrib.h"
+#include "lodNode.h"
+#include "switchNode.h"
+#include "sequenceNode.h"
+#include "collisionNode.h"
+#include "collisionPolygon.h"
+#include "collisionPlane.h"
+#include "collisionSphere.h"
+#include "collisionInvSphere.h"
+#include "collisionTube.h"
+#include "textureStage.h"
+#include "geomNode.h"
+#include "geom.h"
+#include "geomTriangles.h"
+#include "geomPatches.h"
+#include "geomPoints.h"
+#include "geomLines.h"
+#include "geomVertexReader.h"
+#include "transformTable.h"
+#include "modelNode.h"
+#include "animBundleNode.h"
+#include "animChannelMatrixXfmTable.h"
+#include "characterJoint.h"
+#include "character.h"
+#include "string_utils.h"
+#include "bamFile.h"
+#include "bamCacheRecord.h"
+#include "eggSAnimData.h"
+#include "eggXfmAnimData.h"
+#include "eggXfmSAnim.h"
+#include "eggGroup.h"
+#include "eggVertexPool.h"
+#include "eggVertex.h"
+#include "eggPrimitive.h"
+#include "eggPolygon.h"
+#include "eggPatch.h"
+#include "eggPoint.h"
+#include "eggLine.h"
+#include "eggTexture.h"
+#include "eggMaterial.h"
+#include "eggRenderMode.h"
+#include "eggTable.h"
+#include "dcast.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggSaver::
+EggSaver(EggData *data) :
+  _data(data)
+{
+  if (_data == NULL) {
+    _data = new EggData;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::add_node
+//       Access: Published
+//  Description: Adds the scene graph rooted at the indicated node to
+//               the accumulated egg data within this object.  Call
+//               get_egg_data() to retrieve the result.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+add_node(PandaNode *node) {
+  _vpool = new EggVertexPool(node->get_name());
+  _data->add_child(_vpool);
+
+  NodePath root(node);
+  convert_node(WorkingNodePath(root), _data, false);
+
+  // Remove the vertex pool if it has no vertices.
+  if (_vpool->empty()) {
+    _data->remove_child(_vpool);
+  }
+  _vpool = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_node
+//       Access: Private
+//  Description: Converts the indicated node to the corresponding Egg
+//               constructs, by first determining what kind of node it
+//               is.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
+             bool has_decal) {
+  PandaNode *node = node_path.node();
+  if (node->is_geom_node()) {
+    convert_geom_node(DCAST(GeomNode, node), node_path, egg_parent, has_decal);
+
+  } else if (node->is_of_type(LODNode::get_class_type())) {
+    convert_lod_node(DCAST(LODNode, node), node_path, egg_parent, has_decal);
+
+  } else if (node->is_of_type(SequenceNode::get_class_type())) {
+    convert_sequence_node(DCAST(SequenceNode, node), node_path, egg_parent, has_decal);
+
+  } else if (node->is_of_type(SwitchNode::get_class_type())) {
+    convert_switch_node(DCAST(SwitchNode, node), node_path, egg_parent, has_decal);
+
+  } else if (node->is_of_type(CollisionNode::get_class_type())) {
+    convert_collision_node(DCAST(CollisionNode, node), node_path, egg_parent, has_decal);
+
+  } else if (node->is_of_type(AnimBundleNode::get_class_type())) {
+    convert_anim_node(DCAST(AnimBundleNode, node), node_path, egg_parent, has_decal);
+
+  } else if (node->is_of_type(Character::get_class_type())) {
+    convert_character_node(DCAST(Character, node), node_path, egg_parent, has_decal);
+
+  } else {
+    // Just a generic node.
+    EggGroup *egg_group = new EggGroup(node->get_name());
+    egg_parent->add_child(egg_group);
+    apply_node_properties(egg_group, node);
+    
+    recurse_nodes(node_path, egg_group, has_decal);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_lod_node
+//       Access: Private
+//  Description: Converts the indicated LODNode to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_lod_node(LODNode *node, const WorkingNodePath &node_path,
+                 EggGroupNode *egg_parent, bool has_decal) {
+  // An LOD node gets converted to an ordinary EggGroup, but we apply
+  // the appropriate switch conditions to each of our children.
+  EggGroup *egg_group = new EggGroup(node->get_name());
+  egg_parent->add_child(egg_group);
+  apply_node_properties(egg_group, node);
+
+  int num_children = node->get_num_children();
+  int num_switches = node->get_num_switches();
+
+  num_children = min(num_children, num_switches);
+
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child = node->get_child(i);
+
+    // Convert just this one node to an EggGroup.
+    PT(EggGroup) next_group = new EggGroup;
+    convert_node(WorkingNodePath(node_path, child), next_group, has_decal);
+
+    if (next_group->size() == 1) {
+      // If we have exactly one child, and that child is an EggGroup,
+      // collapse.
+      EggNode *child_node = *next_group->begin();
+      if (child_node->is_of_type(EggGroup::get_class_type())) {
+        PT(EggGroup) child = DCAST(EggGroup, child_node);
+        next_group->remove_child(child.p());
+        next_group = child;
+      }
+    }
+
+    // Now set up the switching properties appropriately.
+    PN_stdfloat in = node->get_in(i);
+    PN_stdfloat out = node->get_out(i);
+    LPoint3 center = node->get_center();
+    EggSwitchConditionDistance dist(in, out, LCAST(double, center));
+    next_group->set_lod(dist);
+    egg_group->add_child(next_group.p());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_sequence_node
+//       Access: Private
+//  Description: Converts the indicated SequenceNode to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_sequence_node(SequenceNode *node, const WorkingNodePath &node_path,
+                      EggGroupNode *egg_parent, bool has_decal) {
+  // A sequence node gets converted to an ordinary EggGroup, we only apply
+  // the appropriate switch attributes to turn it into a sequence
+  EggGroup *egg_group = new EggGroup(node->get_name());
+  egg_parent->add_child(egg_group);
+  apply_node_properties(egg_group, node);
+
+  // turn it into a sequence with the right frame-rate
+  egg_group->set_switch_flag(true);
+  egg_group->set_switch_fps(node->get_frame_rate());
+
+  int num_children = node->get_num_children();
+
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child = node->get_child(i);
+
+    // Convert just this one node to an EggGroup.
+    PT(EggGroup) next_group = new EggGroup;
+    convert_node(WorkingNodePath(node_path, child), next_group, has_decal);
+
+    egg_group->add_child(next_group.p());
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_switch_node
+//       Access: Private
+//  Description: Converts the indicated SwitchNode to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_switch_node(SwitchNode *node, const WorkingNodePath &node_path,
+                    EggGroupNode *egg_parent, bool has_decal) {
+  // A sequence node gets converted to an ordinary EggGroup, we only apply
+  // the appropriate switch attributes to turn it into a sequence
+  EggGroup *egg_group = new EggGroup(node->get_name());
+  egg_parent->add_child(egg_group);
+  apply_node_properties(egg_group, node);
+
+  // turn it into a switch..
+  egg_group->set_switch_flag(true);
+
+  int num_children = node->get_num_children();
+
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child = node->get_child(i);
+
+    // Convert just this one node to an EggGroup.
+    PT(EggGroup) next_group = new EggGroup;
+    convert_node(WorkingNodePath(node_path, child), next_group, has_decal);
+
+    egg_group->add_child(next_group.p());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_animGroup_node
+//       Access: Private
+//  Description: Converts the indicated AnimationGroupNodes to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+EggGroupNode * EggSaver::convert_animGroup_node(AnimGroup *animGroup, double fps ) {
+  int num_children = animGroup->get_num_children();
+
+  EggGroupNode *eggNode = NULL;
+  if (animGroup->is_of_type(AnimBundle::get_class_type())) {
+    EggTable *eggTable = new EggTable(animGroup->get_name());
+    eggTable ->set_table_type(EggTable::TT_bundle);
+    eggNode = eggTable;
+  } else if (animGroup->is_of_type(AnimGroup::get_class_type())) {
+    EggTable *eggTable = new EggTable(animGroup->get_name());
+    eggTable ->set_table_type(EggTable::TT_table);
+    eggNode = eggTable;
+  }
+
+  if (animGroup->is_of_type(AnimChannelMatrixXfmTable::get_class_type())) {
+    AnimChannelMatrixXfmTable *xmfTable = DCAST(AnimChannelMatrixXfmTable, animGroup);
+    EggXfmSAnim *egg_anim = new EggXfmSAnim("xform");
+    egg_anim->set_fps(fps);
+    for (int i = 0; i < num_matrix_components; i++) {
+      string componentName(1, matrix_component_letters[i]);
+      char table_id = matrix_component_letters[i];
+      CPTA_stdfloat table = xmfTable->get_table(table_id);
+
+      if (xmfTable->has_table(table_id)) {
+        for (unsigned int j = 0; j < table.size(); j++) {
+          egg_anim->add_component_data(componentName, table[(int)j]);
+        }
+      }
+    }
+    eggNode->add_child(egg_anim);
+  }
+  for (int i = 0; i < num_children; i++) {
+    AnimGroup *animChild = animGroup->get_child(i);
+    EggGroupNode *eggChildNode = convert_animGroup_node(animChild, fps);
+    if (eggChildNode!=NULL) {
+      nassertr(eggNode!=NULL, NULL);
+      eggNode->add_child(eggChildNode);
+    }
+  } 
+  return eggNode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_anim_node
+//       Access: Private
+//  Description: Converts the indicated AnimNode to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_anim_node(AnimBundleNode *node, const WorkingNodePath &node_path,
+                    EggGroupNode *egg_parent, bool has_decal) {
+  
+  // A sequence node gets converted to an ordinary EggGroup, we only apply
+  // the appropriate switch attributes to turn it into a sequence
+  EggTable *eggTable = new EggTable();
+  //egg_parent->add_child(eggTable);
+  _data->add_child(eggTable);
+ 
+  AnimBundle *animBundle = node->get_bundle();
+  // turn it into a switch..
+  //egg_group->set_switch_flag(true);
+
+  EggGroupNode *eggAnimation = convert_animGroup_node(animBundle, animBundle->get_base_frame_rate());
+  eggTable->add_child(eggAnimation);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_character_bundle
+//       Access: Private
+//  Description: Converts the indicated Character Bundle to the corresponding
+//               Egg joints structure.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, CharacterJointMap *jointMap) {
+  int num_children = bundleNode->get_num_children();
+  
+  EggGroupNode *joint_group = egg_parent;
+  if (bundleNode->is_of_type(CharacterJoint::get_class_type())) {
+    CharacterJoint *character_joint = DCAST(CharacterJoint, bundleNode);
+
+    LMatrix4 transformf;
+    character_joint->get_net_transform(transformf);
+    LMatrix4d transformd(LCAST(double, transformf));
+    EggGroup *joint = new EggGroup(bundleNode->get_name());
+    joint->add_matrix4(transformd);
+    joint->set_group_type(EggGroup::GT_joint);
+    joint_group = joint;
+    egg_parent->add_child(joint_group);
+    if (jointMap!=NULL) {
+      CharacterJointMap::iterator mi = jointMap->find(character_joint);
+      if (mi != jointMap->end()) {
+        pvector<pair<EggVertex*,PN_stdfloat> > &joint_vertices = (*mi).second;
+        pvector<pair<EggVertex*,PN_stdfloat> >::const_iterator vi;
+        for (vi = joint_vertices.begin(); vi != joint_vertices.end(); ++vi) {
+          joint->set_vertex_membership((*vi).first, (*vi).second);
+        }
+      }
+    }
+  }
+
+  for (int i = 0; i < num_children ; i++) {
+    PartGroup *partGroup= bundleNode->get_child(i);
+    convert_character_bundle(partGroup, joint_group, jointMap);
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_character_node
+//       Access: Private
+//  Description: Converts the indicated Character to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_character_node(Character *node, const WorkingNodePath &node_path,
+                    EggGroupNode *egg_parent, bool has_decal) {
+  
+  // A sequence node gets converted to an ordinary EggGroup, we only apply
+  // the appropriate switch attributes to turn it into a sequence
+  EggGroup *egg_group = new EggGroup(node->get_name());
+  egg_group->set_dart_type(EggGroup::DT_default);
+  egg_parent->add_child(egg_group);
+  apply_node_properties(egg_group, node);
+
+  CharacterJointMap jointMap;
+  
+  // turn it into a switch..
+  //egg_group->set_switch_flag(true);
+
+  int num_children = node->get_num_children();
+  int num_bundles = node->get_num_bundles();
+
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child = node->get_child(i);
+
+    if (child->is_geom_node()) {
+      convert_geom_node(DCAST(GeomNode, child), WorkingNodePath(node_path, child), egg_group, has_decal, &jointMap);
+    }
+  }
+
+  for (int i = 0; i < num_bundles ; i++) {
+    PartBundle *bundle= node->get_bundle(i);
+    convert_character_bundle(bundle, egg_group, &jointMap);
+  }
+
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_collision_node
+//       Access: Private
+//  Description: Converts the indicated CollisionNode to the corresponding
+//               Egg constructs.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
+                       EggGroupNode *egg_parent, bool has_decal) {
+  // A sequence node gets converted to an ordinary EggGroup, we only apply
+  // the appropriate switch attributes to turn it into a sequence
+  EggGroup *egg_group = new EggGroup(node->get_name());
+  egg_parent->add_child(egg_group);
+  apply_node_properties(egg_group, node, false);
+
+  // turn it into a collision node
+  egg_group->set_cs_type(EggGroup::CST_polyset);
+  egg_group->set_collide_flags(EggGroup::CF_descend);
+
+  NodePath np = node_path.get_node_path();
+  CPT(TransformState) net_transform = np.get_net_transform();
+  LMatrix4 net_mat = net_transform->get_mat();
+  LMatrix4 inv = LCAST(PN_stdfloat, egg_parent->get_vertex_frame_inv());
+  net_mat = net_mat * inv;
+
+  int num_solids = node->get_num_solids();
+
+  if (num_solids > 0) {
+    // create vertex pool for collisions
+    EggVertexPool *cvpool = new EggVertexPool("vpool-collision");
+    egg_group->add_child(cvpool);
+
+    // traverse solids
+    for (int i = 0; i < num_solids; i++) {
+      CPT(CollisionSolid) child = node->get_solid(i);
+      if (child->is_of_type(CollisionPolygon::get_class_type())) {
+        EggPolygon *egg_poly = new EggPolygon;
+        egg_group->add_child(egg_poly);
+
+        CPT(CollisionPolygon) poly = DCAST(CollisionPolygon, child);
+        int num_points = poly->get_num_points();
+        for (int j = 0; j < num_points; j++) {
+          EggVertex egg_vert;
+          egg_vert.set_pos(LCAST(double, poly->get_point(j) * net_mat));
+          egg_vert.set_normal(LCAST(double, poly->get_normal() * net_mat));
+
+          EggVertex *new_egg_vert = cvpool->create_unique_vertex(egg_vert);
+          egg_poly->add_vertex(new_egg_vert);
+        }
+      } else if (child->is_of_type(CollisionPlane::get_class_type())) {
+        nout << "Encountered unhandled collsion type: CollisionPlane" << "\n";
+      } else if (child->is_of_type(CollisionSphere::get_class_type())) {
+        nout << "Encountered unhandled collsion type: CollisionSphere" << "\n";
+      } else if (child->is_of_type(CollisionInvSphere::get_class_type())) {
+        nout << "Encountered unhandled collsion type: CollisionInvSphere" << "\n";
+      } else if (child->is_of_type(CollisionTube::get_class_type())) {
+        nout << "Encountered unhandled collsion type: CollisionTube" << "\n";
+      } else {
+        nout << "Encountered unknown CollisionSolid" << "\n";
+      }
+    }
+  }
+
+  // recurse over children - hm. do I need to do this?
+  recurse_nodes(node_path, egg_group, has_decal);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_geom_node
+//       Access: Private
+//  Description: Converts a GeomNode to the corresponding egg
+//               structures.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_geom_node(GeomNode *node, const WorkingNodePath &node_path, 
+                  EggGroupNode *egg_parent, bool has_decal, CharacterJointMap *jointMap) {
+  PT(EggGroup) egg_group = new EggGroup(node->get_name());
+  bool fancy_attributes = apply_node_properties(egg_group, node);
+
+  if (node->get_effects()->has_decal()) {
+    has_decal = true;
+  }
+
+  if (has_decal) {
+    egg_group->set_decal_flag(true);
+  }
+
+  if (fancy_attributes || has_decal || !node->get_name().empty()) {
+    // If we have any fancy attributes on the node, or if we're making
+    // decal geometry, we have to make a special node to hold the
+    // geometry (normally it would just appear within its parent).
+    egg_parent->add_child(egg_group.p());
+    egg_parent = egg_group;
+  }
+
+  NodePath np = node_path.get_node_path();
+  CPT(RenderState) net_state = np.get_net_state();
+  CPT(TransformState) net_transform = np.get_net_transform();
+  LMatrix4 net_mat = net_transform->get_mat();
+  LMatrix4 inv = LCAST(PN_stdfloat, egg_parent->get_vertex_frame_inv());
+  net_mat = net_mat * inv;
+
+  // Now get out all the various kinds of geometry.
+  int num_geoms = node->get_num_geoms();
+  for (int i = 0; i < num_geoms; ++i) {
+    CPT(RenderState) geom_state = net_state->compose(node->get_geom_state(i));
+
+    const Geom *geom = node->get_geom(i);
+    int num_primitives = geom->get_num_primitives();
+    for (int j = 0; j < num_primitives; ++j) {
+      const GeomPrimitive *primitive = geom->get_primitive(j);
+      CPT(GeomPrimitive) simple = primitive->decompose();
+      CPT(GeomVertexData) vdata = geom->get_vertex_data();
+      //        vdata = vdata->animate_vertices(true, Thread::get_current_thread());
+      convert_primitive(vdata, simple, geom_state,
+                        net_mat, egg_parent, jointMap);
+    }
+  }
+  
+  recurse_nodes(node_path, egg_parent, has_decal);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::convert_primitive
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+convert_primitive(const GeomVertexData *vertex_data,
+                  const GeomPrimitive *primitive,
+                  const RenderState *net_state, 
+                  const LMatrix4 &net_mat, EggGroupNode *egg_parent,
+                  CharacterJointMap *jointMap) {
+  GeomVertexReader reader(vertex_data);
+
+  // Check for a color scale.
+  LVecBase4 color_scale(1.0f, 1.0f, 1.0f, 1.0f);
+  const ColorScaleAttrib *csa = DCAST(ColorScaleAttrib, net_state->get_attrib(ColorScaleAttrib::get_class_type()));
+  if (csa != (const ColorScaleAttrib *)NULL) {
+    color_scale = csa->get_scale();
+  }
+
+  // Check for a color override.
+  bool has_color_override = false;
+  bool has_color_off = false;
+  LColor color_override;
+  const ColorAttrib *ca = DCAST(ColorAttrib, net_state->get_attrib(ColorAttrib::get_class_type()));
+  if (ca != (const ColorAttrib *)NULL) {
+    if (ca->get_color_type() == ColorAttrib::T_flat) {
+      has_color_override = true;
+      color_override = ca->get_color();
+      color_override.set(color_override[0] * color_scale[0],
+                         color_override[1] * color_scale[1],
+                         color_override[2] * color_scale[2],
+                         color_override[3] * color_scale[3]);
+
+    } else if (ca->get_color_type() == ColorAttrib::T_off) {
+      has_color_off = true;
+    }
+  }
+
+  // Check for a texture.
+  EggTexture *egg_tex = (EggTexture *)NULL;
+  const TextureAttrib *ta = DCAST(TextureAttrib, net_state->get_attrib(TextureAttrib::get_class_type()));
+  if (ta != (const TextureAttrib *)NULL) {
+    egg_tex = get_egg_texture(ta->get_texture());
+  }
+
+  // Check the texture environment
+  if ((ta != (const TextureAttrib *)NULL) && (egg_tex != (const EggTexture *)NULL)) {
+    TextureStage* tex_stage = ta->get_on_stage(0);
+    if (tex_stage != (const TextureStage *)NULL) {
+      switch (tex_stage->get_mode()) {
+        case TextureStage::M_modulate:
+          if (has_color_off == true) {
+            egg_tex->set_env_type(EggTexture::ET_replace);
+          } else {
+            egg_tex->set_env_type(EggTexture::ET_modulate);
+          }
+          break;
+        case TextureStage::M_decal:
+          egg_tex->set_env_type(EggTexture::ET_decal);
+          break;
+        case TextureStage::M_blend:
+          egg_tex->set_env_type(EggTexture::ET_blend);
+          break;
+        case TextureStage::M_replace:
+          egg_tex->set_env_type(EggTexture::ET_replace);
+          break;
+        case TextureStage::M_add:
+          egg_tex->set_env_type(EggTexture::ET_add);
+          break;
+        case TextureStage::M_blend_color_scale:
+          egg_tex->set_env_type(EggTexture::ET_blend_color_scale);
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  // Check the backface flag.
+  bool bface = false;
+  const RenderAttrib *cf_attrib = net_state->get_attrib(CullFaceAttrib::get_class_type());
+  if (cf_attrib != (const RenderAttrib *)NULL) {
+    const CullFaceAttrib *cfa = DCAST(CullFaceAttrib, cf_attrib);
+    if (cfa->get_effective_mode() == CullFaceAttrib::M_cull_none) {
+      bface = true;
+    }
+  }
+
+  // Check the depth write flag - only needed for AM_blend_no_occlude
+  bool has_depthwrite = false;
+  DepthWriteAttrib::Mode depthwrite = DepthWriteAttrib::M_on;
+  const RenderAttrib *dw_attrib = net_state->get_attrib(DepthWriteAttrib::get_class_type());
+  if (dw_attrib != (const RenderAttrib *)NULL) {
+    const DepthWriteAttrib *dwa = DCAST(DepthWriteAttrib, dw_attrib);
+    depthwrite = dwa->get_mode();
+    has_depthwrite = true;
+  }
+
+  // Check the transparency flag.
+  bool has_transparency = false;
+  TransparencyAttrib::Mode transparency = TransparencyAttrib::M_none;
+  const RenderAttrib *tr_attrib = net_state->get_attrib(TransparencyAttrib::get_class_type());
+  if (tr_attrib != (const RenderAttrib *)NULL) {
+    const TransparencyAttrib *tra = DCAST(TransparencyAttrib, tr_attrib);
+    transparency = tra->get_mode();
+    has_transparency = true;
+  }
+  if (has_transparency && (egg_tex != (EggTexture *)NULL)) {
+    EggRenderMode::AlphaMode tex_trans = EggRenderMode::AM_unspecified;
+    switch (transparency) {
+      case TransparencyAttrib::M_none:
+        tex_trans = EggRenderMode::AM_off;
+        break;
+      case TransparencyAttrib::M_alpha:
+        if (has_depthwrite && (depthwrite == DepthWriteAttrib::M_off)) {
+            tex_trans = EggRenderMode::AM_blend_no_occlude;
+                has_depthwrite = false;
+        } else {
+          tex_trans = EggRenderMode::AM_blend;
+        }
+        break;
+      case TransparencyAttrib::M_multisample:
+        tex_trans = EggRenderMode::AM_ms;
+        break;
+      case TransparencyAttrib::M_multisample_mask:
+        tex_trans = EggRenderMode::AM_ms_mask;
+        break;
+      case TransparencyAttrib::M_binary:
+        tex_trans = EggRenderMode::AM_binary;
+        break;
+      case TransparencyAttrib::M_dual:
+        tex_trans = EggRenderMode::AM_dual;
+        break;
+      default:  // intentional fall-through
+      case TransparencyAttrib::M_notused:
+        break;
+    }
+    if (tex_trans != EggRenderMode::AM_unspecified) {
+      egg_tex->set_alpha_mode(tex_trans);
+    }
+  }
+
+
+  LNormal normal;
+  LColor color;
+  CPT(TransformBlendTable) transformBlendTable = vertex_data->get_transform_blend_table();
+
+  int num_primitives = primitive->get_num_primitives();
+  int num_vertices = primitive->get_num_vertices_per_primitive();
+
+  EggPrimitive *(*make_func)(void);
+
+  if (primitive->is_of_type(GeomTriangles::get_class_type())) {
+    make_func = make_egg_polygon;
+  } else if (primitive->is_of_type(GeomPatches::get_class_type())) {
+    make_func = make_egg_patch;
+  } else if (primitive->is_of_type(GeomPoints::get_class_type())) {
+    make_func = make_egg_point;
+  } else if (primitive->is_of_type(GeomLines::get_class_type())) {
+    make_func = make_egg_line;
+  } else {
+    // Huh, an unknown geometry type.
+    return;
+  }
+  
+  for (int i = 0; i < num_primitives; ++i) {
+    PT(EggPrimitive) egg_prim = (*make_func)();
+
+    egg_parent->add_child(egg_prim);
+    if (egg_tex != (EggTexture *)NULL) {
+      egg_prim->set_texture(egg_tex);
+    }
+    
+    if (bface) {
+      egg_prim->set_bface_flag(true);
+    }
+
+    for (int j = 0; j < num_vertices; j++) {
+      EggVertex egg_vert;
+        
+      // Get per-vertex properties.
+      reader.set_row(primitive->get_vertex(i * num_vertices + j));
+        
+      reader.set_column(InternalName::get_vertex());
+      LVertex vertex = reader.get_data3();
+      egg_vert.set_pos(LCAST(double, vertex * net_mat));
+        
+      if (vertex_data->has_column(InternalName::get_normal())) {
+        reader.set_column(InternalName::get_normal());
+        LNormal normal = reader.get_data3();
+        egg_vert.set_normal(LCAST(double, normal * net_mat));
+      }
+      if (has_color_override) {
+        egg_vert.set_color(color_override);
+          
+      } else if (!has_color_off) {
+        LColor color(1.0f, 1.0f, 1.0f, 1.0f);
+        if (vertex_data->has_column(InternalName::get_color())) {
+          reader.set_column(InternalName::get_color());
+          color = reader.get_data4();
+        }
+        egg_vert.set_color(LColor(color[0] * color_scale[0],
+                                  color[1] * color_scale[1],
+                                  color[2] * color_scale[2],
+                                  color[3] * color_scale[3]));
+      }
+        
+      if (vertex_data->has_column(InternalName::get_texcoord())) {
+        reader.set_column(InternalName::get_texcoord());
+        LTexCoord uv = reader.get_data2();
+        egg_vert.set_uv(LCAST(double, uv));
+      }
+        
+      EggVertex *new_egg_vert = _vpool->create_unique_vertex(egg_vert);
+        
+      if ((vertex_data->has_column(InternalName::get_transform_blend())) && 
+          (jointMap!=NULL) && (transformBlendTable!=NULL)) {
+        reader.set_column(InternalName::get_transform_blend());
+        int idx = reader.get_data1i();
+        const TransformBlend &blend = transformBlendTable->get_blend(idx);
+        int num_weights = blend.get_num_transforms();
+        for (int k = 0; k < num_weights; ++k) {
+          PN_stdfloat weight = blend.get_weight(k);
+          if (weight!=0) {
+            const VertexTransform *vertex_transform = blend.get_transform(k);
+            if (vertex_transform->is_of_type(JointVertexTransform::get_class_type())) {
+              const JointVertexTransform *joint_vertex_transform = DCAST(const JointVertexTransform, vertex_transform);
+
+              CharacterJointMap::iterator mi = jointMap->find(joint_vertex_transform->get_joint());
+              if (mi == jointMap->end()) {
+                mi = jointMap->insert(CharacterJointMap::value_type(joint_vertex_transform->get_joint(), pvector<pair<EggVertex*,PN_stdfloat> >())).first;
+              }
+              pvector<pair<EggVertex*,PN_stdfloat> > &joint_vertices = (*mi).second;
+              joint_vertices.push_back(pair<EggVertex*,PN_stdfloat>(new_egg_vert, weight));
+            }
+          }
+        }
+      }
+
+      egg_prim->add_vertex(new_egg_vert);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::recurse_nodes
+//       Access: Private
+//  Description: Converts all the children of the indicated node.
+////////////////////////////////////////////////////////////////////
+void EggSaver::
+recurse_nodes(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
+              bool has_decal) {
+  PandaNode *node = node_path.node();
+  int num_children = node->get_num_children();
+  
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child = node->get_child(i);
+    convert_node(WorkingNodePath(node_path, child), egg_parent, has_decal);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::apply_node_properties
+//       Access: Private
+//  Description: Applies any special properties that might be stored
+//               on the node, like billboarding.  Returns true if any
+//               were applied, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggSaver::
+apply_node_properties(EggGroup *egg_group, PandaNode *node, bool allow_backstage) {
+  bool any_applied = false;
+
+  if (node->is_overall_hidden() && allow_backstage) {
+    // This node is hidden.  We'll go ahead and convert it, but we'll
+    // put in the "backstage" flag to mean it's not real geometry.
+    // unless the caller wants to keep it (by setting allow_backstage to false)
+    egg_group->add_object_type("backstage");
+  }
+
+  if (node->has_tags()) {
+    if (apply_tags(egg_group, node)) {
+      any_applied = true;
+    }
+  }
+
+  if (node->is_of_type(ModelNode::get_class_type())) {
+    ModelNode *model_node = DCAST(ModelNode, node);
+    switch (model_node->get_preserve_transform()) {
+    case ModelNode::PT_none:
+    case ModelNode::PT_drop_node:
+      break;
+
+    case ModelNode::PT_net:
+      egg_group->set_dcs_type(EggGroup::DC_net);
+      break;
+
+    case ModelNode::PT_local:
+      egg_group->set_dcs_type(EggGroup::DC_local);
+      break;
+
+    case ModelNode::PT_no_touch:
+      egg_group->set_dcs_type(EggGroup::DC_no_touch);
+      break;
+    }
+  }
+
+  const RenderEffects *effects = node->get_effects();
+  const RenderEffect *effect = effects->get_effect(BillboardEffect::get_class_type());
+  if (effect != (RenderEffect *)NULL) {
+    const BillboardEffect *bbe = DCAST(BillboardEffect, effect);
+    if (bbe->get_axial_rotate()) {
+      egg_group->set_billboard_type(EggGroup::BT_axis);
+      any_applied = true;
+
+    } else if (bbe->get_eye_relative()) {
+      egg_group->set_billboard_type(EggGroup::BT_point_camera_relative);
+      any_applied = true;
+
+    } else {
+      egg_group->set_billboard_type(EggGroup::BT_point_world_relative);
+      any_applied = true;
+    }
+  }
+
+  const TransformState *transform = node->get_transform();
+  if (!transform->is_identity()) {
+    if (transform->has_components()) {
+      // If the transform can be represented componentwise, we prefer
+      // storing it that way in the egg file.
+      const LVecBase3 &scale = transform->get_scale();
+      const LQuaternion &quat = transform->get_quat();
+      const LVecBase3 &pos = transform->get_pos();
+      if (!scale.almost_equal(LVecBase3(1.0f, 1.0f, 1.0f))) {
+        egg_group->add_scale3d(LCAST(double, scale));
+      }
+      if (!quat.is_identity()) {
+        egg_group->add_rotate3d(LCAST(double, quat));
+      }
+      if (!pos.almost_equal(LVecBase3::zero())) {
+        egg_group->add_translate3d(LCAST(double, pos));
+      }
+
+    } else if (transform->has_mat()) {
+      // Otherwise, we store the raw matrix.
+      const LMatrix4 &mat = transform->get_mat();
+      egg_group->set_transform3d(LCAST(double, mat));
+    }
+    any_applied = true;
+  }
+
+  return any_applied;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::apply_tags
+//       Access: Private
+//  Description: Applies string tags to the egg file.  Returns true if
+//               any were applied, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggSaver::
+apply_tags(EggGroup *egg_group, PandaNode *node) {
+  ostringstream strm;
+  char delimiter = '\n';
+  string delimiter_str(1, delimiter);
+  node->list_tags(strm, delimiter_str);
+
+  string data = strm.str();
+  if (data.empty()) {
+    return false;
+  }
+
+  bool any_applied = false;
+
+  size_t p = 0;
+  size_t q = data.find(delimiter);
+  while (q != string::npos) {
+    string tag = data.substr(p, q);
+    if (apply_tag(egg_group, node, tag)) {
+      any_applied = true;
+    }
+    p = q + 1;
+    q = data.find(delimiter, p);
+  }
+  
+  string tag = data.substr(p);
+  if (apply_tag(egg_group, node, tag)) {
+    any_applied = true;
+  }
+
+  return any_applied;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::apply_tag
+//       Access: Private
+//  Description: Applies the named string tags to the egg file.
+////////////////////////////////////////////////////////////////////
+bool EggSaver::
+apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag) {
+  if (!node->has_tag(tag)) {
+    return false;
+  }
+
+  string value = node->get_tag(tag);
+  egg_group->set_tag(tag, value);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::get_egg_texture
+//       Access: Private
+//  Description: Returns an EggTexture pointer that corresponds to the
+//               indicated Texture.
+////////////////////////////////////////////////////////////////////
+EggTexture *EggSaver::
+get_egg_texture(Texture *tex) {
+  if (tex != (Texture *)NULL) {
+    if (tex->has_filename()) {
+      Filename filename = tex->get_filename();
+      EggTexture temp(filename.get_basename_wo_extension(), filename);
+      if (tex->has_alpha_filename()) {
+        Filename alpha = tex->get_alpha_filename();
+        temp.set_alpha_filename(alpha);
+      }
+
+      switch (tex->get_minfilter()) {
+      case Texture::FT_nearest:
+        temp.set_minfilter(EggTexture::FT_nearest);
+        break;
+      case Texture::FT_linear:
+        temp.set_minfilter(EggTexture::FT_linear);
+        break;
+      case Texture::FT_nearest_mipmap_nearest:
+        temp.set_minfilter(EggTexture::FT_nearest_mipmap_nearest);
+        break;
+      case Texture::FT_linear_mipmap_nearest:
+        temp.set_minfilter(EggTexture::FT_linear_mipmap_nearest);
+        break;
+      case Texture::FT_nearest_mipmap_linear:
+        temp.set_minfilter(EggTexture::FT_nearest_mipmap_linear);
+        break;
+      case Texture::FT_linear_mipmap_linear:
+        temp.set_minfilter(EggTexture::FT_linear_mipmap_linear);
+        break;
+
+      default:
+        break;
+      }
+
+      switch (tex->get_magfilter()) {
+      case Texture::FT_nearest:
+        temp.set_magfilter(EggTexture::FT_nearest);
+        break;
+      case Texture::FT_linear:
+        temp.set_magfilter(EggTexture::FT_linear);
+        break;
+
+      default:
+        break;
+      }
+
+      switch (tex->get_wrap_u()) {
+      case Texture::WM_clamp:
+        temp.set_wrap_u(EggTexture::WM_clamp);
+        break;
+      case Texture::WM_repeat:
+        temp.set_wrap_u(EggTexture::WM_repeat);
+        break;
+
+      default:
+        // There are some new wrap options on Texture that aren't yet
+        // supported in egg.
+        break;
+      }
+
+      switch (tex->get_wrap_v()) {
+      case Texture::WM_clamp:
+        temp.set_wrap_v(EggTexture::WM_clamp);
+        break;
+      case Texture::WM_repeat:
+        temp.set_wrap_v(EggTexture::WM_repeat);
+        break;
+
+      default:
+        // There are some new wrap options on Texture that aren't yet
+        // supported in egg.
+        break;
+      }
+
+      switch (tex->get_format()) {
+      case Texture::F_red:
+        temp.set_format(EggTexture::F_red);
+        break;
+      case Texture::F_green:
+        temp.set_format(EggTexture::F_green);
+        break;
+      case Texture::F_blue:
+        temp.set_format(EggTexture::F_blue);
+        break;
+      case Texture::F_alpha:
+        temp.set_format(EggTexture::F_alpha);
+        break;
+      case Texture::F_rgb:
+        temp.set_format(EggTexture::F_rgb);
+        break;
+      case Texture::F_rgb5:
+        temp.set_format(EggTexture::F_rgb5);
+        break;
+      case Texture::F_rgb8:
+        temp.set_format(EggTexture::F_rgb8);
+        break;
+      case Texture::F_rgb12:
+        temp.set_format(EggTexture::F_rgb12);
+        break;
+      case Texture::F_rgb332:
+        temp.set_format(EggTexture::F_rgb332);
+        break;
+      case Texture::F_rgba:
+        temp.set_format(EggTexture::F_rgba);
+        break;
+      case Texture::F_rgbm:
+        temp.set_format(EggTexture::F_rgbm);
+        break;
+      case Texture::F_rgba4:
+        temp.set_format(EggTexture::F_rgba4);
+        break;
+      case Texture::F_rgba5:
+        temp.set_format(EggTexture::F_rgba5);
+        break;
+      case Texture::F_rgba8:
+        temp.set_format(EggTexture::F_rgba8);
+        break;
+      case Texture::F_rgba12:
+        temp.set_format(EggTexture::F_rgba12);
+        break;
+      case Texture::F_luminance:
+        temp.set_format(EggTexture::F_luminance);
+        break;
+      case Texture::F_luminance_alpha:
+        temp.set_format(EggTexture::F_luminance_alpha);
+        break;
+      case Texture::F_luminance_alphamask:
+        temp.set_format(EggTexture::F_luminance_alphamask);
+        break;
+      default:
+        break;
+      }
+
+      return _textures.create_unique_texture(temp, ~EggTexture::E_tref_name);
+    }
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::make_egg_polygon
+//       Access: Private, Static
+//  Description: A factory function to make a new EggPolygon instance.
+////////////////////////////////////////////////////////////////////
+EggPrimitive *EggSaver::
+make_egg_polygon() {
+  return new EggPolygon;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::make_egg_patch
+//       Access: Private, Static
+//  Description: A factory function to make a new EggPatch instance.
+////////////////////////////////////////////////////////////////////
+EggPrimitive *EggSaver::
+make_egg_patch() {
+  return new EggPatch;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::make_egg_point
+//       Access: Private, Static
+//  Description: A factory function to make a new EggPoint instance.
+////////////////////////////////////////////////////////////////////
+EggPrimitive *EggSaver::
+make_egg_point() {
+  return new EggPoint;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::make_egg_line
+//       Access: Private, Static
+//  Description: A factory function to make a new EggLine instance.
+////////////////////////////////////////////////////////////////////
+EggPrimitive *EggSaver::
+make_egg_line() {
+  return new EggLine;
+}

+ 110 - 0
panda/src/egg2pg/eggSaver.h

@@ -0,0 +1,110 @@
+// Filename: eggSaver.h
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef EGGSAVER_H
+#define EGGSAVER_H
+
+#include "pandabase.h"
+
+#include "luse.h"
+#include "eggTextureCollection.h"
+#include "eggMaterialCollection.h"
+
+class WorkingNodePath;
+class EggGroup;
+class EggGroupNode;
+class EggVertexPool;
+class EggTexture;
+class LODNode;
+class SequenceNode;
+class SwitchNode;
+class AnimBundleNode;
+class AnimGroup;
+class Character;
+class PartGroup;
+class CollisionNode;
+class GeomNode;
+class GeomVertexData;
+class GeomPrimitive;
+class PandaNode;
+class RenderState;
+class Texture;
+class CharacterJoint;
+class EggVertex;
+
+////////////////////////////////////////////////////////////////////
+//       Class : EggSaver
+// Description : Converts the scene graph beginning at the indicated
+//               node into an EggData structure, for writing to an egg
+//               file.  The conversion is not necessarily complete
+//               (some Panda or egg constructs are not fully supported
+//               by this class).
+////////////////////////////////////////////////////////////////////
+class EggSaver {
+PUBLISHED:
+  EggSaver(EggData *data = NULL);
+
+  void add_node(PandaNode *node);
+  INLINE EggData *get_egg_data() const;
+
+private:
+  typedef pmap<const CharacterJoint*, pvector<pair<EggVertex*,PN_stdfloat> > > CharacterJointMap;
+
+  void convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
+                    bool has_decal);
+  void convert_lod_node(LODNode *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  void convert_sequence_node(SequenceNode *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  void convert_switch_node(SwitchNode *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  EggGroupNode *convert_animGroup_node(AnimGroup *animGroup, double fps );
+  void convert_anim_node(AnimBundleNode *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  void convert_character_node(Character *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  void convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, CharacterJointMap *jointMap);
+  void convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
+                        EggGroupNode *egg_parent, bool has_decal);
+  void convert_geom_node(GeomNode *node, const WorkingNodePath &node_path, 
+                         EggGroupNode *egg_parent, bool has_decal, CharacterJointMap *jointMap=NULL);
+  void convert_primitive(const GeomVertexData *vertex_data,
+                         const GeomPrimitive *primitive, 
+                         const RenderState *net_state, 
+                         const LMatrix4 &net_mat, EggGroupNode *egg_parent,
+                         CharacterJointMap *jointMap);
+
+  void recurse_nodes(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
+                     bool has_decal);
+  bool apply_node_properties(EggGroup *egg_group, PandaNode *node, bool allow_backstage = true);
+  bool apply_tags(EggGroup *egg_group, PandaNode *node);
+  bool apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag);
+
+  EggTexture *get_egg_texture(Texture *tex);
+
+  static EggPrimitive *make_egg_polygon();
+  static EggPrimitive *make_egg_patch();
+  static EggPrimitive *make_egg_point();
+  static EggPrimitive *make_egg_line();
+
+  PT(EggData) _data;
+
+  PT(EggVertexPool) _vpool;
+  EggTextureCollection _textures;
+  EggMaterialCollection _materials;
+};
+
+#include "eggSaver.I"
+
+#endif

+ 36 - 0
panda/src/egg2pg/loaderFileTypeEgg.cxx

@@ -14,6 +14,7 @@
 
 #include "loaderFileTypeEgg.h"
 #include "load_egg_file.h"
+#include "save_egg_file.h"
 
 #include "eggData.h"
 
@@ -60,6 +61,30 @@ supports_compressed() const {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeEgg::supports_load
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to load
+//               files, and load_file() is supported.  Returns false
+//               if load_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypeEgg::
+supports_load() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeEgg::supports_save
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to save
+//               files, and save_file() is supported.  Returns false
+//               if save_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypeEgg::
+supports_save() const {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: LoaderFileTypeEgg::load_file
 //       Access: Public, Virtual
@@ -71,3 +96,14 @@ load_file(const Filename &path, const LoaderOptions &,
   PT(PandaNode) result = load_egg_file(path, CS_default, record);
   return result;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeEgg::save_file
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypeEgg::
+save_file(const Filename &path, const LoaderOptions &options,
+          PandaNode *node) const {
+  return save_egg_file(path, node);
+}

+ 5 - 0
panda/src/egg2pg/loaderFileTypeEgg.h

@@ -31,8 +31,13 @@ public:
   virtual string get_extension() const;
   virtual bool supports_compressed() const;
 
+  virtual bool supports_load() const;
+  virtual bool supports_save() const;
+
   virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &optoins,
                                   BamCacheRecord *record) const;
+  virtual bool save_file(const Filename &path, const LoaderOptions &options,
+                         PandaNode *node) const;
 
 public:
   static TypeHandle get_class_type() {

+ 1 - 0
panda/src/egg2pg/p3egg2pg_composite1.cxx

@@ -3,3 +3,4 @@
 #include "config_egg2pg.cxx"
 #include "egg_parametrics.cxx"
 #include "eggRenderState.cxx"
+#include "deferredNodeProperty.cxx"

+ 2 - 1
panda/src/egg2pg/p3egg2pg_composite2.cxx

@@ -1,6 +1,7 @@
-#include "deferredNodeProperty.cxx"
 #include "eggBinner.cxx"
 #include "eggLoader.cxx"
+#include "eggSaver.cxx"
 #include "load_egg_file.cxx"
+#include "save_egg_file.cxx"
 #include "loaderFileTypeEgg.cxx"
 

+ 52 - 0
panda/src/egg2pg/save_egg_file.cxx

@@ -0,0 +1,52 @@
+// Filename: save_egg_file.cxx
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "save_egg_file.h"
+#include "eggSaver.h"
+#include "config_egg2pg.h"
+#include "sceneGraphReducer.h"
+#include "virtualFileSystem.h"
+#include "config_util.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: save_egg_file
+//  Description: A convenience function; converts the indicated scene
+//               graph to an egg file and writes it to disk.
+////////////////////////////////////////////////////////////////////
+bool
+save_egg_file(const Filename &filename, PandaNode *node, CoordinateSystem cs) {
+  PT(EggData) data = new EggData;
+  if (cs == CS_default) {
+    cs = get_default_coordinate_system();
+  }
+  data->set_coordinate_system(cs);
+
+  EggSaver saver(data);
+  saver.add_node(node);
+
+  return data->write_egg(filename);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: save_egg_data
+//  Description: Another convenience function; works like
+//               save_egg_file() but populates an EggData instead of
+//               writing the results to disk.
+////////////////////////////////////////////////////////////////////
+bool
+save_egg_data(EggData *data, PandaNode *node) {
+  EggSaver saver(data);
+  saver.add_node(node);
+  return true;
+}

+ 44 - 0
panda/src/egg2pg/save_egg_file.h

@@ -0,0 +1,44 @@
+// Filename: save_egg_file.h
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef SAVE_EGG_FILE_H
+#define SAVE_EGG_FILE_H
+
+#include "pandabase.h"
+
+#include "pandaNode.h"
+#include "coordinateSystem.h"
+#include "eggData.h"
+
+BEGIN_PUBLISH
+////////////////////////////////////////////////////////////////////
+//     Function: save_egg_file
+//  Description: A convenience function; converts the indicated scene
+//               graph to an egg file and writes it to disk.
+////////////////////////////////////////////////////////////////////
+EXPCL_PANDAEGG bool
+save_egg_file(const Filename &filename, PandaNode *node,
+              CoordinateSystem cs = CS_default);
+
+////////////////////////////////////////////////////////////////////
+//     Function: save_egg_data
+//  Description: Another convenience function; works like
+//               save_egg_file() but populates an EggData instead of
+//               writing the results to disk.
+////////////////////////////////////////////////////////////////////
+EXPCL_PANDAEGG bool
+save_egg_data(EggData *data, PandaNode *node);
+END_PUBLISH
+
+#endif

+ 3 - 0
panda/src/pgraph/Sources.pp

@@ -67,6 +67,7 @@
     materialCollection.I materialCollection.h \
     modelFlattenRequest.I modelFlattenRequest.h \
     modelLoadRequest.I modelLoadRequest.h \
+    modelSaveRequest.I modelSaveRequest.h \
     modelNode.I modelNode.h \
     modelPool.I modelPool.h \
     modelRoot.I modelRoot.h \
@@ -167,6 +168,7 @@
     materialCollection.cxx \
     modelFlattenRequest.cxx \
     modelLoadRequest.cxx \
+    modelSaveRequest.cxx \
     modelNode.cxx \
     modelPool.cxx \
     modelRoot.cxx \
@@ -262,6 +264,7 @@
     materialCollection.I materialCollection.h \
     modelFlattenRequest.I modelFlattenRequest.h \
     modelLoadRequest.I modelLoadRequest.h \
+    modelSaveRequest.I modelSaveRequest.h \
     modelNode.I modelNode.h \
     modelPool.I modelPool.h \
     modelRoot.I modelRoot.h \

+ 2 - 0
panda/src/pgraph/config_pgraph.cxx

@@ -55,6 +55,7 @@
 #include "materialAttrib.h"
 #include "modelFlattenRequest.h"
 #include "modelLoadRequest.h"
+#include "modelSaveRequest.h"
 #include "modelNode.h"
 #include "modelRoot.h"
 #include "nodePath.h"
@@ -473,6 +474,7 @@ init_libpgraph() {
   MaterialAttrib::init_type();
   ModelFlattenRequest::init_type();
   ModelLoadRequest::init_type();
+  ModelSaveRequest::init_type();
   ModelNode::init_type();
   ModelRoot::init_type();
   NodePath::init_type();

+ 44 - 5
panda/src/pgraph/loader.I

@@ -200,11 +200,11 @@ load_sync(const Filename &filename, const LoaderOptions &options) const {
 //     Function: Loader::load_async
 //       Access: Published
 //  Description: Begins an asynchronous load request.  To use this
-//               call, first create a new ModelLoadRequest object with
-//               the filename you wish to load, and then add that
-//               object to the Loader with load_async.  This function
-//               will return immediately, and the model will be loaded
-//               in the background.
+//               call, first call make_async_request() to create a new
+//               ModelLoadRequest object with the filename you wish to
+//               load, and then add that object to the Loader with
+//               load_async.  This function will return immediately,
+//               and the model will be loaded in the background.
 //
 //               To determine when the model has completely loaded,
 //               you may poll request->is_ready() from time to time,
@@ -218,6 +218,45 @@ load_async(AsyncTask *request) {
   _task_manager->add(request);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Loader::save_sync
+//       Access: Published
+//  Description: Saves the file immediately, waiting for it to
+//               complete.
+////////////////////////////////////////////////////////////////////
+INLINE bool Loader::
+save_sync(const Filename &filename, const LoaderOptions &options,
+          PandaNode *node) const {
+  if (!_file_types_loaded) {
+    load_file_types();
+  }
+  return save_file(filename, options, node);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Loader::save_async
+//       Access: Published
+//  Description: Begins an asynchronous save request.  To use this
+//               call, first call make_async_save_request() to create
+//               a new ModelSaveRequest object with the filename you
+//               wish to load, and then add that object to the Loader
+//               with save_async.  This function will return
+//               immediately, and the model will be loaded in the
+//               background.
+//
+//               To determine when the model has completely loaded,
+//               you may poll request->is_ready() from time to time,
+//               or set the done_event on the request object and
+//               listen for that event.  When the request is ready,
+//               you may retrieve the success or failure via
+//               request->get_success().
+////////////////////////////////////////////////////////////////////
+INLINE void Loader::
+save_async(AsyncTask *request) {
+  request->set_task_chain(_task_chain);
+  _task_manager->add(request);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Loader::get_global_ptr
 //       Access: Published

+ 117 - 0
panda/src/pgraph/loader.cxx

@@ -18,6 +18,7 @@
 #include "config_pgraph.h"
 #include "modelPool.h"
 #include "modelLoadRequest.h"
+#include "modelSaveRequest.h"
 #include "config_express.h"
 #include "config_util.h"
 #include "virtualFileSystem.h"
@@ -84,6 +85,19 @@ make_async_request(const Filename &filename, const LoaderOptions &options) {
                               filename, options, this);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Loader::make_async_save_request
+//       Access: Published
+//  Description: Returns a new AsyncTask object suitable for adding to
+//               save_async() to start an asynchronous model save.
+////////////////////////////////////////////////////////////////////
+PT(AsyncTask) Loader::
+make_async_save_request(const Filename &filename, const LoaderOptions &options,
+                        PandaNode *node) {
+  return new ModelSaveRequest(string("model_save:")+filename.get_basename(),
+                              filename, options, node, this);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Loader::load_bam_stream
 //       Access: Published
@@ -176,6 +190,13 @@ load_file(const Filename &filename, const LoaderOptions &options) const {
       reg->write(loader_cat.error(false), 2);
     }
     return NULL;
+  } else if (!requested_type->supports_load()) {
+    if (report_errors) {
+      loader_cat.error()
+        << requested_type->get_name() << " file type (."
+        << extension << ") does not support loading.\n";
+    }
+    return NULL;
   } else if (pz_file && !requested_type->supports_compressed()) {
     if (report_errors) {
       loader_cat.error()
@@ -338,6 +359,102 @@ try_load_file(const Filename &pathname, const LoaderOptions &options,
   return NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Loader::save_file
+//       Access: Private
+//  Description: Saves a scene graph to a single file, if possible.
+//               The file type written is implicit in the filename
+//               extension.
+////////////////////////////////////////////////////////////////////
+bool Loader::
+save_file(const Filename &filename, const LoaderOptions &options,
+          PandaNode *node) const {
+  Filename this_filename(filename);
+  LoaderOptions this_options(options);
+
+  bool report_errors = (this_options.get_flags() & LoaderOptions::LF_report_errors) != 0;
+
+  string extension = this_filename.get_extension();
+  if (extension.empty()) {
+    // If the filename has no filename extension, append the default
+    // extension specified in the Config file.
+    this_filename = this_filename.get_fullpath() + default_model_extension.get_value();
+    extension = this_filename.get_extension();
+  }
+
+  bool pz_file = false;
+#ifdef HAVE_ZLIB
+  if (extension == "pz") {
+    pz_file = true;
+    extension = Filename(this_filename.get_basename_wo_extension()).get_extension();
+  }
+#endif  // HAVE_ZLIB
+
+  if (extension.empty()) {
+    if (report_errors) {
+      loader_cat.error()
+        << "Cannot save " << this_filename
+        << " without filename extension.\n";
+    }
+    return NULL;
+  }
+
+  LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
+  LoaderFileType *requested_type =
+    reg->get_type_from_extension(extension);
+  if (requested_type == (LoaderFileType *)NULL) {
+    if (report_errors) {
+      loader_cat.error()
+        << "Extension of file " << this_filename
+        << " is unrecognized; cannot save.\n";
+      loader_cat.error(false)
+        << "Currently known scene file types are:\n";
+      reg->write(loader_cat.error(false), 2);
+    }
+    return NULL;
+  } else if (!requested_type->supports_save()) {
+    if (report_errors) {
+      loader_cat.error()
+        << requested_type->get_name() << " file type (."
+        << extension << ") does not support saving.\n";
+    }
+    return NULL;
+  } else if (pz_file && !requested_type->supports_compressed()) {
+    if (report_errors) {
+      loader_cat.error()
+        << requested_type->get_name() << " file type (."
+        << extension << ") does not support in-line compression.\n";
+    }
+    return NULL;
+  }
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+  bool result = try_save_file(this_filename, this_options, node, requested_type);
+  if (!result) {
+    if (report_errors) {
+      loader_cat.error()
+        << "Couldn't save file " << this_filename << ".\n";
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Loader::try_save_file
+//       Access: Private
+//  Description: The implementation of save_file(), this tries to
+//               write a specific file type.
+////////////////////////////////////////////////////////////////////
+bool Loader::
+try_save_file(const Filename &pathname, const LoaderOptions &options,
+              PandaNode *node, LoaderFileType *requested_type) const {
+  bool report_errors = ((options.get_flags() & LoaderOptions::LF_report_errors) != 0 || loader_cat.is_debug());
+  
+  bool result = requested_type->save_file(pathname, options, node);
+  return result;
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Loader::load_file_types

+ 12 - 0
panda/src/pgraph/loader.h

@@ -92,6 +92,13 @@ PUBLISHED:
                                    const LoaderOptions &options = LoaderOptions());
   INLINE void load_async(AsyncTask *request);
 
+  INLINE bool save_sync(const Filename &filename, const LoaderOptions &options,
+                        PandaNode *node) const;
+  PT(AsyncTask) make_async_save_request(const Filename &filename, 
+                                        const LoaderOptions &options,
+                                        PandaNode *node);
+  INLINE void save_async(AsyncTask *request);
+
   BLOCKING PT(PandaNode) load_bam_stream(istream &in);
 
   virtual void output(ostream &out) const;
@@ -103,6 +110,11 @@ private:
   PT(PandaNode) try_load_file(const Filename &pathname, const LoaderOptions &options,
                               LoaderFileType *requested_type) const;
 
+  bool save_file(const Filename &filename, const LoaderOptions &options,
+                 PandaNode *node) const;
+  bool try_save_file(const Filename &filename, const LoaderOptions &options,
+                     PandaNode *node, LoaderFileType *requested_type) const;
+
   static void make_global_ptr();
 
   PT(AsyncTaskManager) _task_manager;

+ 38 - 1
panda/src/pgraph/loaderFileType.cxx

@@ -81,7 +81,7 @@ get_allow_disk_cache(const LoaderOptions &options) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: LoaderFileType::get_allow_ram_cache
-//       Access: Published
+//       Access: Published, Virtual
 //  Description: Returns true if the loader flags allow retrieving the
 //               model from the in-memory ModelPool cache, false
 //               otherwise.
@@ -91,6 +91,30 @@ get_allow_ram_cache(const LoaderOptions &options) const {
   return (options.get_flags() & (LoaderOptions::LF_no_ram_cache | _no_cache_flags)) == 0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileType::supports_load
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to load
+//               files, and load_file() is supported.  Returns false
+//               if load_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileType::
+supports_load() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileType::supports_save
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to save
+//               files, and save_file() is supported.  Returns false
+//               if save_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileType::
+supports_save() const {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: LoaderFileType::load_file
 //       Access: Public, Virtual
@@ -103,3 +127,16 @@ load_file(const Filename &path, const LoaderOptions &options,
     << get_type() << " cannot read PandaNode objects.\n";
   return NULL;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileType::save_file
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool LoaderFileType::
+save_file(const Filename &path, const LoaderOptions &options,
+          PandaNode *node) const {
+  loader_cat.error()
+    << get_type() << " cannot save PandaNode objects.\n";
+  return NULL;
+}

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

@@ -49,9 +49,14 @@ PUBLISHED:
   virtual bool get_allow_disk_cache(const LoaderOptions &options) const;
   virtual bool get_allow_ram_cache(const LoaderOptions &options) const;
 
+  virtual bool supports_load() const;
+  virtual bool supports_save() const;
+
 public:
   virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &options,
                                   BamCacheRecord *record) const;
+  virtual bool save_file(const Filename &path, const LoaderOptions &options,
+                         PandaNode *node) const;
 
 protected:
   int _no_cache_flags;

+ 47 - 0
panda/src/pgraph/loaderFileTypeBam.cxx

@@ -63,6 +63,30 @@ supports_compressed() const {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeBam::supports_load
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to load
+//               files, and load_file() is supported.  Returns false
+//               if load_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypeBam::
+supports_load() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeBam::supports_save
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to save
+//               files, and save_file() is supported.  Returns false
+//               if save_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypeBam::
+supports_save() const {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: LoaderFileTypeBam::load_file
 //       Access: Public, Virtual
@@ -94,3 +118,26 @@ load_file(const Filename &path, const LoaderOptions &options,
   return node;
 }
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypeBam::save_file
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypeBam::
+save_file(const Filename &path, const LoaderOptions &options,
+          PandaNode *node) const {
+  BamFile bam_file;
+
+  bool report_errors = (options.get_flags() & LoaderOptions::LF_report_errors) != 0;
+
+  bool okflag = false;
+
+  if (bam_file.open_write(path, report_errors)) {
+    if (bam_file.write_object(node)) {
+      okflag = true;
+    }
+    bam_file.close();
+  }
+  return okflag;
+}

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

@@ -31,8 +31,13 @@ public:
   virtual string get_extension() const;
   virtual bool supports_compressed() const;
 
+  virtual bool supports_load() const;
+  virtual bool supports_save() const;
+
   virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &options,
                                   BamCacheRecord *record) const;
+  virtual bool save_file(const Filename &path, const LoaderOptions &options,
+                         PandaNode *node) const;
 
 public:
   static TypeHandle get_class_type() {

+ 82 - 0
panda/src/pgraph/modelSaveRequest.I

@@ -0,0 +1,82 @@
+// Filename: modelSaveRequest.I
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::get_filename
+//       Access: Published
+//  Description: Returns the filename associated with this
+//               asynchronous ModelSaveRequest.
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &ModelSaveRequest::
+get_filename() const {
+  return _filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::get_options
+//       Access: Published
+//  Description: Returns the LoaderOptions associated with this
+//               asynchronous ModelSaveRequest.
+////////////////////////////////////////////////////////////////////
+INLINE const LoaderOptions &ModelSaveRequest::
+get_options() const {
+  return _options;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::get_node
+//       Access: Published
+//  Description: Returns the node that was passed to the constructor.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *ModelSaveRequest::
+get_node() const {
+  return _node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::get_loader
+//       Access: Published
+//  Description: Returns the Loader object associated with this
+//               asynchronous ModelSaveRequest.
+////////////////////////////////////////////////////////////////////
+INLINE Loader *ModelSaveRequest::
+get_loader() const {
+  return _loader;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::is_ready
+//       Access: Published
+//  Description: Returns true if this request has completed, false if
+//               it is still pending.  When this returns true, you may
+//               retrieve the success flag with get_success().
+////////////////////////////////////////////////////////////////////
+INLINE bool ModelSaveRequest::
+is_ready() const {
+  return _is_ready;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::get_success
+//       Access: Published
+//  Description: Returns the true if the model was saved successfully,
+//               false otherwise.  It is an error to call this unless
+//               is_ready() returns true.
+////////////////////////////////////////////////////////////////////
+INLINE bool ModelSaveRequest::
+get_success() const {
+  nassertr(_is_ready, NULL);
+  return _success;
+}

+ 58 - 0
panda/src/pgraph/modelSaveRequest.cxx

@@ -0,0 +1,58 @@
+// Filename: modelSaveRequest.cxx
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "modelSaveRequest.h"
+#include "loader.h"
+#include "config_pgraph.h"
+
+TypeHandle ModelSaveRequest::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::Constructor
+//       Access: Published
+//  Description: Create a new ModelSaveRequest, and add it to the loader
+//               via save_async(), to begin an asynchronous save.
+////////////////////////////////////////////////////////////////////
+ModelSaveRequest::
+ModelSaveRequest(const string &name, 
+                 const Filename &filename, const LoaderOptions &options,
+                 PandaNode *node, Loader *loader) :
+  AsyncTask(name),
+  _filename(filename),
+  _options(options),
+  _node(node),
+  _loader(loader),
+  _is_ready(false),
+  _success(false)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelSaveRequest::do_task
+//       Access: Protected, Virtual
+//  Description: Performs the task: that is, saves the one model.
+////////////////////////////////////////////////////////////////////
+AsyncTask::DoneStatus ModelSaveRequest::
+do_task() {
+  double delay = async_load_delay;
+  if (delay != 0.0) {
+    Thread::sleep(delay);
+  }
+
+  _success = _loader->save_sync(_filename, _options, _node);
+  _is_ready = true;
+
+  // Don't continue the task; we're done.
+  return DS_done;
+}

+ 83 - 0
panda/src/pgraph/modelSaveRequest.h

@@ -0,0 +1,83 @@
+// Filename: modelSaveRequest.h
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef MODELSAVEREQUEST
+#define MODELSAVEREQUEST
+
+#include "pandabase.h"
+
+#include "asyncTask.h"
+#include "filename.h"
+#include "loaderOptions.h"
+#include "pandaNode.h"
+#include "pointerTo.h"
+#include "loader.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : ModelSaveRequest
+// Description : A class object that manages a single asynchronous
+//               model save request.  Create a new ModelSaveRequest,
+//               and add it to the loader via save_async(), to begin
+//               an asynchronous save.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_PGRAPH ModelSaveRequest : public AsyncTask {
+public:
+  ALLOC_DELETED_CHAIN(ModelSaveRequest);
+
+PUBLISHED:
+  ModelSaveRequest(const string &name,
+                   const Filename &filename, 
+                   const LoaderOptions &options,
+                   PandaNode *node, Loader *loader);
+  
+  INLINE const Filename &get_filename() const;
+  INLINE const LoaderOptions &get_options() const;
+  INLINE PandaNode *get_node() const;
+  INLINE Loader *get_loader() const;
+  
+  INLINE bool is_ready() const;
+  INLINE bool get_success() const;
+  
+protected:
+  virtual DoneStatus do_task();
+  
+private:
+  Filename _filename;
+  LoaderOptions _options;
+  PT(PandaNode) _node;
+  PT(Loader) _loader;
+  bool _is_ready;
+  bool _success;
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    AsyncTask::init_type();
+    register_type(_type_handle, "ModelSaveRequest",
+                  AsyncTask::get_class_type());
+    }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "modelSaveRequest.I"
+
+#endif

+ 1 - 0
panda/src/pgraph/p3pgraph_composite3.cxx

@@ -11,6 +11,7 @@
 #include "materialCollection.cxx"
 #include "modelFlattenRequest.cxx"
 #include "modelLoadRequest.cxx"
+#include "modelSaveRequest.cxx"
 #include "modelNode.cxx"
 #include "modelPool.cxx"
 #include "modelRoot.cxx"

+ 3 - 1092
pandatool/src/bam/bamToEgg.cxx

@@ -13,63 +13,11 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "bamToEgg.h"
-
-#include "pandaNode.h"
-#include "workingNodePath.h"
-#include "nodePath.h"
-#include "billboardEffect.h"
-#include "renderEffects.h"
-#include "transformState.h"
-#include "colorScaleAttrib.h"
-#include "colorAttrib.h"
-#include "textureAttrib.h"
-#include "cullFaceAttrib.h"
-#include "transparencyAttrib.h"
-#include "depthWriteAttrib.h"
-#include "lodNode.h"
-#include "switchNode.h"
-#include "sequenceNode.h"
-#include "collisionNode.h"
-#include "collisionPolygon.h"
-#include "collisionPlane.h"
-#include "collisionSphere.h"
-#include "collisionInvSphere.h"
-#include "collisionTube.h"
-#include "textureStage.h"
-#include "geomNode.h"
-#include "geom.h"
-#include "geomTriangles.h"
-#include "geomPatches.h"
-#include "geomPoints.h"
-#include "geomLines.h"
-#include "geomVertexReader.h"
-#include "transformTable.h"
-#include "modelNode.h"
-#include "animBundleNode.h"
-#include "animChannelMatrixXfmTable.h"
-#include "characterJoint.h"
-#include "character.h"
+#include "save_egg_file.h"
+#include "pystub.h"
 #include "string_utils.h"
 #include "bamFile.h"
 #include "bamCacheRecord.h"
-#include "eggSAnimData.h"
-#include "eggXfmAnimData.h"
-#include "eggXfmSAnim.h"
-#include "eggGroup.h"
-#include "eggVertexPool.h"
-#include "eggVertex.h"
-#include "eggPrimitive.h"
-#include "eggPolygon.h"
-#include "eggPatch.h"
-#include "eggPoint.h"
-#include "eggLine.h"
-#include "eggTexture.h"
-#include "eggMaterial.h"
-#include "eggRenderMode.h"
-#include "eggTable.h"
-#include "somethingToEggConverter.h"
-#include "dcast.h"
-#include "pystub.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BamToEgg::Constructor
@@ -80,9 +28,6 @@ BamToEgg::
 BamToEgg() :
   SomethingToEgg("bam", ".bam")
 {
-  add_path_replace_options();
-  add_path_store_options();
-
   set_program_description
     ("This program converts native Panda bam files to egg.  The conversion "
      "is somewhat incomplete; running egg2bam followed by bam2egg should not "
@@ -142,1054 +87,20 @@ run() {
   bam_file.close();
 
   _data->set_coordinate_system(_coordinate_system);
-  _vpool = new EggVertexPool("vpool");
-  _data->add_child(_vpool);
 
   if (objects.size() == 1 && 
       objects[0]->is_of_type(PandaNode::get_class_type())) {
     PandaNode *node = DCAST(PandaNode, objects[0]);
-    NodePath root(node);
-    convert_node(WorkingNodePath(root), _data, false);
+    save_egg_data(_data, node);
 
   } else {
     nout << "File does not contain a scene graph.\n";
     exit(1);
   }
 
-  // Remove the vertex pool if it has no vertices.
-  if (_vpool->empty()) {
-    _data->remove_child(_vpool);
-  }
-
   write_egg_file();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_node
-//       Access: Private
-//  Description: Converts the indicated node to the corresponding Egg
-//               constructs, by first determining what kind of node it
-//               is.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
-             bool has_decal) {
-  PandaNode *node = node_path.node();
-  if (node->is_geom_node()) {
-    convert_geom_node(DCAST(GeomNode, node), node_path, egg_parent, has_decal);
-
-  } else if (node->is_of_type(LODNode::get_class_type())) {
-    convert_lod_node(DCAST(LODNode, node), node_path, egg_parent, has_decal);
-
-  } else if (node->is_of_type(SequenceNode::get_class_type())) {
-    convert_sequence_node(DCAST(SequenceNode, node), node_path, egg_parent, has_decal);
-
-  } else if (node->is_of_type(SwitchNode::get_class_type())) {
-    convert_switch_node(DCAST(SwitchNode, node), node_path, egg_parent, has_decal);
-
-  } else if (node->is_of_type(CollisionNode::get_class_type())) {
-    convert_collision_node(DCAST(CollisionNode, node), node_path, egg_parent, has_decal);
-
-  } else if (node->is_of_type(AnimBundleNode::get_class_type())) {
-    convert_anim_node(DCAST(AnimBundleNode, node), node_path, egg_parent, has_decal);
-
-  } else if (node->is_of_type(Character::get_class_type())) {
-    convert_character_node(DCAST(Character, node), node_path, egg_parent, has_decal);
-
-  } else {
-    // Just a generic node.
-    EggGroup *egg_group = new EggGroup(node->get_name());
-    egg_parent->add_child(egg_group);
-    apply_node_properties(egg_group, node);
-    
-    recurse_nodes(node_path, egg_group, has_decal);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_lod_node
-//       Access: Private
-//  Description: Converts the indicated LODNode to the corresponding
-//               Egg constructs.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_lod_node(LODNode *node, const WorkingNodePath &node_path,
-                 EggGroupNode *egg_parent, bool has_decal) {
-  // An LOD node gets converted to an ordinary EggGroup, but we apply
-  // the appropriate switch conditions to each of our children.
-  EggGroup *egg_group = new EggGroup(node->get_name());
-  egg_parent->add_child(egg_group);
-  apply_node_properties(egg_group, node);
-
-  int num_children = node->get_num_children();
-  int num_switches = node->get_num_switches();
-
-  num_children = min(num_children, num_switches);
-
-  for (int i = 0; i < num_children; i++) {
-    PandaNode *child = node->get_child(i);
-
-    // Convert just this one node to an EggGroup.
-    PT(EggGroup) next_group = new EggGroup;
-    convert_node(WorkingNodePath(node_path, child), next_group, has_decal);
-
-    if (next_group->size() == 1) {
-      // If we have exactly one child, and that child is an EggGroup,
-      // collapse.
-      EggNode *child_node = *next_group->begin();
-      if (child_node->is_of_type(EggGroup::get_class_type())) {
-        PT(EggGroup) child = DCAST(EggGroup, child_node);
-        next_group->remove_child(child.p());
-        next_group = child;
-      }
-    }
-
-    // Now set up the switching properties appropriately.
-    PN_stdfloat in = node->get_in(i);
-    PN_stdfloat out = node->get_out(i);
-    LPoint3 center = node->get_center();
-    EggSwitchConditionDistance dist(in, out, LCAST(double, center));
-    next_group->set_lod(dist);
-    egg_group->add_child(next_group.p());
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_sequence_node
-//       Access: Private
-//  Description: Converts the indicated SequenceNode to the corresponding
-//               Egg constructs.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_sequence_node(SequenceNode *node, const WorkingNodePath &node_path,
-                      EggGroupNode *egg_parent, bool has_decal) {
-  // A sequence node gets converted to an ordinary EggGroup, we only apply
-  // the appropriate switch attributes to turn it into a sequence
-  EggGroup *egg_group = new EggGroup(node->get_name());
-  egg_parent->add_child(egg_group);
-  apply_node_properties(egg_group, node);
-
-  // turn it into a sequence with the right frame-rate
-  egg_group->set_switch_flag(true);
-  egg_group->set_switch_fps(node->get_frame_rate());
-
-  int num_children = node->get_num_children();
-
-  for (int i = 0; i < num_children; i++) {
-    PandaNode *child = node->get_child(i);
-
-    // Convert just this one node to an EggGroup.
-    PT(EggGroup) next_group = new EggGroup;
-    convert_node(WorkingNodePath(node_path, child), next_group, has_decal);
-
-    egg_group->add_child(next_group.p());
-  }
-
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_switch_node
-//       Access: Private
-//  Description: Converts the indicated SwitchNode to the corresponding
-//               Egg constructs.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_switch_node(SwitchNode *node, const WorkingNodePath &node_path,
-                    EggGroupNode *egg_parent, bool has_decal) {
-  // A sequence node gets converted to an ordinary EggGroup, we only apply
-  // the appropriate switch attributes to turn it into a sequence
-  EggGroup *egg_group = new EggGroup(node->get_name());
-  egg_parent->add_child(egg_group);
-  apply_node_properties(egg_group, node);
-
-  // turn it into a switch..
-  egg_group->set_switch_flag(true);
-
-  int num_children = node->get_num_children();
-
-  for (int i = 0; i < num_children; i++) {
-    PandaNode *child = node->get_child(i);
-
-    // Convert just this one node to an EggGroup.
-    PT(EggGroup) next_group = new EggGroup;
-    convert_node(WorkingNodePath(node_path, child), next_group, has_decal);
-
-    egg_group->add_child(next_group.p());
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_animGroup_node
-//       Access: Private
-//  Description: Converts the indicated AnimationGroupNodes to the corresponding
-//               Egg constructs.
-////////////////////////////////////////////////////////////////////
-EggGroupNode * BamToEgg::convert_animGroup_node(AnimGroup *animGroup, double fps ) {
-  int num_children = animGroup->get_num_children();
-
-  EggGroupNode *eggNode = NULL;
-  if (animGroup->is_of_type(AnimBundle::get_class_type())) {
-    EggTable *eggTable = new EggTable(animGroup->get_name());
-    eggTable ->set_table_type(EggTable::TT_bundle);
-    eggNode = eggTable;
-  } else if (animGroup->is_of_type(AnimGroup::get_class_type())) {
-    EggTable *eggTable = new EggTable(animGroup->get_name());
-    eggTable ->set_table_type(EggTable::TT_table);
-    eggNode = eggTable;
-  }
-
-  if (animGroup->is_of_type(AnimChannelMatrixXfmTable::get_class_type())) {
-    AnimChannelMatrixXfmTable *xmfTable = DCAST(AnimChannelMatrixXfmTable, animGroup);
-    EggXfmSAnim *egg_anim = new EggXfmSAnim("xform");
-    egg_anim->set_fps(fps);
-    for (int i = 0; i < num_matrix_components; i++) {
-      string componentName(1, matrix_component_letters[i]);
-      char table_id = matrix_component_letters[i];
-      CPTA_stdfloat table = xmfTable->get_table(table_id);
-
-      if (xmfTable->has_table(table_id)) {
-        for (unsigned int j = 0; j < table.size(); j++) {
-          egg_anim->add_component_data(componentName, table[(int)j]);
-        }
-      }
-    }
-    eggNode->add_child(egg_anim);
-  }
-  for (int i = 0; i < num_children; i++) {
-    AnimGroup *animChild = animGroup->get_child(i);
-    EggGroupNode *eggChildNode = convert_animGroup_node(animChild, fps);
-    if (eggChildNode!=NULL) {
-      nassertr(eggNode!=NULL, NULL);
-      eggNode->add_child(eggChildNode);
-    }
-  } 
-  return eggNode;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_anim_node
-//       Access: Private
-//  Description: Converts the indicated AnimNode to the corresponding
-//               Egg constructs.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_anim_node(AnimBundleNode *node, const WorkingNodePath &node_path,
-                    EggGroupNode *egg_parent, bool has_decal) {
-  
-  // A sequence node gets converted to an ordinary EggGroup, we only apply
-  // the appropriate switch attributes to turn it into a sequence
-  EggTable *eggTable = new EggTable();
-  //egg_parent->add_child(eggTable);
-  _data->add_child(eggTable);
- 
-  AnimBundle *animBundle = node->get_bundle();
-  // turn it into a switch..
-  //egg_group->set_switch_flag(true);
-
-  EggGroupNode *eggAnimation = convert_animGroup_node(animBundle, animBundle->get_base_frame_rate());
-  eggTable->add_child(eggAnimation);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_character_bundle
-//       Access: Private
-//  Description: Converts the indicated Character Bundle to the corresponding
-//               Egg joints structure.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, CharacterJointMap *jointMap) {
-  int num_children = bundleNode->get_num_children();
-  
-  EggGroupNode *joint_group = egg_parent;
-  if (bundleNode->is_of_type(CharacterJoint::get_class_type())) {
-    CharacterJoint *character_joint = DCAST(CharacterJoint, bundleNode);
-
-    LMatrix4 transformf;
-    character_joint->get_net_transform(transformf);
-    LMatrix4d transformd(LCAST(double, transformf));
-    EggGroup *joint = new EggGroup(bundleNode->get_name());
-    joint->add_matrix4(transformd);
-    joint->set_group_type(EggGroup::GT_joint);
-    joint_group = joint;
-    egg_parent->add_child(joint_group);
-    if (jointMap!=NULL) {
-      CharacterJointMap::iterator mi = jointMap->find(character_joint);
-      if (mi != jointMap->end()) {
-        pvector<pair<EggVertex*,PN_stdfloat> > &joint_vertices = (*mi).second;
-        pvector<pair<EggVertex*,PN_stdfloat> >::const_iterator vi;
-        for (vi = joint_vertices.begin(); vi != joint_vertices.end(); ++vi) {
-          joint->set_vertex_membership((*vi).first, (*vi).second);
-        }
-      }
-    }
-  }
-
-  for (int i = 0; i < num_children ; i++) {
-    PartGroup *partGroup= bundleNode->get_child(i);
-    convert_character_bundle(partGroup, joint_group, jointMap);
-  }
-
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_character_node
-//       Access: Private
-//  Description: Converts the indicated Character to the corresponding
-//               Egg constructs.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_character_node(Character *node, const WorkingNodePath &node_path,
-                    EggGroupNode *egg_parent, bool has_decal) {
-  
-  // A sequence node gets converted to an ordinary EggGroup, we only apply
-  // the appropriate switch attributes to turn it into a sequence
-  EggGroup *egg_group = new EggGroup(node->get_name());
-  egg_group->set_dart_type(EggGroup::DT_default);
-  egg_parent->add_child(egg_group);
-  apply_node_properties(egg_group, node);
-
-  CharacterJointMap jointMap;
-  
-  // turn it into a switch..
-  //egg_group->set_switch_flag(true);
-
-  int num_children = node->get_num_children();
-  int num_bundles = node->get_num_bundles();
-
-  for (int i = 0; i < num_children; i++) {
-    PandaNode *child = node->get_child(i);
-
-    if (child->is_geom_node()) {
-      convert_geom_node(DCAST(GeomNode, child), WorkingNodePath(node_path, child), egg_group, has_decal, &jointMap);
-    }
-  }
-
-  for (int i = 0; i < num_bundles ; i++) {
-    PartBundle *bundle= node->get_bundle(i);
-    convert_character_bundle(bundle, egg_group, &jointMap);
-  }
-
-}
-
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_collision_node
-//       Access: Private
-//  Description: Converts the indicated CollisionNode to the corresponding
-//               Egg constructs.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
-                       EggGroupNode *egg_parent, bool has_decal) {
-  // A sequence node gets converted to an ordinary EggGroup, we only apply
-  // the appropriate switch attributes to turn it into a sequence
-  EggGroup *egg_group = new EggGroup(node->get_name());
-  egg_parent->add_child(egg_group);
-  apply_node_properties(egg_group, node, false);
-
-  // turn it into a collision node
-  egg_group->set_cs_type(EggGroup::CST_polyset);
-  egg_group->set_collide_flags(EggGroup::CF_descend);
-
-  NodePath np = node_path.get_node_path();
-  CPT(TransformState) net_transform = np.get_net_transform();
-  LMatrix4 net_mat = net_transform->get_mat();
-  LMatrix4 inv = LCAST(PN_stdfloat, egg_parent->get_vertex_frame_inv());
-  net_mat = net_mat * inv;
-
-  int num_solids = node->get_num_solids();
-
-  if (num_solids > 0) {
-    // create vertex pool for collisions
-    EggVertexPool *cvpool = new EggVertexPool("vpool-collision");
-    egg_group->add_child(cvpool);
-
-    // traverse solids
-    for (int i = 0; i < num_solids; i++) {
-      CPT(CollisionSolid) child = node->get_solid(i);
-      if (child->is_of_type(CollisionPolygon::get_class_type())) {
-        EggPolygon *egg_poly = new EggPolygon;
-        egg_group->add_child(egg_poly);
-
-        CPT(CollisionPolygon) poly = DCAST(CollisionPolygon, child);
-        int num_points = poly->get_num_points();
-        for (int j = 0; j < num_points; j++) {
-          EggVertex egg_vert;
-          egg_vert.set_pos(LCAST(double, poly->get_point(j) * net_mat));
-          egg_vert.set_normal(LCAST(double, poly->get_normal() * net_mat));
-
-          EggVertex *new_egg_vert = cvpool->create_unique_vertex(egg_vert);
-          egg_poly->add_vertex(new_egg_vert);
-        }
-      } else if (child->is_of_type(CollisionPlane::get_class_type())) {
-        nout << "Encountered unhandled collsion type: CollisionPlane" << "\n";
-      } else if (child->is_of_type(CollisionSphere::get_class_type())) {
-        nout << "Encountered unhandled collsion type: CollisionSphere" << "\n";
-      } else if (child->is_of_type(CollisionInvSphere::get_class_type())) {
-        nout << "Encountered unhandled collsion type: CollisionInvSphere" << "\n";
-      } else if (child->is_of_type(CollisionTube::get_class_type())) {
-        nout << "Encountered unhandled collsion type: CollisionTube" << "\n";
-      } else {
-        nout << "Encountered unknown CollisionSolid" << "\n";
-      }
-    }
-  }
-
-  // recurse over children - hm. do I need to do this?
-  recurse_nodes(node_path, egg_group, has_decal);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_geom_node
-//       Access: Private
-//  Description: Converts a GeomNode to the corresponding egg
-//               structures.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_geom_node(GeomNode *node, const WorkingNodePath &node_path, 
-                  EggGroupNode *egg_parent, bool has_decal, CharacterJointMap *jointMap) {
-  PT(EggGroup) egg_group = new EggGroup(node->get_name());
-  bool fancy_attributes = apply_node_properties(egg_group, node);
-
-  if (node->get_effects()->has_decal()) {
-    has_decal = true;
-  }
-
-  if (has_decal) {
-    egg_group->set_decal_flag(true);
-  }
-
-  if (fancy_attributes || has_decal || !node->get_name().empty()) {
-    // If we have any fancy attributes on the node, or if we're making
-    // decal geometry, we have to make a special node to hold the
-    // geometry (normally it would just appear within its parent).
-    egg_parent->add_child(egg_group.p());
-    egg_parent = egg_group;
-  }
-
-  NodePath np = node_path.get_node_path();
-  CPT(RenderState) net_state = np.get_net_state();
-  CPT(TransformState) net_transform = np.get_net_transform();
-  LMatrix4 net_mat = net_transform->get_mat();
-  LMatrix4 inv = LCAST(PN_stdfloat, egg_parent->get_vertex_frame_inv());
-  net_mat = net_mat * inv;
-
-  // Now get out all the various kinds of geometry.
-  int num_geoms = node->get_num_geoms();
-  for (int i = 0; i < num_geoms; ++i) {
-    CPT(RenderState) geom_state = net_state->compose(node->get_geom_state(i));
-
-    const Geom *geom = node->get_geom(i);
-    int num_primitives = geom->get_num_primitives();
-    for (int j = 0; j < num_primitives; ++j) {
-      const GeomPrimitive *primitive = geom->get_primitive(j);
-      CPT(GeomPrimitive) simple = primitive->decompose();
-      CPT(GeomVertexData) vdata = geom->get_vertex_data();
-      //        vdata = vdata->animate_vertices(true, Thread::get_current_thread());
-      convert_primitive(vdata, simple, geom_state,
-                        net_mat, egg_parent, jointMap);
-    }
-  }
-  
-  recurse_nodes(node_path, egg_parent, has_decal);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::convert_primitive
-//       Access: Private
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-convert_primitive(const GeomVertexData *vertex_data,
-                  const GeomPrimitive *primitive,
-                  const RenderState *net_state, 
-                  const LMatrix4 &net_mat, EggGroupNode *egg_parent,
-                  CharacterJointMap *jointMap) {
-  GeomVertexReader reader(vertex_data);
-
-  // Check for a color scale.
-  LVecBase4 color_scale(1.0f, 1.0f, 1.0f, 1.0f);
-  const ColorScaleAttrib *csa = DCAST(ColorScaleAttrib, net_state->get_attrib(ColorScaleAttrib::get_class_type()));
-  if (csa != (const ColorScaleAttrib *)NULL) {
-    color_scale = csa->get_scale();
-  }
-
-  // Check for a color override.
-  bool has_color_override = false;
-  bool has_color_off = false;
-  LColor color_override;
-  const ColorAttrib *ca = DCAST(ColorAttrib, net_state->get_attrib(ColorAttrib::get_class_type()));
-  if (ca != (const ColorAttrib *)NULL) {
-    if (ca->get_color_type() == ColorAttrib::T_flat) {
-      has_color_override = true;
-      color_override = ca->get_color();
-      color_override.set(color_override[0] * color_scale[0],
-                         color_override[1] * color_scale[1],
-                         color_override[2] * color_scale[2],
-                         color_override[3] * color_scale[3]);
-
-    } else if (ca->get_color_type() == ColorAttrib::T_off) {
-      has_color_off = true;
-    }
-  }
-
-  // Check for a texture.
-  EggTexture *egg_tex = (EggTexture *)NULL;
-  const TextureAttrib *ta = DCAST(TextureAttrib, net_state->get_attrib(TextureAttrib::get_class_type()));
-  if (ta != (const TextureAttrib *)NULL) {
-    egg_tex = get_egg_texture(ta->get_texture());
-  }
-
-  // Check the texture environment
-  if ((ta != (const TextureAttrib *)NULL) && (egg_tex != (const EggTexture *)NULL)) {
-    TextureStage* tex_stage = ta->get_on_stage(0);
-    if (tex_stage != (const TextureStage *)NULL) {
-      switch (tex_stage->get_mode()) {
-        case TextureStage::M_modulate:
-          if (has_color_off == true) {
-            egg_tex->set_env_type(EggTexture::ET_replace);
-          } else {
-            egg_tex->set_env_type(EggTexture::ET_modulate);
-          }
-          break;
-        case TextureStage::M_decal:
-          egg_tex->set_env_type(EggTexture::ET_decal);
-          break;
-        case TextureStage::M_blend:
-          egg_tex->set_env_type(EggTexture::ET_blend);
-          break;
-        case TextureStage::M_replace:
-          egg_tex->set_env_type(EggTexture::ET_replace);
-          break;
-        case TextureStage::M_add:
-          egg_tex->set_env_type(EggTexture::ET_add);
-          break;
-        case TextureStage::M_blend_color_scale:
-          egg_tex->set_env_type(EggTexture::ET_blend_color_scale);
-          break;
-        default:
-          break;
-      }
-    }
-  }
-
-  // Check the backface flag.
-  bool bface = false;
-  const RenderAttrib *cf_attrib = net_state->get_attrib(CullFaceAttrib::get_class_type());
-  if (cf_attrib != (const RenderAttrib *)NULL) {
-    const CullFaceAttrib *cfa = DCAST(CullFaceAttrib, cf_attrib);
-    if (cfa->get_effective_mode() == CullFaceAttrib::M_cull_none) {
-      bface = true;
-    }
-  }
-
-  // Check the depth write flag - only needed for AM_blend_no_occlude
-  bool has_depthwrite = false;
-  DepthWriteAttrib::Mode depthwrite = DepthWriteAttrib::M_on;
-  const RenderAttrib *dw_attrib = net_state->get_attrib(DepthWriteAttrib::get_class_type());
-  if (dw_attrib != (const RenderAttrib *)NULL) {
-    const DepthWriteAttrib *dwa = DCAST(DepthWriteAttrib, dw_attrib);
-    depthwrite = dwa->get_mode();
-    has_depthwrite = true;
-  }
-
-  // Check the transparency flag.
-  bool has_transparency = false;
-  TransparencyAttrib::Mode transparency = TransparencyAttrib::M_none;
-  const RenderAttrib *tr_attrib = net_state->get_attrib(TransparencyAttrib::get_class_type());
-  if (tr_attrib != (const RenderAttrib *)NULL) {
-    const TransparencyAttrib *tra = DCAST(TransparencyAttrib, tr_attrib);
-    transparency = tra->get_mode();
-    has_transparency = true;
-  }
-  if (has_transparency && (egg_tex != (EggTexture *)NULL)) {
-    EggRenderMode::AlphaMode tex_trans = EggRenderMode::AM_unspecified;
-    switch (transparency) {
-      case TransparencyAttrib::M_none:
-        tex_trans = EggRenderMode::AM_off;
-        break;
-      case TransparencyAttrib::M_alpha:
-        if (has_depthwrite && (depthwrite == DepthWriteAttrib::M_off)) {
-            tex_trans = EggRenderMode::AM_blend_no_occlude;
-                has_depthwrite = false;
-        } else {
-          tex_trans = EggRenderMode::AM_blend;
-        }
-        break;
-      case TransparencyAttrib::M_multisample:
-        tex_trans = EggRenderMode::AM_ms;
-        break;
-      case TransparencyAttrib::M_multisample_mask:
-        tex_trans = EggRenderMode::AM_ms_mask;
-        break;
-      case TransparencyAttrib::M_binary:
-        tex_trans = EggRenderMode::AM_binary;
-        break;
-      case TransparencyAttrib::M_dual:
-        tex_trans = EggRenderMode::AM_dual;
-        break;
-      default:  // intentional fall-through
-      case TransparencyAttrib::M_notused:
-        break;
-    }
-    if (tex_trans != EggRenderMode::AM_unspecified) {
-      egg_tex->set_alpha_mode(tex_trans);
-    }
-  }
-
-
-  LNormal normal;
-  LColor color;
-  CPT(TransformBlendTable) transformBlendTable = vertex_data->get_transform_blend_table();
-
-  int num_primitives = primitive->get_num_primitives();
-  int num_vertices = primitive->get_num_vertices_per_primitive();
-
-  EggPrimitive *(*make_func)(void);
-
-  if (primitive->is_of_type(GeomTriangles::get_class_type())) {
-    make_func = make_egg_polygon;
-  } else if (primitive->is_of_type(GeomPatches::get_class_type())) {
-    make_func = make_egg_patch;
-  } else if (primitive->is_of_type(GeomPoints::get_class_type())) {
-    make_func = make_egg_point;
-  } else if (primitive->is_of_type(GeomLines::get_class_type())) {
-    make_func = make_egg_line;
-  } else {
-    // Huh, an unknown geometry type.
-    return;
-  }
-  
-  for (int i = 0; i < num_primitives; ++i) {
-    PT(EggPrimitive) egg_prim = (*make_func)();
-
-    egg_parent->add_child(egg_prim);
-    if (egg_tex != (EggTexture *)NULL) {
-      egg_prim->set_texture(egg_tex);
-    }
-    
-    if (bface) {
-      egg_prim->set_bface_flag(true);
-    }
-
-    for (int j = 0; j < num_vertices; j++) {
-      EggVertex egg_vert;
-        
-      // Get per-vertex properties.
-      reader.set_row(primitive->get_vertex(i * num_vertices + j));
-        
-      reader.set_column(InternalName::get_vertex());
-      LVertex vertex = reader.get_data3();
-      egg_vert.set_pos(LCAST(double, vertex * net_mat));
-        
-      if (vertex_data->has_column(InternalName::get_normal())) {
-        reader.set_column(InternalName::get_normal());
-        LNormal normal = reader.get_data3();
-        egg_vert.set_normal(LCAST(double, normal * net_mat));
-      }
-      if (has_color_override) {
-        egg_vert.set_color(color_override);
-          
-      } else if (!has_color_off) {
-        LColor color(1.0f, 1.0f, 1.0f, 1.0f);
-        if (vertex_data->has_column(InternalName::get_color())) {
-          reader.set_column(InternalName::get_color());
-          color = reader.get_data4();
-        }
-        egg_vert.set_color(LColor(color[0] * color_scale[0],
-                                  color[1] * color_scale[1],
-                                  color[2] * color_scale[2],
-                                  color[3] * color_scale[3]));
-      }
-        
-      if (vertex_data->has_column(InternalName::get_texcoord())) {
-        reader.set_column(InternalName::get_texcoord());
-        LTexCoord uv = reader.get_data2();
-        egg_vert.set_uv(LCAST(double, uv));
-      }
-        
-      EggVertex *new_egg_vert = _vpool->create_unique_vertex(egg_vert);
-        
-      if ((vertex_data->has_column(InternalName::get_transform_blend())) && 
-          (jointMap!=NULL) && (transformBlendTable!=NULL)) {
-        reader.set_column(InternalName::get_transform_blend());
-        int idx = reader.get_data1i();
-        const TransformBlend &blend = transformBlendTable->get_blend(idx);
-        int num_weights = blend.get_num_transforms();
-        for (int k = 0; k < num_weights; ++k) {
-          PN_stdfloat weight = blend.get_weight(k);
-          if (weight!=0) {
-            const VertexTransform *vertex_transform = blend.get_transform(k);
-            if (vertex_transform->is_of_type(JointVertexTransform::get_class_type())) {
-              const JointVertexTransform *joint_vertex_transform = DCAST(const JointVertexTransform, vertex_transform);
-
-              CharacterJointMap::iterator mi = jointMap->find(joint_vertex_transform->get_joint());
-              if (mi == jointMap->end()) {
-                mi = jointMap->insert(CharacterJointMap::value_type(joint_vertex_transform->get_joint(), pvector<pair<EggVertex*,PN_stdfloat> >())).first;
-              }
-              pvector<pair<EggVertex*,PN_stdfloat> > &joint_vertices = (*mi).second;
-              joint_vertices.push_back(pair<EggVertex*,PN_stdfloat>(new_egg_vert, weight));
-            }
-          }
-        }
-      }
-
-      egg_prim->add_vertex(new_egg_vert);
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::recurse_nodes
-//       Access: Private
-//  Description: Converts all the children of the indicated node.
-////////////////////////////////////////////////////////////////////
-void BamToEgg::
-recurse_nodes(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
-              bool has_decal) {
-  PandaNode *node = node_path.node();
-  int num_children = node->get_num_children();
-  
-  for (int i = 0; i < num_children; i++) {
-    PandaNode *child = node->get_child(i);
-    convert_node(WorkingNodePath(node_path, child), egg_parent, has_decal);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::apply_node_properties
-//       Access: Public
-//  Description: Applies any special properties that might be stored
-//               on the node, like billboarding.  Returns true if any
-//               were applied, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool BamToEgg::
-apply_node_properties(EggGroup *egg_group, PandaNode *node, bool allow_backstage) {
-  bool any_applied = false;
-
-  if (node->is_overall_hidden() && allow_backstage) {
-    // This node is hidden.  We'll go ahead and convert it, but we'll
-    // put in the "backstage" flag to mean it's not real geometry.
-    // unless the caller wants to keep it (by setting allow_backstage to false)
-    egg_group->add_object_type("backstage");
-  }
-
-  if (node->has_tags()) {
-    if (apply_tags(egg_group, node)) {
-      any_applied = true;
-    }
-  }
-
-  if (node->is_of_type(ModelNode::get_class_type())) {
-    ModelNode *model_node = DCAST(ModelNode, node);
-    switch (model_node->get_preserve_transform()) {
-    case ModelNode::PT_none:
-    case ModelNode::PT_drop_node:
-      break;
-
-    case ModelNode::PT_net:
-      egg_group->set_dcs_type(EggGroup::DC_net);
-      break;
-
-    case ModelNode::PT_local:
-      egg_group->set_dcs_type(EggGroup::DC_local);
-      break;
-
-    case ModelNode::PT_no_touch:
-      egg_group->set_dcs_type(EggGroup::DC_no_touch);
-      break;
-    }
-  }
-
-  const RenderEffects *effects = node->get_effects();
-  const RenderEffect *effect = effects->get_effect(BillboardEffect::get_class_type());
-  if (effect != (RenderEffect *)NULL) {
-    const BillboardEffect *bbe = DCAST(BillboardEffect, effect);
-    if (bbe->get_axial_rotate()) {
-      egg_group->set_billboard_type(EggGroup::BT_axis);
-      any_applied = true;
-
-    } else if (bbe->get_eye_relative()) {
-      egg_group->set_billboard_type(EggGroup::BT_point_camera_relative);
-      any_applied = true;
-
-    } else {
-      egg_group->set_billboard_type(EggGroup::BT_point_world_relative);
-      any_applied = true;
-    }
-  }
-
-  const TransformState *transform = node->get_transform();
-  if (!transform->is_identity()) {
-    if (transform->has_components()) {
-      // If the transform can be represented componentwise, we prefer
-      // storing it that way in the egg file.
-      const LVecBase3 &scale = transform->get_scale();
-      const LQuaternion &quat = transform->get_quat();
-      const LVecBase3 &pos = transform->get_pos();
-      if (!scale.almost_equal(LVecBase3(1.0f, 1.0f, 1.0f))) {
-        egg_group->add_scale3d(LCAST(double, scale));
-      }
-      if (!quat.is_identity()) {
-        egg_group->add_rotate3d(LCAST(double, quat));
-      }
-      if (!pos.almost_equal(LVecBase3::zero())) {
-        egg_group->add_translate3d(LCAST(double, pos));
-      }
-
-    } else if (transform->has_mat()) {
-      // Otherwise, we store the raw matrix.
-      const LMatrix4 &mat = transform->get_mat();
-      egg_group->set_transform3d(LCAST(double, mat));
-    }
-    any_applied = true;
-  }
-
-  return any_applied;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::apply_tags
-//       Access: Public
-//  Description: Applies string tags to the egg file.  Returns true if
-//               any were applied, false otherwise.
-////////////////////////////////////////////////////////////////////
-bool BamToEgg::
-apply_tags(EggGroup *egg_group, PandaNode *node) {
-  ostringstream strm;
-  char delimiter = '\n';
-  string delimiter_str(1, delimiter);
-  node->list_tags(strm, delimiter_str);
-
-  string data = strm.str();
-  if (data.empty()) {
-    return false;
-  }
-
-  bool any_applied = false;
-
-  size_t p = 0;
-  size_t q = data.find(delimiter);
-  while (q != string::npos) {
-    string tag = data.substr(p, q);
-    if (apply_tag(egg_group, node, tag)) {
-      any_applied = true;
-    }
-    p = q + 1;
-    q = data.find(delimiter, p);
-  }
-  
-  string tag = data.substr(p);
-  if (apply_tag(egg_group, node, tag)) {
-    any_applied = true;
-  }
-
-  return any_applied;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::apply_tag
-//       Access: Public
-//  Description: Applies the named string tags to the egg file.
-////////////////////////////////////////////////////////////////////
-bool BamToEgg::
-apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag) {
-  if (!node->has_tag(tag)) {
-    return false;
-  }
-
-  string value = node->get_tag(tag);
-  egg_group->set_tag(tag, value);
-  return true;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::get_egg_texture
-//       Access: Public
-//  Description: Returns an EggTexture pointer that corresponds to the
-//               indicated Texture.
-////////////////////////////////////////////////////////////////////
-EggTexture *BamToEgg::
-get_egg_texture(Texture *tex) {
-  if (tex != (Texture *)NULL) {
-    if (tex->has_filename()) {
-      Filename filename = _path_replace->convert_path(tex->get_filename());
-      EggTexture temp(filename.get_basename_wo_extension(), filename);
-      if (tex->has_alpha_filename()) {
-        Filename alpha = _path_replace->convert_path(tex->get_alpha_filename());
-        temp.set_alpha_filename(alpha);
-      }
-
-      switch (tex->get_minfilter()) {
-      case Texture::FT_nearest:
-        temp.set_minfilter(EggTexture::FT_nearest);
-        break;
-      case Texture::FT_linear:
-        temp.set_minfilter(EggTexture::FT_linear);
-        break;
-      case Texture::FT_nearest_mipmap_nearest:
-        temp.set_minfilter(EggTexture::FT_nearest_mipmap_nearest);
-        break;
-      case Texture::FT_linear_mipmap_nearest:
-        temp.set_minfilter(EggTexture::FT_linear_mipmap_nearest);
-        break;
-      case Texture::FT_nearest_mipmap_linear:
-        temp.set_minfilter(EggTexture::FT_nearest_mipmap_linear);
-        break;
-      case Texture::FT_linear_mipmap_linear:
-        temp.set_minfilter(EggTexture::FT_linear_mipmap_linear);
-        break;
-
-      default:
-        break;
-      }
-
-      switch (tex->get_magfilter()) {
-      case Texture::FT_nearest:
-        temp.set_magfilter(EggTexture::FT_nearest);
-        break;
-      case Texture::FT_linear:
-        temp.set_magfilter(EggTexture::FT_linear);
-        break;
-
-      default:
-        break;
-      }
-
-      switch (tex->get_wrap_u()) {
-      case Texture::WM_clamp:
-        temp.set_wrap_u(EggTexture::WM_clamp);
-        break;
-      case Texture::WM_repeat:
-        temp.set_wrap_u(EggTexture::WM_repeat);
-        break;
-
-      default:
-        // There are some new wrap options on Texture that aren't yet
-        // supported in egg.
-        break;
-      }
-
-      switch (tex->get_wrap_v()) {
-      case Texture::WM_clamp:
-        temp.set_wrap_v(EggTexture::WM_clamp);
-        break;
-      case Texture::WM_repeat:
-        temp.set_wrap_v(EggTexture::WM_repeat);
-        break;
-
-      default:
-        // There are some new wrap options on Texture that aren't yet
-        // supported in egg.
-        break;
-      }
-
-      switch (tex->get_format()) {
-      case Texture::F_red:
-        temp.set_format(EggTexture::F_red);
-        break;
-      case Texture::F_green:
-        temp.set_format(EggTexture::F_green);
-        break;
-      case Texture::F_blue:
-        temp.set_format(EggTexture::F_blue);
-        break;
-      case Texture::F_alpha:
-        temp.set_format(EggTexture::F_alpha);
-        break;
-      case Texture::F_rgb:
-        temp.set_format(EggTexture::F_rgb);
-        break;
-      case Texture::F_rgb5:
-        temp.set_format(EggTexture::F_rgb5);
-        break;
-      case Texture::F_rgb8:
-        temp.set_format(EggTexture::F_rgb8);
-        break;
-      case Texture::F_rgb12:
-        temp.set_format(EggTexture::F_rgb12);
-        break;
-      case Texture::F_rgb332:
-        temp.set_format(EggTexture::F_rgb332);
-        break;
-      case Texture::F_rgba:
-        temp.set_format(EggTexture::F_rgba);
-        break;
-      case Texture::F_rgbm:
-        temp.set_format(EggTexture::F_rgbm);
-        break;
-      case Texture::F_rgba4:
-        temp.set_format(EggTexture::F_rgba4);
-        break;
-      case Texture::F_rgba5:
-        temp.set_format(EggTexture::F_rgba5);
-        break;
-      case Texture::F_rgba8:
-        temp.set_format(EggTexture::F_rgba8);
-        break;
-      case Texture::F_rgba12:
-        temp.set_format(EggTexture::F_rgba12);
-        break;
-      case Texture::F_luminance:
-        temp.set_format(EggTexture::F_luminance);
-        break;
-      case Texture::F_luminance_alpha:
-        temp.set_format(EggTexture::F_luminance_alpha);
-        break;
-      case Texture::F_luminance_alphamask:
-        temp.set_format(EggTexture::F_luminance_alphamask);
-        break;
-      default:
-        break;
-      }
-
-      return _textures.create_unique_texture(temp, ~EggTexture::E_tref_name);
-    }
-  }
-
-  return NULL;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::make_egg_polygon
-//       Access: Public, Static
-//  Description: A factory function to make a new EggPolygon instance.
-////////////////////////////////////////////////////////////////////
-EggPrimitive *BamToEgg::
-make_egg_polygon() {
-  return new EggPolygon;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::make_egg_patch
-//       Access: Public, Static
-//  Description: A factory function to make a new EggPatch instance.
-////////////////////////////////////////////////////////////////////
-EggPrimitive *BamToEgg::
-make_egg_patch() {
-  return new EggPatch;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::make_egg_point
-//       Access: Public, Static
-//  Description: A factory function to make a new EggPoint instance.
-////////////////////////////////////////////////////////////////////
-EggPrimitive *BamToEgg::
-make_egg_point() {
-  return new EggPoint;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: BamToEgg::make_egg_line
-//       Access: Public, Static
-//  Description: A factory function to make a new EggLine instance.
-////////////////////////////////////////////////////////////////////
-EggPrimitive *BamToEgg::
-make_egg_line() {
-  return new EggLine;
-}
-
 int main(int argc, char *argv[]) {
   // A call to pystub() to force libpystub.so to be linked in.
   pystub();

+ 0 - 67
pandatool/src/bam/bamToEgg.h

@@ -18,33 +18,6 @@
 #include "pandatoolbase.h"
 
 #include "somethingToEgg.h"
-#include "luse.h"
-#include "eggTextureCollection.h"
-#include "eggMaterialCollection.h"
-
-class WorkingNodePath;
-class EggGroup;
-class EggGroupNode;
-class EggVertexPool;
-class EggTexture;
-class LODNode;
-class SequenceNode;
-class SwitchNode;
-class AnimBundleNode;
-class AnimGroup;
-class Character;
-class PartGroup;
-class CollisionNode;
-class GeomNode;
-class GeomVertexData;
-class GeomPrimitive;
-class PandaNode;
-class RenderState;
-class Texture;
-class CharacterJoint;
-class EggVertex;
-
-typedef pmap<const CharacterJoint*, pvector<pair<EggVertex*,PN_stdfloat> > > CharacterJointMap;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : BamToEgg
@@ -59,46 +32,6 @@ public:
   void run();
 
 private:
-  void convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
-                    bool has_decal);
-  void convert_lod_node(LODNode *node, const WorkingNodePath &node_path,
-                        EggGroupNode *egg_parent, bool has_decal);
-  void convert_sequence_node(SequenceNode *node, const WorkingNodePath &node_path,
-                        EggGroupNode *egg_parent, bool has_decal);
-  void convert_switch_node(SwitchNode *node, const WorkingNodePath &node_path,
-                        EggGroupNode *egg_parent, bool has_decal);
-  EggGroupNode *convert_animGroup_node(AnimGroup *animGroup, double fps );
-  void convert_anim_node(AnimBundleNode *node, const WorkingNodePath &node_path,
-                        EggGroupNode *egg_parent, bool has_decal);
-  void convert_character_node(Character *node, const WorkingNodePath &node_path,
-                        EggGroupNode *egg_parent, bool has_decal);
-  void convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, CharacterJointMap *jointMap);
-  void convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
-                        EggGroupNode *egg_parent, bool has_decal);
-  void convert_geom_node(GeomNode *node, const WorkingNodePath &node_path, 
-                         EggGroupNode *egg_parent, bool has_decal, CharacterJointMap *jointMap=NULL);
-  void convert_primitive(const GeomVertexData *vertex_data,
-                         const GeomPrimitive *primitive, 
-                         const RenderState *net_state, 
-                         const LMatrix4 &net_mat, EggGroupNode *egg_parent,
-                         CharacterJointMap *jointMap);
-
-  void recurse_nodes(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
-                     bool has_decal);
-  bool apply_node_properties(EggGroup *egg_group, PandaNode *node, bool allow_backstage = true);
-  bool apply_tags(EggGroup *egg_group, PandaNode *node);
-  bool apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag);
-
-  EggTexture *get_egg_texture(Texture *tex);
-
-  static EggPrimitive *make_egg_polygon();
-  static EggPrimitive *make_egg_patch();
-  static EggPrimitive *make_egg_point();
-  static EggPrimitive *make_egg_line();
-
-  EggVertexPool *_vpool;
-  EggTextureCollection _textures;
-  EggMaterialCollection _materials;
 };
 
 #endif

+ 5 - 2
pandatool/src/converter/Sources.pp

@@ -13,9 +13,12 @@
 
   #define SOURCES \
     somethingToEggConverter.I somethingToEggConverter.cxx \
-    somethingToEggConverter.h
+    somethingToEggConverter.h \
+    eggToSomethingConverter.I eggToSomethingConverter.cxx \
+    eggToSomethingConverter.h
 
   #define INSTALL_HEADERS \
-    somethingToEggConverter.I somethingToEggConverter.h
+    somethingToEggConverter.I somethingToEggConverter.h \
+    eggToSomethingConverter.I eggToSomethingConverter.h
 
 #end ss_lib_target

+ 81 - 0
pandatool/src/converter/eggToSomethingConverter.I

@@ -0,0 +1,81 @@
+// Filename: eggToSomethingConverter.I
+// Created by:  drose (26Sep12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::clear_error
+//       Access: Public
+//  Description: Resets the error flag to the no-error state.
+//               had_error() will return false until a new error is
+//               generated.
+////////////////////////////////////////////////////////////////////
+INLINE void EggToSomethingConverter::
+clear_error() {
+  _error = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::had_error
+//       Access: Public
+//  Description: Returns true if an error was detected during the
+//               conversion process, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggToSomethingConverter::
+had_error() const {
+  return _error;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::clear_egg_data
+//       Access: Public
+//  Description: Sets the EggData to NULL and makes the converter
+//               invalid.
+////////////////////////////////////////////////////////////////////
+INLINE void EggToSomethingConverter::
+clear_egg_data() {
+  set_egg_data((EggData *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::get_egg_data
+//       Access: Public
+//  Description: Returns the EggData structure.
+////////////////////////////////////////////////////////////////////
+INLINE EggData *EggToSomethingConverter::
+get_egg_data() {
+  return _egg_data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::set_output_units
+//       Access: Public
+//  Description: Specifies the units that the EggData has already been
+//               scaled to.  This is informational only; if the target
+//               file format supports it, this information will be
+//               written to the header.
+////////////////////////////////////////////////////////////////////
+void EggToSomethingConverter::
+set_output_units(DistanceUnit output_units) {
+  _output_units = output_units;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::get_output_units
+//       Access: Public
+//  Description: Returns the value supplied to set_output_units().
+////////////////////////////////////////////////////////////////////
+DistanceUnit EggToSomethingConverter::
+get_output_units() const {
+  return _output_units;
+}

+ 85 - 0
pandatool/src/converter/eggToSomethingConverter.cxx

@@ -0,0 +1,85 @@
+// Filename: eggToSomethingConverter.cxx
+// Created by:  drose (26Apr01)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "eggToSomethingConverter.h"
+
+#include "eggData.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToSomethingConverter::
+EggToSomethingConverter() {
+  _egg_data = (EggData *)NULL;
+  _error = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToSomethingConverter::
+EggToSomethingConverter(const EggToSomethingConverter &copy) {
+  _egg_data = (EggData *)NULL;
+  _error = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToSomethingConverter::
+~EggToSomethingConverter() {
+  clear_egg_data();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::set_egg_data
+//       Access: Public
+//  Description: Sets the egg data that will be filled in when
+//               convert_file() is called.  This must be called before
+//               convert_file().
+////////////////////////////////////////////////////////////////////
+void EggToSomethingConverter::
+set_egg_data(EggData *egg_data) {
+  _egg_data = egg_data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::get_additional_extensions
+//       Access: Public, Virtual
+//  Description: Returns a space-separated list of extension, in
+//               addition to the one returned by get_extension(), that
+//               are recognized by this converter.
+////////////////////////////////////////////////////////////////////
+string EggToSomethingConverter::
+get_additional_extensions() const {
+  return string();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToSomethingConverter::supports_compressed
+//       Access: Published, Virtual
+//  Description: Returns true if this file type can transparently save
+//               compressed files (with a .pz extension), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggToSomethingConverter::
+supports_compressed() const {
+  return false;
+}

+ 77 - 0
pandatool/src/converter/eggToSomethingConverter.h

@@ -0,0 +1,77 @@
+// Filename: eggToSomethingConverter.h
+// Created by:  drose (26Sep12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef EGGTOSOMETHINGCONVERTER_H
+#define EGGTOSOMETHINGCONVERTER_H
+
+#include "pandatoolbase.h"
+
+#include "filename.h"
+#include "pointerTo.h"
+#include "distanceUnit.h"
+#include "coordinateSystem.h"
+
+class EggData;
+class EggGroupNode;
+
+////////////////////////////////////////////////////////////////////
+//       Class : EggToSomethingConverter
+// Description : This is a base class for a family of converter
+//               classes that manage a conversion from egg format to
+//               some other file type.
+//
+//               Classes of this type can be used to implement egg2xxx
+//               converter programs, as well as LoaderFileTypeXXX
+//               run-time savers.
+////////////////////////////////////////////////////////////////////
+class EggToSomethingConverter {
+public:
+  EggToSomethingConverter();
+  EggToSomethingConverter(const EggToSomethingConverter &copy);
+  virtual ~EggToSomethingConverter();
+
+  virtual EggToSomethingConverter *make_copy()=0;
+
+  INLINE void clear_error();
+  INLINE bool had_error() const;
+
+  void set_egg_data(EggData *egg_data);
+  INLINE void clear_egg_data();
+  INLINE EggData *get_egg_data();
+
+  INLINE void set_output_units(DistanceUnit output_units);
+  INLINE DistanceUnit get_output_units() const;
+  INLINE void set_output_coordinate_system(CoordinateSystem output_coordinate_system) const;
+  INLINE CoordinateSystem get_output_coordinate_system() const;
+
+  virtual string get_name() const=0;
+  virtual string get_extension() const=0;
+  virtual string get_additional_extensions() const;
+  virtual bool supports_compressed() const;
+
+  virtual bool write_file(const Filename &filename)=0;
+
+protected:
+  PT(EggData) _egg_data;
+  DistanceUnit _output_units;
+  CoordinateSystem _output_coordinate_system;
+
+  bool _error;
+};
+
+#include "eggToSomethingConverter.I"
+
+#endif
+
+

+ 4 - 2
pandatool/src/objegg/Sources.pp

@@ -15,9 +15,11 @@
 
   #define SOURCES \
     config_objegg.cxx config_objegg.h \
-    objToEggConverter.cxx objToEggConverter.h
+    objToEggConverter.cxx objToEggConverter.h \
+    eggToObjConverter.cxx eggToObjConverter.h
 
   #define INSTALL_HEADERS \
-    objToEggConverter.h
+    objToEggConverter.h \
+    eggToObjConverter.h
 
 #end ss_lib_target

+ 445 - 0
pandatool/src/objegg/eggToObjConverter.cxx

@@ -0,0 +1,445 @@
+// Filename: eggToObjConverter.cxx
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "eggToObjConverter.h"
+#include "config_objegg.h"
+#include "eggData.h"
+#include "string_utils.h"
+#include "streamReader.h"
+#include "virtualFileSystem.h"
+#include "eggPolygon.h"
+#include "dcast.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::
+EggToObjConverter() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::
+EggToObjConverter(const EggToObjConverter &copy) :
+  EggToSomethingConverter(copy)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::Destructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::
+~EggToObjConverter() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::make_copy
+//       Access: Public, Virtual
+//  Description: Allocates and returns a new copy of the converter.
+////////////////////////////////////////////////////////////////////
+EggToSomethingConverter *EggToObjConverter::
+make_copy() {
+  return new EggToObjConverter(*this);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::get_name
+//       Access: Public, Virtual
+//  Description: Returns the English name of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string EggToObjConverter::
+get_name() const {
+  return "obj";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::get_extension
+//       Access: Public, Virtual
+//  Description: Returns the common extension of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string EggToObjConverter::
+get_extension() const {
+  return "obj";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::supports_compressed
+//       Access: Published, Virtual
+//  Description: Returns true if this file type can transparently save
+//               compressed files (with a .pz extension), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggToObjConverter::
+supports_compressed() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_file
+//       Access: Public, Virtual
+//  Description: Handles the conversion of the internal EggData to the
+//               target file format, written to the specified
+//               filename.
+////////////////////////////////////////////////////////////////////
+bool EggToObjConverter::
+write_file(const Filename &filename) {
+  clear_error();
+
+  if (_egg_data->get_coordinate_system() == CS_default) {
+    _egg_data->set_coordinate_system(CS_zup_right);
+  }
+
+  if (!process(filename)) {
+    _error = true;
+  }
+  return !had_error();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::process
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool EggToObjConverter::
+process(const Filename &filename) {
+  _egg_data->flatten_transforms();
+  collect_vertices(_egg_data);
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  Filename obj_filename = Filename::text_filename(filename);
+  vfs->delete_file(obj_filename);
+  ostream *file = vfs->open_write_file(obj_filename, true, true);
+  if (file == (ostream *)NULL) {
+    return false;
+  }
+
+  _current_group = NULL;
+
+  /*
+  (*file) << "\n#\n"
+          << "# obj file generated by the following command:\n"
+          << "# " << get_exec_command() << "\n"
+          << "#\n\n";
+  */
+
+  write_vertices(*file, "v", 3, _unique_vert3);
+  write_vertices(*file, "v", 4, _unique_vert4);
+  write_vertices(*file, "vt", 2, _unique_uv2);
+  write_vertices(*file, "vt", 3, _unique_uv3);
+  write_vertices(*file, "vn", 3, _unique_norm);
+
+  write_faces(*file, _egg_data);
+
+  bool success = (void *)(*file) != NULL;
+  vfs->close_write_file(file);
+
+  return success;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::collect_vertices
+//       Access: Private
+//  Description: Recursively walks the egg structure, looking for
+//               vertices referenced by polygons.  Any such vertices
+//               are added to the vertex tables for writing to the obj
+//               file.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+collect_vertices(EggNode *egg_node) {
+  if (egg_node->is_of_type(EggPolygon::get_class_type())) {
+    EggPolygon *egg_poly = DCAST(EggPolygon, egg_node);
+    EggPolygon::iterator pi;
+    for (pi = egg_poly->begin(); pi != egg_poly->end(); ++pi) {
+      record_vertex(*pi);
+    }
+
+  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
+    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
+
+    EggGroupNode::iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      collect_vertices(*ci);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_faces
+//       Access: Private
+//  Description: Recursively walks the egg structure again, this time
+//               writing out the face records for any polygons
+//               encountered.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+write_faces(ostream &out, EggNode *egg_node) {
+  if (egg_node->is_of_type(EggPolygon::get_class_type())) {
+    write_group_reference(out, egg_node);
+
+    EggPolygon *egg_poly = DCAST(EggPolygon, egg_node);
+
+    out << "f";
+    EggPolygon::iterator pi;
+    for (pi = egg_poly->begin(); pi != egg_poly->end(); ++pi) {
+      VertexDef &vdef = _vmap[(*pi)];
+      int vert_index = -1;
+      int uv_index = -1;
+      int norm_index = -1;
+
+      if (vdef._vert3_index != -1) {
+        vert_index = vdef._vert3_index + 1;
+      } else if (vdef._vert4_index != -1) {
+        vert_index = vdef._vert4_index + 1 + (int)_unique_vert3.size();
+      }
+
+      if (vdef._uv2_index != -1) {
+        uv_index = vdef._uv2_index + 1;
+      } else if (vdef._uv3_index != -1) {
+        uv_index = vdef._uv3_index + 1 + (int)_unique_uv2.size();
+      }
+
+      if (vdef._norm_index != -1) {
+        norm_index = vdef._norm_index + 1;
+      }
+
+      if (vert_index == -1) {
+        continue;
+      }
+
+      if (norm_index != -1) {
+        if (uv_index != -1) {
+          out << " " << vert_index << "/" << uv_index << "/" << norm_index;
+        } else {
+          out << " " << vert_index << "//" << norm_index;
+        }
+      } else if (uv_index != -1) {
+        out << " " << vert_index << "/" << uv_index;
+      } else {
+        out << " " << vert_index;
+      }
+    }
+    out << "\n";
+
+  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
+    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
+
+    EggGroupNode::iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      write_faces(out, *ci);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_group_reference
+//       Access: Private
+//  Description: Writes the "g" tag to describe this polygon's group,
+//               if needed.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+write_group_reference(ostream &out, EggNode *egg_node) {
+  EggGroupNode *egg_group = egg_node->get_parent();
+  if (egg_group == _current_group) {
+    // Same group we wrote last time.
+    return;
+  }
+
+  string group_name;
+  get_group_name(group_name, egg_group);
+  if (group_name.empty()) {
+    out << "g default\n";
+  } else {
+    out << "g" << group_name << "\n";
+  }
+  _current_group = egg_group;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::get_group_name
+//       Access: Private
+//  Description: Recursively determines the appropriate string to
+//               write for the "g" tag to describe a particular
+//               EggGroupNode.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+get_group_name(string &group_name, EggGroupNode *egg_group) {
+  string name = trim(egg_group->get_name());
+  if (!name.empty()) {
+    group_name += ' ';
+
+    // Remove nonstandard characters.
+    for (string::const_iterator ni = name.begin(); ni != name.end(); ++ni) {
+      char c = (*ni);
+      if (!isalnum(c)) {
+        c = '_';
+      }
+      group_name += c;
+    }
+  }
+
+  // Now recurse.
+  EggGroupNode *egg_parent = egg_group->get_parent();
+  if (egg_parent != NULL) {
+    get_group_name(group_name, egg_parent);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_vertex
+//       Access: Private
+//  Description: Adds the indicated EggVertex to the unique vertex
+//               tables, for writing later by write_vertices().
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+record_vertex(EggVertex *vertex) {
+  VertexDef &vdef = _vmap[vertex];
+
+  switch (vertex->get_num_dimensions()) {
+  case 1:
+    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos1());
+    break;
+  case 2:
+    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos2());
+    break;
+  case 3:
+    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos3());
+    break;
+  case 4:
+    vdef._vert4_index = record_unique(_unique_vert4, vertex->get_pos4());
+    break;
+  }
+
+  if (vertex->has_uv("")) {
+    vdef._uv2_index = record_unique(_unique_uv2, vertex->get_uv(""));
+  } else if (vertex->has_uvw("")) {
+    vdef._uv3_index = record_unique(_unique_uv3, vertex->get_uvw(""));
+  }
+
+  if (vertex->has_normal()) {
+    vdef._norm_index = record_unique(_unique_norm, vertex->get_normal());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, const LVecBase4d &vec) {
+  // We record a zero-based index.  Note that we will actually write
+  // out a one-based index to the obj file, as required by the
+  // standard.
+  int index = unique.size();
+  UniqueVertices::iterator ui = unique.insert(UniqueVertices::value_type(vec, index)).first;
+  return (*ui).second;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, const LVecBase3d &vec) {
+  return record_unique(unique, LVecBase4d(vec[0], vec[1], vec[2], 0.0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, const LVecBase2d &vec) {
+  return record_unique(unique, LVecBase4d(vec[0], vec[1], 0.0, 0.0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::record_unique
+//       Access: Private
+//  Description: Records the indicated vertex value, returning the
+//               shared index if this value already appears elsewhere
+//               in the table, or the new unique index if this is the
+//               first time this value appears.
+////////////////////////////////////////////////////////////////////
+int EggToObjConverter::
+record_unique(UniqueVertices &unique, double pos) {
+  return record_unique(unique, LVecBase4d(pos, 0.0, 0.0, 0.0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::write_vertices
+//       Access: Private
+//  Description: Actually writes the vertex values recorded in the
+//               indicated table to the obj output stream.
+////////////////////////////////////////////////////////////////////
+void EggToObjConverter::
+write_vertices(ostream &out, const string &prefix, int num_components, 
+               const UniqueVertices &unique) {
+  // First, sort the list into numeric order.
+  int num_vertices = (int)unique.size();
+  const LVecBase4d **vertices = (const LVecBase4d **)alloca(num_vertices * sizeof(LVecBase4d *));
+  memset(vertices, 0, num_vertices * sizeof(LVecBase4d *));
+  UniqueVertices::const_iterator ui;
+  for (ui = unique.begin(); ui != unique.end(); ++ui) {
+    int index = (*ui).second;
+    const LVecBase4d &vec = (*ui).first;
+    nassertv(index >= 0 && index < num_vertices);
+    nassertv(vertices[index] == NULL);
+    vertices[index] = &vec;
+  }
+
+  for (int i = 0; i < num_vertices; ++i) {
+    out << prefix;
+    const LVecBase4d &vec = *(vertices[i]);
+    for (int ci = 0; ci < num_components; ++ci) {
+      out << " " << vec[ci];
+    }
+    out << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggToObjConverter::VertexDef::Constructor
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+EggToObjConverter::VertexDef::
+VertexDef() :
+  _vert3_index(-1),
+  _vert4_index(-1),
+  _uv2_index(-1),
+  _uv3_index(-1),
+  _norm_index(-1)
+{
+}

+ 79 - 0
pandatool/src/objegg/eggToObjConverter.h

@@ -0,0 +1,79 @@
+// Filename: eggToObjConverter.h
+// Created by:  drose (19Dec12)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef EGGTOOBJCONVERTER_H
+#define EGGTOOBJCONVERTER_H
+
+#include "pandatoolbase.h"
+
+#include "eggToSomethingConverter.h"
+#include "eggVertexPool.h"
+#include "eggGroup.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : EggToObjConverter
+// Description : Convert an obj file to egg data.
+////////////////////////////////////////////////////////////////////
+class EggToObjConverter : public EggToSomethingConverter {
+public:
+  EggToObjConverter();
+  EggToObjConverter(const EggToObjConverter &copy);
+  ~EggToObjConverter();
+
+  virtual EggToSomethingConverter *make_copy();
+
+  virtual string get_name() const;
+  virtual string get_extension() const;
+  virtual bool supports_compressed() const;
+
+  virtual bool write_file(const Filename &filename);
+
+private:
+  typedef pmap<LVecBase4d, int> UniqueVertices;
+  class VertexDef {
+  public:
+    VertexDef();
+    int _vert3_index;
+    int _vert4_index;
+    int _uv2_index;
+    int _uv3_index;
+    int _norm_index;
+  };
+  typedef pmap<EggVertex *, VertexDef> VertexMap;
+
+  bool process(const Filename &filename);
+
+  void collect_vertices(EggNode *egg_node);
+  void write_faces(ostream &out, EggNode *egg_node);
+  void write_group_reference(ostream &out, EggNode *egg_node);
+  void get_group_name(string &group_name, EggGroupNode *egg_group);
+
+  void record_vertex(EggVertex *vertex);
+  int record_unique(UniqueVertices &unique, const LVecBase4d &vec);
+  int record_unique(UniqueVertices &unique, const LVecBase3d &vec);
+  int record_unique(UniqueVertices &unique, const LVecBase2d &vec);
+  int record_unique(UniqueVertices &unique, double pos);
+
+  void write_vertices(ostream &out, const string &prefix, int num_components, 
+                      const UniqueVertices &unique);
+
+private:
+  bool _triangulate_polygons;
+
+  UniqueVertices _unique_vert3, _unique_vert4, _unique_uv2, _unique_uv3, _unique_norm;
+  VertexMap _vmap;
+  EggGroupNode *_current_group;
+};
+
+#endif

+ 2 - 2
pandatool/src/objegg/objToEggConverter.h

@@ -12,8 +12,8 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#ifndef ObjTOEGGCONVERTER_H
-#define ObjTOEGGCONVERTER_H
+#ifndef OBJTOEGGCONVERTER_H
+#define OBJTOEGGCONVERTER_H
 
 #include "pandatoolbase.h"
 

+ 1 - 1
pandatool/src/objprogs/Sources.pp

@@ -22,7 +22,7 @@
 
 #begin bin_target
   #define TARGET egg2obj
-  #define LOCAL_LIBS p3eggbase p3progbase
+  #define LOCAL_LIBS p3objegg p3eggbase p3progbase
 
   #define SOURCES \
     eggToObj.cxx eggToObj.h

+ 3 - 306
pandatool/src/objprogs/eggToObj.cxx

@@ -60,26 +60,10 @@ run() {
     nout << "  (" << num_produced << " triangles produced.)\n";
   }
 
-  _data->flatten_transforms();
-  collect_vertices(_data);
+  EggToObjConverter saver;
+  saver.set_egg_data(_data);
 
-  ostream &out = get_output();
-  _current_group = NULL;
-
-  out << "\n#\n"
-      << "# obj file generated by the following command:\n"
-      << "# " << get_exec_command() << "\n"
-      << "#\n\n";
-
-  write_vertices(out, "v", 3, _unique_vert3);
-  write_vertices(out, "v", 4, _unique_vert4);
-  write_vertices(out, "vt", 2, _unique_uv2);
-  write_vertices(out, "vt", 3, _unique_uv3);
-  write_vertices(out, "vn", 3, _unique_norm);
-
-  write_faces(out, _data);
-
-  if (!out) {
+  if (!saver.write_file(get_output_filename())) {
     nout << "An error occurred while writing.\n";
     exit(1);
   }
@@ -98,293 +82,6 @@ handle_args(ProgramBase::Args &args) {
   return EggToSomething::handle_args(args);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::collect_vertices
-//       Access: Private
-//  Description: Recursively walks the egg structure, looking for
-//               vertices referenced by polygons.  Any such vertices
-//               are added to the vertex tables for writing to the obj
-//               file.
-////////////////////////////////////////////////////////////////////
-void EggToObj::
-collect_vertices(EggNode *egg_node) {
-  if (egg_node->is_of_type(EggPolygon::get_class_type())) {
-    EggPolygon *egg_poly = DCAST(EggPolygon, egg_node);
-    EggPolygon::iterator pi;
-    for (pi = egg_poly->begin(); pi != egg_poly->end(); ++pi) {
-      record_vertex(*pi);
-    }
-
-  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
-    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
-
-    EggGroupNode::iterator ci;
-    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
-      collect_vertices(*ci);
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::write_faces
-//       Access: Private
-//  Description: Recursively walks the egg structure again, this time
-//               writing out the face records for any polygons
-//               encountered.
-////////////////////////////////////////////////////////////////////
-void EggToObj::
-write_faces(ostream &out, EggNode *egg_node) {
-  if (egg_node->is_of_type(EggPolygon::get_class_type())) {
-    write_group_reference(out, egg_node);
-
-    EggPolygon *egg_poly = DCAST(EggPolygon, egg_node);
-
-    out << "f";
-    EggPolygon::iterator pi;
-    for (pi = egg_poly->begin(); pi != egg_poly->end(); ++pi) {
-      VertexDef &vdef = _vmap[(*pi)];
-      int vert_index = -1;
-      int uv_index = -1;
-      int norm_index = -1;
-
-      if (vdef._vert3_index != -1) {
-        vert_index = vdef._vert3_index + 1;
-      } else if (vdef._vert4_index != -1) {
-        vert_index = vdef._vert4_index + 1 + (int)_unique_vert3.size();
-      }
-
-      if (vdef._uv2_index != -1) {
-        uv_index = vdef._uv2_index + 1;
-      } else if (vdef._uv3_index != -1) {
-        uv_index = vdef._uv3_index + 1 + (int)_unique_uv2.size();
-      }
-
-      if (vdef._norm_index != -1) {
-        norm_index = vdef._norm_index + 1;
-      }
-
-      if (vert_index == -1) {
-        continue;
-      }
-
-      if (norm_index != -1) {
-        if (uv_index != -1) {
-          out << " " << vert_index << "/" << uv_index << "/" << norm_index;
-        } else {
-          out << " " << vert_index << "//" << norm_index;
-        }
-      } else if (uv_index != -1) {
-        out << " " << vert_index << "/" << uv_index;
-      } else {
-        out << " " << vert_index;
-      }
-    }
-    out << "\n";
-
-  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
-    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
-
-    EggGroupNode::iterator ci;
-    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
-      write_faces(out, *ci);
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::write_group_reference
-//       Access: Private
-//  Description: Writes the "g" tag to describe this polygon's group,
-//               if needed.
-////////////////////////////////////////////////////////////////////
-void EggToObj::
-write_group_reference(ostream &out, EggNode *egg_node) {
-  EggGroupNode *egg_group = egg_node->get_parent();
-  if (egg_group == _current_group) {
-    // Same group we wrote last time.
-    return;
-  }
-
-  string group_name;
-  get_group_name(group_name, egg_group);
-  if (group_name.empty()) {
-    out << "g default\n";
-  } else {
-    out << "g" << group_name << "\n";
-  }
-  _current_group = egg_group;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::get_group_name
-//       Access: Private
-//  Description: Recursively determines the appropriate string to
-//               write for the "g" tag to describe a particular
-//               EggGroupNode.
-////////////////////////////////////////////////////////////////////
-void EggToObj::
-get_group_name(string &group_name, EggGroupNode *egg_group) {
-  string name = trim(egg_group->get_name());
-  if (!name.empty()) {
-    group_name += ' ';
-
-    // Remove nonstandard characters.
-    for (string::const_iterator ni = name.begin(); ni != name.end(); ++ni) {
-      char c = (*ni);
-      if (!isalnum(c)) {
-        c = '_';
-      }
-      group_name += c;
-    }
-  }
-
-  // Now recurse.
-  EggGroupNode *egg_parent = egg_group->get_parent();
-  if (egg_parent != NULL) {
-    get_group_name(group_name, egg_parent);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::record_vertex
-//       Access: Private
-//  Description: Adds the indicated EggVertex to the unique vertex
-//               tables, for writing later by write_vertices().
-////////////////////////////////////////////////////////////////////
-void EggToObj::
-record_vertex(EggVertex *vertex) {
-  VertexDef &vdef = _vmap[vertex];
-
-  switch (vertex->get_num_dimensions()) {
-  case 1:
-    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos1());
-    break;
-  case 2:
-    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos2());
-    break;
-  case 3:
-    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos3());
-    break;
-  case 4:
-    vdef._vert4_index = record_unique(_unique_vert4, vertex->get_pos4());
-    break;
-  }
-
-  if (vertex->has_uv("")) {
-    vdef._uv2_index = record_unique(_unique_uv2, vertex->get_uv(""));
-  } else if (vertex->has_uvw("")) {
-    vdef._uv3_index = record_unique(_unique_uv3, vertex->get_uvw(""));
-  }
-
-  if (vertex->has_normal()) {
-    vdef._norm_index = record_unique(_unique_norm, vertex->get_normal());
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObj::
-record_unique(UniqueVertices &unique, const LVecBase4d &vec) {
-  // We record a zero-based index.  Note that we will actually write
-  // out a one-based index to the obj file, as required by the
-  // standard.
-  int index = unique.size();
-  UniqueVertices::iterator ui = unique.insert(UniqueVertices::value_type(vec, index)).first;
-  return (*ui).second;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObj::
-record_unique(UniqueVertices &unique, const LVecBase3d &vec) {
-  return record_unique(unique, LVecBase4d(vec[0], vec[1], vec[2], 0.0));
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObj::
-record_unique(UniqueVertices &unique, const LVecBase2d &vec) {
-  return record_unique(unique, LVecBase4d(vec[0], vec[1], 0.0, 0.0));
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::record_unique
-//       Access: Private
-//  Description: Records the indicated vertex value, returning the
-//               shared index if this value already appears elsewhere
-//               in the table, or the new unique index if this is the
-//               first time this value appears.
-////////////////////////////////////////////////////////////////////
-int EggToObj::
-record_unique(UniqueVertices &unique, double pos) {
-  return record_unique(unique, LVecBase4d(pos, 0.0, 0.0, 0.0));
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::write_vertices
-//       Access: Private
-//  Description: Actually writes the vertex values recorded in the
-//               indicated table to the obj output stream.
-////////////////////////////////////////////////////////////////////
-void EggToObj::
-write_vertices(ostream &out, const string &prefix, int num_components, 
-               const UniqueVertices &unique) {
-  // First, sort the list into numeric order.
-  int num_vertices = (int)unique.size();
-  const LVecBase4d **vertices = (const LVecBase4d **)alloca(num_vertices * sizeof(LVecBase4d *));
-  memset(vertices, 0, num_vertices * sizeof(LVecBase4d *));
-  UniqueVertices::const_iterator ui;
-  for (ui = unique.begin(); ui != unique.end(); ++ui) {
-    int index = (*ui).second;
-    const LVecBase4d &vec = (*ui).first;
-    nassertv(index >= 0 && index < num_vertices);
-    nassertv(vertices[index] == NULL);
-    vertices[index] = &vec;
-  }
-
-  for (int i = 0; i < num_vertices; ++i) {
-    out << prefix;
-    const LVecBase4d &vec = *(vertices[i]);
-    for (int ci = 0; ci < num_components; ++ci) {
-      out << " " << vec[ci];
-    }
-    out << "\n";
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggToObj::VertexDef::Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-EggToObj::VertexDef::
-VertexDef() :
-  _vert3_index(-1),
-  _vert4_index(-1),
-  _uv2_index(-1),
-  _uv3_index(-1),
-  _norm_index(-1)
-{
-}
-
 int main(int argc, char *argv[]) {
   // A call to pystub() to force libpystub.so to be linked in.
   pystub();

+ 1 - 35
pandatool/src/objprogs/eggToObj.h

@@ -17,10 +17,7 @@
 
 #include "pandatoolbase.h"
 #include "eggToSomething.h"
-#include "pmap.h"
-
-class EggNode;
-class EggVertex;
+#include "eggToObjConverter.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : EggToObj
@@ -35,39 +32,8 @@ public:
 protected:
   virtual bool handle_args(Args &args);
 
-private:
-  typedef pmap<LVecBase4d, int> UniqueVertices;
-  class VertexDef {
-  public:
-    VertexDef();
-    int _vert3_index;
-    int _vert4_index;
-    int _uv2_index;
-    int _uv3_index;
-    int _norm_index;
-  };
-  typedef pmap<EggVertex *, VertexDef> VertexMap;
-
-  void collect_vertices(EggNode *egg_node);
-  void write_faces(ostream &out, EggNode *egg_node);
-  void write_group_reference(ostream &out, EggNode *egg_node);
-  void get_group_name(string &group_name, EggGroupNode *egg_group);
-
-  void record_vertex(EggVertex *vertex);
-  int record_unique(UniqueVertices &unique, const LVecBase4d &vec);
-  int record_unique(UniqueVertices &unique, const LVecBase3d &vec);
-  int record_unique(UniqueVertices &unique, const LVecBase2d &vec);
-  int record_unique(UniqueVertices &unique, double pos);
-
-  void write_vertices(ostream &out, const string &prefix, int num_components, 
-                      const UniqueVertices &unique);
-
 private:
   bool _triangulate_polygons;
-
-  UniqueVertices _unique_vert3, _unique_vert4, _unique_uv2, _unique_uv3, _unique_norm;
-  VertexMap _vmap;
-  EggGroupNode *_current_group;
 };
 
 #endif

+ 4 - 2
pandatool/src/ptloader/config_ptloader.cxx

@@ -28,6 +28,7 @@
 #include "dxfToEggConverter.h"
 #include "vrmlToEggConverter.h"
 #include "objToEggConverter.h"
+#include "eggToObjConverter.h"
 #include "config_xfile.h"
 #include "xFileToEggConverter.h"
 
@@ -91,8 +92,9 @@ init_libptloader() {
   XFileToEggConverter *xfile = new XFileToEggConverter;
   reg->register_type(new LoaderFileTypePandatool(xfile));
 
-  ObjToEggConverter *obj = new ObjToEggConverter;
-  reg->register_type(new LoaderFileTypePandatool(obj));
+  ObjToEggConverter *obj_egg = new ObjToEggConverter;
+  EggToObjConverter *egg_obj = new EggToObjConverter;
+  reg->register_type(new LoaderFileTypePandatool(obj_egg, egg_obj));
 
 //#ifdef HAVE_FCOLLADA
 //  DAEToEggConverter *dae = new DAEToEggConverter;

+ 87 - 15
pandatool/src/ptloader/loaderFileTypePandatool.cxx

@@ -15,8 +15,10 @@
 #include "loaderFileTypePandatool.h"
 #include "config_ptloader.h"
 #include "somethingToEggConverter.h"
+#include "eggToSomethingConverter.h"
 #include "config_util.h"
 #include "load_egg_file.h"
+#include "save_egg_file.h"
 #include "eggData.h"
 #include "loaderOptions.h"
 #include "bamCacheRecord.h"
@@ -29,10 +31,13 @@ TypeHandle LoaderFileTypePandatool::_type_handle;
 //  Description:
 ////////////////////////////////////////////////////////////////////
 LoaderFileTypePandatool::
-LoaderFileTypePandatool(SomethingToEggConverter *converter) :
-  _converter(converter)
+LoaderFileTypePandatool(SomethingToEggConverter *loader,
+                        EggToSomethingConverter *saver) :
+  _loader(loader), _saver(saver)
 {
-  converter->set_merge_externals(true);
+  if (_loader != (SomethingToEggConverter *)NULL) {
+    _loader->set_merge_externals(true);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -51,7 +56,10 @@ LoaderFileTypePandatool::
 ////////////////////////////////////////////////////////////////////
 string LoaderFileTypePandatool::
 get_name() const {
-  return _converter->get_name();
+  if (_loader != (SomethingToEggConverter *)NULL) {
+    return _loader->get_name();
+  }
+  return _saver->get_name();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -61,7 +69,10 @@ get_name() const {
 ////////////////////////////////////////////////////////////////////
 string LoaderFileTypePandatool::
 get_extension() const {
-  return _converter->get_extension();
+  if (_loader != (SomethingToEggConverter *)NULL) {
+    return _loader->get_extension();
+  }
+  return _saver->get_extension();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -73,7 +84,10 @@ get_extension() const {
 ////////////////////////////////////////////////////////////////////
 string LoaderFileTypePandatool::
 get_additional_extensions() const {
-  return _converter->get_additional_extensions();
+  if (_loader != (SomethingToEggConverter *)NULL) {
+    return _loader->get_additional_extensions();
+  }
+  return _saver->get_additional_extensions();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -85,7 +99,34 @@ get_additional_extensions() const {
 ////////////////////////////////////////////////////////////////////
 bool LoaderFileTypePandatool::
 supports_compressed() const {
-  return _converter->supports_compressed();
+  if (_loader != (SomethingToEggConverter *)NULL) {
+    return _loader->supports_compressed();
+  }
+  return _saver->supports_compressed();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypePandatool::supports_load
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to load
+//               files, and load_file() is supported.  Returns false
+//               if load_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypePandatool::
+supports_load() const {
+  return (_loader != NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypePandatool::supports_save
+//       Access: Published, Virtual
+//  Description: Returns true if the file type can be used to save
+//               files, and save_file() is supported.  Returns false
+//               if save_file() is unimplemented and will always fail.
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypePandatool::
+supports_save() const {
+  return (_saver != NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -108,39 +149,44 @@ resolve_filename(Filename &path) const {
 PT(PandaNode) LoaderFileTypePandatool::
 load_file(const Filename &path, const LoaderOptions &options,
           BamCacheRecord *record) const {
+  if (_loader == NULL) {
+    return NULL;
+  }
+
   if (record != (BamCacheRecord *)NULL) {
     record->add_dependent_file(path);
   }
 
   PT(PandaNode) result;
 
+  SomethingToEggConverter *loader = _loader->make_copy();
   PT(EggData) egg_data = new EggData;
-  _converter->set_egg_data(egg_data);
+  loader->set_egg_data(egg_data);
 
   DSearchPath file_path;
   file_path.append_directory(path.get_dirname());
-  _converter->get_path_replace()->_path = file_path;
+  loader->get_path_replace()->_path = file_path;
 
   // Convert animation, if the converter supports it.
   switch (options.get_flags() & LoaderOptions::LF_convert_anim) {
   case LoaderOptions::LF_convert_anim:
-    _converter->set_animation_convert(AC_both);
+    loader->set_animation_convert(AC_both);
     break;
     
   case LoaderOptions::LF_convert_skeleton:
-    _converter->set_animation_convert(AC_model);
+    loader->set_animation_convert(AC_model);
     break;
     
   case LoaderOptions::LF_convert_channels:
-    _converter->set_animation_convert(AC_chan);
+    loader->set_animation_convert(AC_chan);
     break;
 
   default:
     break;
   }
 
-  if (_converter->convert_file(path)) {
-    DistanceUnit input_units = _converter->get_input_units();
+  if (loader->convert_file(path)) {
+    DistanceUnit input_units = loader->get_input_units();
     if (input_units != DU_invalid && ptloader_units != DU_invalid && 
         input_units != ptloader_units) {
       // Convert the file to the units specified by the ptloader-units
@@ -160,6 +206,32 @@ load_file(const Filename &path, const LoaderOptions &options,
 
     result = load_egg_data(egg_data);
   }
-  _converter->clear_egg_data();
+  delete loader;
+
   return result.p();
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: LoaderFileTypePandatool::save_file
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool LoaderFileTypePandatool::
+save_file(const Filename &path, const LoaderOptions &options,
+          PandaNode *node) const {
+  if (_saver == NULL) {
+    return false;
+  }
+
+  PT(EggData) egg_data = new EggData;
+  if (!save_egg_data(egg_data, node)) {
+    return false;
+  }
+
+  EggToSomethingConverter *saver = _saver->make_copy();
+  saver->set_egg_data(egg_data);
+
+  bool result = saver->write_file(path);
+
+  delete saver;
+}

+ 10 - 2
pandatool/src/ptloader/loaderFileTypePandatool.h

@@ -20,6 +20,7 @@
 #include "loaderFileType.h"
 
 class SomethingToEggConverter;
+class EggToSomethingConverter;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : LoaderFileTypePandatool
@@ -30,7 +31,8 @@ class SomethingToEggConverter;
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PTLOADER LoaderFileTypePandatool : public LoaderFileType {
 public:
-  LoaderFileTypePandatool(SomethingToEggConverter *converter);
+  LoaderFileTypePandatool(SomethingToEggConverter *loader,
+                          EggToSomethingConverter *saver = NULL);
   virtual ~LoaderFileTypePandatool();
 
   virtual string get_name() const;
@@ -38,12 +40,18 @@ public:
   virtual string get_additional_extensions() const;
   virtual bool supports_compressed() const;
 
+  virtual bool supports_load() const;
+  virtual bool supports_save() const;
+
   virtual void resolve_filename(Filename &path) const;
   virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &options,
                                   BamCacheRecord *record) const;
+  virtual bool save_file(const Filename &path, const LoaderOptions &options,
+                         PandaNode *node) const;
 
 private:
-  SomethingToEggConverter *_converter;
+  SomethingToEggConverter *_loader;
+  EggToSomethingConverter *_saver;
 
 public:
   static TypeHandle get_class_type() {