ソースを参照

Repaired some damaged caused by license change

Josh Yelon 17 年 前
コミット
c1f4a937b6

+ 216 - 0
pandatool/src/maxegg/maxNodeDesc.cxx

@@ -4,3 +4,219 @@
 //
 ////////////////////////////////////////////////////////////////////
 //
+// 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 "maxNodeDesc.h"
+#include "maxLogger.h"
+#define MTEC Logger::ST_MAP_ME_TO_APP_SPECIFIC_SYSTEM4
+
+TypeHandle MaxNodeDesc::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc::
+MaxNodeDesc(MaxNodeDesc *parent, const string &name) :
+  Namable(name),
+  _parent(parent)
+{
+  _max_node = (INode *)NULL;
+  _egg_group = (EggGroup *)NULL;
+  _egg_table = (EggTable *)NULL;
+  _anim = (EggXfmSAnim *)NULL;
+  _joint_type = JT_none;
+  _joint_entry = NULL;
+
+  // Add ourselves to our parent.
+  if (_parent != (MaxNodeDesc *)NULL) {
+    _parent->_children.push_back(this);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc::
+~MaxNodeDesc() {}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::from_INode
+//       Access: Public
+//  Description: Indicates an associated between the MaxNodeDesc and
+//               some Max Node instance.
+////////////////////////////////////////////////////////////////////
+void MaxNodeDesc::
+from_INode(INode *max_node) {
+  if (_max_node == (INode *)NULL) {
+    _max_node = max_node;
+
+    // This is how I decided to check to see if this max node is a 
+    // joint.  It works in all instances I've seen so far, but this 
+    // may be a good starting place to look if joints are not being
+    // picked up correctly in the future.
+
+    //Check to see if the node's controller is a biped
+    //If so treat it as a joint
+    // Get the node's transform control
+    Control *c = max_node->GetTMController();
+    if (_max_node->GetBoneNodeOnOff() ||
+        (c && //c exists and it's type is a biped
+        ((c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) ||
+         (c->ClassID() == BIPBODY_CONTROL_CLASS_ID) ||
+         (c->ClassID() == FOOTPRINT_CLASS_ID)))) {
+      Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Found a joint." );
+      
+      // This node is a joint.
+      _joint_type = JT_node_joint;
+      if (_parent != (MaxNodeDesc *)NULL) {
+        _parent->mark_joint_parent();
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::has_max_node
+//       Access: Public
+//  Description: Returns true if a Max INode has been associated
+//               with this node, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool MaxNodeDesc::
+has_max_node() const {
+  return (_max_node != (INode *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::get_max_node
+//       Access: Public
+//  Description: Returns the INode associated with this node.  It
+//               is an error to call this unless has_max_node()
+//               returned true.
+////////////////////////////////////////////////////////////////////
+INode *MaxNodeDesc::
+get_max_node() const {
+  nassertr(_max_node != (INode *)NULL, _max_node);
+  return _max_node;
+}
+
+
+void MaxNodeDesc::
+set_joint(bool onoff) {
+  if (onoff)
+    _joint_type = JT_joint;
+  else
+    _joint_type = JT_none;
+}
+    
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::is_joint
+//       Access: Private
+//  Description: Returns true if the node should be treated as a joint
+//               by the converter.
+////////////////////////////////////////////////////////////////////
+bool MaxNodeDesc::
+is_joint() const {
+  return _joint_type == JT_joint || _joint_type == JT_pseudo_joint;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::is_joint_parent
+//       Access: Private
+//  Description: Returns true if the node is the parent or ancestor of
+//               a joint.
+////////////////////////////////////////////////////////////////////
+bool MaxNodeDesc::
+is_joint_parent() const {
+  return _joint_type == JT_joint_parent;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::is_joint_parent
+//       Access: Private
+//  Description: Returns true if the node is the parent or ancestor of
+//               a joint.
+////////////////////////////////////////////////////////////////////
+bool MaxNodeDesc::
+is_node_joint() const {
+  return _joint_type == JT_node_joint;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::clear_egg
+//       Access: Private
+//  Description: Recursively clears the egg pointers from this node
+//               and all children.
+////////////////////////////////////////////////////////////////////
+void MaxNodeDesc::
+clear_egg() {
+  _egg_group = (EggGroup *)NULL;
+  _egg_table = (EggTable *)NULL;
+  _anim = (EggXfmSAnim *)NULL;
+
+  Children::const_iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    MaxNodeDesc *child = (*ci);
+    child->clear_egg();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::mark_joint_parent
+//       Access: Private
+//  Description: Indicates that this node has at least one child that
+//               is a joint or a pseudo-joint.
+////////////////////////////////////////////////////////////////////
+void MaxNodeDesc::
+mark_joint_parent() {
+  if (_joint_type == JT_none) {
+    _joint_type = JT_joint_parent;
+    if (_parent != (MaxNodeDesc *)NULL) {
+      _parent->mark_joint_parent();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeDesc::check_pseudo_joints
+//       Access: Private
+//  Description: Walks the hierarchy, looking for non-joint nodes that
+//               are both children and parents of a joint.  These
+//               nodes are deemed to be pseudo joints, since the
+//               converter must treat them as joints.
+////////////////////////////////////////////////////////////////////
+void MaxNodeDesc::
+check_pseudo_joints(bool joint_above) {
+  if (_joint_type == JT_joint_parent && joint_above) {
+    // This is one such node: it is the parent of a joint
+    // (JT_joint_parent is set), and it is the child of a joint
+    // (joint_above is set).
+    _joint_type = JT_pseudo_joint;
+  }
+
+  if (_joint_type == JT_joint) {
+    // If this node is itself a joint, then joint_above is true for
+    // all child nodes.
+    joint_above = true;
+  }
+
+  // Don't bother traversing further if _joint_type is none, since
+  // that means this node has no joint children.
+  if (_joint_type != JT_none) {
+    Children::const_iterator ci;
+    for (ci = _children.begin(); ci != _children.end(); ++ci) {
+      MaxNodeDesc *child = (*ci);
+      child->check_pseudo_joints(joint_above);
+    }
+  }
+}

+ 105 - 0
pandatool/src/maxegg/maxNodeDesc.h

@@ -4,3 +4,108 @@
 //
 ////////////////////////////////////////////////////////////////////
 //
+// 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 MAXNODEDESC_H
+#define MAXNODEDESC_H
+
+#pragma conform(forScope, off)
+
+#include "pandatoolbase.h"
+
+#include "referenceCount.h"
+#include "pointerTo.h"
+#include "namable.h"
+
+#ifdef MAX5
+//Disable the "Too many actual parameters in istdplug.h" warning in Max5
+#pragma warning(push)
+#pragma warning(disable: 4002)
+#include "max_pre_include.h"
+#endif
+#include <Max.h>
+#include "bipexp.h"
+#ifdef MAX5
+#include "max_post_include.h"
+#pragma warning(pop)
+#endif
+
+class EggGroup;
+class EggTable;
+class EggXfmSAnim;
+
+////////////////////////////////////////////////////////////////////
+//       Class : MaxNodeDesc
+// Description : Describes a single instance of a node in the Max
+//               scene graph, relating it to the corresponding egg
+//               structures (e.g. node, group, or table entry) that
+//               will be created.
+////////////////////////////////////////////////////////////////////
+class MaxNodeDesc : public ReferenceCount, public Namable {
+ public:
+  MaxNodeDesc(MaxNodeDesc *parent = NULL, const string &name = string());
+  ~MaxNodeDesc();
+
+  void from_INode(INode *max_node);
+  bool has_max_node() const;
+  INode *get_max_node() const;
+
+  void set_joint(bool onoff);
+  bool is_joint() const;
+  bool is_joint_parent() const;
+  bool is_node_joint() const;
+
+  MaxNodeDesc *_parent;
+  MaxNodeDesc *_joint_entry;
+  typedef pvector< MaxNodeDesc* > Children;
+  Children _children;
+  
+ private:
+  void clear_egg();
+  void mark_joint_parent();
+  void check_pseudo_joints(bool joint_above);
+
+  INode *_max_node;
+
+  EggGroup *_egg_group;
+  EggTable *_egg_table;
+  EggXfmSAnim *_anim;
+
+  enum JointType {
+    JT_none,         // Not a joint.
+    JT_node_joint,    // Node that represents a joint in the geometry 
+                                         // but not the actual joint itself
+        JT_joint,        // An actual joint in Max.
+    JT_pseudo_joint, // Not a joint in Max, but treated just like a
+                     // joint for the purposes of the converter.
+    JT_joint_parent, // A parent or ancestor of a joint or pseudo joint.
+  };
+  JointType _joint_type;
+
+
+ public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ReferenceCount::init_type();
+    Namable::init_type();
+    register_type(_type_handle, "MaxNodeDesc",
+                  ReferenceCount::get_class_type(),
+                  Namable::get_class_type());
+  }
+
+ private:
+  static TypeHandle _type_handle;
+
+  friend class MaxNodeTree;
+};
+
+#endif

+ 485 - 0
pandatool/src/maxegg/maxNodeTree.cxx

@@ -4,3 +4,488 @@
 //
 ////////////////////////////////////////////////////////////////////
 //
+// 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 "maxNodeTree.h"
+#include "eggGroup.h"
+#include "eggTable.h"
+#include "eggXfmSAnim.h"
+#include "eggData.h"
+
+#ifdef MAX5
+//Disable the "Too many actual parameters in istdplug.h" warning in Max5
+#pragma warning(push)
+#pragma warning(disable: 4002)
+#include "max_pre_include.h"
+#endif
+#include "Max.h"
+#ifdef MAX5
+#include "max_post_include.h"
+#pragma warning(pop)
+#endif
+#include "maxToEggConverter.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MaxNodeTree::
+MaxNodeTree() {
+  _root = new MaxNodeDesc;
+  _fps = 0.0;
+  _egg_data = (EggData *)NULL;
+  _egg_root = (EggGroupNode *)NULL;
+  _skeleton_node = (EggGroupNode *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::build_node
+//       Access: Public
+//  Description: Returns a pointer to the node corresponding to the
+//               indicated INode object, creating it first if
+//               necessary.
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc *MaxNodeTree::
+build_node(INode *max_node) {
+  MaxNodeDesc *node_desc = r_build_node(max_node);
+  node_desc->from_INode(max_node);
+  if (node_desc->is_node_joint())
+    node_desc->_joint_entry = build_joint(max_node, node_desc);
+  return node_desc;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::build_node
+//       Access: Public
+//  Description: Returns a pointer to the node corresponding to the
+//               indicated INode object, creating it first if
+//               necessary.
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc *MaxNodeTree::
+build_joint(INode *max_node, MaxNodeDesc *node_joint) {
+  MaxNodeDesc *node_desc = r_build_joint(node_joint, max_node);
+  node_desc->from_INode(max_node);
+  node_desc->set_joint(true);  
+  return node_desc;
+}
+
+bool MaxNodeTree::node_in_list(ULONG handle, ULONG *list, int len) {
+  if (!list) return true;
+  for (int i = 0; i < len; i++)
+    if (list[i] == handle) return true;
+  return false;
+}
+
+bool MaxNodeTree::is_joint(INode *node) {
+  Control *c = node->GetTMController();
+  return (node->GetBoneNodeOnOff() ||                    //joints
+         (c &&                                           //bipeds
+         ((c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) ||
+         (c->ClassID() == BIPBODY_CONTROL_CLASS_ID) ||
+         (c->ClassID() == FOOTPRINT_CLASS_ID))));
+}
+
+bool MaxNodeTree::
+r_build_hierarchy(INode *root, ULONG *selection_list, int len) {
+  if (node_in_list(root->GetHandle(), selection_list, len))
+    build_node(root);
+  // Export children
+  for ( int i = 0; i < root->NumberOfChildren(); i++ ) {
+    // *** Should probably be checking the return value of the following line
+    r_build_hierarchy(root->GetChildNode(i), selection_list, len);
+  }
+  return true;
+}
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::build_complete_hierarchy
+//       Access: Public
+//  Description: Walks through the complete Max hierarchy and builds
+//               up the corresponding tree.
+////////////////////////////////////////////////////////////////////
+bool MaxNodeTree::
+build_complete_hierarchy(INode *root, ULONG *selection_list, int len) {
+
+  // Get the entire Max scene.
+  if (root == NULL) {
+    // *** Log an error
+    return false;
+  }
+    
+  bool all_ok = true;
+  r_build_hierarchy(root, selection_list, len);
+
+  if (all_ok) {
+    _root->check_pseudo_joints(false);
+  }
+
+  Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, 
+               "finished building complete hierarchy" );
+  
+  return all_ok;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::build_selected_hierarchy
+//       Access: Public
+//  Description: Walks through the selected subset of the Max
+//               hierarchy (or the complete hierarchy, if nothing is
+//               selected) and builds up the corresponding tree.
+////////////////////////////////////////////////////////////////////
+bool MaxNodeTree::
+build_selected_hierarchy(INode *root) {
+  // *** Write this later when it's time to do selection
+  /*
+  MStatus status;
+
+  MItDag dag_iterator(MItDag::kDepthFirst, MFn::kTransform, &status);
+  if (!status) {
+    status.perror("MItDag constructor");
+    return false;
+  }
+
+  // Get only the selected geometry.
+  MSelectionList selection;
+  status = MGlobal::getActiveSelectionList(selection);
+  if (!status) {
+    status.perror("MGlobal::getActiveSelectionList");
+    return false;
+  }
+  
+  // Get the selected geometry only if the selection is nonempty;
+  // otherwise, get the whole scene anyway.
+  if (selection.isEmpty()) {
+    mayaegg_cat.info()
+      << "Selection list is empty.\n";
+    return build_complete_hierarchy();
+  }
+  */
+  bool all_ok = true;
+  /*
+  unsigned int length = selection.length();
+  for (unsigned int i = 0; i < length; i++) {
+    MDagPath root_path;
+    status = selection.getDagPath(i, root_path);
+    if (!status) {
+      status.perror("MSelectionList::getDagPath");
+    } else {
+      // Now traverse through the selected dag path and all nested
+      // dag paths.
+      dag_iterator.reset(root_path);
+      while (!dag_iterator.isDone()) {
+        MDagPath dag_path;
+        status = dag_iterator.getPath(dag_path);
+        if (!status) {
+          status.perror("MItDag::getPath");
+        } else {
+          build_node(dag_path);
+        }
+        
+        dag_iterator.next();
+      }
+    }
+  }
+
+  if (all_ok) {
+    _root->check_pseudo_joints(false);
+  }
+  */
+  return all_ok;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::get_num_nodes
+//       Access: Public
+//  Description: Returns the total number of nodes in the hierarchy,
+//               not counting the root node.
+////////////////////////////////////////////////////////////////////
+int MaxNodeTree::
+get_num_nodes() const {
+  return _nodes.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::get_node
+//       Access: Public
+//  Description: Returns the nth node in the hierarchy, in an
+//               arbitrary ordering.
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc *MaxNodeTree::
+get_node(int n) const {
+  nassertr(n >= 0 && n < (int)_nodes.size(), NULL);
+  return _nodes[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::clear_egg
+//       Access: Public
+//  Description: Removes all of the references to generated egg
+//               structures from the tree, and prepares the tree for
+//               generating new egg structures.
+////////////////////////////////////////////////////////////////////
+void MaxNodeTree::
+clear_egg(EggData *egg_data, EggGroupNode *egg_root, 
+          EggGroupNode *skeleton_node) {
+  _root->clear_egg();
+  _egg_data = egg_data;
+  _egg_root = egg_root;
+  _skeleton_node = skeleton_node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::get_egg_group
+//       Access: Public
+//  Description: Returns the EggGroupNode corresponding to the group
+//               or joint for the indicated node.  Creates the group
+//               node if it has not already been created.
+////////////////////////////////////////////////////////////////////
+EggGroup *MaxNodeTree::
+get_egg_group(MaxNodeDesc *node_desc) {
+  nassertr(_egg_root != (EggGroupNode *)NULL, NULL);
+
+  if (node_desc->_egg_group == (EggGroup *)NULL) {
+    // We need to make a new group node.
+    EggGroup *egg_group;
+
+    nassertr(node_desc->_parent != (MaxNodeDesc *)NULL, NULL);
+    egg_group = new EggGroup(node_desc->get_name());
+    if (node_desc->is_joint()) {
+      egg_group->set_group_type(EggGroup::GT_joint);
+    }
+
+    if (node_desc->_parent == _root) {
+      // The parent is the root.
+      _egg_root->add_child(egg_group);
+
+    } else {
+      // The parent is another node.
+      EggGroup *parent_egg_group = get_egg_group(node_desc->_parent);
+      parent_egg_group->add_child(egg_group);
+    }
+
+    // *** This is probably something that a Max plugin would need to be 
+    //     written for.  May want to ask Disney about it
+    /*
+    if (node_desc->has_dag_path()) {
+      // Check for an object type setting, from Oliver's plug-in.
+      MObject dag_object = node_desc->get_dag_path().node();
+      string object_type;
+      if (get_enum_attribute(dag_object, "eggObjectTypes1", object_type)) {
+        egg_group->add_object_type(object_type);
+      }
+      if (get_enum_attribute(dag_object, "eggObjectTypes2", object_type)) {
+        egg_group->add_object_type(object_type);
+      }
+      if (get_enum_attribute(dag_object, "eggObjectTypes3", object_type)) {
+        egg_group->add_object_type(object_type);
+      }
+
+      // We treat the object type "billboard" as a special case: we
+      // apply this one right away and also flag the group as an
+      // instance.
+      if (egg_group->has_object_type("billboard")) {    
+        egg_group->remove_object_type("billboard");
+        egg_group->set_group_type(EggGroup::GT_instance);
+        egg_group->set_billboard_type(EggGroup::BT_axis);
+        
+      } else if (egg_group->has_object_type("billboard-point")) {    
+        egg_group->remove_object_type("billboard-point");
+        egg_group->set_group_type(EggGroup::GT_instance);
+        egg_group->set_billboard_type(EggGroup::BT_point_camera_relative);
+      }
+      
+      // We also treat the object type "dcs" and "model" as a special
+      // case, so we can test for these flags later.
+      if (egg_group->has_object_type("dcs")) {
+        egg_group->remove_object_type("dcs");
+        egg_group->set_dcs_type(EggGroup::DC_default);
+      }
+      if (egg_group->has_object_type("model")) {
+        egg_group->remove_object_type("model");
+        egg_group->set_model_flag(true);
+      }
+      
+      // And "vertex-color" has meaning only to this converter.
+      if (egg_group->has_object_type("vertex-color")) {
+        egg_group->remove_object_type("vertex-color");
+        MaxEggGroupUserData *user_data = new MaxEggGroupUserData;
+        user_data->_vertex_color = true;
+        egg_group->set_user_data(user_data);
+      }
+    }
+    */
+    node_desc->_egg_group = egg_group;
+  }
+
+  return node_desc->_egg_group;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::get_egg_table
+//       Access: Public
+//  Description: Returns the EggTable corresponding to the joint
+//               for the indicated node.  Creates the table node if it
+//               has not already been created.
+////////////////////////////////////////////////////////////////////
+EggTable *MaxNodeTree::
+get_egg_table(MaxNodeDesc *node_desc) {
+  nassertr(_skeleton_node != (EggGroupNode *)NULL, NULL);
+  nassertr(node_desc->is_joint(), NULL);
+
+  if (node_desc->_egg_table == (EggTable *)NULL) {
+    // We need to make a new table node.
+    nassertr(node_desc->_parent != (MaxNodeDesc *)NULL, NULL);
+
+    EggTable *egg_table = new EggTable(node_desc->get_name());
+    node_desc->_anim = new EggXfmSAnim("xform", 
+                                       _egg_data->get_coordinate_system());
+    node_desc->_anim->set_fps(_fps);
+    egg_table->add_child(node_desc->_anim);
+
+    if (!node_desc->_parent->is_joint()) {
+      // The parent is not a joint; put it at the top.
+      _skeleton_node->add_child(egg_table);
+
+    } else {
+      // The parent is another joint.
+      EggTable *parent_egg_table = get_egg_table(node_desc->_parent);
+      parent_egg_table->add_child(egg_table);
+    }
+
+    node_desc->_egg_table = egg_table;
+  }
+
+  return node_desc->_egg_table;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::get_egg_anim
+//       Access: Public
+//  Description: Returns the anim table corresponding to the joint
+//               for the indicated node.  Creates the table node if it
+//               has not already been created.
+////////////////////////////////////////////////////////////////////
+EggXfmSAnim *MaxNodeTree::
+get_egg_anim(MaxNodeDesc *node_desc) 
+{
+  get_egg_table(node_desc);
+  return node_desc->_anim;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::r_build_node
+//       Access: Private
+//  Description: The recursive implementation of build_node().
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc *MaxNodeTree::
+r_build_node(INode* max_node) 
+{
+  // If we have already encountered this pathname, return the
+  // corresponding MaxNodeDesc immediately.
+  
+  ULONG node_handle = 0;
+  
+  if (max_node) {
+    node_handle = max_node->GetHandle();
+  }    
+
+  NodesByPath::const_iterator ni = _nodes_by_path.find(node_handle);
+  if (ni != _nodes_by_path.end()) {
+    return (*ni).second;
+  }
+
+  // Otherwise, we have to create it.  Do this recursively, so we
+  // create each node along the path.
+  MaxNodeDesc *node_desc;
+
+  if (!max_node) {
+    // This is the top.
+    node_desc = _root;
+
+  } else {
+    INode *parent_node; 
+    string local_name = max_node->GetName();
+    if (max_node->IsRootNode()) {
+      parent_node = NULL;
+    } else {
+      parent_node = max_node->GetParentNode();
+    }
+
+    MaxNodeDesc *parent_node_desc = r_build_node(parent_node);
+    node_desc = new MaxNodeDesc(parent_node_desc, local_name);
+    _nodes.push_back(node_desc);
+  }
+
+  _nodes_by_path.insert(NodesByPath::value_type(node_handle, node_desc));
+  return node_desc;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::r_build_joint
+//       Access: Private
+//  Description: The recursive implementation of build_joint().
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc *MaxNodeTree::
+r_build_joint(MaxNodeDesc *node_desc, INode *max_node) 
+{
+  MaxNodeDesc *node_joint;
+  if (node_desc == _root) {
+    node_joint =  new MaxNodeDesc(_root, max_node->GetName());
+    _nodes.push_back(node_joint);
+        return node_joint;
+  }     else if (node_desc->is_node_joint() && node_desc->_joint_entry) {
+    node_joint =  new MaxNodeDesc(node_desc->_joint_entry, max_node->GetName());
+    _nodes.push_back(node_joint);
+        return node_joint;
+  } else {
+        return r_build_joint(node_desc->_parent, max_node);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::find_node
+//       Access: Private
+//  Description: The recursive implementation of build_node().
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc *MaxNodeTree::
+find_node(INode* max_node) 
+{
+  // If we have already encountered this pathname, return the
+  // corresponding MaxNodeDesc immediately.
+  
+  ULONG node_handle = 0;
+  
+  if (max_node) {
+    node_handle = max_node->GetHandle();
+  }    
+
+  NodesByPath::const_iterator ni = _nodes_by_path.find(node_handle);
+  if (ni != _nodes_by_path.end()) {
+    return (*ni).second;
+  } 
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxNodeTree::find_joint
+//       Access: Private
+//  Description: The recursive implementation of build_node().
+////////////////////////////////////////////////////////////////////
+MaxNodeDesc *MaxNodeTree::
+find_joint(INode* max_node) 
+{
+  MaxNodeDesc *node = find_node(max_node);
+  if (!node || (is_joint(max_node) && !node->is_node_joint()))
+    node = build_node(max_node);
+  return node->_joint_entry;
+}

+ 65 - 0
pandatool/src/maxegg/maxNodeTree.h

@@ -4,3 +4,68 @@
 //
 ////////////////////////////////////////////////////////////////////
 //
+// 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 MAXNODETREE_H
+#define MAXNODETREE_H
+
+#include "pandatoolbase.h"
+
+#include "maxNodeDesc.h"
+
+class EggData;
+class EggGroupNode;
+
+////////////////////////////////////////////////////////////////////
+//       Class : MaxNodeTree
+// Description : Describes a complete tree of max nodes for
+//               conversion.
+////////////////////////////////////////////////////////////////////
+class MaxNodeTree {
+public:
+  MaxNodeTree();
+  MaxNodeDesc *build_node(INode *max_node);
+  MaxNodeDesc *build_joint(INode *max_node, MaxNodeDesc *node_joint);
+  bool build_complete_hierarchy(INode *root, ULONG *selection_list, int len);
+  bool build_selected_hierarchy(INode *root);
+  MaxNodeDesc *find_node(INode *max_node);
+  MaxNodeDesc *find_joint(INode *max_node);
+
+  int get_num_nodes() const;
+  MaxNodeDesc *get_node(int n) const;
+
+  void clear_egg(EggData *egg_data, EggGroupNode *egg_root, 
+                 EggGroupNode *skeleton_node);
+  EggGroup *get_egg_group(MaxNodeDesc *node_desc);
+  EggTable *get_egg_table(MaxNodeDesc *node_desc);
+  EggXfmSAnim *get_egg_anim(MaxNodeDesc *node_desc);
+
+  MaxNodeDesc* _root;
+  float _fps;
+
+private:
+  EggData *_egg_data;
+  EggGroupNode *_egg_root;
+  EggGroupNode *_skeleton_node;
+
+  MaxNodeDesc *r_build_node(INode *max_node);
+  MaxNodeDesc *r_build_joint(MaxNodeDesc *node_desc, INode *max_node);
+  bool node_in_list(ULONG handle, ULONG *list, int len);
+  bool r_build_hierarchy(INode *root, ULONG *selection_list, int len);
+  bool is_joint(INode *node);
+
+  typedef pmap<ULONG, MaxNodeDesc *> NodesByPath;
+  NodesByPath _nodes_by_path;
+
+  typedef pvector<MaxNodeDesc *> Nodes;
+  Nodes _nodes;
+};
+
+#endif

+ 2445 - 0
pandatool/src/maxegg/maxToEggConverter.cxx

@@ -4,3 +4,2448 @@
 //
 ////////////////////////////////////////////////////////////////////
 //
+// 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 "maxToEggConverter.h"
+#include "modstack.h"
+#include "eggtable.h"
+
+#define MNEG Logger::ST_MAP_ME_TO_APP_SPECIFIC_SYSTEM2
+#define MNEG_GEOMETRY_GENERATION Logger::ST_MAP_ME_TO_APP_SPECIFIC_SYSTEM3
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MaxToEggConverter::
+MaxToEggConverter(const string &program_name) :
+  _program_name(program_name)
+{
+  _from_selection = false;
+  _polygon_output = false;
+  _polygon_tolerance = 0.01;
+  _transform_type = TT_model;
+  _cur_tref = 0;
+  _current_frame = 0;
+  _selection_list = NULL;
+  _selection_len = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MaxToEggConverter::
+MaxToEggConverter(const MaxToEggConverter &copy) :
+  maxInterface(copy.maxInterface)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MaxToEggConverter::
+~MaxToEggConverter() 
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::make_copy
+//       Access: Public, Virtual
+//  Description: Allocates and returns a new copy of the converter.
+////////////////////////////////////////////////////////////////////
+SomethingToEggConverter *MaxToEggConverter::
+make_copy() 
+{
+  return new MaxToEggConverter(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::get_name
+//       Access: Public, Virtual
+//  Description: Returns the English name of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string MaxToEggConverter::
+get_name() const 
+{
+  return "Max";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::get_extension
+//       Access: Public, Virtual
+//  Description: Returns the common extension of the file type this
+//               converter supports.
+////////////////////////////////////////////////////////////////////
+string MaxToEggConverter::
+get_extension() const 
+{
+  return "max";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::convert_file
+//       Access: Public, Virtual
+//  Description: Handles the reading of the input file and converting
+//               it to egg.  Returns true if successful, false
+//               otherwise.
+//
+//               This is designed to be as generic as possible,
+//               generally in support of run-time loading.
+//               Also see convert_max().
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+convert_file(const Filename &filename) 
+{
+
+  //If there is no Max interface to speak of, we log an error and exit.
+  if ( !maxInterface ) {
+    Logger::Log( MTEC, Logger::SAT_NULL_ERROR, "pMaxInterface is null!" );
+    //    Logger::FunctionExit();
+    return false;
+  }
+  
+  if (_character_name.empty()) {
+    _character_name = Filename(maxInterface->GetCurFileName()).get_basename_wo_extension();
+  }
+
+  return convert_max(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::convert_max
+//       Access: Public
+//  Description: Fills up the egg_data structure according to the
+//               global Max model data.  Returns true if successful,
+//               false if there is an error.  If from_selection is
+//               true, the converted geometry is based on that which
+//               is selected; otherwise, it is the entire Max scene.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+convert_max(bool from_selection) {
+
+  _from_selection = from_selection;
+  _textures.clear();
+  // *** I don't know if we're going to handle shaders in Max
+  //_shaders.clear();
+
+  if ( !maxInterface ) {
+    Logger::Log( MTEC, Logger::SAT_NULL_ERROR, "pMaxInterface is null!" );
+    //    Logger::FunctionExit();
+    return false;
+  }
+  // *** Should probably figure out how to use the progress bar effectively 
+  //     and also figure out why it is having linkage errors
+  //maxInterface->ProgressStart( "Exporting", TRUE, ProgressBarFunction, NULL );
+
+  // *** May want to make this something that is calculated as apposed to 
+  //     set explicitly, but it's not a priority
+  if (_egg_data->get_coordinate_system() == CS_default) {
+    _egg_data->set_coordinate_system(CS_zup_right);
+  }
+
+  // Figure out the animation parameters.
+  double start_frame, end_frame, frame_inc, input_frame_rate, output_frame_rate;
+
+  // Get the start and end frames and the animation frame rate from Max
+  Interval anim_range = maxInterface->GetAnimRange();
+  start_frame = anim_range.Start()/GetTicksPerFrame();
+  end_frame = anim_range.End()/GetTicksPerFrame();
+  frame_inc = 1;
+  input_frame_rate = GetFrameRate();
+
+  if (has_start_frame() && get_start_frame() > start_frame) {
+    start_frame = get_start_frame();
+  } 
+
+  if (has_end_frame() && get_end_frame() < end_frame) {
+    end_frame = get_end_frame();
+  } 
+
+  if (has_frame_inc()) {
+    frame_inc = get_frame_inc();
+  } 
+
+  if (has_input_frame_rate()) {
+    input_frame_rate = get_input_frame_rate();
+  } 
+
+  if (has_output_frame_rate()) {
+    output_frame_rate = get_output_frame_rate();
+  } else {
+    output_frame_rate = input_frame_rate;
+  }
+
+  bool all_ok = true;
+
+  if (_from_selection) {
+    all_ok = _tree.build_selected_hierarchy(maxInterface->GetRootNode());
+  } else {
+    all_ok = _tree.build_complete_hierarchy(maxInterface->GetRootNode(), _selection_list, _selection_len);
+  }
+
+  if (all_ok) {
+    switch (get_animation_convert()) {
+    case AC_pose:
+      // pose: set to a specific frame, then get out the static geometry.
+      sprintf(Logger::GetLogString(), "Extracting geometry from frame #%d.",
+              start_frame); 
+      Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, Logger::GetLogString() );
+          _current_frame = start_frame;
+      // fall through
+      
+    case AC_none:
+      // none: just get out a static model, no animation.
+      Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Converting static model." );
+      all_ok = convert_hierarchy(get_egg_data());
+      break;
+      
+    case AC_flip:
+    case AC_strobe:
+      // flip or strobe: get out a series of static models, one per
+      // frame, under a sequence node for AC_flip.
+      all_ok = convert_flip(start_frame, end_frame, frame_inc,
+                            output_frame_rate);
+      break;
+
+    case AC_model:
+      // model: get out an animatable model with joints and vertex
+      // membership.
+      all_ok = convert_char_model();
+      break;
+
+    case AC_chan:
+      // chan: get out a series of animation tables.
+      all_ok = convert_char_chan(start_frame, end_frame, frame_inc,
+                                 output_frame_rate);
+      break;
+      
+    case AC_both:
+      // both: Put a model and its animation into the same egg file.
+      _animation_convert = AC_model;
+      if (!convert_char_model()) {
+        all_ok = false;
+      }
+      _animation_convert = AC_chan;
+      if (!convert_char_chan(start_frame, end_frame, frame_inc,
+                             output_frame_rate)) {
+        all_ok = false;
+      }
+      break;
+    };
+
+    reparent_decals(get_egg_data());
+  }
+
+  if (all_ok) {
+    Logger::Log(MTEC, Logger::SAT_MEDIUM_LEVEL, "Converted, no errors." );
+  } else {
+    Logger::Log(MTEC, Logger::SAT_MEDIUM_LEVEL,
+                "Errors encountered in conversion." );
+  }
+
+  maxInterface->ProgressEnd();
+
+  //  Logger::FunctionExit();
+  return all_ok;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::setMaxInterface
+//       Access: Public
+//  Description: Sets the interface to 3D Studio Max through
+//               which the scene graph is read.
+////////////////////////////////////////////////////////////////////
+/* setMaxInterface(Interface *) - Sets the interface to 3D Studio Max through 
+ * which the scene graph is read.
+ */
+void MaxToEggConverter::setMaxInterface(Interface *pInterface) 
+{
+  maxInterface = pInterface;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::convert_flip
+//       Access: Private
+//  Description: Converts the animation as a series of models that
+//               cycle (flip) from one to the next at the appropriate
+//               frame rate.  This is the most likely to convert
+//               precisely (since we ask Maya to tell us the vertex
+//               position each time) but it is the most wasteful in
+//               terms of memory utilization (since a complete of the
+//               model is stored for each frame).
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+convert_flip(double start_frame, double end_frame, double frame_inc,
+             double output_frame_rate) {
+  bool all_ok = true;
+
+  EggGroup *sequence_node = new EggGroup(_character_name);
+  get_egg_data()->add_child(sequence_node);
+  if (_animation_convert == AC_flip) { 
+    sequence_node->set_switch_flag(true);
+    sequence_node->set_switch_fps(output_frame_rate / frame_inc);
+  }
+
+  double frame = start_frame;
+  double frame_stop = end_frame;
+  while (frame <= frame_stop) {
+    sprintf(Logger::GetLogString(), "Extracting geometry from frame #%lf.",
+            frame); 
+    Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, Logger::GetLogString() );
+    ostringstream name_strm;
+    name_strm << "frame" << frame;
+    EggGroup *frame_root = new EggGroup(name_strm.str());
+    sequence_node->add_child(frame_root);
+
+    // Set the frame and then export that frame
+    _current_frame = frame;
+    if (!convert_hierarchy(frame_root)) {
+      all_ok = false;
+    }
+
+    frame += frame_inc;
+  }
+
+  return all_ok;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::convert_char_model
+//       Access: Private
+//  Description: Converts the file as an animatable character
+//               model, with joints and vertex membership.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+convert_char_model() {
+  if (has_neutral_frame()) {
+    _current_frame = get_neutral_frame();
+  }
+
+  EggGroup *char_node = new EggGroup(_character_name);
+  get_egg_data()->add_child(char_node);
+  char_node->set_dart_type(EggGroup::DT_default);
+
+  return convert_hierarchy(char_node);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::convert_char_chan
+//       Access: Private
+//  Description: Converts the animation as a series of tables to apply
+//               to the character model, as retrieved earlier via
+//               AC_model.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+convert_char_chan(double start_frame, double end_frame, double frame_inc,
+                  double output_frame_rate) {
+
+  EggTable *root_table_node = new EggTable();
+  get_egg_data()->add_child(root_table_node);
+  EggTable *bundle_node = new EggTable(_character_name);
+  bundle_node->set_table_type(EggTable::TT_bundle);
+  root_table_node->add_child(bundle_node);
+  EggTable *skeleton_node = new EggTable("<skeleton>");
+  bundle_node->add_child(skeleton_node);
+
+  // Set the frame rate before we start asking for anim tables to be
+  // created.
+  _tree._fps = output_frame_rate / frame_inc;
+  _tree.clear_egg(get_egg_data(), NULL, skeleton_node);
+
+  // Now we can get the animation data by walking through all of the
+  // frames, one at a time, and getting the joint angles at each
+  // frame.
+
+  // This is just a temporary EggGroup to receive the transform for
+  // each joint each frame.
+  EggGroup* tgroup;
+
+  int num_nodes = _tree.get_num_nodes();
+  int i;
+
+  sprintf(Logger::GetLogString(), 
+          "sf %lf ef %lf inc %lf ofr %lf.", 
+          start_frame, end_frame, frame_inc, output_frame_rate );
+  Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_LOW_LEVEL, 
+              Logger::GetLogString() );
+
+  TimeValue frame = start_frame;
+  TimeValue frame_stop = end_frame;
+  while (frame <= frame_stop) {
+    _current_frame = frame;
+    sprintf(Logger::GetLogString(), 
+            "Current frame: %lf.", 
+            _current_frame );
+    Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_LOW_LEVEL, 
+                Logger::GetLogString() );
+
+    for (i = 0; i < num_nodes; i++) {
+      // Find all joints in the hierarchy
+      MaxNodeDesc *node_desc = _tree.get_node(i);
+      if (node_desc->is_joint()) {
+              tgroup = new EggGroup();
+              INode *max_node = node_desc->get_max_node();
+
+              if (node_desc->_parent && node_desc->_parent->is_joint()) {
+              // If this joint also has a joint as a parent, the parent's 
+              // transformation has to be divided out of this joint's TM
+              get_joint_transform(max_node, node_desc->_parent->get_max_node(), 
+                                              tgroup);
+              } else {
+                get_joint_transform(max_node, NULL, tgroup);
+              }
+        
+        EggXfmSAnim *anim = _tree.get_egg_anim(node_desc);
+        if (!anim->add_data(tgroup->get_transform3d())) {
+                // *** log an error
+              }
+              delete tgroup;
+      }
+    }
+    
+    frame += frame_inc;
+  }
+
+  // Now optimize all of the tables we just filled up, for no real
+  // good reason, except that it makes the resulting egg file a little
+  // easier to read.
+  for (i = 0; i < num_nodes; i++) {
+    MaxNodeDesc *node_desc = _tree.get_node(i);
+    if (node_desc->is_joint()) {
+      _tree.get_egg_anim(node_desc)->optimize();
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::convert_hierarchy
+//       Access: Private
+//  Description: Generates egg structures for each node in the Max
+//               hierarchy.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+convert_hierarchy(EggGroupNode *egg_root) {
+  //int num_nodes = _tree.get_num_nodes();
+
+  _tree.clear_egg(get_egg_data(), egg_root, NULL);
+  for (int i = 0; i < _tree.get_num_nodes(); i++) {
+    if (!process_model_node(_tree.get_node(i))) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::process_model_node
+//       Access: Private
+//  Description: Converts the indicated Max node to the
+//               corresponding Egg structure.  Returns true if
+//               successful, false if an error was encountered.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+process_model_node(MaxNodeDesc *node_desc) {
+  if (!node_desc->has_max_node()) {
+    // If the node has no Max equivalent, never mind.
+    return true;
+  }
+
+  // Skip all nodes that represent joints in the geometry, but aren't 
+  // the actual joints themselves
+  if (node_desc->is_node_joint()) {
+    return true;
+  }
+
+  TimeValue time = 0;
+  INode *max_node = node_desc->get_max_node();
+
+  ObjectState state;
+  state = max_node->EvalWorldState(_current_frame * GetTicksPerFrame());
+
+  if (node_desc->is_joint()) {
+    EggGroup *egg_group = _tree.get_egg_group(node_desc);
+    // Don't bother with joints unless we're getting an animatable
+    // model.
+    if (_animation_convert == AC_model) { 
+      get_joint_transform(max_node, egg_group);
+    }
+  } else {
+    if (state.obj) {
+      EggGroup *egg_group = NULL;
+      TriObject *myMaxTriObject;
+      Mesh max_mesh;
+      //Call the correct exporter based on what type of object this is.
+      switch( state.obj->SuperClassID() ){
+        //A geometric object.
+        case GEOMOBJECT_CLASS_ID:
+          Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, 
+                       "Found a geometric object in the hierarchy!" );
+    egg_group = _tree.get_egg_group(node_desc);
+          get_transform(max_node, egg_group);
+    
+    //Try converting this geometric object to a mesh we can use.
+          if (!state.obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) {
+            Logger::Log(MTEC, Logger::SAT_OTHER_ERROR, 
+                        "Cannot create geometry from state.obj!");
+            // Logger::FunctionExit();
+            return false;
+          } 
+          //Convert our state object to a TriObject.
+          myMaxTriObject = (TriObject *) state.obj->ConvertToType(time, Class_ID(TRIOBJ_CLASS_ID, 0 ));
+          // *** Want to figure this problem out 
+          // If actual conversion was required, then we want to delete this 
+          // new mesh later to avoid mem leaks. **BROKEN. doesnt delete
+
+          //Now, get the mesh.
+          max_mesh = myMaxTriObject->GetMesh();
+          Logger::Log( MTEC, Logger::SAT_LOW_LEVEL, 
+                       "TriObject attached and mesh generated!" );
+
+          make_polyset(max_node, &max_mesh, egg_group);
+
+          if (myMaxTriObject != state.obj)
+            delete myMaxTriObject;
+          break;
+
+        case SHAPE_CLASS_ID:
+    if (state.obj->ClassID() == EDITABLE_SURF_CLASS_ID) {
+            Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, 
+                         "Found a NURB object in the hierarchy!" );
+      NURBSSet getSet;
+      if (GetNURBSSet(state.obj, time, getSet, TRUE)) {
+        NURBSObject *nObj = getSet.GetNURBSObject(0);
+        if (nObj->GetType() == kNCVCurve) {
+          //It's a CV Curve, process it
+          egg_group = _tree.get_egg_group(node_desc);
+          get_transform(max_node, egg_group);
+          make_nurbs_curve((NURBSCVCurve *)nObj, string(max_node->GetName()),
+                           time, egg_group);
+        }
+      }
+    }
+    break;
+
+        case CAMERA_CLASS_ID:
+          Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, 
+                       "Found a camera object in the hierarchy!" );
+          break;
+
+        case LIGHT_CLASS_ID:
+          Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, 
+                       "Found a light object in the hierarchy!" );
+          break;
+
+        case HELPER_CLASS_ID:
+          Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, 
+                       "Found a helper object in the hierarchy!" );
+          break;
+/*        default:
+          char buf[1024];
+          sprintf(buf, "Unknown Superclass ID: %x, ClassID: %x,%x", state.obj->SuperClassID(),
+            state.obj->ClassID().PartA(), state.obj->ClassID().PartB());
+          Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, 
+                       buf ); */
+
+      }
+    }
+  }
+
+  // *** Check if any of these Maya options have a Max equivalent
+  /*
+  } else if (dag_node.inUnderWorld()) {
+    if (mayaegg_cat.is_debug()) {
+      mayaegg_cat.debug()
+        << "Ignoring underworld node " << path
+        << "\n";
+    }
+
+  } else if (dag_node.isIntermediateObject()) {
+    if (mayaegg_cat.is_debug()) {
+      mayaegg_cat.debug()
+        << "Ignoring intermediate object " << path
+        << "\n";
+    }
+
+  } else if (dag_path.hasFn(MFn::kNurbsSurface)) {
+    EggGroup *egg_group = _tree.get_egg_group(node_desc);
+    get_transform(dag_path, egg_group);
+    
+    MFnNurbsSurface surface(dag_path, &status);
+    if (!status) {
+      mayaegg_cat.info()
+        << "Error in node " << path
+        << ":\n"
+        << "  it appears to have a NURBS surface, but does not.\n";
+    } else {
+      make_nurbs_surface(dag_path, surface, egg_group);
+    }
+
+  } else if (dag_path.hasFn(MFn::kNurbsCurve)) {
+    // Only convert NurbsCurves if we aren't making an animated model.
+    // Animated models, as a general rule, don't want these sorts of
+    // things in them.
+    if (_animation_convert != AC_model) {
+      EggGroup *egg_group = _tree.get_egg_group(node_desc);
+      get_transform(dag_path, egg_group);
+      
+      MFnNurbsCurve curve(dag_path, &status);
+      if (!status) {
+        mayaegg_cat.info()
+          << "Error in node " << path << ":\n"
+          << "  it appears to have a NURBS curve, but does not.\n";
+      } else {
+        make_nurbs_curve(dag_path, curve, egg_group);
+      }
+    }
+      
+  } else if (dag_path.hasFn(MFn::kMesh)) {
+    EggGroup *egg_group = _tree.get_egg_group(node_desc);
+    get_transform(dag_path, egg_group);
+
+    MFnMesh mesh(dag_path, &status);
+    if (!status) {
+      mayaegg_cat.info()
+        << "Error in node " << path << ":\n"
+        << "  it appears to have a polygon mesh, but does not.\n";
+    } else {
+      make_polyset(dag_path, mesh, egg_group);
+    }
+
+  } else if (dag_path.hasFn(MFn::kLocator)) {
+    EggGroup *egg_group = _tree.get_egg_group(node_desc);
+
+    if (mayaegg_cat.is_debug()) {
+      mayaegg_cat.debug()
+        << "Locator at " << path << "\n";
+    }
+    
+    // Presumably, the locator's position has some meaning to the
+    // end-user, so we will implicitly tag it with the DCS flag so it
+    // won't get flattened out.
+    if (_animation_convert != AC_model) {
+      // For now, don't set the DCS flag on locators within
+      // character models, since egg-optchar doesn't understand
+      // this.  Perhaps there's no reason to ever change this, since
+      // locators within character models may not be meaningful.
+      egg_group->set_dcs_type(EggGroup::DC_net);
+    }
+    get_transform(dag_path, egg_group);
+    make_locator(dag_path, dag_node, egg_group);
+
+  } else {
+    // Just a generic node.
+    EggGroup *egg_group = _tree.get_egg_group(node_desc);
+    get_transform(dag_path, egg_group);
+  }
+  */
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::get_transform
+//       Access: Private
+//  Description: Extracts the transform on the indicated Maya node,
+//               and applies it to the corresponding Egg node.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+get_transform(INode *max_node, EggGroup *egg_group) {
+  if (_animation_convert == AC_model) {
+    // When we're getting an animated model, we only get transforms
+    // for joints.
+    return;
+  }
+
+  // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix
+  // encapsulates all the scales, rotates, and transforms it takes
+  // to "get to" the pivot point.
+  Matrix3 pivot;
+  //This is the Panda-flava-flav-style matrix we'll be exporting to.
+  Point3 row0;
+  Point3 row1;
+  Point3 row2;
+  Point3 row3;
+
+  Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup");
+  if ( !egg_group ) {
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_NULL_ERROR, 
+                 "Destination EggGroup is null!" );
+    Logger::FunctionExit();
+    return;
+  }
+
+  // *** A special case that I don't think we're supporting in Max
+  /*
+  // A special case: if the group is a billboard, we center the
+  // transform on the rotate pivot and ignore whatever transform might
+  // be there.
+  if (egg_group->get_billboard_type() != EggGroup::BT_none) {
+    MFnTransform transform(transformNode, &status);
+    if (!status) {
+      status.perror("MFnTransform constructor");
+      return;
+    }
+
+    MPoint pivot = transform.rotatePivot(MSpace::kObject, &status);
+    if (!status) {
+      status.perror("Can't get rotate pivot");
+      return;
+    }
+
+    // We need to convert the pivot to world coordinates.
+    // Unfortunately, Maya can only tell it to us in local
+    // coordinates.
+    MMatrix mat = dag_path.inclusiveMatrix(&status);
+    if (!status) {
+      status.perror("Can't get coordinate space for pivot");
+      return;
+    }
+    LMatrix4d n2w(mat[0][0], mat[0][1], mat[0][2], mat[0][3],
+                  mat[1][0], mat[1][1], mat[1][2], mat[1][3],
+                  mat[2][0], mat[2][1], mat[2][2], mat[2][3],
+                  mat[3][0], mat[3][1], mat[3][2], mat[3][3]);
+    LPoint3d p3d(pivot[0], pivot[1], pivot[2]);
+    p3d = p3d * n2w;
+
+    if (egg_group->get_parent() != (EggGroupNode *)NULL) {
+      // Now convert the pivot point into the group's parent's space.
+      p3d = p3d * egg_group->get_parent()->get_vertex_frame_inv();
+    }
+
+    egg_group->clear_transform();
+    egg_group->add_translate(p3d);
+    return;
+  }
+  */
+
+  // *** Make sure this is happening correctly; I'm not sure at the moment
+  //     how these flags get specified
+/*
+  switch (_transform_type) {
+  case TT_all:
+    break;
+    
+  case TT_model:
+    if (!egg_group->get_model_flag() &&
+        !egg_group->has_dcs_type()) {
+      return;
+    }
+    break;
+    
+  case TT_dcs: 
+    if (!egg_group->get_dcs_type()) {
+      return;
+    }
+    break;
+    
+  case TT_none:
+  case TT_invalid:
+    return;
+  }
+*/
+
+  // Gets the TM for this node, a matrix which encapsulates all transformations
+  // it takes to get to the current node, including parent transformations.
+  pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
+  row0 = pivot.GetRow(0);
+  row1 = pivot.GetRow(1);
+  row2 = pivot.GetRow(2);
+  row3 = pivot.GetRow(3);
+
+  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
+                row1.x, row1.y, row1.z, 0.0f,
+                row2.x, row2.y, row2.z, 0.0f,
+                row3.x, row3.y, row3.z, 1.0f );
+  // Now here's the tricky part. I believe this command strips out the node
+  // "frame" which is the sum of all transformations enacted by the parent of
+  // this node. This should reduce to the transformation relative to this 
+  // node's parent
+  m4d = m4d * egg_group->get_node_frame_inv();
+  if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
+    egg_group->add_matrix4(m4d);
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, 
+                 "Non-identity matrix applied to node!" );
+  } else {
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, 
+                 "Resultant matrix too close to identity; no transformation applied!" );
+  }
+  Logger::FunctionExit();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::get_object_transform
+//       Access: Private
+//  Description: Extracts the transform on the indicated Maya node,
+//               and applies it to the corresponding Egg node.
+////////////////////////////////////////////////////////////////////
+LMatrix4d MaxToEggConverter::
+get_object_transform(INode *max_node) {
+
+  // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix
+  // encapsulates all the scales, rotates, and transforms it takes
+  // to "get to" the pivot point.
+  Matrix3 pivot;
+  //This is the Panda-flava-flav-style matrix we'll be exporting to.
+  Point3 row0;
+  Point3 row1;
+  Point3 row2;
+  Point3 row3;
+
+  Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup");
+
+  // Gets the TM for this node, a matrix which encapsulates all transformations
+  // it takes to get to the current node, including parent transformations.
+  pivot = max_node->GetObjectTM(_current_frame * GetTicksPerFrame());
+  row0 = pivot.GetRow(0);
+  row1 = pivot.GetRow(1);
+  row2 = pivot.GetRow(2);
+  row3 = pivot.GetRow(3);
+
+  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
+                row1.x, row1.y, row1.z, 0.0f,
+                row2.x, row2.y, row2.z, 0.0f,
+                row3.x, row3.y, row3.z, 1.0f );
+  Logger::FunctionExit();
+  return m4d;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::get_joint_transform
+//       Access: Private
+//  Description: Extracts the transform on the indicated Maya node,
+//               as appropriate for a joint in an animated character,
+//               and applies it to the indicated node.  This is
+//               different from get_transform() in that it does not
+//               respect the _transform_type flag, and it does not
+//               consider the relative transforms within the egg file.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+get_joint_transform(INode *max_node, EggGroup *egg_group) {
+
+  // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix
+  // encapsulates all the scales, rotates, and transforms it takes
+  // to "get to" the pivot point.
+  Matrix3 pivot;
+  //This is the Panda-flava-flav-style matrix we'll be exporting to.
+  Point3 row0;
+  Point3 row1;
+  Point3 row2;
+  Point3 row3;
+
+  Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup");
+  if ( !egg_group ) {
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_NULL_ERROR, 
+                 "Destination EggGroup is null!" );
+    Logger::FunctionExit();
+    return;
+  }
+
+  // Gets the TM for this node, a matrix which encapsulates all transformations
+  // it takes to get to the current node, including parent transformations.
+  pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
+  row0 = pivot.GetRow(0);
+  row1 = pivot.GetRow(1);
+  row2 = pivot.GetRow(2);
+  row3 = pivot.GetRow(3);
+
+  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
+                row1.x, row1.y, row1.z, 0.0f,
+                row2.x, row2.y, row2.z, 0.0f,
+                row3.x, row3.y, row3.z, 1.0f );
+  // Now here's the tricky part. I believe this command strips out the node
+  // "frame" which is the sum of all transformations enacted by the parent of
+  // this node. This should reduce to the transformation relative to this 
+  // node's parent
+  m4d = m4d * egg_group->get_node_frame_inv();
+  if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
+    egg_group->add_matrix4(m4d);
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, 
+                 "Non-identity matrix applied to node!" );
+  } else {
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, 
+                 "Resultant matrix too close to identity; no transformation applied!" );
+  }
+  Logger::FunctionExit();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::get_joint_transform
+//       Access: Private
+//  Description: Extracts the transform on the indicated Maya node,
+//               as appropriate for a joint in an animated character,
+//               and applies it to the indicated node.  This is
+//               different from get_transform() in that it does not
+//               respect the _transform_type flag, and it does not
+//               consider the relative transforms within the egg file.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+get_joint_transform(INode *max_node, INode *parent_node, EggGroup *egg_group) {
+
+  // The 3dsMax-style matrix that contains the pivot matrix...this pivot matrix
+  // encapsulates all the scales, rotates, and transforms it takes
+  // to "get to" the pivot point.
+  Matrix3 pivot;
+  Matrix3 parent_pivot;
+  //This is the Panda-flava-flav-style matrix we'll be exporting to.
+  Point3 row0;
+  Point3 row1;
+  Point3 row2;
+  Point3 row3;
+
+  Logger::FunctionEntry("MaxNodeEggGroup::ApplyTransformFromMaxNodeToEggGroup");
+  if ( !egg_group ) {
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_NULL_ERROR, 
+                 "Destination EggGroup is null!" );
+    Logger::FunctionExit();
+    return;
+  }
+
+  // Gets the TM for this node, a matrix which encapsulates all transformations
+  // it takes to get to the current node, including parent transformations.
+  pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
+  row0 = pivot.GetRow(0);
+  row1 = pivot.GetRow(1);
+  row2 = pivot.GetRow(2);
+  row3 = pivot.GetRow(3);
+
+  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
+                row1.x, row1.y, row1.z, 0.0f,
+                row2.x, row2.y, row2.z, 0.0f,
+                row3.x, row3.y, row3.z, 1.0f );
+
+if (parent_node) {
+  parent_pivot = parent_node->GetNodeTM(_current_frame * GetTicksPerFrame());
+//  parent_pivot.Invert();
+  row0 = parent_pivot.GetRow(0);
+  row1 = parent_pivot.GetRow(1);
+  row2 = parent_pivot.GetRow(2);
+  row3 = parent_pivot.GetRow(3);
+
+  LMatrix4d pi_m4d(row0.x, row0.y, row0.z, 0.0f,
+                row1.x, row1.y, row1.z, 0.0f,
+                row2.x, row2.y, row2.z, 0.0f,
+                row3.x, row3.y, row3.z, 1.0f );
+
+  // Now here's the tricky part. I believe this command strips out the node
+  // "frame" which is the sum of all transformations enacted by the parent of
+  // this node. This should reduce to the transformation relative to this 
+  // node's parent
+  pi_m4d.invert_in_place();
+  m4d = m4d * pi_m4d;
+}
+  if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
+    egg_group->add_matrix4(m4d);
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, 
+                 "Non-identity matrix applied to node!" );
+  } else {
+    Logger::Log( MNEG_GEOMETRY_GENERATION, Logger::SAT_DEBUG_SPAM_LEVEL, 
+                 "Resultant matrix too close to identity; no transformation applied!" );
+  }
+  Logger::FunctionExit();
+}
+
+// *** I am skipping all NURBS stuff until I figure if Max can/needs to support
+//     it 
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::make_nurbs_surface
+//       Access: Private
+//  Description: Converts the indicated Maya NURBS surface to a
+//               corresponding egg structure, and attaches it to the
+//               indicated egg group.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+make_nurbs_surface(const MDagPath &dag_path, MFnNurbsSurface &surface,
+                   EggGroup *egg_group) {
+  MStatus status;
+  string name = surface.name().asChar();
+
+  if (mayaegg_cat.is_spam()) {
+    mayaegg_cat.spam()
+      << "  numCVs: "
+      << surface.numCVsInU()
+      << " * "
+      << surface.numCVsInV()
+      << "\n";
+    mayaegg_cat.spam()
+      << "  numKnots: "
+      << surface.numKnotsInU()
+      << " * "
+      << surface.numKnotsInV()
+      << "\n";
+    mayaegg_cat.spam()
+      << "  numSpans: "
+      << surface.numSpansInU()
+      << " * "
+      << surface.numSpansInV()
+      << "\n";
+  }
+
+  MayaShader *shader = _shaders.find_shader_for_node(surface.object());
+
+  if (_polygon_output) {
+    // If we want polygon output only, tesselate the NURBS and output
+    // that.
+    MTesselationParams params;
+    params.setFormatType(MTesselationParams::kStandardFitFormat);
+    params.setOutputType(MTesselationParams::kQuads);
+    params.setStdFractionalTolerance(_polygon_tolerance);
+
+    // We'll create the tesselation as a sibling of the NURBS surface.
+    // That way we inherit all of the transformations.
+    MDagPath polyset_path = dag_path;
+    MObject polyset_parent = polyset_path.node();
+    MObject polyset =
+      surface.tesselate(params, polyset_parent, &status);
+    if (!status) {
+      status.perror("MFnNurbsSurface::tesselate");
+      return;
+    }
+
+    status = polyset_path.push(polyset);
+    if (!status) {
+      status.perror("MDagPath::push");
+    }
+
+    MFnMesh polyset_fn(polyset, &status);
+    if (!status) {
+      status.perror("MFnMesh constructor");
+      return;
+    }
+    make_polyset(polyset_path, polyset_fn, egg_group, shader);
+
+    // Now remove the polyset we created.
+    MFnDagNode parent_node(polyset_parent, &status);
+    if (!status) {
+      status.perror("MFnDagNode constructor");
+      return;
+    }
+    status = parent_node.removeChild(polyset);
+    if (!status) {
+      status.perror("MFnDagNode::removeChild");
+    }
+
+    return;
+  }
+
+  MPointArray cv_array;
+  status = surface.getCVs(cv_array, MSpace::kWorld);
+  if (!status) {
+    status.perror("MFnNurbsSurface::getCVs");
+    return;
+  }
+  MDoubleArray u_knot_array, v_knot_array;
+  status = surface.getKnotsInU(u_knot_array);
+  if (!status) {
+    status.perror("MFnNurbsSurface::getKnotsInU");
+    return;
+  }
+  status = surface.getKnotsInV(v_knot_array);
+  if (!status) {
+    status.perror("MFnNurbsSurface::getKnotsInV");
+    return;
+  }
+
+  
+  //  We don't use these variables currently.
+  //MFnNurbsSurface::Form u_form = surface.formInU();
+  //MFnNurbsSurface::Form v_form = surface.formInV();
+ 
+
+  int u_degree = surface.degreeU();
+  int v_degree = surface.degreeV();
+
+  int u_cvs = surface.numCVsInU();
+  int v_cvs = surface.numCVsInV();
+
+  int u_knots = surface.numKnotsInU();
+  int v_knots = surface.numKnotsInV();
+
+  assert(u_knots == u_cvs + u_degree - 1);
+  assert(v_knots == v_cvs + v_degree - 1);
+
+  string vpool_name = name + ".cvs";
+  EggVertexPool *vpool = new EggVertexPool(vpool_name);
+  egg_group->add_child(vpool);
+
+  EggNurbsSurface *egg_nurbs = new EggNurbsSurface(name);
+  egg_nurbs->setup(u_degree + 1, v_degree + 1,
+                   u_knots + 2, v_knots + 2);
+
+  int i;
+
+  egg_nurbs->set_u_knot(0, u_knot_array[0]);
+  for (i = 0; i < u_knots; i++) {
+    egg_nurbs->set_u_knot(i + 1, u_knot_array[i]);
+  }
+  egg_nurbs->set_u_knot(u_knots + 1, u_knot_array[u_knots - 1]);
+
+  egg_nurbs->set_v_knot(0, v_knot_array[0]);
+  for (i = 0; i < v_knots; i++) {
+    egg_nurbs->set_v_knot(i + 1, v_knot_array[i]);
+  }
+  egg_nurbs->set_v_knot(v_knots + 1, v_knot_array[v_knots - 1]);
+
+  LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv();
+
+  for (i = 0; i < egg_nurbs->get_num_cvs(); i++) {
+    int ui = egg_nurbs->get_u_index(i);
+    int vi = egg_nurbs->get_v_index(i);
+
+    double v[4];
+    MStatus status = cv_array[v_cvs * ui + vi].get(v);
+    if (!status) {
+      status.perror("MPoint::get");
+    } else {
+      EggVertex vert;
+      LPoint4d p4d(v[0], v[1], v[2], v[3]);
+      p4d = p4d * vertex_frame_inv;
+      vert.set_pos(p4d);
+      egg_nurbs->add_vertex(vpool->create_unique_vertex(vert));
+    }
+  }
+
+  // Now consider the trim curves, if any.
+  unsigned num_trims = surface.numRegions();
+  int trim_curve_index = 0;
+  for (unsigned ti = 0; ti < num_trims; ti++) {
+    unsigned num_loops = surface.numBoundaries(ti);
+
+    if (num_loops > 0) {
+      egg_nurbs->_trims.push_back(EggNurbsSurface::Trim());
+      EggNurbsSurface::Trim &egg_trim = egg_nurbs->_trims.back();
+
+      for (unsigned li = 0; li < num_loops; li++) {
+        egg_trim.push_back(EggNurbsSurface::Loop());
+        EggNurbsSurface::Loop &egg_loop = egg_trim.back();
+        
+        MFnNurbsSurface::BoundaryType type =
+          surface.boundaryType(ti, li, &status);
+        bool keep_loop = false;
+        
+        if (!status) {
+          status.perror("MFnNurbsSurface::BoundaryType");
+        } else {
+          keep_loop = (type == MFnNurbsSurface::kInner ||
+                       type == MFnNurbsSurface::kOuter);
+        }
+        
+        if (keep_loop) {
+          unsigned num_edges = surface.numEdges(ti, li);
+          for (unsigned ei = 0; ei < num_edges; ei++) {
+            MObjectArray edge = surface.edge(ti, li, ei, true, &status);
+            if (!status) {
+              status.perror("MFnNurbsSurface::edge");
+            } else {
+              unsigned num_segs = edge.length();
+              for (unsigned si = 0; si < num_segs; si++) {
+                MObject segment = edge[si];
+                if (segment.hasFn(MFn::kNurbsCurve)) {
+                  MFnNurbsCurve curve(segment, &status);
+                  if (!status) {
+                    mayaegg_cat.error()
+                      << "Trim curve appears to be a nurbs curve, but isn't.\n";
+                  } else {
+                    // Finally, we have a valid curve!
+                    EggNurbsCurve *egg_curve =
+                      make_trim_curve(curve, name, egg_group, trim_curve_index);
+                    trim_curve_index++;
+                    if (egg_curve != (EggNurbsCurve *)NULL) {
+                      egg_loop.push_back(egg_curve);
+                    }
+                  }
+                } else {
+                  mayaegg_cat.error()
+                    << "Trim curve segment is not a nurbs curve.\n";
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // We add the NURBS to the group down here, after all of the vpools
+  // for the trim curves have been added.
+  egg_group->add_child(egg_nurbs);
+
+  if (shader != (MayaShader *)NULL) {
+    set_shader_attributes(*egg_nurbs, *shader);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::make_trim_curve
+//       Access: Private
+//  Description: Converts the indicated Maya NURBS trim curve to a
+//               corresponding egg structure, and returns it, or NULL
+//               if there is a problem.
+////////////////////////////////////////////////////////////////////
+EggNurbsCurve *MaxToEggConverter::
+make_trim_curve(const MFnNurbsCurve &curve, const string &nurbs_name,
+                EggGroupNode *egg_group, int trim_curve_index) {
+  if (mayaegg_cat.is_spam()) {
+    mayaegg_cat.spam()
+      << "Trim curve:\n";
+    mayaegg_cat.spam()
+      << "  numCVs: "
+      << curve.numCVs()
+      << "\n";
+    mayaegg_cat.spam()
+      << "  numKnots: "
+      << curve.numKnots()
+      << "\n";
+    mayaegg_cat.spam()
+      << "  numSpans: "
+      << curve.numSpans()
+      << "\n";
+  }
+
+  MStatus status;
+
+  MPointArray cv_array;
+  status = curve.getCVs(cv_array, MSpace::kWorld);
+  if (!status) {
+    status.perror("MFnNurbsCurve::getCVs");
+    return (EggNurbsCurve *)NULL;
+  }
+  MDoubleArray knot_array;
+  status = curve.getKnots(knot_array);
+  if (!status) {
+    status.perror("MFnNurbsCurve::getKnots");
+    return (EggNurbsCurve *)NULL;
+  }
+
+  //  MFnNurbsCurve::Form form = curve.form();
+
+  int degree = curve.degree();
+  int cvs = curve.numCVs();
+  int knots = curve.numKnots();
+
+  assert(knots == cvs + degree - 1);
+
+  string trim_name = "trim" + format_string(trim_curve_index);
+
+  string vpool_name = nurbs_name + "." + trim_name;
+  EggVertexPool *vpool = new EggVertexPool(vpool_name);
+  egg_group->add_child(vpool);
+
+  EggNurbsCurve *egg_curve = new EggNurbsCurve(trim_name);
+  egg_curve->setup(degree + 1, knots + 2);
+
+  int i;
+
+  egg_curve->set_knot(0, knot_array[0]);
+  for (i = 0; i < knots; i++) {
+    egg_curve->set_knot(i + 1, knot_array[i]);
+  }
+  egg_curve->set_knot(knots + 1, knot_array[knots - 1]);
+
+  for (i = 0; i < egg_curve->get_num_cvs(); i++) {
+    double v[4];
+    MStatus status = cv_array[i].get(v);
+    if (!status) {
+      status.perror("MPoint::get");
+    } else {
+      EggVertex vert;
+      vert.set_pos(LPoint3d(v[0], v[1], v[3]));
+      egg_curve->add_vertex(vpool->create_unique_vertex(vert));
+    }
+  }
+
+  return egg_curve;
+} */
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::make_nurbs_curve
+//       Access: Private
+//  Description: Converts the indicated Maya NURBS curve (a standalone
+//               curve, not a trim curve) to a corresponding egg
+//               structure and attaches it to the indicated egg group.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+make_nurbs_curve(NURBSCVCurve *curve, const string &name,
+                 TimeValue time, EggGroup *egg_group) 
+{
+                   
+/*  MStatus status;
+  string name = curve.name().asChar();
+
+  if (mayaegg_cat.is_spam()) {
+    mayaegg_cat.spam()
+      << "  numCVs: "
+      << curve.numCVs()
+      << "\n";
+    mayaegg_cat.spam()
+      << "  numKnots: "
+      << curve.numKnots()
+      << "\n";
+    mayaegg_cat.spam()
+      << "  numSpans: "
+      << curve.numSpans()
+      << "\n";
+  }
+
+  MPointArray cv_array;
+  status = curve.getCVs(cv_array, MSpace::kWorld);
+  if (!status) {
+    status.perror("MFnNurbsCurve::getCVs");
+    return;
+  }
+  MDoubleArray knot_array;
+  status = curve.getKnots(knot_array);
+  if (!status) {
+    status.perror("MFnNurbsCurve::getKnots");
+    return;
+  }
+
+  //  MFnNurbsCurve::Form form = curve.form();
+*/
+
+  int degree = curve->GetOrder();
+  int cvs = curve->GetNumCVs();
+  int knots = curve->GetNumKnots();
+  int i;
+
+  if (knots != cvs + degree) {
+    char buf[1024];
+    sprintf(buf, "NURBS knots count incorrect. Order %d, CVs %d, knots %d", degree, cvs, knots);
+    Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, buf);
+    return false;
+  }
+
+  string vpool_name = name + ".cvs";
+  EggVertexPool *vpool = new EggVertexPool(vpool_name);
+  egg_group->add_child(vpool);
+
+  EggNurbsCurve *egg_curve = new EggNurbsCurve(name);
+  egg_group->add_child(egg_curve);
+  egg_curve->setup(degree, knots);
+
+  for (i = 0; i < knots; i++)
+    egg_curve->set_knot(i, curve->GetKnot(i));
+
+  LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv();
+
+  for (i = 0; i < cvs; i++) {
+    NURBSControlVertex *cv = curve->GetCV(i);
+    if (!cv) {
+      char buf[1024];
+      sprintf(buf, "Error getting CV %d", i);
+      Logger::Log( MTEC, Logger::SAT_HIGH_LEVEL, buf);
+      return false;
+    } else {
+      EggVertex vert;
+      LPoint4d p4d(0, 0, 0, 1.0);
+      cv->GetPosition(time, p4d[0], p4d[1], p4d[2]);
+      p4d = p4d * vertex_frame_inv;
+      vert.set_pos(p4d);
+      egg_curve->add_vertex(vpool->create_unique_vertex(vert));
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::make_polyset
+//       Access: Private
+//  Description: Converts the indicated Maya polyset to a bunch of
+//               EggPolygons and parents them to the indicated egg
+//               group.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+make_polyset(INode *max_node, Mesh *mesh,
+             EggGroup *egg_group, Shader *default_shader) {
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Entered make_poly_set." );
+
+  //bool double_sided = false;
+
+  // *** I think this needs to have a plugin written to support
+  /*
+  MObject mesh_object = mesh.object();
+  bool double_sided = false;
+  get_bool_attribute(mesh_object, "doubleSided", double_sided);
+  */
+
+  mesh->buildNormals();
+
+  if (mesh->getNumFaces() == 0) {
+    Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_MEDIUM_LEVEL, 
+                "Ignoring empty mesh ");
+    return;
+  }
+
+  string vpool_name = string(max_node->GetName()) + ".verts";
+  EggVertexPool *vpool = new EggVertexPool(vpool_name);
+  egg_group->add_child(vpool);
+
+  // One way to convert the mesh would be to first get out all the
+  // vertices in the mesh and add them into the vpool, then when we
+  // traverse the polygons we would only have to index them into the
+  // vpool according to their Maya vertex index.
+
+  // Unfortunately, since Maya may store multiple normals and/or
+  // colors for each vertex according to which polygon it is in, that
+  // approach won't necessarily work.  In egg, those split-property
+  // vertices have to become separate vertices.  So instead of adding
+  // all the vertices up front, we'll start with an empty vpool, and
+  // add vertices to it on the fly.
+
+  // *** Need to find out if I even need to deal with shaders
+  /*
+  MObjectArray shaders;
+  MIntArray poly_shader_indices;
+
+  status = mesh.getConnectedShaders(dag_path.instanceNumber(),
+                                    shaders, poly_shader_indices);
+  if (!status) {
+    status.perror("MFnMesh::getConnectedShaders");
+  }
+  */
+
+  // We will need to transform all vertices from world coordinate
+  // space into the vertex space appropriate to this node.  Usually,
+  // this is the same thing as world coordinate space, and this matrix
+  // will be identity; but if the node is under an instance
+  // (particularly, for instance, a billboard) then the vertex space
+  // will be different from world space.
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Before obtaining transform." );
+ LMatrix4d vertex_frame = get_object_transform(max_node) * 
+                           egg_group->get_vertex_frame_inv();
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "After obtaining transform." );
+
+
+  // *** Not quite sure how this vertex color flag is handled.  Check on later
+  /*
+  // Save this modeling flag for the vertex color check later (see the
+  // comment below).
+  bool egg_vertex_color = false;
+  if (egg_group->has_user_data(MayaEggGroupUserData::get_class_type())) {
+    egg_vertex_color = 
+      DCAST(MayaEggGroupUserData, egg_group->get_user_data())->_vertex_color;
+  }
+  */
+
+  for ( int iFace=0; iFace < mesh->getNumFaces(); iFace++ ) {
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Getting face." );
+   EggPolygon *egg_poly = new EggPolygon;
+    egg_group->add_child(egg_poly);
+
+    egg_poly->set_bface_flag(double_sided);
+
+    Face face = mesh->faces[iFace];
+
+    // *** Once again skipping shaders until I determine if I need them
+    /*
+    // Determine the shader for this particular polygon.
+    MayaShader *shader = NULL;
+    int index = pi.index();
+    nassertv(index >= 0 && index < (int)poly_shader_indices.length());
+    int shader_index = poly_shader_indices[index];
+    if (shader_index != -1) {
+      nassertv(shader_index >= 0 && shader_index < (int)shaders.length());
+      MObject engine = shaders[shader_index];
+      shader =
+        _shaders.find_shader_for_shading_engine(engine);
+
+    } else if (default_shader != (MayaShader *)NULL) {
+      shader = default_shader;
+    }
+
+    const MayaShaderColorDef &color_def = shader->_color;
+    */
+
+    // Should we extract the color from the vertices?  Normally, in
+    // Maya a texture completely replaces the vertex color, so we
+    // should ignore the vertex color if we have a texture. 
+
+    // However, this is an inconvenient property of Maya; sometimes we
+    // really do want both vertex color and texture applied to the
+    // same object.  To allow this, we define the special egg flag
+    // "vertex-color", which when set indicates that we should
+    // respect the vertex color anyway.
+
+    // *** Ignoring vertex colors for now
+    bool ignore_vertex_color = true;
+    /*
+    bool ignore_vertex_color = false;
+    if (shader != (MayaShader *)NULL) {
+      ignore_vertex_color = color_def._has_texture && !egg_vertex_color;
+    }
+    */
+
+    // *** More shader stuff to ignore
+    /*
+    LPoint3d centroid(0.0, 0.0, 0.0);
+
+    if (shader != (MayaShader *)NULL && color_def.has_projection()) {
+      // If the shader has a projection, we may need to compute the
+      // polygon's centroid to avoid seams at the edges.
+      for (i = 0; i < num_verts; i++) {
+        MPoint p = pi.point(i, MSpace::kWorld);
+        LPoint3d p3d(p[0], p[1], p[2]);
+        p3d = p3d * vertex_frame_inv;
+        centroid += p3d;
+      }
+      centroid /= (double)num_verts;
+    }
+    */
+
+   // Get the vertices for the polygon.
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Before getting vertices." );
+
+        for ( int iVertex=0; iVertex < 3; iVertex++ ) {
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Getting vertex." );
+
+                EggVertex vert;
+
+      // Get the vertex position
+      Point3 vertex = mesh->getVert(face.v[iVertex]);
+      LPoint3d p3d(vertex.x, vertex.y, vertex.z);
+      p3d = p3d * vertex_frame;
+      vert.set_pos(p3d);
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "After getting vertex pos before getting normal." );
+
+
+      // Get the vertex normal
+      Point3 normal = get_max_vertex_normal(mesh, iFace, iVertex);
+      LVector3d n3d(normal.x, normal.y, normal.z);
+      // *** Not quite sure if this transform should be applied, but it may 
+      //     explain why normals were weird previously
+      n3d = n3d * vertex_frame;
+      vert.set_normal(n3d);
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "After getting normal." );
+
+      // *** More shader stuff to ignore for now
+      /*
+      if (shader != (MayaShader *)NULL && color_def.has_projection()) {
+        // If the shader has a projection, use it instead of the
+        // polygon's built-in UV's.
+        vert.set_uv(color_def.project_uv(p3d, centroid));
+
+      } else if (pi.hasUVs()) {
+        // Get the UV's from the polygon.
+        float2 uvs;
+        status = pi.getUV(i, uvs);
+        if (!status) {
+          status.perror("MItMeshPolygon::getUV");
+        } else {
+          vert.set_uv(TexCoordd(uvs[0], uvs[1]));
+        }
+      }
+      */
+
+      // Get the UVs for this vertex
+      if (mesh->getNumTVerts()) {
+        UVVert vertTexCoord = mesh->getTVert(mesh->tvFace[iFace].t[iVertex]);
+        vert.set_uv( TexCoordd(vertTexCoord.x, vertTexCoord.y));
+        sprintf(Logger::GetLogString(), 
+                "Got tex vertex %d of %d from local tex data.", 
+                iVertex, mesh->getNumTVerts() );
+        Logger::Log(MNEG_GEOMETRY_GENERATION, Logger::SAT_LOW_LEVEL, 
+                    Logger::GetLogString() );
+      }
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "after getting TVerts." );
+
+
+      // *** Leaving out vertex colors for now
+      /*
+      if (pi.hasColor() && !ignore_vertex_color) {
+        MColor c;
+        status = pi.getColor(c, i);
+        if (!status) {
+          status.perror("MItMeshPolygon::getColor");
+        } else {
+          vert.set_color(Colorf(c.r, c.g, c.b, 1.0));
+        }
+      }
+      */
+
+      vert.set_external_index(face.v[iVertex]);
+
+      egg_poly->add_vertex(vpool->create_unique_vertex(vert));
+    }
+
+  Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Fixing windings" );
+  //Max uses normals, not winding, to determine which way a 
+  //polygon faces. Make sure the winding and that normal agree
+  EggVertex *verts[3];
+  LPoint3d points[3];
+
+  for (int i = 0; i < 3; i++) {
+    verts[i] = egg_poly->get_vertex(i);
+    points[i] = verts[i]->get_pos3();
+  }
+
+  LVector3d realNorm = ((points[1] - points[0]).cross( 
+                         points[2] - points[0]));
+  Point3 maxNormTemp = mesh->getFaceNormal(iFace);
+  LVector3d maxNorm = (LVector3d(maxNormTemp.x, maxNormTemp.y, maxNormTemp.z) *
+                       vertex_frame);
+
+  if (realNorm.dot(maxNorm) < 0.0) {
+    egg_poly->set_vertex(0, verts[2]);
+    egg_poly->set_vertex(2, verts[0]);
+  }
+
+        // *** More shader stuff to ignore
+        /*
+    // Now apply the shader.
+    if (shader != (MayaShader *)NULL) {
+      set_shader_attributes(*egg_poly, *shader);
+    }
+        */
+        Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Before set_material_attributes" );
+
+    set_material_attributes(*egg_poly, max_node->GetMtl(), &face);
+  }
+   
+  // Now that we've added all the polygons (and created all the
+  // vertices), go back through the vertex pool and set up the
+  // appropriate joint membership for each of the vertices.
+
+  if (_animation_convert == AC_model) {
+      get_vertex_weights(max_node, vpool);
+  }
+
+}
+
+// *** I don't know if there is a Max equivalent to this.  I will implement 
+//     this if I find one
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::make_locator
+//       Access: Private
+//  Description: Locators are used in Maya to indicate a particular
+//               position in space to the user or the modeler.  We
+//               represent that in egg with an ordinary Group node,
+//               which we transform by the locator's position, so that
+//               the indicated point becomes the origin at this node
+//               and below.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+make_locator(const MDagPath &dag_path, const MFnDagNode &dag_node,
+             EggGroup *egg_group) {
+  MStatus status;
+
+  unsigned int num_children = dag_node.childCount();
+  MObject locator;
+  bool found_locator = false;
+  for (unsigned int ci = 0; ci < num_children && !found_locator; ci++) {
+    locator = dag_node.child(ci);
+    found_locator = (locator.apiType() == MFn::kLocator);
+  }
+
+  if (!found_locator) {
+    mayaegg_cat.error()
+      << "Couldn't find locator within locator node " 
+      << dag_path.fullPathName().asChar() << "\n";
+    return;
+  }
+
+  LPoint3d p3d;
+  if (!get_vec3d_attribute(locator, "localPosition", p3d)) {
+    mayaegg_cat.error()
+      << "Couldn't get position of locator " 
+      << dag_path.fullPathName().asChar() << "\n";
+    return;
+  }
+
+  // We need to convert the position to world coordinates.  For some
+  // reason, Maya can only tell it to us in local coordinates.
+  MMatrix mat = dag_path.inclusiveMatrix(&status);
+  if (!status) {
+    status.perror("Can't get coordinate space for locator");
+    return;
+  }
+  LMatrix4d n2w(mat[0][0], mat[0][1], mat[0][2], mat[0][3],
+                mat[1][0], mat[1][1], mat[1][2], mat[1][3],
+                mat[2][0], mat[2][1], mat[2][2], mat[2][3],
+                mat[3][0], mat[3][1], mat[3][2], mat[3][3]);
+  p3d = p3d * n2w;
+
+  // Now convert the locator point into the group's space.
+  p3d = p3d * egg_group->get_node_frame_inv();
+
+  egg_group->add_translate(p3d);
+}
+*/
+
+Point3 MaxToEggConverter::get_max_vertex_normal(Mesh *mesh, int faceNo, int vertNo)
+{
+  Face f = mesh->faces[faceNo];
+  DWORD smGroup = f.smGroup;
+  int vert = f.getVert(vertNo);
+  RVertex *rv = mesh->getRVertPtr(vert);
+  
+  int numNormals;
+  Point3 vertexNormal;
+
+  // Is normal specified
+  // SPCIFIED is not currently used, but may be used in future versions.
+  if (rv->rFlags & SPECIFIED_NORMAL) {
+    vertexNormal = rv->rn.getNormal();
+  }
+  // If normal is not specified it's only available if the face belongs
+  // to a smoothing group
+  else if ((numNormals = rv->rFlags & NORCT_MASK) && smGroup) {
+    // If there is only one vertex is found in the rn member.
+    if (numNormals == 1) {
+      vertexNormal = rv->rn.getNormal();
+    }
+    else {
+      // If two or more vertices are there you need to step through them
+      // and find the vertex with the same smoothing group as the current face.
+      // You will find multiple normals in the ern member.
+      for (int i = 0; i < numNormals; i++) {
+        if (rv->ern[i].getSmGroup() & smGroup) {
+          vertexNormal = rv->ern[i].getNormal();
+        }
+      }
+    }
+  }
+  else {
+    // Get the normal from the Face if no smoothing groups are there
+    vertexNormal = mesh->getFaceNormal(faceNo);
+  }
+  
+  return vertexNormal;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::get_vertex_weights
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+get_vertex_weights(INode *max_node, EggVertexPool *vpool) {
+  //Try to get the weights out of a physique if one exists
+  Modifier *mod = FindSkinModifier(max_node, PHYSIQUE_CLASSID);
+  EggVertexPool::iterator vi;
+
+  if (mod) {
+    // create a physique export interface
+    IPhysiqueExport *pPhysiqueExport = (IPhysiqueExport *)mod->GetInterface(I_PHYINTERFACE);
+    if (pPhysiqueExport) {
+      // create a context export interface
+      IPhyContextExport *pContextExport = 
+        (IPhyContextExport *)pPhysiqueExport->GetContextInterface(max_node);
+      if (pContextExport) {
+        // set the flags in the context export interface
+        pContextExport->ConvertToRigid(TRUE);
+        pContextExport->AllowBlending(TRUE);
+  
+        for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
+          EggVertex *vert = (*vi);
+          int max_vi = vert->get_external_index();
+
+          // get the vertex export interface
+          IPhyVertexExport *pVertexExport = 
+            (IPhyVertexExport *)pContextExport->GetVertexInterface(max_vi);
+          if (pVertexExport) {
+            int vertexType = pVertexExport->GetVertexType();
+
+            // handle the specific vertex type
+            if(vertexType == RIGID_TYPE) {
+              // typecast to rigid vertex
+              IPhyRigidVertex *pTypeVertex = (IPhyRigidVertex *)pVertexExport;
+              INode *bone_node = pTypeVertex->GetNode();
+              MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node);
+              if (joint_node_desc){
+                EggGroup *joint = _tree.get_egg_group(joint_node_desc);
+                if (joint != (EggGroup *)NULL)
+                  joint->ref_vertex(vert, 1.0f);
+                else
+                  Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, bone_node->GetName() );
+              }
+            }
+            else if(vertexType == RIGID_BLENDED_TYPE) {
+              // typecast to blended vertex
+              IPhyBlendedRigidVertex *pTypeVertex = (IPhyBlendedRigidVertex *)pVertexExport;
+
+              for (int ji = 0; ji < pTypeVertex->GetNumberNodes(); ++ji) {
+                float weight = pTypeVertex->GetWeight(ji);
+                if (weight > 0.0f) {
+                  INode *bone_node = pTypeVertex->GetNode(ji);
+                  MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node);
+                  if (joint_node_desc){
+                    EggGroup *joint = _tree.get_egg_group(joint_node_desc);
+                    if (joint != (EggGroup *)NULL)
+                      joint->ref_vertex(vert, weight);
+                    else
+                      Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, bone_node->GetName() );
+                  }
+                }
+              }
+            }
+            //Release the vertex interface
+            pContextExport->ReleaseVertexInterface(pVertexExport);
+          }
+        }
+        //Release the context interface
+        pPhysiqueExport->ReleaseContextInterface(pContextExport);
+      }
+      //Release the physique export interface
+      mod->ReleaseInterface(I_PHYINTERFACE, pPhysiqueExport);
+    }
+  }
+  else {
+    //No physique, try to find a skin
+    mod = FindSkinModifier(max_node, SKIN_CLASSID);
+    if (mod) {
+      ISkin *skin = (ISkin*)mod->GetInterface(I_SKIN);
+      if (skin) {
+        ISkinContextData *skinMC = skin->GetContextInterface(max_node);
+        if (skinMC) {
+          for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
+            EggVertex *vert = (*vi);
+            int max_vi = vert->get_external_index();
+  
+            for (int ji = 0; ji < skinMC->GetNumAssignedBones(max_vi); ++ji) {
+              float weight = skinMC->GetBoneWeight(max_vi, ji);
+              if (weight > 0.0f) {
+                INode *bone_node = skin->GetBone(skinMC->GetAssignedBone(max_vi, ji));
+                MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node);
+                if (joint_node_desc){
+                  EggGroup *joint = _tree.get_egg_group(joint_node_desc);
+                  if (joint != (EggGroup *)NULL) {
+                    joint->ref_vertex(vert, weight);
+                  } else {
+                    Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, bone_node->GetName() );
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// *** More ignored shader stuff.  I am replacing this with 
+//     set_material_attributes for now
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: MayaShader::set_shader_attributes
+//       Access: Private
+//  Description: Applies the known shader attributes to the indicated
+//               egg primitive.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+set_shader_attributes(EggPrimitive &primitive, const MayaShader &shader) {
+  // In Maya, a polygon is either textured or colored.  The texture,
+  // if present, replaces the color.
+  const MayaShaderColorDef &color_def = shader._color;
+  const MayaShaderColorDef &trans_def = shader._transparency;
+  if (color_def._has_texture || trans_def._has_texture) {
+    EggTexture tex(shader.get_name(), "");
+
+    if (color_def._has_texture) {
+      // If we have a texture on color, apply it as the filename.
+      Filename filename = Filename::from_os_specific(color_def._texture);
+      Filename fullpath,outpath;
+      _path_replace->full_convert_path(filename, get_texture_path(),
+                                       fullpath, outpath);
+      tex.set_filename(outpath);
+      tex.set_fullpath(fullpath);
+      apply_texture_properties(tex, color_def);
+
+      // If we also have a texture on transparency, apply it as the
+      // alpha filename.
+      if (trans_def._has_texture) {
+        if (color_def._wrap_u != trans_def._wrap_u ||
+            color_def._wrap_u != trans_def._wrap_u) {
+          mayaegg_cat.warning()
+            << "Shader " << shader.get_name()
+            << " has contradictory wrap modes on color and texture.\n";
+        }
+          
+        if (!compare_texture_properties(tex, trans_def)) {
+          // Only report each broken shader once.
+          static pset<string> bad_shaders;
+          if (bad_shaders.insert(shader.get_name()).second) {
+            mayaegg_cat.error()
+              << "Color and transparency texture properties differ on shader "
+              << shader.get_name() << "\n";
+          }
+        }
+        tex.set_format(EggTexture::F_rgba);
+          
+        // We should try to be smarter about whether the transparency
+        // value is connected to the texture's alpha channel or to its
+        // grayscale channel.  However, I'm not sure how to detect
+        // this at the moment; rather than spending days trying to
+        // figure out, for now I'll just assume that if the same
+        // texture image is used for both color and transparency, then
+        // the artist meant to use the alpha channel for transparency.
+        if (trans_def._texture == color_def._texture) {
+          // That means that we don't need to do anything special: use
+          // all the channels of the texture.
+
+        } else {
+          // Otherwise, pull the alpha channel from the other image
+          // file.  Ideally, we should figure out which channel from
+          // the other image supplies alpha (and specify this via
+          // set_alpha_file_channel()), but for now we assume it comes
+          // from the grayscale data.
+          filename = Filename::from_os_specific(trans_def._texture);
+          _path_replace->full_convert_path(filename, get_texture_path(),
+                                           fullpath, outpath);
+          tex.set_alpha_filename(outpath);
+          tex.set_alpha_fullpath(fullpath);
+        }
+
+      } else {
+        // If there is no transparency texture specified, we don't
+        // have any transparency, so tell the egg format to ignore any
+        // alpha channel that might be on the color texture.
+        tex.set_format(EggTexture::F_rgb);
+      }
+
+    } else {  // trans_def._has_texture
+      // We have a texture on transparency only.  Apply it as the
+      // primary filename, and set the format accordingly.
+      Filename filename = Filename::from_os_specific(trans_def._texture);
+      Filename fullpath, outpath;
+      _path_replace->full_convert_path(filename, get_texture_path(),
+                                       fullpath, outpath);
+      tex.set_filename(outpath);
+      tex.set_fullpath(fullpath);
+      tex.set_format(EggTexture::F_alpha);
+      apply_texture_properties(tex, trans_def);
+    }
+  
+    EggTexture *new_tex =
+      _textures.create_unique_texture(tex, ~EggTexture::E_tref_name);
+    
+    primitive.set_texture(new_tex);
+
+  }
+
+  // Also apply an overall color to the primitive.
+  Colorf rgba = shader.get_rgba();
+
+  // The existence of a texture on either color channel completely
+  // replaces the corresponding flat color.
+  if (color_def._has_texture) {
+    rgba[0] = 1.0f;
+    rgba[1] = 1.0f;
+    rgba[2] = 1.0f;
+  }
+  if (trans_def._has_texture) {
+    rgba[3] = 1.0f;
+  }
+
+  // But the color gain always gets applied.
+  rgba[0] *= color_def._color_gain[0];
+  rgba[1] *= color_def._color_gain[1];
+  rgba[2] *= color_def._color_gain[2];
+  rgba[3] *= color_def._color_gain[3];
+
+  primitive.set_color(rgba);
+}
+*/
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: MayaShader::set_material_attributes
+//       Access: Private
+//  Description: Applies the known shader attributes to the indicated
+//               egg primitive.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+set_material_attributes(EggPrimitive &primitive, Mtl *maxMaterial, Face *face) {
+  Bitmap *maxBitmap;
+  BitmapTex *maxBitmapTex;
+//  Mtl *maxMaterial;
+  StdMat *maxStandardMaterial;
+  Texmap *maxTexmap;
+  EggTexture *myEggTexture = null;
+  string outString;
+  string outHandle;
+  bool has_diffuse_texture = false;
+  bool has_trans_texture = false;
+  
+  Point3 diffuseColor = Point3(1, 1, 1);
+
+  Logger::FunctionEntry( "MaxToEggConverter::CreateEggTextureFromINode" );
+
+  //First, get the material data associated with this node.
+//  maxMaterial = max_node->GetMtl();
+  if ( !maxMaterial ) {
+    Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!");
+    Logger::FunctionExit();
+    return;
+  }
+
+  //Now, determine wether it's a standard or multi material
+  if ( maxMaterial->ClassID() == Class_ID(DMTL_CLASS_ID, 0 )) {
+      
+    // Access the Diffuse map and see if it's a Bitmap texture
+    maxStandardMaterial = (StdMat *)maxMaterial;
+    maxTexmap = maxMaterial->GetSubTexmap(ID_DI);
+    //Determine whether this texture is a bitmap.
+    if (maxTexmap && (maxTexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) {
+     ostringstream name_strm;
+      name_strm << "Tex" << ++_cur_tref;
+      EggTexture tex(name_strm.str(), "");
+
+      // It is! 
+      has_diffuse_texture = true;
+
+      maxBitmapTex = (BitmapTex *) maxTexmap;
+      Filename filename = Filename::from_os_specific(maxBitmapTex->GetMapName());
+      Filename fullpath, outpath;
+      _path_replace->full_convert_path(filename, get_texture_path(),
+                                       fullpath, outpath);
+      tex.set_filename(outpath);
+      tex.set_fullpath(fullpath);
+      apply_texture_properties(tex, maxStandardMaterial);
+      // *** Must add stuff here for looking for transparencies
+      maxBitmap = maxBitmapTex->GetBitmap(0);
+      //Query some parameters of the bitmap to get the format option.
+      if ( maxBitmap && maxBitmap->HasAlpha() ) {
+        has_trans_texture = true;
+        tex.set_format(EggTexture::F_rgba);
+      } else {
+        tex.set_format(EggTexture::F_rgb);
+      }
+      EggTexture *new_tex =
+        _textures.create_unique_texture(tex, ~EggTexture::E_tref_name);
+    
+      primitive.set_texture(new_tex);
+    }
+
+    // Also apply an overall color to the primitive.
+    Colorf rgba(1.0f, 1.0f, 1.0f, 1.0f);
+
+    // The existence of a texture on either color channel completely
+    // replaces the corresponding flat color.
+    if (!has_diffuse_texture) {
+      // Get the default diffuse color of the material without the texture map
+      diffuseColor = Point3(maxMaterial->GetDiffuse());
+      rgba[0] = diffuseColor.x;
+      rgba[1] = diffuseColor.y;
+      rgba[2] = diffuseColor.z;
+    }
+    if (!has_trans_texture) {
+      // *** Figure out how to actually get the opacity here
+      rgba[3] = 1.0f;
+    }
+
+    // *** May need color gain, but I don't know what it is
+    /*
+    // But the color gain always gets applied.
+    rgba[0] *= color_def._color_gain[0];
+    rgba[1] *= color_def._color_gain[1];
+    rgba[2] *= color_def._color_gain[2];
+    rgba[3] *= color_def._color_gain[3];
+    */
+/*
+    primitive.set_color(rgba);
+
+  } else if ( maxMaterial->ClassID() == Class_ID(MULTI_CLASS_ID, 0 )) {
+        // It's a multi-material.  Find the submaterial for this face.
+    // and call set_material_attributes again on the submaterial.
+        MtlID matID = face->getMatID();
+        if (matID < maxMaterial->NumSubMtls()) {
+          set_material_attributes(primitive, maxMaterial->GetSubMtl(matID), face);
+        } else {
+                sprintf(Logger::GetLogString(),
+                            "SubMaterial ID %d is greater than the total submaterial for this material",
+                                matID);
+          Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!");
+        }
+  } else {
+    // It's non-standard material. At the moment, let's just 
+    // return
+    Logger::FunctionExit();
+    return;
+  }
+
+  Logger::FunctionExit();
+}
+*/
+
+////////////////////////////////////////////////////////////////////
+//     Function: MaxToEggConverter::set_material_attributes
+//       Access: Private
+//  Description: Applies the known material attributes to the indicated
+//               egg primitive.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+set_material_attributes(EggPrimitive &primitive, Mtl *maxMaterial, Face *face) {
+  Bitmap *diffuseBitmap;
+  BitmapTex *diffuseBitmapTex;
+  BitmapTex *transBitmapTex;
+//  Mtl *maxMaterial;
+  StdMat *maxStandardMaterial;
+  Texmap *diffuseTexmap;
+  Texmap *transTexmap;
+  EggTexture *myEggTexture = null;
+  string outString;
+  string outHandle;
+  bool has_diffuse_texture = false;
+  bool has_trans_texture = false;
+  
+  Point3 diffuseColor = Point3(1, 1, 1);
+
+  Logger::FunctionEntry( "MaxToEggConverter::CreateEggTextureFromINode" );
+
+  //First, get the material data associated with this node.
+//  maxMaterial = max_node->GetMtl();
+  if ( !maxMaterial ) {
+    Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!");
+    Logger::FunctionExit();
+    return;
+  }
+
+  //Now, determine wether it's a standard or multi material
+  if ( maxMaterial->ClassID() == Class_ID(DMTL_CLASS_ID, 0 )) {
+    // *** Eventuall we should probably deal with multi-materials
+     
+    maxStandardMaterial = (StdMat *)maxMaterial;
+
+        // Access the Diffuse map and see if it's a Bitmap texture
+    diffuseTexmap = maxMaterial->GetSubTexmap(ID_DI);
+        if (diffuseTexmap && (diffuseTexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) {
+      has_diffuse_texture = true;
+          diffuseBitmapTex = (BitmapTex *) diffuseTexmap;
+        }
+
+    // Access the Opacity map and see if it's a Bitmap texture
+        transTexmap = maxMaterial->GetSubTexmap(ID_OP);
+        if (transTexmap && (transTexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) {
+      has_trans_texture = true;
+          transBitmapTex = (BitmapTex *) transTexmap;
+        }
+
+    if (has_diffuse_texture || has_trans_texture) {
+      ostringstream name_strm;
+      name_strm << "Tex" << ++_cur_tref;
+      EggTexture tex(name_strm.str(), "");
+      
+      if (has_diffuse_texture) {
+        // It is! 
+        Filename filename = Filename::from_os_specific(diffuseBitmapTex->GetMapName());
+        Filename fullpath, outpath;
+        _path_replace->full_convert_path(filename, get_texture_path(),
+                                         fullpath, outpath);
+        tex.set_filename(outpath);
+        tex.set_fullpath(fullpath);
+        apply_texture_properties(tex, maxStandardMaterial);
+        // *** Must add stuff here for looking for transparencies
+        diffuseBitmap = diffuseBitmapTex->GetBitmap(0);
+        //Query some parameters of the bitmap to get the format option.
+        if ( has_trans_texture ) {
+          tex.set_format(EggTexture::F_rgba);
+          if (stricmp(diffuseBitmapTex->GetMapName(),
+                      transBitmapTex->GetMapName()) == 0) {
+            // nothing more needs to be done
+          } else {
+            filename = Filename::from_os_specific(transBitmapTex->GetMapName());
+            _path_replace->full_convert_path(filename, get_texture_path(),
+                                             fullpath, outpath);
+            tex.set_alpha_filename(outpath);
+            tex.set_alpha_fullpath(fullpath);
+          }
+        } else {
+          if ( diffuseBitmap && diffuseBitmap->HasAlpha()) {
+            tex.set_format(EggTexture::F_rgba);
+          } else {
+            tex.set_format(EggTexture::F_rgb);
+          }
+        }
+      } else {
+        // We have a texture on transparency only.  Apply it as the
+        // primary filename, and set the format accordingly.
+        Filename filename = Filename::from_os_specific(transBitmapTex->GetMapName());
+        Filename fullpath, outpath;
+        _path_replace->full_convert_path(filename, get_texture_path(),
+                                         fullpath, outpath);
+        tex.set_filename(outpath);
+        tex.set_fullpath(fullpath);
+        tex.set_format(EggTexture::F_alpha);
+        apply_texture_properties(tex, maxStandardMaterial);
+      }
+      EggTexture *new_tex =
+        _textures.create_unique_texture(tex, ~EggTexture::E_tref_name);
+      
+      primitive.set_texture(new_tex);
+    }
+    
+    // Also apply an overall color to the primitive.
+    Colorf rgba(1.0f, 1.0f, 1.0f, 1.0f);
+
+    // The existence of a texture on either color channel completely
+    // replaces the corresponding flat color.
+    if (!has_diffuse_texture) {
+      // Get the default diffuse color of the material without the texture map
+      diffuseColor = Point3(maxMaterial->GetDiffuse());
+      rgba[0] = diffuseColor.x;
+      rgba[1] = diffuseColor.y;
+      rgba[2] = diffuseColor.z;
+    }
+    if (!has_trans_texture) {
+      // *** Figure out how to actually get the opacity here
+      rgba[3] = maxStandardMaterial->GetOpacity(_current_frame * GetTicksPerFrame());
+    }
+
+    // *** May need color gain, but I don't know what it is
+    /*
+    // But the color gain always gets applied.
+    rgba[0] *= color_def._color_gain[0];
+    rgba[1] *= color_def._color_gain[1];
+    rgba[2] *= color_def._color_gain[2];
+    rgba[3] *= color_def._color_gain[3];
+    */
+
+    primitive.set_color(rgba);
+    
+  } else if ( maxMaterial->ClassID() == Class_ID(MULTI_CLASS_ID, 0 )) {
+        // It's a multi-material.  Find the submaterial for this face.
+    // and call set_material_attributes again on the submaterial.
+        MtlID matID = face->getMatID();
+        if (matID < maxMaterial->NumSubMtls()) {
+          set_material_attributes(primitive, maxMaterial->GetSubMtl(matID), face);
+        } else {
+                sprintf(Logger::GetLogString(),
+                            "SubMaterial ID %d is greater than the total submaterial for this material",
+                                matID);
+          Logger::Log(MTEC, Logger::SAT_NULL_ERROR, "maxMaterial is null!");
+        }
+  } else {
+        // It's another non-standard material. At the moment, let's just 
+    // return
+    Logger::FunctionExit();
+    return;
+  }
+
+  Logger::FunctionExit();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MayaShader::apply_texture_properties
+//       Access: Private
+//  Description: Applies all the appropriate texture properties to the
+//               EggTexture object, including wrap modes and texture
+//               matrix.
+////////////////////////////////////////////////////////////////////
+void MaxToEggConverter::
+apply_texture_properties(EggTexture &tex, StdMat *maxMaterial) {
+  // Let's mipmap all textures by default.
+  tex.set_minfilter(EggTexture::FT_linear_mipmap_linear);
+  tex.set_magfilter(EggTexture::FT_linear);
+
+  
+  // *** Need to figure out how to get the wrap options from Max
+  EggTexture::WrapMode wrap_u = EggTexture::WM_repeat;
+  EggTexture::WrapMode wrap_v = EggTexture::WM_repeat;
+  
+  /*
+  EggTexture::WrapMode wrap_u = color_def._wrap_u ? EggTexture::WM_repeat : EggTexture::WM_clamp;
+  EggTexture::WrapMode wrap_v = color_def._wrap_v ? EggTexture::WM_repeat : EggTexture::WM_clamp;
+  */
+
+  tex.set_wrap_u(wrap_u);
+  tex.set_wrap_v(wrap_v);
+  
+  // *** I may need to find this too
+  /*
+  LMatrix3d mat = color_def.compute_texture_matrix();
+  if (!mat.almost_equal(LMatrix3d::ident_mat())) {
+    tex.set_transform(mat);
+  }
+  */
+}
+
+// *** I don't think I need this right now
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: MayaShader::compare_texture_properties
+//       Access: Private
+//  Description: Compares the texture properties already on the
+//               texture (presumably set by a previous call to
+//               apply_texture_properties()) and returns false if they
+//               differ from that specified by the indicated color_def
+//               object, or true if they match.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+compare_texture_properties(EggTexture &tex, 
+                           const MayaShaderColorDef &color_def) {
+  bool okflag = true;
+
+  EggTexture::WrapMode wrap_u = color_def._wrap_u ? EggTexture::WM_repeat : EggTexture::WM_clamp;
+  EggTexture::WrapMode wrap_v = color_def._wrap_v ? EggTexture::WM_repeat : EggTexture::WM_clamp;
+  
+  if (wrap_u != tex.determine_wrap_u()) {
+    // Choose the more general of the two.
+    if (wrap_u == EggTexture::WM_repeat) {
+      tex.set_wrap_u(wrap_u);
+    }
+    okflag = false;
+  }
+  if (wrap_v != tex.determine_wrap_v()) {
+    if (wrap_v == EggTexture::WM_repeat) {
+      tex.set_wrap_v(wrap_v);
+    }
+    okflag = false;
+  }
+  
+  LMatrix3d mat = color_def.compute_texture_matrix();
+  if (!mat.almost_equal(tex.get_transform())) {
+    okflag = false;
+  }
+
+  return okflag;
+}
+*/
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MayaShader::reparent_decals
+//       Access: Private
+//  Description: Recursively walks the egg hierarchy, reparenting
+//               "decal" type nodes below their corresponding
+//               "decalbase" type nodes, and setting the flags.
+//
+//               Returns true on success, false if some nodes were
+//               incorrect.
+////////////////////////////////////////////////////////////////////
+bool MaxToEggConverter::
+reparent_decals(EggGroupNode *egg_parent) {
+  bool okflag = true;
+
+  // First, walk through all children of this node, looking for the
+  // one decal base, if any.
+  EggGroup *decal_base = (EggGroup *)NULL;
+  pvector<EggGroup *> decal_children;
+
+  EggGroupNode::iterator ci;
+  for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) {
+    EggNode *child =  (*ci);
+    if (child->is_of_type(EggGroup::get_class_type())) {
+      EggGroup *child_group = (EggGroup *) child;
+      if (child_group->has_object_type("decalbase")) {
+        if (decal_base != (EggNode *)NULL) {
+          // error
+          okflag = false;
+        }
+        child_group->remove_object_type("decalbase");
+        decal_base = child_group;
+
+      } else if (child_group->has_object_type("decal")) {
+        child_group->remove_object_type("decal");
+        decal_children.push_back(child_group);
+      }
+    }
+  }
+
+  if (decal_base == (EggGroup *)NULL) {
+    if (!decal_children.empty()) {
+      // warning
+    }
+
+  } else {
+    if (decal_children.empty()) {
+      // warning
+
+    } else {
+      // All the decal children get moved to be a child of decal base.
+      // This usually will not affect the vertex positions, but it
+      // could if the decal base has a transform and the decal child
+      // is an instance node.  So don't do that.
+      pvector<EggGroup *>::iterator di;
+      for (di = decal_children.begin(); di != decal_children.end(); ++di) {
+        EggGroup *child_group = (*di);
+        decal_base->add_child(child_group);
+      }
+
+      // Also set the decal state on the base.
+      decal_base->set_decal_flag(true);
+    }
+  }
+
+  // Now recurse on each of the child nodes.
+  for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) {
+    EggNode *child =  (*ci);
+    if (child->is_of_type(EggGroupNode::get_class_type())) {
+      EggGroupNode *child_group = (EggGroupNode *) child;
+      if (!reparent_decals(child_group)) {
+        okflag = false;
+      }
+    }
+  }
+
+  return okflag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MayaShader::string_transform_type
+//       Access: Public, Static
+//  Description: Returns the TransformType value corresponding to the
+//               indicated string, or TT_invalid.
+////////////////////////////////////////////////////////////////////
+MaxToEggConverter::TransformType MaxToEggConverter::
+string_transform_type(const string &arg) {
+  if (strcmp(arg.c_str(), "all") == 0) {
+    return TT_all;
+  } else if (strcmp(arg.c_str(), "model") == 0) {
+    return TT_model;
+  } else if (strcmp(arg.c_str(), "dcs") == 0) {
+    return TT_dcs;
+  } else if (strcmp(arg.c_str(), "none") == 0) {
+    return TT_none;
+  } else {
+    return TT_invalid;
+  }
+}
+
+Modifier* MaxToEggConverter::FindSkinModifier (INode* node, const Class_ID &type)
+{
+  // Get object from node. Abort if no object.
+  Object* pObj = node->GetObjectRef();
+  if (!pObj) return NULL;
+
+  // Is derived object ?
+  while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID)
+  {
+    // Yes -> Cast.
+    IDerivedObject* pDerObj = static_cast<IDerivedObject*>(pObj);
+
+    // Iterate over all entries of the modifier stack.
+    for (int stackId = 0; stackId < pDerObj->NumModifiers(); ++stackId)
+    {
+      // Get current modifier.
+      Modifier* mod = pDerObj->GetModifier(stackId);
+
+      // Is this what we are looking for?
+                        if (mod->ClassID() == type )
+                                return mod;
+                }
+
+    // continue with next derived object
+    pObj = pDerObj->GetObjRef();
+  }
+
+  // Not found.
+  return NULL;
+}

+ 15 - 0
pandatool/src/maxegg/max_post_include.h

@@ -3,3 +3,18 @@
 //
 ////////////////////////////////////////////////////////////////////
 //
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+// This header file works in conjunction with pre_maya_include.h; it
+// cleans up some of the definitions that it left open.
+
+// Remove the symbols defined from pre_maya_include.h.
+#undef ostream
+#undef istream

+ 26 - 0
pandatool/src/maxegg/max_pre_include.h

@@ -3,3 +3,29 @@
 //
 ////////////////////////////////////////////////////////////////////
 //
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+// This header file defines a few things that are necessary to define
+// before including any Maya headers, just to work around some of
+// Max's assumptions about the compiler.  It must not try to protect
+// itself from multiple inclusion with #ifdef .. #endif, since it must
+// be used each time it is included.
+
+// Max will try to typedef bool unless this symbol is defined.
+#ifndef _BOOL
+#define _BOOL 1
+#endif
+
+// Max tries to make a forward declaration for class ostream, but
+// this is not necessarily a class!  Curses.  We can't use any of the
+// built-in Max stream operators, and we have to protect ourselves
+// from them.
+#define ostream max_ostream
+#define istream max_istream