Browse Source

Merge branch 'master' into interrogate-overhaul

rdb 10 years ago
parent
commit
238cbad2a1
62 changed files with 1822 additions and 461 deletions
  1. 1 1
      dtool/src/dtoolutil/executionEnvironment.cxx
  2. 69 0
      dtool/src/dtoolutil/filename.I
  3. 11 1
      dtool/src/dtoolutil/filename.h
  4. 1 1
      makepanda/makepanda.py
  5. 5 1
      makepanda/makepandacore.py
  6. 1 1
      panda/src/bullet/bulletAllHitsRayResult.cxx
  7. 1 1
      panda/src/bullet/bulletAllHitsRayResult.h
  8. 5 4
      panda/src/bullet/bulletWorld.cxx
  9. 2 1
      panda/src/bullet/bulletWorld.h
  10. 10 6
      panda/src/collide/collisionBox.cxx
  11. 45 24
      panda/src/display/graphicsStateGuardian.cxx
  12. 6 0
      panda/src/downloader/httpDate.cxx
  13. 19 22
      panda/src/egg2pg/eggLoader.cxx
  14. 49 2
      panda/src/egg2pg/eggSaver.cxx
  15. 2 1
      panda/src/egg2pg/eggSaver.h
  16. 27 0
      panda/src/express/datagram.I
  17. 5 0
      panda/src/express/datagram.h
  18. 90 0
      panda/src/express/pointerToArray.I
  19. 23 0
      panda/src/express/pointerToArray.h
  20. 25 11
      panda/src/express/pointerToArrayBase.I
  21. 4 0
      panda/src/express/pointerToArrayBase.h
  22. 41 23
      panda/src/express/pointerToArray_ext.I
  23. 20 0
      panda/src/express/pointerToArray_ext.h
  24. 8 3
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  25. 5 0
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  26. 39 5
      panda/src/framework/windowFramework.cxx
  27. 5 2
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  28. 1 1
      panda/src/gobj/internalName_ext.cxx
  29. 1 0
      panda/src/gobj/samplerContext.h
  30. 1 0
      panda/src/gobj/samplerState.h
  31. 1 1
      panda/src/linmath/lmatrix3_src.cxx
  32. 1 1
      panda/src/linmath/lmatrix4_src.cxx
  33. 28 0
      panda/src/mathutil/pta_LMatrix3_ext.h
  34. 28 0
      panda/src/mathutil/pta_LMatrix4_ext.h
  35. 30 0
      panda/src/mathutil/pta_LVecBase2_ext.h
  36. 30 0
      panda/src/mathutil/pta_LVecBase3_ext.h
  37. 30 0
      panda/src/mathutil/pta_LVecBase4_ext.h
  38. 3 0
      panda/src/pgraph/Sources.pp
  39. 2 0
      panda/src/pgraph/config_pgraph.cxx
  40. 1 0
      panda/src/pgraph/cullBinManager.h
  41. 3 3
      panda/src/pgraph/nodePath.h
  42. 1 0
      panda/src/pgraph/p3pgraph_composite3.cxx
  43. 60 0
      panda/src/pgraph/paramNodePath.I
  44. 108 0
      panda/src/pgraph/paramNodePath.cxx
  45. 75 0
      panda/src/pgraph/paramNodePath.h
  46. 2 3
      panda/src/pgraph/polylightNode.I
  47. 24 0
      panda/src/pgraph/polylightNode.cxx
  48. 4 4
      panda/src/pgraph/polylightNode.h
  49. 0 27
      panda/src/pgraph/shaderInput.I
  50. 30 2
      panda/src/pgraph/shaderInput.cxx
  51. 4 4
      panda/src/pgraph/shaderInput.h
  52. 1 0
      panda/src/pgraph/shaderPool.cxx
  53. 2 0
      panda/src/putil/Sources.pp
  54. 2 0
      panda/src/putil/callbackObject.h
  55. 32 0
      panda/src/putil/callbackObject_ext.I
  56. 47 0
      panda/src/putil/callbackObject_ext.h
  57. 270 42
      pandatool/src/daeegg/daeCharacter.cxx
  58. 43 12
      pandatool/src/daeegg/daeCharacter.h
  59. 11 10
      pandatool/src/daeegg/daeMaterials.cxx
  60. 396 221
      pandatool/src/daeegg/daeToEggConverter.cxx
  61. 23 20
      pandatool/src/daeegg/daeToEggConverter.h
  62. 8 0
      pandatool/src/daeprogs/daeToEgg.cxx

+ 1 - 1
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -60,7 +60,7 @@ extern char **environ;
 #include <sys/sysctl.h>
 #endif
 
-#if defined(IS_LINUX) || defined(IS_OSX)
+#if defined(IS_LINUX) || defined(IS_OSX) || defined(IS_FREEBSD)
 // For link_map and dlinfo.
 #include <link.h>
 #include <dlfcn.h>

+ 69 - 0
dtool/src/dtoolutil/filename.I

@@ -64,6 +64,39 @@ Filename(const Filename &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename::
+Filename(string &&filename) NOEXCEPT {
+  _flags = 0;
+  (*this) = move(filename);
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename::
+Filename(Filename &&from) NOEXCEPT :
+  _filename(move(from._filename)),
+  _dirname_end(from._dirname_end),
+  _basename_start(from._basename_start),
+  _basename_end(from._basename_end),
+  _extension_start(from._extension_start),
+  _hash_start(from._hash_start),
+  _hash_end(from._hash_end),
+  _flags(from._flags)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Filename::text_filename named constructor
 //       Access: Published
@@ -216,6 +249,42 @@ operator = (const Filename &copy) {
   return *this;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move assignment operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename &Filename::
+operator = (string &&filename) NOEXCEPT {
+  _filename = move(filename);
+
+  locate_basename();
+  locate_extension();
+  locate_hash();
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Filename::Move assignment operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Filename &Filename::
+operator = (Filename &&from) NOEXCEPT {
+  _filename = MOVE(from._filename);
+  _dirname_end = from._dirname_end;
+  _basename_start = from._basename_start;
+  _basename_end = from._basename_end;
+  _extension_start = from._extension_start;
+  _hash_start = from._hash_start;
+  _hash_end = from._hash_end;
+  _flags = from._flags;
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Filename::string typecast operator

+ 11 - 1
dtool/src/dtoolutil/filename.h

@@ -68,6 +68,11 @@ PUBLISHED:
   Filename(const Filename &dirname, const Filename &basename);
   INLINE ~Filename();
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE Filename(string &&filename) NOEXCEPT;
+  INLINE Filename(Filename &&from) NOEXCEPT;
+#endif
+
 #ifdef HAVE_PYTHON
   EXTENSION(PyObject *__reduce__(PyObject *self) const);
 #endif
@@ -88,7 +93,7 @@ PUBLISHED:
                                    Type type = T_general);
   static Filename from_os_specific_w(const wstring &os_specific,
                                      Type type = T_general);
-  static Filename expand_from(const string &user_string, 
+  static Filename expand_from(const string &user_string,
                               Type type = T_general);
   static Filename temporary(const string &dirname, const string &prefix,
                             const string &suffix = string(),
@@ -105,6 +110,11 @@ PUBLISHED:
   INLINE Filename &operator = (const char *filename);
   INLINE Filename &operator = (const Filename &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE Filename &operator = (string &&filename) NOEXCEPT;
+  INLINE Filename &operator = (Filename &&from) NOEXCEPT;
+#endif
+
   // And retrieval is by any of the classic string operations.
   INLINE operator const string & () const;
   INLINE const char *c_str() const;

+ 1 - 1
makepanda/makepanda.py

@@ -4609,7 +4609,7 @@ if (PkgSkip("DIRECT")==0):
   OPTS=['DIR:direct/src/dcparser', 'WITHINPANDA', 'BUILDING:DIRECT', 'BISONPREFIX_dcyy']
   CreateFile(GetOutputDir()+"/include/dcParser.h")
   TargetAdd('p3dcparser_dcParser.obj', opts=OPTS, input='dcParser.yxx')
-  TargetAdd('dcParser.h', input='p3egg_parser.obj', opts=['DEPENDENCYONLY'])
+  TargetAdd('dcParser.h', input='p3dcparser_dcParser.obj', opts=['DEPENDENCYONLY'])
   TargetAdd('p3dcparser_dcLexer.obj', opts=OPTS, input='dcLexer.lxx')
   TargetAdd('p3dcparser_composite1.obj', opts=OPTS, input='p3dcparser_composite1.cxx')
   TargetAdd('p3dcparser_composite2.obj', opts=OPTS, input='p3dcparser_composite2.cxx')

+ 5 - 1
makepanda/makepandacore.py

@@ -210,6 +210,8 @@ def PrettyTime(t):
     return "%d sec" % (seconds)
 
 def ProgressOutput(progress, msg, target = None):
+    sys.stdout.flush()
+    sys.stderr.flush()
     prefix = ""
     thisthread = threading.currentThread()
     if thisthread is MAINTHREAD:
@@ -235,6 +237,8 @@ def ProgressOutput(progress, msg, target = None):
         suffix = GetColor()
 
     print(''.join((prefix, msg, suffix)))
+    sys.stdout.flush()
+    sys.stderr.flush()
 
 def exit(msg = ""):
     sys.stdout.flush()
@@ -498,7 +502,7 @@ def oscmd(cmd, ignoreError = False):
         print(GetColor("blue") + cmd.split(" ", 1)[0] + " " + GetColor("magenta") + cmd.split(" ", 1)[1] + GetColor())
     sys.stdout.flush()
 
-    if sys.platform == "win32":
+    if sys.platform in ("win32", "cygwin"):
         exe = cmd.split()[0]
         exe_path = LocateBinary(exe)
         if exe_path is None:

+ 1 - 1
panda/src/bullet/bulletAllHitsRayResult.cxx

@@ -156,7 +156,7 @@ get_hit_fraction() const {
 //       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
-PandaNode *BulletRayHit::
+const PandaNode *BulletRayHit::
 get_node() const {
 
   return (_object) ? (PandaNode *)_object->getUserPointer() : NULL;

+ 1 - 1
panda/src/bullet/bulletAllHitsRayResult.h

@@ -33,7 +33,7 @@ struct EXPCL_PANDABULLET BulletRayHit {
 PUBLISHED:
   INLINE static BulletRayHit empty();
 
-  PandaNode *get_node() const;
+  const PandaNode *get_node() const;
   LPoint3 get_hit_pos() const;
   LVector3 get_hit_normal() const;
   PN_stdfloat get_hit_fraction() const;

+ 5 - 4
panda/src/bullet/bulletWorld.cxx

@@ -608,11 +608,12 @@ remove_vehicle(BulletVehicle *vehicle) {
 ////////////////////////////////////////////////////////////////////
 //     Function: BulletWorld::attach_constraint
 //       Access: Published
-//  Description: Deprecated!
-//               Please use BulletWorld::attach
+//  Description: Attaches a single constraint to a world. Collision
+//               checks between the linked objects will be disabled
+//               if the second parameter is set to TRUE.
 ////////////////////////////////////////////////////////////////////
 void BulletWorld::
-attach_constraint(BulletConstraint *constraint) {
+attach_constraint(BulletConstraint *constraint, bool linked_collision) {
 
   nassertv(constraint);
 
@@ -622,7 +623,7 @@ attach_constraint(BulletConstraint *constraint) {
 
   if (found == _constraints.end()) {
     _constraints.push_back(constraint);
-    _world->addConstraint(constraint->ptr());
+    _world->addConstraint(constraint->ptr(), linked_collision);
   }
   else {
     bullet_cat.warning() << "constraint already attached" << endl;

+ 2 - 1
panda/src/bullet/bulletWorld.h

@@ -70,6 +70,8 @@ PUBLISHED:
 
   // Attach/Remove
   void attach(TypedObject *object);
+  void attach_constraint(BulletConstraint *constraint, bool linked_collision=false);
+
   void remove(TypedObject *object);
 
   // Ghost object
@@ -172,7 +174,6 @@ PUBLISHED: // Deprecated methods, will become private soon
   void attach_character(BulletBaseCharacterControllerNode *node);
   void remove_character(BulletBaseCharacterControllerNode *node);
 
-  void attach_constraint(BulletConstraint *constraint);
   void remove_constraint(BulletConstraint *constraint);
 
 public:

+ 10 - 6
panda/src/collide/collisionBox.cxx

@@ -144,15 +144,19 @@ test_intersection(const CollisionEntry &entry) const {
 ////////////////////////////////////////////////////////////////////
 void CollisionBox::
 xform(const LMatrix4 &mat) {
+  _min = _min * mat;
+  _max = _max * mat;
   _center = _center * mat;
-  for(int v = 0; v < 8; v++)
+  for(int v = 0; v < 8; v++) {
     _vertex[v] = _vertex[v] * mat;
-  for(int p = 0; p < 6 ; p++)
+  }
+  for(int p = 0; p < 6 ; p++) {
     _planes[p] = set_plane(p);
-  _x = _vertex[0].get_x()-_center.get_x(); 
-  _y = _vertex[0].get_y()-_center.get_y();
-  _z = _vertex[0].get_z()-_center.get_z();
-  _radius = sqrt( _x*_x + _y*_y + _z*_z );
+  }
+  _x = _vertex[0].get_x() - _center.get_x();
+  _y = _vertex[0].get_y() - _center.get_y();
+  _z = _vertex[0].get_z() - _center.get_z();
+  _radius = sqrt(_x * _x + _y * _y + _z * _z);
   setup_box();
   mark_viz_stale();
   mark_internal_bounds_stale();

+ 45 - 24
panda/src/display/graphicsStateGuardian.cxx

@@ -1407,22 +1407,29 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     static const CPT_InternalName IN_quadraticAttenuation("quadraticAttenuation");
 
     if (attrib == IN_ambient) {
-#ifndef NDEBUG
       Light *light = np.node()->as_light();
       nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
-#endif
-      // Lights don't currently have an ambient color in Panda3D.
-      // We still have to support the attribute.
-      t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        LColor c = light->get_color();
+        c.componentwise_mult(_light_color_scale);
+        t.set_row(3, c);
+      } else {
+        // Non-ambient lights don't currently have an ambient color in Panda3D.
+        t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
+      }
       return &t;
 
     } else if (attrib == IN_diffuse) {
       Light *light = np.node()->as_light();
       nassertr(light != (Light *)NULL, &LMatrix4::ones_mat());
-
-      LColor c = light->get_color();
-      c.componentwise_mult(_light_color_scale);
-      t.set_row(3, c);
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no diffuse color.
+        t.set_row(3, LColor(0.0f, 0.0f, 0.0f, 1.0f));
+      } else {
+        LColor c = light->get_color();
+        c.componentwise_mult(_light_color_scale);
+        t.set_row(3, c);
+      }
       return &t;
 
     } else if (attrib == IN_specular) {
@@ -1432,7 +1439,11 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       return &t;
 
     } else if (attrib == IN_position) {
-      if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no position.
+        t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+        return &t;
+      } else if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
         DirectionalLight *light;
         DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
 
@@ -1458,7 +1469,11 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       }
 
     } else if (attrib == IN_halfVector) {
-      if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no half-vector.
+        t = LMatrix4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
+        return &t;
+      } else if (np.node()->is_of_type(DirectionalLight::get_class_type())) {
         DirectionalLight *light;
         DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
 
@@ -1490,19 +1505,25 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       }
 
     } else if (attrib == IN_spotDirection) {
-      LightLensNode *light;
-      DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
-      Lens *lens = light->get_lens();
-      nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
-
-      CPT(TransformState) transform =
-        get_scene()->get_cs_world_transform()->compose(
-          np.get_transform(_scene_setup->get_scene_root().get_parent()));
-
-      const LMatrix4 &light_mat = transform->get_mat();
-      LVector3 dir = lens->get_view_vector() * light_mat;
-      t.set_row(3, dir);
-      return &t;
+      if (np.node()->is_of_type(AmbientLight::get_class_type())) {
+        // Ambient light has no spot direction.
+        t.set_row(3, LVector3(0.0f, 0.0f, 0.0f));
+        return &t;
+      } else {
+        LightLensNode *light;
+        DCAST_INTO_R(light, np.node(), &LMatrix4::ident_mat());
+        Lens *lens = light->get_lens();
+        nassertr(lens != (Lens *)NULL, &LMatrix4::ident_mat());
+
+        CPT(TransformState) transform =
+          get_scene()->get_cs_world_transform()->compose(
+            np.get_transform(_scene_setup->get_scene_root().get_parent()));
+
+        const LMatrix4 &light_mat = transform->get_mat();
+        LVector3 dir = lens->get_view_vector() * light_mat;
+        t.set_row(3, dir);
+        return &t;
+      }
 
     } else if (attrib == IN_spotCutoff) {
       if (np.node()->is_of_type(Spotlight::get_class_type())) {

+ 6 - 0
panda/src/downloader/httpDate.cxx

@@ -223,8 +223,14 @@ HTTPDate(const string &format) {
   if (_time != (time_t)-1) {
     // Unfortunately, mktime() assumes local time; convert this back
     // to GMT.
+#ifdef IS_FREEBSD
+    time_t now = time(NULL);
+    struct tm *tp = localtime(&now);
+    _time -= tp->tm_gmtoff;
+#else /* IS_FREEBSD */
     extern long int timezone;
     _time -= timezone;
+#endif /* IS_FREEBSD */
   }
 #endif  // __GNUC__
 }

+ 19 - 22
panda/src/egg2pg/eggLoader.cxx

@@ -1865,6 +1865,10 @@ make_node(EggGroup *egg_group, PandaNode *parent) {
     node = new CollisionNode(egg_group->get_name());
 
     make_collision_solids(egg_group, egg_group, (CollisionNode *)node.p());
+
+    // Transform all of the collision solids into local space.
+    node->xform(LCAST(PN_stdfloat, egg_group->get_vertex_to_node()));
+
     if ((egg_group->get_collide_flags() & EggGroup::CF_keep) != 0) {
       // If we also specified to keep the geometry, continue the
       // traversal.  In this case, we create a new PandaNode to be the
@@ -1925,8 +1929,11 @@ make_node(EggGroup *egg_group, PandaNode *parent) {
     pnode->set_pos(center);
     pnode->set_color(color);
     pnode->set_radius(radius);
+
+    pnode->xform(LCAST(PN_stdfloat, egg_group->get_vertex_to_node()));
+
     node = pnode;
-    
+
   } else if (egg_group->get_switch_flag()) {
     if (egg_group->get_switch_fps() != 0.0) {
       // Create a sequence node.
@@ -2790,7 +2797,6 @@ find_first_polygon(EggGroup *egg_group) {
 bool EggLoader::
 make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags, 
             LPoint3 &center, PN_stdfloat &radius, LColor &color) {
-  bool success=false;
   EggGroup *geom_group = find_collision_geometry(egg_group, flags);
   if (geom_group != (EggGroup *)NULL) {
     // Collect all of the vertices.
@@ -2822,15 +2828,12 @@ make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags,
       d_center /= (double)num_vertices;
       //egg2pg_cat.debug() << "make_sphere d_center: " << d_center << "\n";
 
-      LMatrix4d mat = egg_group->get_vertex_to_node();
-      d_center = d_center * mat;
-
       // And the furthest vertex determines the radius.
       double radius2 = 0.0;
       for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
         EggVertex *vtx = (*vi);
         LPoint3d p3 = vtx->get_pos3();
-                LVector3d v = p3 * mat - d_center;
+                LVector3d v = p3 - d_center;
         radius2 = max(radius2, v.length_squared());
       }
 
@@ -2841,10 +2844,10 @@ make_sphere(EggGroup *egg_group, EggGroup::CollideFlags flags,
       vi = vertices.begin();
       EggVertex *clr_vtx = (*vi);
       color = clr_vtx->get_color();
-      success = true;
+      return true;
     }
   }
-  return success;
+  return false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -2900,9 +2903,8 @@ make_box(EggGroup *egg_group, EggGroup::CollideFlags flags,
                  max(max_pd[2], pos[2]));
     }
 
-    LMatrix4d mat = egg_group->get_vertex_to_node();
-    min_p = LCAST(PN_stdfloat, min_pd * mat);
-    max_p = LCAST(PN_stdfloat, max_pd * mat);
+    min_p = LCAST(PN_stdfloat, min_pd);
+    max_p = LCAST(PN_stdfloat, max_pd);
     return (min_pd != max_pd);
   }
   return false;
@@ -3170,7 +3172,6 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
     // also determine the centroid).  We compute this in node space.
     size_t num_vertices = vertices.size();
     if (num_vertices != 0) {
-      LMatrix4d mat = egg_group->get_vertex_to_node();
       pvector<LPoint3d> vpos;
       vpos.reserve(num_vertices);
       
@@ -3178,7 +3179,7 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode,
       pset<EggVertex *>::const_iterator vi;
       for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
         EggVertex *vtx = (*vi);
-        LPoint3d pos = vtx->get_pos3() * mat;
+        const LPoint3d &pos = vtx->get_pos3();
         vpos.push_back(pos);
         center += pos;
       }
@@ -3420,20 +3421,18 @@ create_collision_plane(EggPolygon *egg_poly, EggGroup *parent_group) {
       << "\n";
   }
 
-  LMatrix4d mat = egg_poly->get_vertex_to_node();
-
   pvector<LVertex> vertices;
   if (!egg_poly->empty()) {
     EggPolygon::const_iterator vi;
     vi = egg_poly->begin();
 
-    LVertexd vert = (*vi)->get_pos3() * mat;
+    LVertexd vert = (*vi)->get_pos3();
     vertices.push_back(LCAST(PN_stdfloat, vert));
 
     LVertexd last_vert = vert;
     ++vi;
     while (vi != egg_poly->end()) {
-      vert = (*vi)->get_pos3() * mat;
+      vert = (*vi)->get_pos3();
       if (!vert.almost_equal(last_vert)) {
         vertices.push_back(LCAST(PN_stdfloat, vert));
       }
@@ -3461,7 +3460,6 @@ void EggLoader::
 create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
                           EggGroup *parent_group,
                           EggGroup::CollideFlags flags) {
-  LMatrix4d mat = egg_poly->get_vertex_to_node();
 
   PT(EggGroup) group = new EggGroup;
 
@@ -3489,13 +3487,13 @@ create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
       EggPolygon::const_iterator vi;
       vi = poly->begin();
 
-      LVertexd vert = (*vi)->get_pos3() * mat;
+      LVertexd vert = (*vi)->get_pos3();
       vertices.push_back(LCAST(PN_stdfloat, vert));
 
       LVertexd last_vert = vert;
       ++vi;
       while (vi != poly->end()) {
-        vert = (*vi)->get_pos3() * mat;
+        vert = (*vi)->get_pos3();
         if (!vert.almost_equal(last_vert)) {
           vertices.push_back(LCAST(PN_stdfloat, vert));
         }
@@ -3513,12 +3511,11 @@ create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly,
       if (cspoly->is_valid()) {
         apply_collision_flags(cspoly, flags);
         cnode->add_solid(cspoly);
-      }        
+      }
     }
   }
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: EggLoader::create_collision_floor_mesh
 //       Access: Private

+ 49 - 2
panda/src/egg2pg/eggSaver.cxx

@@ -22,6 +22,7 @@
 #include "transformState.h"
 #include "colorScaleAttrib.h"
 #include "colorAttrib.h"
+#include "materialAttrib.h"
 #include "textureAttrib.h"
 #include "cullFaceAttrib.h"
 #include "transparencyAttrib.h"
@@ -347,7 +348,7 @@ convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, Charac
     CharacterJoint *character_joint = DCAST(CharacterJoint, bundleNode);
 
     LMatrix4 transformf;
-    character_joint->get_net_transform(transformf);
+    character_joint->get_transform(transformf);
     LMatrix4d transformd(LCAST(double, transformf));
     EggGroup *joint = new EggGroup(bundleNode->get_name());
     joint->add_matrix4(transformd);
@@ -577,6 +578,13 @@ convert_primitive(const GeomVertexData *vertex_data,
     }
   }
 
+  // Check for a material.
+  EggMaterial *egg_mat = (EggMaterial *)NULL;
+  const MaterialAttrib *ma = DCAST(MaterialAttrib, net_state->get_attrib(MaterialAttrib::get_class_type()));
+  if (ma != (const MaterialAttrib *)NULL) {
+    egg_mat = get_egg_material(ma->get_material());
+  }
+
   // Check for a texture.
   EggTexture *egg_tex = (EggTexture *)NULL;
   const TextureAttrib *ta = DCAST(TextureAttrib, net_state->get_attrib(TextureAttrib::get_class_type()));
@@ -703,11 +711,15 @@ convert_primitive(const GeomVertexData *vertex_data,
     // Huh, an unknown geometry type.
     return;
   }
-  
+
   for (int i = 0; i < num_primitives; ++i) {
     PT(EggPrimitive) egg_prim = (*make_func)();
 
     egg_parent->add_child(egg_prim);
+
+    if (egg_mat != (EggMaterial *)NULL) {
+      egg_prim->set_material(egg_mat);
+    }
     if (egg_tex != (EggTexture *)NULL) {
       egg_prim->set_texture(egg_tex);
     }
@@ -947,6 +959,41 @@ apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggSaver::get_egg_material
+//       Access: Private
+//  Description: Returns an EggMaterial pointer that corresponds to
+//               the indicated Material.
+////////////////////////////////////////////////////////////////////
+EggMaterial *EggSaver::
+get_egg_material(Material *mat) {
+  if (mat != (Material *)NULL) {
+    EggMaterial temp(mat->get_name());
+    if (mat->has_ambient()) {
+      temp.set_amb(mat->get_ambient());
+    }
+
+    if (mat->has_diffuse()) {
+      temp.set_diff(mat->get_diffuse());
+    }
+
+    if (mat->has_specular()) {
+      temp.set_spec(mat->get_specular());
+    }
+
+    if (mat->has_emission()) {
+      temp.set_emit(mat->get_emission());
+    }
+
+    temp.set_shininess(mat->get_shininess());
+    temp.set_local(mat->get_local());
+
+    return _materials.create_unique_material(temp, ~EggMaterial::E_mref_name);
+  }
+
+  return NULL;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggSaver::get_egg_texture
 //       Access: Private

+ 2 - 1
panda/src/egg2pg/eggSaver.h

@@ -92,6 +92,7 @@ private:
   bool apply_tags(EggGroup *egg_group, PandaNode *node);
   bool apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag);
 
+  EggMaterial *get_egg_material(Material *tex);
   EggTexture *get_egg_texture(Texture *tex);
 
   static EggPrimitive *make_egg_polygon();
@@ -102,8 +103,8 @@ private:
   PT(EggData) _data;
 
   PT(EggVertexPool) _vpool;
-  EggTextureCollection _textures;
   EggMaterialCollection _materials;
+  EggTextureCollection _textures;
 };
 
 #include "eggSaver.I"

+ 27 - 0
panda/src/express/datagram.I

@@ -82,6 +82,33 @@ operator = (const Datagram &copy) {
   _stdfloat_double = copy._stdfloat_double;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Datagram::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Datagram::
+Datagram(Datagram &&from) NOEXCEPT :
+  _data(move(from._data)),
+  _stdfloat_double(from._stdfloat_double)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: Datagram::Move Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void Datagram::
+operator = (Datagram &&from) NOEXCEPT {
+  _data = move(from._data);
+  _stdfloat_double = from._stdfloat_double;
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Datagram::add_bool
 //       Access: Public

+ 5 - 0
panda/src/express/datagram.h

@@ -48,6 +48,11 @@ PUBLISHED:
   INLINE Datagram(const Datagram &copy);
   INLINE void operator = (const Datagram &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE Datagram(Datagram &&from) NOEXCEPT;
+  INLINE void operator = (Datagram &&from) NOEXCEPT;
+#endif
+
   virtual ~Datagram();
 
   virtual void clear();

+ 90 - 0
panda/src/express/pointerToArray.I

@@ -77,6 +77,21 @@ PointerToArray(const PointerToArray<Element> &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: PointerToArray::Move Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE PointerToArray<Element>::
+PointerToArray(PointerToArray<Element> &&from) NOEXCEPT :
+  PointerToArrayBase<Element>(move(from)),
+  _type_handle(from._type_handle)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArray::begin
 //       Access: Public
@@ -682,6 +697,21 @@ operator = (const PointerToArray<Element> &copy) {
   return *this;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: PointerToArray::Assignment operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE PointerToArray<Element> &PointerToArray<Element>::
+operator = (PointerToArray<Element> &&from) NOEXCEPT {
+  _type_handle = from._type_handle;
+  ((PointerToArray<Element> *)this)->reassign(move(from));
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArray::clear
 //       Access: Public
@@ -736,6 +766,36 @@ ConstPointerToArray(const ConstPointerToArray<Element> &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Move Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element>::
+ConstPointerToArray(PointerToArray<Element> &&from) NOEXCEPT :
+  PointerToArrayBase<Element>(move(from)),
+  _type_handle(from._type_handle)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Move Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element>::
+ConstPointerToArray(ConstPointerToArray<Element> &&from) NOEXCEPT :
+  PointerToArrayBase<Element>(move(from)),
+  _type_handle(from._type_handle)
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ConstPointerToArray::begin
 //       Access: Public
@@ -1141,6 +1201,36 @@ operator = (const ConstPointerToArray<Element> &copy) {
   return *this;
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Assignment operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element> &ConstPointerToArray<Element>::
+operator = (PointerToArray<Element> &&from) NOEXCEPT {
+  _type_handle = from._type_handle;
+  ((ConstPointerToArray<Element> *)this)->reassign(move(from));
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ConstPointerToArray::Assignment operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE ConstPointerToArray<Element> &ConstPointerToArray<Element>::
+operator = (ConstPointerToArray<Element> &&from) NOEXCEPT {
+  _type_handle = from._type_handle;
+  ((ConstPointerToArray<Element> *)this)->reassign(move(from));
+  return *this;
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ConstPointerToArray::clear
 //       Access: Public

+ 23 - 0
panda/src/express/pointerToArray.h

@@ -151,6 +151,10 @@ public:
   INLINE PointerToArray(size_type n, const Element &value, TypeHandle type_handle = get_type_handle(Element));
   INLINE PointerToArray(const PointerToArray<Element> &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE PointerToArray(PointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
 public:
   // Duplicating the interface of vector.  The following member
   // functions are all const, because they do not reassign the
@@ -231,6 +235,12 @@ public:
   operator = (ReferenceCountedVector<Element> *ptr);
   INLINE PointerToArray<Element> &
   operator = (const PointerToArray<Element> &copy);
+
+#ifdef USE_MOVE_SEMANTICS
+  INLINE PointerToArray<Element> &
+  operator = (PointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
   INLINE void clear();
 
 private:
@@ -305,6 +315,11 @@ PUBLISHED:
   INLINE ConstPointerToArray(const PointerToArray<Element> &copy);
   INLINE ConstPointerToArray(const ConstPointerToArray<Element> &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE ConstPointerToArray(PointerToArray<Element> &&from) NOEXCEPT;
+  INLINE ConstPointerToArray(ConstPointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
   // Duplicating the interface of vector.
 
   INLINE iterator begin() const;
@@ -355,6 +370,14 @@ PUBLISHED:
   operator = (const PointerToArray<Element> &copy);
   INLINE ConstPointerToArray<Element> &
   operator = (const ConstPointerToArray<Element> &copy);
+
+#ifdef USE_MOVE_SEMANTICS
+  INLINE ConstPointerToArray<Element> &
+  operator = (PointerToArray<Element> &&from) NOEXCEPT;
+  INLINE ConstPointerToArray<Element> &
+  operator = (ConstPointerToArray<Element> &&from) NOEXCEPT;
+#endif
+
   INLINE void clear();
 
 private:

+ 25 - 11
panda/src/express/pointerToArrayBase.I

@@ -57,11 +57,11 @@ template<class Element>
 INLINE ReferenceCountedVector<Element>::
 ~ReferenceCountedVector() {
 }
- 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::size
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE TYPENAME ReferenceCountedVector<Element>::size_type ReferenceCountedVector<Element>::
@@ -72,7 +72,7 @@ size() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::insert
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE TYPENAME ReferenceCountedVector<Element>::iterator ReferenceCountedVector<Element>::
@@ -83,7 +83,7 @@ insert(iterator position, const Element &x) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::insert
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -94,7 +94,7 @@ insert(iterator position, size_type n, const Element &x) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::erase
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -105,7 +105,7 @@ erase(iterator position) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::erase
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -116,7 +116,7 @@ erase(iterator first, iterator last) {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::pop_back
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -127,7 +127,7 @@ pop_back() {
 ////////////////////////////////////////////////////////////////////
 //     Function: ReferenceCountedVector::clear
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE void ReferenceCountedVector<Element>::
@@ -138,7 +138,7 @@ clear() {
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArrayBase::Constructor
 //       Access: Protected
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE PointerToArrayBase<Element>::
@@ -150,7 +150,7 @@ PointerToArrayBase(ReferenceCountedVector<Element> *ptr) :
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArrayBase::Copy Constructor
 //       Access: Protected
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE PointerToArrayBase<Element>::
@@ -159,10 +159,24 @@ PointerToArrayBase(const PointerToArrayBase<Element> &copy) :
 {
 }
 
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: PointerToArrayBase::Move Constructor
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class Element>
+INLINE PointerToArrayBase<Element>::
+PointerToArrayBase(PointerToArrayBase<Element> &&from) NOEXCEPT :
+  PointerToBase<ReferenceCountedVector<Element> >(move(from))
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PointerToArrayBase::Destructor
 //       Access: Published
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 template<class Element>
 INLINE PointerToArrayBase<Element>::

+ 4 - 0
panda/src/express/pointerToArrayBase.h

@@ -82,6 +82,10 @@ protected:
   INLINE PointerToArrayBase(ReferenceCountedVector<Element> *ptr);
   INLINE PointerToArrayBase(const PointerToArrayBase<Element> &copy);
 
+#ifdef USE_MOVE_SEMANTICS
+  INLINE PointerToArrayBase(PointerToArrayBase<Element> &&from) NOEXCEPT;
+#endif
+
 PUBLISHED:
   INLINE ~PointerToArrayBase();
 };

+ 41 - 23
panda/src/express/pointerToArray_ext.I

@@ -22,26 +22,33 @@
 //               Python buffer protocol.
 ////////////////////////////////////////////////////////////////////
 template<class Element>
-void Extension<PointerToArray<Element> >::
+INLINE void Extension<PointerToArray<Element> >::
 __init__(PyObject *self, PyObject *source) {
 #if PY_VERSION_HEX >= 0x02060000
   if (PyObject_CheckBuffer(source)) {
     // User passed a buffer object.
     Py_buffer view;
     if (PyObject_GetBuffer(source, &view, PyBUF_CONTIG_RO) == -1) {
-      PyErr_SetString(PyExc_TypeError, "PointerToArray constructor requires a contiguous buffer");
+      PyErr_SetString(PyExc_TypeError,
+                      "PointerToArray constructor requires a contiguous buffer");
       return;
     }
 
     if (view.itemsize != 1 && view.itemsize != sizeof(Element)) {
-      PyErr_SetString(PyExc_TypeError, "buffer.itemsize does not match PointerToArray element size");
+      PyErr_SetString(PyExc_TypeError,
+                      "buffer.itemsize does not match PointerToArray element size");
       return;
     }
 
-    int num_elements = view.len / sizeof(Element);
-    this->_this->insert(this->_this->begin(), num_elements, Element());
+    if (view.len % sizeof(Element) != 0) {
+      PyErr_Format(PyExc_ValueError,
+                   "byte buffer is not a multiple of %zu bytes",
+                   sizeof(Element));
+      return;
+    }
 
     if (view.len > 0) {
+      this->_this->resize(view.len / sizeof(Element));
       memcpy(this->_this->p(), view.buf, view.len);
     }
 
@@ -52,21 +59,22 @@ __init__(PyObject *self, PyObject *source) {
 
   if (!PySequence_Check(source)) {
     // If passed with a non-sequence, this isn't the right constructor.
-    PyErr_SetString(PyExc_TypeError, "PointerToArray constructor requires a sequence or buffer object");
+    PyErr_SetString(PyExc_TypeError,
+                    "PointerToArray constructor requires a sequence or buffer object");
     return;
   }
 
   // If we were passed a Python string, then instead of storing it
   // character-at-a-time, just load the whole string as a data
-  // buffer.
+  // buffer.  Not sure if this case is still necessary - don't Python
+  // str/bytes objects export the buffer protocol, as above?
 #if PY_MAJOR_VERSION >= 3
   if (PyBytes_Check(source)) {
     int size = PyBytes_Size(source);
     if (size % sizeof(Element) != 0) {
-      ostringstream stream;
-      stream << "Buffer not a multiple of " << sizeof(Element) << " bytes";
-      string str = stream.str();
-      PyErr_SetString(PyExc_ValueError, str.c_str());
+      PyErr_Format(PyExc_ValueError,
+                   "bytes object is not a multiple of %zu bytes",
+                   sizeof(Element));
       return;
     }
 
@@ -85,10 +93,9 @@ __init__(PyObject *self, PyObject *source) {
   if (PyString_CheckExact(source)) {
     int size = PyString_Size(source);
     if (size % sizeof(Element) != 0) {
-      ostringstream stream;
-      stream << "Buffer not a multiple of " << sizeof(Element) << " bytes";
-      string str = stream.str();
-      PyErr_SetString(PyExc_ValueError, str.c_str());
+      PyErr_Format(PyExc_ValueError,
+                   "str object is not a multiple of %zu bytes",
+                   sizeof(Element));
       return;
     }
 
@@ -107,20 +114,29 @@ __init__(PyObject *self, PyObject *source) {
 
   // Now construct the internal list by copying the elements
   // one-at-a-time from Python.
+  PyObject *push_back = PyObject_GetAttrString(self, "push_back");
+  if (push_back == NULL) {
+    PyErr_BadArgument();
+    return;
+  }
+
+  // We need to initialize the this pointer before we can call push_back.
+  ((Dtool_PyInstDef *)self)->_ptr_to_object = (void *)this->_this;
+
   int size = PySequence_Size(source);
   for (int i = 0; i < size; ++i) {
     PyObject *item = PySequence_GetItem(source, i);
     if (item == NULL) {
       return;
     }
-    PyObject *result = PyObject_CallMethod(self, (char *)"push_back", (char *)"O", item);
+    PyObject *result = PyObject_CallFunctionObjArgs(push_back, item, NULL);
     Py_DECREF(item);
     if (result == NULL) {
       // Unable to add item--probably it wasn't of the appropriate type.
-      ostringstream stream;
-      stream << "Element " << i << " in sequence passed to PointerToArray constructor could not be added";
-      string str = stream.str();
-      PyErr_SetString(PyExc_TypeError, str.c_str());
+      PyErr_Print();
+      PyErr_Format(PyExc_TypeError,
+                   "Element %d in sequence passed to PointerToArray "
+                   "constructor could not be added", i);
       return;
     }
     Py_DECREF(result);
@@ -161,7 +177,9 @@ __setitem__(size_t n, const Element &value) {
 template<class Element>
 INLINE void Extension<ConstPointerToArray<Element> >::
 __init__(PyObject *self, PyObject *source) {
-  new (this->_this) ConstPointerToArray<Element>(get_type_handle(Element));
+  PointerToArray<Element> array;
+  invoke_extension(&array).__init__(self, source);
+  *(this->_this) = MOVE(array);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -240,7 +258,7 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
 
   if (view->internal != NULL) {
     // Oh, right, let's not forget to unref this.
-    ((const PointerToArray<Element> *) view->internal)->unref();
+    unref_delete((const PointerToArray<Element> *)view->internal);
     view->internal = NULL;
   }
 }
@@ -314,7 +332,7 @@ __releasebuffer__(PyObject *self, Py_buffer *view) const {
 
   if (view->internal != NULL) {
     // Oh, right, let's not forget to unref this.
-    ((const PointerToArray<Element> *) view->internal)->unref();
+    unref_delete((const PointerToArray<Element> *)view->internal);
     view->internal = NULL;
   }
 }

+ 20 - 0
panda/src/express/pointerToArray_ext.h

@@ -66,6 +66,26 @@ public:
 #endif
 };
 
+#ifdef _MSC_VER
+// Ugh... MSVC needs this because they still don't have a decent linker.
+#include "PTA_uchar.h"
+#include "PTA_ushort.h"
+#include "PTA_float.h"
+#include "PTA_double.h"
+#include "PTA_int.h"
+
+template class EXPORT_THIS Extension<PTA_uchar>;
+template class EXPORT_THIS Extension<PTA_ushort>;
+template class EXPORT_THIS Extension<PTA_float>;
+template class EXPORT_THIS Extension<PTA_double>;
+template class EXPORT_THIS Extension<PTA_int>;
+template class EXPORT_THIS Extension<CPTA_uchar>;
+template class EXPORT_THIS Extension<CPTA_ushort>;
+template class EXPORT_THIS Extension<CPTA_float>;
+template class EXPORT_THIS Extension<CPTA_double>;
+template class EXPORT_THIS Extension<CPTA_int>;
+#endif
+
 // This macro is used to map a data type to a format code
 // as used in the Python 'struct' and 'array' modules.
 #define get_format_code(type) _get_format_code((const type *)0)

+ 8 - 3
panda/src/ffmpeg/ffmpegAudioCursor.cxx

@@ -148,7 +148,11 @@ FfmpegAudioCursor(FfmpegAudio *src) :
   _can_seek = true;
   _can_seek_fast = true;
 
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+  _frame = av_frame_alloc();
+#else
   _frame = avcodec_alloc_frame();
+#endif
 
   _packet = new AVPacket;
   _buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE / 2;
@@ -194,7 +198,9 @@ FfmpegAudioCursor::
 void FfmpegAudioCursor::
 cleanup() {
   if (_frame) {
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101)
+    av_frame_free(&_frame);
+#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
     avcodec_free_frame(&_frame);
 #else
     av_free(&_frame);
@@ -313,8 +319,7 @@ reload_buffer() {
 #ifdef HAVE_SWRESAMPLE
         if (_resample_ctx) {
           // Resample the data to signed 16-bit sample format.
-          uint8_t* out[SWR_CH_MAX] = {(uint8_t*) _buffer, NULL};
-          bufsize = swr_convert(_resample_ctx, out, _buffer_size / 2, (const uint8_t**)_frame->extended_data, _frame->nb_samples);
+          bufsize = swr_convert(_resample_ctx, (uint8_t **)&_buffer, _buffer_size / 2, (const uint8_t**)_frame->extended_data, _frame->nb_samples);
           bufsize *= _audio_channels * 2;
         } else
 #endif

+ 5 - 0
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -94,8 +94,13 @@ init_from(FfmpegVideo *source) {
                                 PIX_FMT_BGR24, SWS_BILINEAR | SWS_PRINT_INFO, NULL, NULL, NULL);
 #endif  // HAVE_SWSCALE
 
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100)
+  _frame = av_frame_alloc();
+  _frame_out = av_frame_alloc();
+#else
   _frame = avcodec_alloc_frame();
   _frame_out = avcodec_alloc_frame();
+#endif
 
   if ((_frame == 0)||(_frame_out == 0)) {
     cleanup();

+ 39 - 5
panda/src/framework/windowFramework.cxx

@@ -794,15 +794,28 @@ next_anim_control() {
   if (_anim_controls_enabled) {
     destroy_anim_controls();
 
+    if (_anim_controls.get_num_anims() == 0) {
+      set_anim_controls(false);
+      return;
+    }
+
+    // Stop the active animation.
+    pause_button();
     ++_anim_index;
+
     if (_anim_index >= _anim_controls.get_num_anims()) {
       set_anim_controls(false);
+      _anim_controls.loop_all(true);
     } else {
       create_anim_controls();
+      play_button();
     }
   } else {
     _anim_index = 0;
     set_anim_controls(true);
+    if (_anim_controls.get_num_anims() > 0) {
+      play_button();
+    }
   }
 }
 
@@ -1447,6 +1460,23 @@ create_anim_controls() {
   AnimControl *control = _anim_controls.get_anim(_anim_index);
   nassertv(control != (AnimControl *)NULL);
 
+  if (control->get_num_frames() <= 1) {
+    // Don't show the controls when the animation has only 0 or 1 frames.
+    ostringstream text;
+    text << _anim_controls.get_anim_name(_anim_index);
+    text << " (" << control->get_num_frames() << " frame"
+         << ((control->get_num_frames() == 1) ? "" : "s") << ")";
+
+    PT(TextNode) label = new TextNode("label");
+    label->set_align(TextNode::A_center);
+    label->set_text(text.str());
+    NodePath tnp = _anim_controls_group.attach_new_node(label);
+    tnp.set_pos(0.0f, 0.0f, 0.07f);
+    tnp.set_scale(0.1f);
+
+    return;
+  }
+
   PT(TextNode) label = new TextNode("anim_name");
   label->set_align(TextNode::A_left);
   label->set_text(_anim_controls.get_anim_name(_anim_index));
@@ -1521,13 +1551,17 @@ update_anim_controls() {
   AnimControl *control = _anim_controls.get_anim(_anim_index);
   nassertv(control != (AnimControl *)NULL);
 
-  if (_anim_slider->is_button_down()) {
-    control->pose((int)(_anim_slider->get_value() + 0.5));
-  } else {
-    _anim_slider->set_value((PN_stdfloat)control->get_frame());
+  if (_anim_slider != NULL) {
+    if (_anim_slider->is_button_down()) {
+      control->pose((int)(_anim_slider->get_value() + 0.5));
+    } else {
+      _anim_slider->set_value((PN_stdfloat)control->get_frame());
+    }
   }
 
-  _frame_number->set_text(format_string(control->get_frame()));
+  if (_frame_number != NULL) {
+    _frame_number->set_text(format_string(control->get_frame()));
+  }
 
   control->set_play_rate(_play_rate_slider->get_value());
 }

+ 5 - 2
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -558,10 +558,13 @@ reset() {
   _glPrimitiveRestartIndex = NULL;
 
   if (gl_support_primitive_restart_index) {
-    if (is_at_least_gl_version(4, 3) || has_extension("GL_ARB_ES3_compatibility")) {
+    if ((is_at_least_gl_version(4, 3) || has_extension("GL_ARB_ES3_compatibility")) &&
+        _gl_renderer.substr(0, 7) != "Gallium") {
       // As long as we enable this, OpenGL will always use the highest possible index
       // for a numeric type as strip cut index, which coincides with our convention.
-      // This saves us a call to glPrimitiveRestartIndex.
+      // This saves us a call to glPrimitiveRestartIndex
+      // ... of course, though, the Gallium driver bugs out here.  See also:
+      // https://www.panda3d.org/forums/viewtopic.php?f=5&t=17512
       glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
       _supported_geom_rendering |= Geom::GR_strip_cut_index;
 

+ 1 - 1
panda/src/gobj/internalName_ext.cxx

@@ -36,7 +36,7 @@ make(PyUnicodeObject *str) {
     }
 
     string name(c_str, len);
-    return InternalName::make(c_str, len);
+    return InternalName::make(name);
   }
 
   InternalName::PyInternTable::const_iterator it;

+ 1 - 0
panda/src/gobj/samplerContext.h

@@ -18,6 +18,7 @@
 #include "pandabase.h"
 
 #include "adaptiveLru.h"
+#include "simpleLru.h"
 #include "samplerState.h"
 #include "savedContext.h"
 

+ 1 - 0
panda/src/gobj/samplerState.h

@@ -21,6 +21,7 @@
 #include "namable.h"
 #include "luse.h"
 #include "numeric_types.h"
+#include "bamReader.h"
 #include "config_gobj.h"
 
 class FactoryParams;

+ 1 - 1
panda/src/linmath/lmatrix3_src.cxx

@@ -353,7 +353,7 @@ bool FLOATNAME(LMatrix3)::
 almost_equal(const FLOATNAME(LMatrix3) &other, FLOATTYPE threshold) const {
   TAU_PROFILE("bool LMatrix3::almost_equal(const LMatrix3 &, FLOATTYPE)", " ", TAU_USER);
 #ifdef HAVE_EIGEN
-  return ((_m - other._m).cwiseAbs().maxCoeff() < NEARLY_ZERO(FLOATTYPE));
+  return ((_m - other._m).cwiseAbs().maxCoeff() < threshold);
 #else
   return (IS_THRESHOLD_EQUAL((*this)(0, 0), other(0, 0), threshold) &&
           IS_THRESHOLD_EQUAL((*this)(0, 1), other(0, 1), threshold) &&

+ 1 - 1
panda/src/linmath/lmatrix4_src.cxx

@@ -296,7 +296,7 @@ bool FLOATNAME(LMatrix4)::
 almost_equal(const FLOATNAME(LMatrix4) &other, FLOATTYPE threshold) const {
   TAU_PROFILE("bool LMatrix4::almost_equal(const LMatrix4 &, FLOATTYPE)", " ", TAU_USER);
 #ifdef HAVE_EIGEN
-  return ((_m - other._m).cwiseAbs().maxCoeff() < NEARLY_ZERO(FLOATTYPE));
+  return ((_m - other._m).cwiseAbs().maxCoeff() < threshold);
 #else
   return (IS_THRESHOLD_EQUAL((*this)(0, 0), other(0, 0), threshold) &&
           IS_THRESHOLD_EQUAL((*this)(0, 1), other(0, 1), threshold) &&

+ 28 - 0
panda/src/mathutil/pta_LMatrix3_ext.h

@@ -0,0 +1,28 @@
+// Filename: pta_LMatrix3_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 PTA_LMATRIX3_EXT_H
+#define PTA_LMATRIX3_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LMatrix3.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LMatrix3f>;
+template class EXPORT_THIS Extension<PTA_LMatrix3d>;
+template class EXPORT_THIS Extension<CPTA_LMatrix3f>;
+template class EXPORT_THIS Extension<CPTA_LMatrix3d>;
+#endif
+
+#endif

+ 28 - 0
panda/src/mathutil/pta_LMatrix4_ext.h

@@ -0,0 +1,28 @@
+// Filename: pta_LMatrix4_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 PTA_LMATRIX4_EXT_H
+#define PTA_LMATRIX4_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LMatrix4.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LMatrix4f>;
+template class EXPORT_THIS Extension<PTA_LMatrix4d>;
+template class EXPORT_THIS Extension<CPTA_LMatrix4f>;
+template class EXPORT_THIS Extension<CPTA_LMatrix4d>;
+#endif
+
+#endif

+ 30 - 0
panda/src/mathutil/pta_LVecBase2_ext.h

@@ -0,0 +1,30 @@
+// Filename: pta_LVecBase2_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 PTA_LVECBASE2_EXT_H
+#define PTA_LVECBASE2_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LVecBase2.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LVecBase2f>;
+template class EXPORT_THIS Extension<PTA_LVecBase2d>;
+template class EXPORT_THIS Extension<PTA_LVecBase2i>;
+template class EXPORT_THIS Extension<CPTA_LVecBase2f>;
+template class EXPORT_THIS Extension<CPTA_LVecBase2d>;
+template class EXPORT_THIS Extension<CPTA_LVecBase2i>;
+#endif
+
+#endif

+ 30 - 0
panda/src/mathutil/pta_LVecBase3_ext.h

@@ -0,0 +1,30 @@
+// Filename: pta_LVecBase3_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 PTA_LVECBASE3_EXT_H
+#define PTA_LVECBASE3_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LVecBase3.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LVecBase3f>;
+template class EXPORT_THIS Extension<PTA_LVecBase3d>;
+template class EXPORT_THIS Extension<PTA_LVecBase3i>;
+template class EXPORT_THIS Extension<CPTA_LVecBase3f>;
+template class EXPORT_THIS Extension<CPTA_LVecBase3d>;
+template class EXPORT_THIS Extension<CPTA_LVecBase3i>;
+#endif
+
+#endif

+ 30 - 0
panda/src/mathutil/pta_LVecBase4_ext.h

@@ -0,0 +1,30 @@
+// Filename: pta_LVecBase4_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 PTA_LVECBASE4_EXT_H
+#define PTA_LVECBASE4_EXT_H
+
+#include "pointerToArray_ext.h"
+#include "pta_LVecBase4.h"
+
+#if defined(_MSC_VER) && !defined(CPPPARSER)
+template class EXPORT_THIS Extension<PTA_LVecBase4f>;
+template class EXPORT_THIS Extension<PTA_LVecBase4d>;
+template class EXPORT_THIS Extension<PTA_LVecBase4i>;
+template class EXPORT_THIS Extension<CPTA_LVecBase4f>;
+template class EXPORT_THIS Extension<CPTA_LVecBase4d>;
+template class EXPORT_THIS Extension<CPTA_LVecBase4i>;
+#endif
+
+#endif

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

@@ -79,6 +79,7 @@
     occluderNode.I occluderNode.h \
     pandaNode.I pandaNode.h \
     pandaNodeChain.I pandaNodeChain.h \
+    paramNodePath.I paramNodePath.h \
     planeNode.I planeNode.h \
     polylightEffect.I polylightEffect.h \
     polylightNode.I polylightNode.h \
@@ -184,6 +185,7 @@
     occluderNode.cxx \
     pandaNode.cxx \
     pandaNodeChain.cxx \
+    paramNodePath.cxx \
     planeNode.cxx \
     polylightEffect.cxx \
     polylightNode.cxx \
@@ -286,6 +288,7 @@
     pandaNode.I pandaNode.h \
     pandaNode_ext.h pandaNode_ext.cxx \
     pandaNodeChain.I pandaNodeChain.h \
+    paramNodePath.I paramNodePath.h \
     planeNode.I planeNode.h \
     polylightEffect.I polylightEffect.h \
     polylightNode.I polylightNode.h \

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

@@ -60,6 +60,7 @@
 #include "nodePath.h"
 #include "nodePathComponent.h"
 #include "pandaNode.h"
+#include "paramNodePath.h"
 #include "planeNode.h"
 #include "polylightEffect.h"
 #include "polylightNode.h"
@@ -436,6 +437,7 @@ init_libpgraph() {
   NodePathComponent::init_type();
   PandaNode::init_type();
   PandaNodePipelineReader::init_type();
+  ParamNodePath::init_type();
   PlaneNode::init_type();
   PolylightNode::init_type();
   PolylightEffect::init_type();

+ 1 - 0
panda/src/pgraph/cullBinManager.h

@@ -20,6 +20,7 @@
 #include "cullBinEnums.h"
 #include "pointerTo.h"
 #include "pvector.h"
+#include "epvector.h"
 #include "pmap.h"
 #include "vector_int.h"
 #include "pStatCollector.h"

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

@@ -38,6 +38,9 @@
 #include "pta_LVecBase3.h"
 #include "pta_LVecBase2.h"
 #include "stl_compares.h"
+#include "shaderInput.h"
+#include "textureCollection.h"
+#include "textureStageCollection.h"
 
 class NodePathCollection;
 class FindApproxPath;
@@ -1009,9 +1012,6 @@ private:
 
 INLINE ostream &operator << (ostream &out, const NodePath &node_path);
 
-// We have to put this down here, to work around a circular include.
-#include "shaderInput.h"
-
 #include "nodePath.I"
 
 #endif

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

@@ -21,6 +21,7 @@
 #include "occluderNode.cxx"
 #include "pandaNode.cxx"
 #include "pandaNodeChain.cxx"
+#include "paramNodePath.cxx"
 #include "planeNode.cxx"
 #include "polylightEffect.cxx"
 #include "polylightNode.cxx"

+ 60 - 0
panda/src/pgraph/paramNodePath.I

@@ -0,0 +1,60 @@
+// Filename: paramNodePath.I
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::Constructor
+//       Access: Published
+//  Description: Creates a new ParamNodePath storing the given
+//               node path object.
+////////////////////////////////////////////////////////////////////
+INLINE ParamNodePath::
+ParamNodePath(const NodePath &node_path) :
+  _node_path(node_path)
+{
+}
+
+#ifdef USE_MOVE_SEMANTICS
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::Move Constructor
+//       Access: Published
+//  Description: Creates a new ParamNodePath storing the given
+//               node path object.
+////////////////////////////////////////////////////////////////////
+INLINE ParamNodePath::
+ParamNodePath(NodePath &&node_path) NOEXCEPT :
+  _node_path(move(node_path))
+{
+}
+#endif  // USE_MOVE_SEMANTICS
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::get_value_type
+//       Access: Published, Virtual
+//  Description: Returns NodePath::get_class_type().
+////////////////////////////////////////////////////////////////////
+INLINE TypeHandle ParamNodePath::
+get_value_type() const {
+  return NodePath::get_class_type();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::get_value
+//       Access: Published
+//  Description: Retrieves the NodePath stored in the parameter.
+////////////////////////////////////////////////////////////////////
+INLINE const NodePath &ParamNodePath::
+get_value() const {
+  return _node_path;
+}

+ 108 - 0
panda/src/pgraph/paramNodePath.cxx

@@ -0,0 +1,108 @@
+// Filename: paramNodePath.cxx
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "paramNodePath.h"
+#include "dcast.h"
+#include "pandaNode.h"
+
+TypeHandle ParamNodePath::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::output
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+output(ostream &out) const {
+  out << "node path " << _node_path;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               ParamValue.
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  ParamValueBase::write_datagram(manager, dg);
+
+  // We can't store a NodePath, so we store a pointer to the
+  // underlying node, and pray that there is an unambiguous path
+  // from the root to it.
+  if (_node_path.is_empty()) {
+    manager->write_pointer(dg, NULL);
+  } else {
+    manager->write_pointer(dg, _node_path.node());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::complete_pointers
+//       Access: Public, Virtual
+//  Description: Receives an array of pointers, one for each time
+//               manager->read_pointer() was called in fillin().
+//               Returns the number of pointers processed.
+////////////////////////////////////////////////////////////////////
+int ParamNodePath::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = ParamValueBase::complete_pointers(p_list, manager);
+  _node_path = NodePath(DCAST(PandaNode, p_list[pi++]));
+
+  return pi;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type ParamValue is encountered
+//               in the Bam file.  It should create the ParamValue
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *ParamNodePath::
+make_from_bam(const FactoryParams &params) {
+  ParamNodePath *param = new ParamNodePath;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  param->fillin(scan, manager);
+
+  return param;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ParamNodePath::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new ParamValue.
+////////////////////////////////////////////////////////////////////
+void ParamNodePath::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  ParamValueBase::fillin(scan, manager);
+  manager->read_pointer(scan);
+}

+ 75 - 0
panda/src/pgraph/paramNodePath.h

@@ -0,0 +1,75 @@
+// Filename: paramNodePath.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 PARAMNODEPATH_H
+#define PARAMNODEPATH_H
+
+#include "pandabase.h"
+#include "paramValue.h"
+#include "nodePath.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : ParamNodePath
+// Description : A class object for storing a NodePath as a parameter.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_GOBJ ParamNodePath : public ParamValueBase {
+protected:
+  INLINE ParamNodePath() {};
+
+PUBLISHED:
+  INLINE ParamNodePath(const NodePath &node_path);
+
+#ifdef USE_MOVE_SEMANTICS
+  INLINE ParamNodePath(NodePath &&node_path) NOEXCEPT;
+#endif
+
+  INLINE virtual TypeHandle get_value_type() const;
+  INLINE const NodePath &get_value() const;
+
+  virtual void output(ostream &out) const;
+
+private:
+  NodePath _node_path;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual int complete_pointers(TypedWritable **plist,
+                                BamReader *manager);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ParamValueBase::init_type();
+    register_type(_type_handle, "ParamNodePath",
+                  ParamValueBase::get_class_type());
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "paramNodePath.I"
+
+#endif

+ 2 - 3
panda/src/pgraph/polylightNode.I

@@ -85,11 +85,10 @@ disable(){
 //  Description: Set this light's position
 ////////////////////////////////////////////////////////////////////
 INLINE void PolylightNode::
-set_pos(const LVecBase3 &position){
+set_pos(const LPoint3 &position) {
   _position = position;
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PolylightNode::set_pos
 //       Access: Published
@@ -107,7 +106,7 @@ set_pos(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z){
 //       Access: Published
 //  Description: Returns position as a LPoint3
 ////////////////////////////////////////////////////////////////////
-INLINE LVecBase3 PolylightNode::
+INLINE LPoint3 PolylightNode::
 get_pos() const {
   return _position;
 }

+ 24 - 0
panda/src/pgraph/polylightNode.cxx

@@ -65,6 +65,30 @@ make_copy() const {
   return new PolylightNode(*this);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PolylightNode::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this node by the indicated
+//               matrix, if it means anything to do so.  For most
+//               kinds of nodes, this does nothing.
+////////////////////////////////////////////////////////////////////
+void PolylightNode::
+xform(const LMatrix4 &mat) {
+  nassertv(!mat.is_nan());
+
+  if (mat.almost_equal(LMatrix4::ident_mat())) {
+    return;
+  }
+
+  _position = _position * mat;
+
+  // This is a little cheesy and fails miserably in the presence of a
+  // non-uniform scale.
+  LVector3 radius_v = LVector3(_radius, 0.0f, 0.0f) * mat;
+  _radius = length(radius_v);
+  mark_internal_bounds_stale();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PolylightNode::Constructor
 //       Access: Public

+ 4 - 4
panda/src/pgraph/polylightNode.h

@@ -58,9 +58,9 @@ PUBLISHED:
   PolylightNode(const string &name);
   INLINE void enable();
   INLINE void disable();
-  INLINE void set_pos(const LVecBase3 &position);
+  INLINE void set_pos(const LPoint3 &position);
   INLINE void set_pos(PN_stdfloat x,PN_stdfloat y, PN_stdfloat z);
-  INLINE LVecBase3 get_pos() const;
+  INLINE LPoint3 get_pos() const;
   INLINE void set_color(const LColor &color);
   INLINE void set_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b);
   INLINE LColor get_color() const;
@@ -100,11 +100,11 @@ PUBLISHED:
 public:
   LColor flicker() const;
   virtual PandaNode *make_copy() const;
+  virtual void xform(const LMatrix4 &mat);
 
-  
 private:
   bool _enabled;
-  LVecBase3 _position;
+  LPoint3 _position;
   LColor _color;
   PN_stdfloat _radius;
   Attenuation_Type _attenuation_type;

+ 0 - 27
panda/src/pgraph/shaderInput.I

@@ -119,23 +119,6 @@ ShaderInput(CPT_InternalName name, ParamValueBase *param, int priority) :
 {
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: ShaderInput::Constructor
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE ShaderInput::
-ShaderInput(CPT_InternalName name, const NodePath &np, int priority) :
-  _name(MOVE(name)),
-  _type(M_nodepath),
-  _priority(priority),
-  _stored_nodepath(np),
-  _bind_layer(0),
-  _bind_level(0),
-  _access(A_read)
-{
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: ShaderInput::Constructor
 //       Access: Published
@@ -678,16 +661,6 @@ get_texture() const {
   return DCAST(Texture, _value);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: ShaderInput::get_nodepath
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE const NodePath &ShaderInput::
-get_nodepath() const {
-  return _stored_nodepath;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: ShaderInput::get_vector
 //       Access: Published

+ 30 - 2
panda/src/pgraph/shaderInput.cxx

@@ -13,6 +13,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "shaderInput.h"
+#include "paramNodePath.h"
 
 TypeHandle ShaderInput::_type_handle;
 
@@ -32,13 +33,40 @@ get_blank() {
   return blank;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ShaderInput::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+ShaderInput::
+ShaderInput(CPT_InternalName name, const NodePath &np, int priority) :
+  _name(MOVE(name)),
+  _type(M_nodepath),
+  _priority(priority),
+  _value(new ParamNodePath(np)),
+  _bind_layer(0),
+  _bind_level(0),
+  _access(A_read)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ShaderInput::get_nodepath
+//       Access: Published
+//  Description: Warning: no error checking is done.  This *will*
+//               crash if get_value_type() is not M_nodepath.
+////////////////////////////////////////////////////////////////////
+const NodePath &ShaderInput::
+get_nodepath() const {
+  return DCAST(ParamNodePath, _value)->get_value();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ShaderInput::register_with_read_factory
 //       Access: Public, Static
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 void ShaderInput::
 register_with_read_factory() {
   // IMPLEMENT ME
 }
-

+ 4 - 4
panda/src/pgraph/shaderInput.h

@@ -19,7 +19,6 @@
 #include "pandabase.h"
 #include "typedWritableReferenceCount.h"
 #include "pointerTo.h"
-#include "nodePath.h"
 #include "internalName.h"
 #include "paramValue.h"
 #include "pta_float.h"
@@ -53,7 +52,6 @@ PUBLISHED:
 
   static const ShaderInput *get_blank();
   INLINE ShaderInput(CPT_InternalName name, int priority=0);
-  INLINE ShaderInput(CPT_InternalName name, const NodePath &np, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, const SamplerState &sampler, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, Texture *tex, bool read, bool write, int z=-1, int n=0, int priority=0);
@@ -90,6 +88,8 @@ PUBLISHED:
   INLINE ShaderInput(CPT_InternalName name, const LVecBase3i &vec, int priority=0);
   INLINE ShaderInput(CPT_InternalName name, const LVecBase2i &vec, int priority=0);
 
+  ShaderInput(CPT_InternalName name, const NodePath &np, int priority=0);
+
   enum ShaderInputType {
     M_invalid = 0,
     M_texture,
@@ -105,11 +105,12 @@ PUBLISHED:
   INLINE int get_value_type() const;
   INLINE int get_priority() const;
   INLINE Texture *get_texture() const;
-  INLINE const NodePath &get_nodepath() const;
   INLINE const LVecBase4 &get_vector() const;
   INLINE const Shader::ShaderPtrData &get_ptr() const;
   INLINE const SamplerState &get_sampler() const;
 
+  const NodePath &get_nodepath() const;
+
 public:
   INLINE ParamValueBase *get_param() const;
 
@@ -118,7 +119,6 @@ public:
 private:
   SamplerState _sampler;
   LVecBase4 _stored_vector;
-  NodePath _stored_nodepath;
   Shader::ShaderPtrData _stored_ptr;
   CPT_InternalName _name;
   PT(TypedWritableReferenceCount) _value;

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

@@ -18,6 +18,7 @@
 #include "virtualFileSystem.h"
 #include "loader.h"
 #include "shader.h"
+#include "string_utils.h"
 
 ShaderPool *ShaderPool::_global_ptr = (ShaderPool *)NULL;
 

+ 2 - 0
panda/src/putil/Sources.pp

@@ -27,6 +27,7 @@
     cachedTypedWritableReferenceCount.h cachedTypedWritableReferenceCount.I \
     callbackData.h callbackData.I \
     callbackObject.h callbackObject.I \
+    callbackObject_ext.h callbackObject_ext.I \
     clockObject.h clockObject.I \
     collideMask.h \
     copyOnWriteObject.h copyOnWriteObject.I \
@@ -138,6 +139,7 @@
     cachedTypedWritableReferenceCount.h cachedTypedWritableReferenceCount.I \
     callbackData.h callbackData.I \
     callbackObject.h callbackObject.I \
+    callbackObject_ext.h callbackObject_ext.I \
     clockObject.h clockObject.I \
     collideMask.h \
     copyOnWriteObject.h copyOnWriteObject.I \

+ 2 - 0
panda/src/putil/callbackObject.h

@@ -38,6 +38,8 @@ public:
 PUBLISHED:
   virtual void output(ostream &out) const;
 
+  EXTENSION(static PT(CallbackObject) make(PyObject *function));
+
 public:
   virtual void do_callback(CallbackData *cbdata);
 

+ 32 - 0
panda/src/putil/callbackObject_ext.I

@@ -0,0 +1,32 @@
+// Filename: callbackObject_ext.I
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: Extension<CallbackObject>::make
+//       Access: Published
+//  Description: This static constructor is merely provided so that
+//               interrogate can automatically coerce Python
+//               functions when passing them to a C++ function that
+//               accepts a CallbackObject.
+////////////////////////////////////////////////////////////////////
+INLINE PT(CallbackObject) Extension<CallbackObject>::
+make(PyObject *function) {
+  if (function != Py_None && !PyCallable_Check(function)) {
+    PyErr_SetString(PyExc_TypeError, "expected callable or None");
+    return NULL;
+  } else {
+    return new PythonCallbackObject(function);
+  }
+}

+ 47 - 0
panda/src/putil/callbackObject_ext.h

@@ -0,0 +1,47 @@
+// Filename: callbackObject_ext.h
+// Created by:  rdb (25Feb15)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 CALLBACKOBJECT_EXT_H
+#define CALLBACKOBJECT_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "pythonCallbackObject.h"
+#include "py_panda.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Extension<CallbackObject>
+// Description : This class defines the extension methods for
+//               CallbackObject, which are called instead of
+//               any C++ methods with the same prototype.
+//
+//               This just defines a static constructor, which makes
+//               it possible for Interrogate to automatically accept
+//               a Python function wherever a CallbackObject is
+//               accepted.
+////////////////////////////////////////////////////////////////////
+template<>
+class Extension<CallbackObject> : public ExtensionBase<CallbackObject> {
+public:
+  INLINE static PT(CallbackObject) make(PyObject *function);
+};
+
+#include "callbackObject_ext.I"
+
+#endif  // HAVE_PYTHON
+
+#endif  // CALLBACKOBJECT_EXT_H

+ 270 - 42
pandatool/src/daeegg/daeCharacter.cxx

@@ -17,88 +17,316 @@
 #include "fcollada_utils.h"
 #include "pt_EggVertex.h"
 #include "eggXfmSAnim.h"
+#include "daeToEggConverter.h"
+#include "daeMaterials.h"
+
+#include "eggExternalReference.h"
 
 #include "FCDocument/FCDocument.h"
 #include "FCDocument/FCDController.h"
+#include "FCDocument/FCDGeometry.h"
 #include "FCDocument/FCDSceneNodeTools.h"
 
+#include "FCDocument/FCDSceneNode.h"
+#include "FCDocument/FCDTransform.h"
+#include "FCDocument/FCDAnimated.h"
+#include "FCDocument/FCDAnimationCurve.h"
+#include "FCDocument/FCDAnimationKey.h"
+
 TypeHandle DaeCharacter::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DaeCharacter::Constructor
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 DaeCharacter::
-DaeCharacter(const string name, const FCDControllerInstance* controller_instance) {
-  _controller_instance = (FCDControllerInstance*) controller_instance;
-  _name = name;
-  _frame_rate = 0;
-  _skin_controller = NULL;
+DaeCharacter(EggGroup *node_group, const FCDControllerInstance *instance) :
+  _node_group(node_group),
+  _name(node_group->get_name()),
+  _instance(instance),
+  _skin_controller(NULL),
+  _skin_mesh(NULL) {
+
+  _bind_shape_mat = LMatrix4d::ident_mat();
+
   // If it's a skin controller, add the controller joints.
-  FCDController* controller = (FCDController*) controller_instance->GetEntity();
-  if (controller == NULL) return;
+  const FCDController *controller = (const FCDController *)instance->GetEntity();
+  if (controller == NULL) {
+    return;
+  }
+  _skin_mesh = controller->GetBaseGeometry()->GetMesh();
+
   if (controller->IsSkin()) {
     _skin_controller = controller->GetSkinController();
-    if (_skin_controller == NULL) return;
-    for (size_t j = 0; j < _skin_controller->GetJointCount(); ++j) {
-      _controller_joints[FROM_FSTRING(_skin_controller->GetJoint(j)->GetId())] = (FCDSkinControllerJoint*) _skin_controller->GetJoint(j);
+    _bind_shape_mat = DAEToEggConverter::convert_matrix(_skin_controller->GetBindShapeTransform());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::bind_joints
+//       Access: Public
+//  Description: Binds the joints to the character.  This means
+//               changing them to the bind pose.  It is necessary
+//               to call this before process_skin_geometry.
+//
+//               Returns the root group.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+bind_joints(JointMap &joint_map) {
+  _joints.clear();
+
+  size_t num_joints = _skin_controller->GetJointCount();
+  _joints.reserve(num_joints);
+
+  // Record the bind pose for each joint.
+  for (size_t j = 0; j < num_joints; ++j) {
+    const FCDSkinControllerJoint *skin_joint = _skin_controller->GetJoint(j);
+    string sid = FROM_FSTRING(skin_joint->GetId());
+    LMatrix4d bind_pose;
+    bind_pose.invert_from(DAEToEggConverter::convert_matrix(
+                          skin_joint->GetBindPoseInverse()));
+
+    // Check that we already encountered this joint during traversal.
+    JointMap::iterator ji = joint_map.find(sid);
+    if (ji != joint_map.end()) {
+      Joint &joint = ji->second;
+
+      if (joint._character != (DaeCharacter *)NULL) {
+        // In some cases, though, multiple controllers share the same joints.
+        // We can't support this without duplicating the joint structure,
+        // so we check if the bind poses are the same.
+        if (!joint._bind_pose.almost_equal(bind_pose, 0.0001)) {
+          // Ugh.  What else could we do?
+          daeegg_cat.error()
+            << "Multiple controllers share joint with sid " << sid
+            << ", with different bind poses.\n";
+        }
+      } else {
+        // Mark the joint as being controlled by this character.
+        joint._bind_pose = bind_pose;
+        joint._character = this;
+      }
+
+      _joints.push_back(joint);
+    } else {
+      daeegg_cat.warning()
+        << "Unknown joint sid being referenced: '" << sid << "'\n";
+
+      // We still have to add a dummy joint or the index will be off.
+      _joints.push_back(Joint(NULL, NULL));
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::adjust_joints
+//       Access: Public
+//  Description: Traverses through the character hierarchy in order
+//               to bind the mesh to the character.  This involves
+//               reorienting the joints to match the bind pose.
+//
+//               It is important that this is called only once.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
+              const LMatrix4d &transform) {
+
+  LMatrix4d this_transform = transform;
+
+  if (node->IsJoint()) {
+    string sid = FROM_FSTRING(node->GetSubId());
+
+    JointMap::const_iterator ji = joint_map.find(sid);
+    if (ji != joint_map.end()) {
+      const Joint &joint = ji->second;
+
+      // Panda needs the joints to be in bind pose.  Not fun!  We copy the joint
+      // transform to the default pose, though, so that Panda will restore the
+      // joint transformation after binding.
+
+      if (joint._character == this) {
+        LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
+                        invert(transform);
+        //LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
+        //                joint._group->get_parent()->get_node_frame_inv();
+
+        this_transform = bind_pose * this_transform;
+        joint._group->set_default_pose(*joint._group);
+        joint._group->set_transform3d(bind_pose);
+
+        /*
+        PT(EggGroup) sphere = new EggGroup;
+        sphere->add_uniform_scale(0.1);
+        sphere->set_group_type(EggGroup::GT_instance);
+        sphere->add_child(new EggExternalReference("", "jack.egg"));
+        joint._group->add_child(sphere);
+        */
+      }
+    }
+  } else {
+    //this_transform = DAEToEggConverter::convert_matrix(node->ToMatrix());
+  }
+
+  // Loop through the children joints
+  for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
+    //if (node->GetChild(ch)->IsJoint()) {
+    adjust_joints(node->GetChild(ch), joint_map, this_transform);
+    //}
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::influence_vertex
+//       Access: Public
+//  Description: Adds the influences for the given vertex.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+influence_vertex(int index, EggVertex *vertex) {
+  const FCDSkinControllerVertex *influence = _skin_controller->GetVertexInfluence(index);
+
+  for (size_t pa = 0; pa < influence->GetPairCount(); ++pa) {
+    const FCDJointWeightPair* jwpair = influence->GetPair(pa);
+
+    if (jwpair->jointIndex >= 0 && jwpair->jointIndex < _joints.size()) {
+      EggGroup *joint = _joints[jwpair->jointIndex]._group.p();
+      if (joint != NULL) {
+        joint->ref_vertex(vertex, jwpair->weight);
+      }
+    } else {
+      daeegg_cat.error()
+        << "Invalid joint index: " << jwpair->jointIndex << "\n";
     }
   }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: DaeCharacter::as_egg_bundle
+//     Function: DaeCharacter::collect_keys
 //       Access: Public
-//  Description: Returns the character as a <Bundle> element,
-//               suited for the animation table.
-////////////////////////////////////////////////////////////////////
-PT(EggTable) DaeCharacter::
-as_egg_bundle() {
-  PT(EggTable) bundle = new EggTable(_name);
-  bundle->set_table_type(EggTable::TT_bundle);
-  PT(EggTable) skeleton = new EggTable("<skeleton>");
-  skeleton->set_table_type(EggTable::TT_table);
-  bundle->add_child(skeleton);
-  // Loop through the joint hierarchy
+//  Description: Collects all animation keys of animations applied
+//               to this character.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+collect_keys(pset<float> &keys) {
 #if FCOLLADA_VERSION < 0x00030005
-  FCDSceneNodeList roots = _controller_instance->FindSkeletonNodes();
+  FCDSceneNodeList roots = _instance->FindSkeletonNodes();
 #else
   FCDSceneNodeList roots;
-  _controller_instance->FindSkeletonNodes(roots);
+  _instance->FindSkeletonNodes(roots);
 #endif
+
   for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-    process_joint(skeleton, *it);
+    r_collect_keys(*it, keys);
   }
-  return bundle;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: DaeCharacter::process_joint
+//     Function: DaeCharacter::r_collect_keys
+//       Access: Public
+//  Description: Collects all animation keys found for the given
+//               node tree.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+r_collect_keys(FCDSceneNode* node, pset<float> &keys) {
+  FCDAnimatedList animateds;
+
+  // Collect all the animation curves
+  for (size_t t = 0; t < node->GetTransformCount(); ++t) {
+    FCDTransform *transform = node->GetTransform(t);
+    FCDAnimated *animated = transform->GetAnimated();
+
+    if (animated != NULL) {
+      const FCDAnimationCurveListList &all_curves = animated->GetCurves();
+
+      for (size_t ci = 0; ci < all_curves.size(); ++ci) {
+        const FCDAnimationCurveTrackList &curves = all_curves[ci];
+        if (curves.empty()) {
+          continue;
+        }
+
+        size_t num_keys = curves.front()->GetKeyCount();
+        const FCDAnimationKey **curve_keys = curves.front()->GetKeys();
+
+        for (size_t c = 0; c < num_keys; ++c) {
+          keys.insert(curve_keys[c]->input);
+        }
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::build_table
 //       Access: Public
 //  Description: Processes a joint node and its transforms.
 ////////////////////////////////////////////////////////////////////
 void DaeCharacter::
-process_joint(PT(EggTable) parent, FCDSceneNode* node) {
+build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys) {
   nassertv(node != NULL);
+
+  if (!node->IsJoint()) {
+    for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
+      build_table(parent, node->GetChild(ch), keys);
+    }
+    return;
+  }
+
   string node_id = FROM_FSTRING(node->GetDaeId());
-  PT(EggTable) joint = new EggTable(node_id);
-  joint->set_table_type(EggTable::TT_table);
-  parent->add_child(joint);
+  PT(EggTable) table = new EggTable(node_id);
+  table->set_table_type(EggTable::TT_table);
+  parent->add_child(table);
+
   PT(EggXfmSAnim) xform = new EggXfmSAnim("xform");
-  joint->add_child(xform);
-  xform->set_fps(_frame_rate);
+  table->add_child(xform);
+
   // Generate the sampled animation and loop through the matrices
-  FCDSceneNodeTools::GenerateSampledAnimation(node);
-  FMMatrix44List matrices = FCDSceneNodeTools::GetSampledAnimationMatrices();
-  for (FMMatrix44List::const_iterator it = matrices.begin(); it != matrices.end(); ++it) {
-    LMatrix4d matr = DAEToEggConverter::convert_matrix(*it);
-    assert(xform->add_data(matr));
+  FCDAnimatedList animateds;
+
+  // Collect all the animation curves
+  for (size_t t = 0; t < node->GetTransformCount(); ++t) {
+    FCDTransform *transform = node->GetTransform(t);
+    FCDAnimated *animated = transform->GetAnimated();
+    if (animated != (FCDAnimated *)NULL) {
+      if (animated->HasCurve()) {
+        animateds.push_back(animated);
+      }
+    }
   }
+
+  // Sample the scene node transform
+  float last_key;
+  float timing_total = 0;
+  pset<float>::const_iterator ki;
+  for (ki = keys.begin(); ki != keys.end(); ++ki) {
+    for (FCDAnimatedList::iterator it = animateds.begin(); it != animateds.end(); ++it) {
+      // Sample each animated, which changes the transform values directly
+      (*it)->Evaluate(*ki);
+    }
+
+    if (ki != keys.begin()) {
+      timing_total += (*ki - last_key);
+    }
+    last_key = *ki;
+
+    // Retrieve the new transform matrix for the COLLADA scene node
+    FMMatrix44 fmat = node->ToMatrix();
+
+    // Work around issue in buggy exporters (like ColladaMax)
+    if (IS_NEARLY_ZERO(fmat[3][3])) {
+      fmat[3][3] = 1;
+    }
+
+    xform->add_data(DAEToEggConverter::convert_matrix(fmat));
+  }
+
+  // Quantize the FPS, otherwise Panda complains about FPS mismatches.
+  float fps = round(((keys.size() - 1) / timing_total) * 100) * 0.01f;
+  xform->set_fps(fps);
+
   // Loop through the children joints
   for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
-    if (node->GetChild(ch)->IsJoint()) {
-      process_joint(joint, node->GetChild(ch));
-    }
+    //if (node->GetChild(ch)->IsJoint()) {
+      build_table(table, node->GetChild(ch), keys);
+    //}
   }
 }

+ 43 - 12
pandatool/src/daeegg/daeCharacter.h

@@ -12,20 +12,22 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#ifndef DAECHARACTER_H
+#define DAECHARACTER_H
+
 #include "pandatoolbase.h"
 #include "typedReferenceCount.h"
 #include "typeHandle.h"
 #include "eggTable.h"
-#include "daeToEggConverter.h"
 
 #include "pre_fcollada_include.h"
 #include "FCollada.h"
 #include "FCDocument/FCDSceneNode.h"
 #include "FCDocument/FCDControllerInstance.h"
 #include "FCDocument/FCDSkinController.h"
+#include "FCDocument/FCDGeometryMesh.h"
 
-#ifndef DAECHARACTER_H
-#define DAECHARACTER_H
+class DAEToEggConverter;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : DaeCharacter
@@ -33,17 +35,46 @@
 ////////////////////////////////////////////////////////////////////
 class DaeCharacter : public TypedReferenceCount {
 public:
-  DaeCharacter(const string name, const FCDControllerInstance* controller_instance);
-  PT(EggTable) as_egg_bundle();
-  void process_joint(PT(EggTable) parent, FCDSceneNode* node);
-  
+  DaeCharacter(EggGroup *node_group, const FCDControllerInstance* controller_instance);
+
+  struct Joint {
+    INLINE Joint(EggGroup *group, const FCDSceneNode *scene_node) :
+      _group(group),
+      _scene_node(scene_node),
+      _character(NULL),
+      _bind_pose(LMatrix4d::ident_mat()) {}
+
+    LMatrix4d _bind_pose;
+    const PT(EggGroup) _group;
+    const FCDSceneNode *_scene_node;
+    DaeCharacter *_character;
+  };
+  typedef pvector<Joint> Joints;
+  typedef pmap<string, Joint> JointMap;
+
+  void bind_joints(JointMap &joint_map);
+  void adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
+                     const LMatrix4d &transform = LMatrix4d::ident_mat());
+
+  void influence_vertex(int index, EggVertex *vertex);
+
+  void collect_keys(pset<float> &keys);
+  void r_collect_keys(FCDSceneNode *node, pset<float> &keys);
+
+  void build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys);
+
+public:
+  PT(EggGroup) _node_group;
+  const FCDGeometryMesh *_skin_mesh;
+  const FCDControllerInstance *_instance;
+  LMatrix4d _bind_shape_mat;
+
 private:
-  int _frame_rate;
   string _name;
-  FCDControllerInstance* _controller_instance;
-  FCDSkinController* _skin_controller;
-  pmap<string, FCDSkinControllerJoint*> _controller_joints;
-  
+  const FCDSkinController *_skin_controller;
+  Joints _joints;
+  JointMap _bound_joints;
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 11 - 10
pandatool/src/daeegg/daeMaterials.cxx

@@ -35,7 +35,7 @@ TypeHandle DaeMaterials::_type_handle;
 ////////////////////////////////////////////////////////////////////
 //     Function: DaeMaterials::Constructor
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 DaeMaterials::
 DaeMaterials(const FCDGeometryInstance* geometry_instance) {
@@ -58,7 +58,7 @@ void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
     return;
   }
   _materials[semantic] = new DaeMaterial();
-  
+
   // Load in the uvsets
   for (size_t vib = 0; vib < instance->GetVertexInputBindingCount(); ++vib) {
     const FCDMaterialInstanceBindVertexInput* mivib = instance->GetVertexInputBinding(vib);
@@ -74,7 +74,7 @@ void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
 #endif
     _materials[semantic]->_uvsets.push_back(bvi);
   }
-  
+
   // Handle the material stuff
   daeegg_cat.spam() << "Trying to process material with semantic " << semantic << endl;
   PT_EggMaterial egg_material = new EggMaterial(semantic);
@@ -204,12 +204,13 @@ process_extra(const string semantic, const FCDExtra* extra) {
   for (size_t et = 0; et < etype->GetTechniqueCount(); ++et) {
     const FCDENode* enode = ((const FCDENode*)(etype->GetTechnique(et)))->FindChildNode("double_sided");
     if (enode != NULL) {
-      if (trim(enode->GetContent()) == "1") {
+      string content = trim(enode->GetContent());
+      if (content == "1" || content == "true") {
         _materials[semantic]->_double_sided = true;
-      } else if (trim(enode->GetContent()) == "0") {
+      } else if (content == "0" || content == "false") {
         _materials[semantic]->_double_sided = false;
       } else {
-        daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << enode->GetContent() << "' instead" << endl;
+        daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << content << "' instead" << endl;
       }
     }
   }
@@ -391,7 +392,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
   blend->_color = LColor::zero();
   blend->_operand_a = EggGroup::BO_unspecified;
   blend->_operand_b = EggGroup::BO_unspecified;
-  
+
   // First fill in the color value.
   if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::A_ZERO) {
     double value = transparent[3] * transparency;
@@ -404,7 +405,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
     blend->_enabled = false;
     return blend;
   }
-  
+
   // Now figure out the operands.
   if (mode == FCDEffectStandard::RGB_ZERO) {// || mode == FCDEffectStandard::A_ZERO) {
     blend->_operand_a = EggGroup::BO_one_minus_constant_color;
@@ -417,7 +418,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
     blend->_enabled = false;
     return blend;
   }
-  
+
   // See if we can optimize out the color.
   if (blend->_operand_a == EggGroup::BO_constant_color) {
     if (blend->_color == LColor::zero()) {
@@ -447,7 +448,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
       blend->_operand_b = EggGroup::BO_zero;
     }
   }
-  
+
   // See if we can entirely disable the blend.
   if (blend->_operand_a == EggGroup::BO_one && blend->_operand_b == EggGroup::BO_zero) {
     blend->_enabled = false;

+ 396 - 221
pandatool/src/daeegg/daeToEggConverter.cxx

@@ -56,9 +56,10 @@
 ////////////////////////////////////////////////////////////////////
 DAEToEggConverter::
 DAEToEggConverter() {
+  _unit_name = "meter";
+  _unit_meters = 1.0;
   _document = NULL;
   _table = NULL;
-  _frame_rate = -1;
   _error_handler = NULL;
   _invert_transparency = false;
 }
@@ -131,18 +132,15 @@ convert_file(const Filename &filename) {
   // Reset stuff
   clear_error();
   _joints.clear();
-  _vertex_pools.clear();
-  _skeletons.clear();
-  _frame_rate = -1;
   if (_error_handler == NULL) {
     _error_handler = new FUErrorSimpleHandler;
   }
-  
+
   // The default coordinate system is Y-up
   if (_egg_data->get_coordinate_system() == CS_default) {
     _egg_data->set_coordinate_system(CS_yup_right);
   }
-  
+
   // Read the file
   FCollada::Initialize();
   _document = FCollada::LoadDocument(filename.to_os_specific().c_str());
@@ -155,201 +153,375 @@ convert_file(const Filename &filename) {
   if (_document->GetAsset() != NULL) {
     FCDocumentTools::StandardizeUpAxisAndLength(_document);
   }
-  
-  _table = new EggTable();
-  _table->set_table_type(EggTable::TT_table);
-  // Process the stuff
+
+  // Process the scene
   process_asset();
-  preprocess();
+  PT(EggGroup) scene_group;
+  string model_name = _character_name;
+
   FCDSceneNode* visual_scene = _document->GetVisualSceneInstance();
   if (visual_scene != NULL) {
-    // First check for an <extra> tag
-    const FCDExtra* extra = visual_scene->GetExtra();
-    //FIXME: eek this looks horrid
-    if (extra != NULL) {
-      const FCDEType* etype = extra->GetDefaultType();
-      if (etype != NULL) {
-        const FCDENode* enode = (const FCDENode*) etype->FindTechnique("MAX3D");
-        if (enode != NULL) {
-          enode = enode->FindChildNode("frame_rate");
-          if (enode != NULL && !string_to_int(enode->GetContent(), _frame_rate)) {
-            daeegg_cat.warning() << "Invalid integer in <frame_rate> tag: '" << enode->GetContent() << "'" << endl;
-    } } } }
-    // Now loop through the children
+    if (model_name.empty()) {
+      // By lack of anything better...
+      model_name = FROM_FSTRING(visual_scene->GetName());
+    }
+    scene_group = new EggGroup(model_name);
+    _egg_data->add_child(scene_group);
+
     for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
-      process_node(DCAST(EggGroupNode, _egg_data), visual_scene->GetChild(ch));
+      process_node(scene_group, visual_scene->GetChild(ch));
     }
+  } else {
+    daeegg_cat.warning()
+      << "No visual scene instance found in COLLADA document.\n";
   }
-  SAFE_DELETE(visual_scene);
-  
-  _egg_data->add_child(_table);
-  
+
+  // Now process the characters.  This depends on information from collected
+  // joints, which is why it's done in a second step.
+  if (get_animation_convert() != AC_none) {
+    Characters::iterator it;
+    DaeCharacter *character;
+    for (it = _characters.begin(); it != _characters.end(); ++it) {
+      character = *it;
+      if (get_animation_convert() != AC_chan) {
+        character->bind_joints(_joints);
+
+        const FCDGeometryMesh *mesh = character->_skin_mesh;
+
+        if (mesh != NULL) {
+          PT(DaeMaterials) materials = new DaeMaterials(character->_instance);
+          daeegg_cat.spam() << "Processing mesh for controller\n";
+          process_mesh(character->_node_group, mesh, materials, character);
+        }
+      }
+    }
+
+    // Put the joints in bind pose.
+    for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
+      character->adjust_joints(visual_scene->GetChild(ch), _joints, LMatrix4d::ident_mat());
+    }
+
+    if (scene_group != NULL) {
+      // Mark the scene as character.
+      if (get_animation_convert() == AC_chan) {
+        _egg_data->remove_child(scene_group);
+      } else {
+        scene_group->set_dart_type(EggGroup::DT_default);
+      }
+    }
+
+    if (get_animation_convert() != AC_model) {
+      _table = new EggTable();
+      _table->set_table_type(EggTable::TT_table);
+      _egg_data->add_child(_table);
+
+      PT(EggTable) bundle = new EggTable(model_name);
+      bundle->set_table_type(EggTable::TT_bundle);
+      _table->add_child(bundle);
+
+      PT(EggTable) skeleton = new EggTable("<skeleton>");
+      skeleton->set_table_type(EggTable::TT_table);
+      bundle->add_child(skeleton);
+
+      pset<float> keys;
+
+      Characters::iterator it;
+      DaeCharacter *character;
+      for (it = _characters.begin(); it != _characters.end(); ++it) {
+        DaeCharacter *character = *it;
+
+        // Collect key frame timings.
+        if (get_animation_convert() == AC_both ||
+            get_animation_convert() == AC_chan) {
+          character->collect_keys(keys);
+        }
+      }
+
+      if (_frame_inc != 0.0) {
+        // A frame increment was given, this means that we have to sample the
+        // animation.
+        float start, end;
+        if (_end_frame != _start_frame) {
+          start = _start_frame;
+          end = _end_frame;
+        } else {
+          // No range was given.  Infer the frame range from the keys.
+          start = *keys.begin();
+          end = *keys.rbegin();
+        }
+        keys.clear();
+
+        for (float t = start; t <= end; t += _frame_inc) {
+          keys.insert(t);
+        }
+      } else {
+        // No sampling parameters given; not necessarily a failure, since the
+        // animation may already be sampled.  We use the key frames as animation
+        // frames.
+        if (_end_frame != 0.0) {
+          // An end frame was given, chop off all keys after that.
+          float end = _end_frame;
+          pset<float>::iterator ki;
+          for (ki = keys.begin(); ki != keys.end(); ++ki) {
+            if (*ki > end && !IS_THRESHOLD_EQUAL(*ki, end, 0.001)) {
+              keys.erase(ki, keys.end());
+              break;
+            }
+          }
+        }
+        if (_start_frame != 0.0) {
+          // A start frame was given, chop off all keys before that.
+          float start = _start_frame;
+          pset<float>::iterator ki;
+          for (ki = keys.begin(); ki != keys.end(); ++ki) {
+            if (*ki > start && !IS_THRESHOLD_EQUAL(*ki, start, 0.001)) {
+              keys.erase(keys.begin(), ki);
+              break;
+            }
+          }
+        }
+
+        // Check that this does indeed look like a sampled animation; if not,
+        // issue an appropriate warning.
+        pset<float>::const_iterator ki = keys.begin();
+        if (ki != keys.end()) {
+          float last = *ki;
+          float diff = 0;
+
+          for (++ki; ki != keys.end(); ++ki) {
+            if (diff != 0 && !IS_THRESHOLD_EQUAL((*ki - last), diff, 0.001)) {
+              daeegg_cat.error()
+                << "This does not appear to be a sampled animation.\n"
+                << "Specify the -sf, -ef and -if options to indicate how the "
+                << "animations should be sampled.\n";
+              break;
+            }
+            diff = (*ki - last);
+            last = *ki;
+          }
+        }
+      }
+
+      // It doesn't really matter which character we grab for this as
+      // it'll iterate over the whole graph right now anyway.
+      for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
+        character->build_table(skeleton, visual_scene->GetChild(ch), keys);
+      }
+    }
+  }
+
   // Clean up and return
+  SAFE_DELETE(visual_scene);
   SAFE_DELETE(_document);
   FCollada::Release();
   return true;
 }
 
-void DAEToEggConverter::process_asset() {
-  if (_document->GetAsset() == NULL) return;
+////////////////////////////////////////////////////////////////////
+//     Function: DAEToEggConverter::get_input_units
+//       Access: Public, Virtual
+//  Description: This may be called after convert_file() has been
+//               called and returned true, indicating a successful
+//               conversion.  It will return the distance units
+//               represented by the converted egg file, if known, or
+//               DU_invalid if not known.
+////////////////////////////////////////////////////////////////////
+DistanceUnit DAEToEggConverter::
+get_input_units() {
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.001)) {
+    return DU_millimeters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.01)) {
+    return DU_centimeters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1.0)) {
+    return DU_meters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1000.0)) {
+    return DU_kilometers;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 3.0 * 12.0 * 0.0254)) {
+    return DU_yards;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 12.0 * 0.0254)) {
+    return DU_feet;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.0254)) {
+    return DU_inches;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1852.0)) {
+    return DU_nautical_miles;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 5280.0 * 12.0 * 0.0254)) {
+    return DU_statute_miles;
+  }
+
+  // Whatever.
+  return DU_invalid;
+}
+
+void DAEToEggConverter::
+process_asset() {
+  const FCDAsset *asset = _document->GetAsset();
+  if (_document->GetAsset() == NULL) {
+    return;
+  }
+
+  _unit_name = FROM_FSTRING(asset->GetUnitName());
+  _unit_meters = asset->GetUnitConversionFactor();
+
   // Read out the coordinate system
-  FMVector3 up_axis (_document->GetAsset()->GetUpAxis());
+  FMVector3 up_axis = asset->GetUpAxis();
+
   if (up_axis == FMVector3(0, 1, 0)) {
     _egg_data->set_coordinate_system(CS_yup_right);
+
   } else if (up_axis == FMVector3(0, 0, 1)) {
     _egg_data->set_coordinate_system(CS_zup_right);
+
   } else {
     _egg_data->set_coordinate_system(CS_invalid);
     daeegg_cat.warning() << "Unrecognized coordinate system!\n";
   }
 }
 
-// This function lists all the joints and referenced skeletons
-void DAEToEggConverter::preprocess(const FCDSceneNode* node) {
-  // If the node is NULL, take the visual scene instance.
-  if (node == NULL) {
-    assert(_document != NULL);
-    _skeletons.clear();
-    _joints.clear();
-    node = _document->GetVisualSceneInstance();
-  }
-  if (node == NULL) return;
-  if (node->IsJoint()) {
-    _joints[FROM_FSTRING(node->GetDaeId())] = NULL;
-  }
-  // Loop through the instances first.
-  for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
-    if (node->GetInstance(in)->GetType() == FCDEntityInstance::CONTROLLER) {
-      // Loop through the skeleton roots now.
-#if FCOLLADA_VERSION < 0x00030005
-      FCDSceneNodeList roots = ((FCDControllerInstance*) node->GetInstance(in))->FindSkeletonNodes();
-#else
-      FCDSceneNodeList roots;
-      ((FCDControllerInstance*) node->GetInstance(in))->FindSkeletonNodes(roots);
-#endif
-      for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-        daeegg_cat.spam() << "Found referenced skeleton root " << FROM_FSTRING((*it)->GetDaeId()) << endl;
-        _skeletons.push_back(FROM_FSTRING((*it)->GetDaeId()));
-      }
-    }
-  }
-  // Now loop through the children and recurse.
-  for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
-    preprocess(node->GetChild(ch));
-  }
-}
-
 // Process the node. If forced is true, it will even process it if its known to be a skeleton root.
-void DAEToEggConverter::process_node(PT(EggGroupNode) parent, const FCDSceneNode* node, bool forced) {
+void DAEToEggConverter::
+process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced) {
   nassertv(node != NULL);
   string node_id = FROM_FSTRING(node->GetDaeId());
   daeegg_cat.spam() << "Processing node with ID '" << node_id << "'" << endl;
-  // Important! If it's known to be a skeleton root, ignore it for now, unless we're processing forced.
-  if (!forced && count(_skeletons.begin(), _skeletons.end(), node_id) > 0) {
-    daeegg_cat.spam() << "Ignoring skeleton root node with ID '" << node_id << "', we'll process it later" << endl;
-    return;
-  }
+
   // Create an egg group for this node
-  PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetName()));
+  PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetDaeId()));
   process_extra(node_group, node->GetExtra());
   parent->add_child(node_group);
+
   // Check if its a joint
   if (node->IsJoint()) {
+    string sid = FROM_FSTRING(node->GetSubId());
     node_group->set_group_type(EggGroup::GT_joint);
-    _joints[node_id] = node_group;
+
+    if (!_joints.insert(DaeCharacter::JointMap::value_type(sid,
+                        DaeCharacter::Joint(node_group, node))).second) {
+      daeegg_cat.error()
+        << "Joint with sid " << sid << " occurs more than once!\n";
+    }
   }
-  // Loop through the transforms and apply them
-  for (size_t tr = 0; tr < node->GetTransformCount(); ++tr) {
-    apply_transform(node_group, node->GetTransform(tr));
+
+  // Loop through the transforms and apply them (in reverse order)
+  for (size_t tr = node->GetTransformCount(); tr > 0; --tr) {
+    apply_transform(node_group, node->GetTransform(tr - 1));
   }
+  //node_group->set_transform3d(convert_matrix(node->ToMatrix()));
+
   // Loop through the instances and process them
   for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
     process_instance(node_group, node->GetInstance(in));
   }
+
   // Loop through the children and recursively process them
   for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
     process_node(DCAST(EggGroupNode, node_group), node->GetChild(ch));
   }
+
   // Loop through any possible scene node instances and process those, too.
   for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
-    if (node->GetInstance(in)->GetEntity() && node->GetInstance(in)->GetEntity()->GetType() == FCDEntity::SCENE_NODE) {
-      process_node(DCAST(EggGroupNode, node_group), (const FCDSceneNode*) node->GetInstance(in)->GetEntity());
+    const FCDEntity *entity = node->GetInstance(in)->GetEntity();
+    if (entity && entity->GetType() == FCDEntity::SCENE_NODE) {
+      process_node(node_group, (const FCDSceneNode*) entity);
     }
   }
 }
 
-void DAEToEggConverter::process_instance(PT(EggGroup) parent, const FCDEntityInstance* instance) {
+void DAEToEggConverter::
+process_instance(EggGroup *parent, const FCDEntityInstance* instance) {
   nassertv(instance != NULL);
   nassertv(instance->GetEntity() != NULL);
   // Check what kind of instance this is
   switch (instance->GetType()) {
-    case FCDEntityInstance::GEOMETRY: {
-      const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
-      assert(geometry != NULL);
-      if (geometry->IsMesh()) {
-        // Now, handle the mesh.
-        process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
-      }
-      if (geometry->IsSpline()) {
-        process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
+  case FCDEntityInstance::GEOMETRY:
+    {
+      if (get_animation_convert() != AC_chan) {
+        const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
+        assert(geometry != NULL);
+        if (geometry->IsMesh()) {
+          // Now, handle the mesh.
+          process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
+        }
+        if (geometry->IsSpline()) {
+          process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
+        }
       }
-      break; }
-    case FCDEntityInstance::CONTROLLER: {
-      // Add the dart tag and process the controller instance
-      parent->set_dart_type(EggGroup::DT_default);
-      process_controller(parent, (const FCDControllerInstance*) instance);
-      break; }
-    case FCDEntityInstance::MATERIAL:
-      // We don't process this directly, handled per-geometry instead.
-      break;
-    case FCDEntityInstance::SIMPLE: {
-      // Grab the entity and check it's type.
+    }
+    break;
+
+  case FCDEntityInstance::CONTROLLER:
+    // Add the dart tag and process the controller instance
+    //parent->set_dart_type(EggGroup::DT_default);
+    process_controller(parent, (const FCDControllerInstance*) instance);
+    break;
+
+  case FCDEntityInstance::MATERIAL:
+    // We don't process this directly, handled per-geometry instead.
+    break;
+
+  case FCDEntityInstance::SIMPLE:
+    {
+      // Grab the entity and check its type.
       const FCDEntity* entity = instance->GetEntity();
       if (entity->GetType() != FCDEntity::SCENE_NODE) {
         daeegg_cat.warning() << "Unsupported entity type found" << endl;
       }
-      break; }
-    default:
-      daeegg_cat.warning() << "Unsupported instance type found" << endl;
+    }
+    break;
+
+  default:
+    daeegg_cat.warning() << "Unsupported instance type found" << endl;
   }
 }
 
 // Processes the given mesh.
-void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh* mesh, PT(DaeMaterials) materials) {
+void DAEToEggConverter::
+process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
+             DaeMaterials *materials, DaeCharacter *character) {
+
   nassertv(mesh != NULL);
   daeegg_cat.debug() << "Processing mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << endl;
-  
+
   // Create the egg stuff to hold this mesh
   PT(EggGroup) mesh_group = new EggGroup(FROM_FSTRING(mesh->GetDaeId()));
   parent->add_child(mesh_group);
   PT(EggVertexPool) mesh_pool = new EggVertexPool(FROM_FSTRING(mesh->GetDaeId()));
   mesh_group->add_child(mesh_pool);
-  _vertex_pools[FROM_FSTRING(mesh->GetDaeId())] = mesh_pool;
-  
+
   // First retrieve the vertex source
   if (mesh->GetSourceCount() == 0) {
     daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no sources" << endl;
     return;
   }
-  const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);  
+  const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);
   if (vsource == NULL) {
     daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no source for POSITION data" << endl;
     return;
   }
-  
+
   // Loop through the polygon groups and add them
   daeegg_cat.spam() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has " << mesh->GetPolygonsCount() << " polygon groups" << endl;
   if (mesh->GetPolygonsCount() == 0) return;
-  
+
   // This is an array of pointers, I know. But since they are refcounted, I don't have a better idea.
   PT(EggGroup) *primitive_holders = new PT(EggGroup) [mesh->GetPolygonsCount()];
   for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) {
     const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr);
+    string material_semantic = FROM_FSTRING(polygons->GetMaterialSemantic());
+
     // Stores which group holds the primitives.
     PT(EggGroup) primitiveholder;
     // If we have materials, make a group for each material. Then, apply the material's per-group stuff.
     if (materials != NULL && (!polygons->GetMaterialSemantic().empty()) && mesh->GetPolygonsCount() > 1) {
-      primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." + FROM_FSTRING(polygons->GetMaterialSemantic()));
+      //primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." + material_semantic);
+      primitiveholder = new EggGroup;
       mesh_group->add_child(primitiveholder);
     } else {
       primitiveholder = mesh_group;
@@ -357,7 +529,7 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
     primitive_holders[gr] = primitiveholder;
     // Apply the per-group data of the materials, if we have it.
     if (materials != NULL) {
-      materials->apply_to_group(FROM_FSTRING(polygons->GetMaterialSemantic()), primitiveholder, _invert_transparency);
+      materials->apply_to_group(material_semantic, primitiveholder, _invert_transparency);
     }
     // Find the position sources
     const FCDGeometryPolygonsInput* pinput = polygons->FindInput(FUDaeGeometryInput::POSITION);
@@ -389,26 +561,47 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
     const uint32* tindices;
     if (tinput != NULL) tindices = tinput->GetIndices();
     // Get a name for potential coordinate sets
-    string tcsetname ("");
+    string tcsetname;
     if (materials != NULL && tcinput != NULL) {
-      daeegg_cat.debug() << "Assigning texcoord set " << tcinput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      tcsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXCOORD, tcinput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning texcoord set " << tcinput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+      }
+      tcsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXCOORD, tcinput->GetSet());
     }
-    string tbsetname ("");
+    string tbsetname;
     if (materials != NULL && binput != NULL) {
-      daeegg_cat.debug() << "Assigning texbinormal set " << binput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      tbsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXBINORMAL, binput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning texbinormal set " << binput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+      }
+      tbsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXBINORMAL, binput->GetSet());
     }
-    string ttsetname ("");
+    string ttsetname;
     if (materials != NULL && tinput != NULL) {
-      daeegg_cat.debug() << "Assigning textangent set " << tinput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      ttsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXTANGENT, tinput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning textangent set " << tinput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+        }
+      ttsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXTANGENT, tinput->GetSet());
     }
     // Loop through the indices and add the vertices.
     for (size_t ix = 0; ix < pinput->GetIndexCount(); ++ix) {
       PT_EggVertex vertex = mesh_pool->make_new_vertex();
       const float* data = &vsource->GetData()[indices[ix]*3];
       vertex->set_pos(LPoint3d(data[0], data[1], data[2]));
+
+      if (character != NULL) {
+        // If this is skinned geometry, add the vertex influences.
+        character->influence_vertex(indices[ix], vertex);
+      }
+
       // Process the normal
       if (nsource != NULL && ninput != NULL) {
         assert(nsource->GetStride() == 3);
@@ -469,26 +662,26 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
       PT(EggPrimitive) primitive = NULL;
       // Create a primitive that matches the fcollada type
       switch (polygons->GetPrimitiveType()) {
-        case FCDGeometryPolygons::LINES:
-          primitive = new EggLine();
-          break;
-        case FCDGeometryPolygons::POLYGONS:
-          primitive = new EggPolygon();
-          break;
-        case FCDGeometryPolygons::TRIANGLE_FANS:
-          primitive = new EggTriangleFan();
-          break;
-        case FCDGeometryPolygons::TRIANGLE_STRIPS:
-          primitive = new EggTriangleStrip();
-          break;
-        case FCDGeometryPolygons::POINTS:
-          primitive = new EggPoint();
-          break;
-        case FCDGeometryPolygons::LINE_STRIPS:
-          daeegg_cat.warning() << "Linestrips not yet supported!" << endl;
-          break;
-        default:
-          daeegg_cat.warning() << "Unsupported primitive type found!" << endl;
+      case FCDGeometryPolygons::LINES:
+        primitive = new EggLine();
+        break;
+      case FCDGeometryPolygons::POLYGONS:
+        primitive = new EggPolygon();
+        break;
+      case FCDGeometryPolygons::TRIANGLE_FANS:
+        primitive = new EggTriangleFan();
+        break;
+      case FCDGeometryPolygons::TRIANGLE_STRIPS:
+        primitive = new EggTriangleStrip();
+        break;
+      case FCDGeometryPolygons::POINTS:
+        primitive = new EggPoint();
+        break;
+      case FCDGeometryPolygons::LINE_STRIPS:
+        daeegg_cat.warning() << "Linestrips not yet supported!" << endl;
+        break;
+      default:
+        daeegg_cat.warning() << "Unsupported primitive type found!" << endl;
       }
       if (primitive != NULL) {
         primitive_holders[gr]->add_child(primitive);
@@ -506,7 +699,8 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
   delete[] primitive_holders;
 }
 
-void DAEToEggConverter::process_spline(PT(EggGroup) parent, const string group_name, FCDGeometrySpline* geometry_spline) {
+void DAEToEggConverter::
+process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline) {
   assert(geometry_spline != NULL);
   PT(EggGroup) result = new EggGroup(group_name);
   parent->add_child(result);
@@ -521,7 +715,8 @@ void DAEToEggConverter::process_spline(PT(EggGroup) parent, const string group_n
   }
 }
 
-void DAEToEggConverter::process_spline(PT(EggGroup) parent, const FCDSpline* spline) {
+void DAEToEggConverter::
+process_spline(EggGroup *parent, const FCDSpline* spline) {
   assert(spline != NULL);
   nassertv(spline->GetSplineType() == FUDaeSplineType::NURBS);
   // Now load in the nurbs curve to the egg library
@@ -542,70 +737,26 @@ void DAEToEggConverter::process_spline(PT(EggGroup) parent, const FCDSpline* spl
   }
 }
 
-void DAEToEggConverter::process_controller(PT(EggGroup) parent, const FCDControllerInstance* instance) {
+void DAEToEggConverter::
+process_controller(EggGroup *parent, const FCDControllerInstance *instance) {
   assert(instance != NULL);
-  const FCDController* controller = (const FCDController*) instance->GetEntity();
+  const FCDController* controller = (const FCDController *)instance->GetEntity();
   assert(controller != NULL);
-  PT(EggVertexPool) vertex_pool = NULL;
-  // Add the skin geometry
-  const FCDGeometry* geometry = controller->GetBaseGeometry();
-  if (geometry != NULL) {
-    if (geometry->IsMesh()) {
-      process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
+
+  if (get_animation_convert() == AC_none) {
+    // If we're exporting a static mesh, export the base geometry as-is.
+    const FCDGeometryMesh *mesh = controller->GetBaseGeometry()->GetMesh();
+    if (mesh != NULL) {
+      PT(DaeMaterials) materials = new DaeMaterials(instance);
       daeegg_cat.spam() << "Processing mesh for controller\n";
-      if (_vertex_pools.count(FROM_FSTRING(geometry->GetMesh()->GetDaeId()))) {
-        daeegg_cat.debug() << "Using vertex pool " << FROM_FSTRING(geometry->GetMesh()->GetDaeId()) << "\n";
-        vertex_pool = _vertex_pools[FROM_FSTRING(geometry->GetMesh()->GetDaeId())];
-      }
-    }
-    if (geometry->IsSpline()) {
-      process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
-    }
-  }
-  // Add the joint hierarchy
-#if FCOLLADA_VERSION < 0x00030005
-  FCDSceneNodeList roots = (const_cast<FCDControllerInstance*> (instance))->FindSkeletonNodes();
-#else
-  FCDSceneNodeList roots;
-  (const_cast<FCDControllerInstance*> (instance))->FindSkeletonNodes(roots);
-#endif
-  for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-    process_node(DCAST(EggGroupNode, parent), *it, true);
-  }
-  if (controller->IsSkin()) {
-    // Load in the vertex influences first
-    pmap<int32, pvector<pair<PT_EggVertex, PN_stdfloat> > > influences;
-    if (vertex_pool) {
-      for (size_t in = 0; in < controller->GetSkinController()->GetInfluenceCount(); ++in) {
-        assert(vertex_pool->has_vertex(in));
-        for (size_t pa = 0; pa < controller->GetSkinController()->GetVertexInfluence(in)->GetPairCount(); ++pa) {
-          const FCDJointWeightPair* jwpair = controller->GetSkinController()->GetVertexInfluence(in)->GetPair(pa);
-          influences[jwpair->jointIndex].push_back(pair<PT_EggVertex, PN_stdfloat> (vertex_pool->get_vertex(in), jwpair->weight));
-        }
-      }
-    }
-    // Loop through the joints in the vertex influences
-    for (pmap<int32, pvector<pair<PT_EggVertex, PN_stdfloat> > >::iterator it = influences.begin(); it != influences.end(); ++it) {
-      if (it->first == -1) {
-        daeegg_cat.warning() << "Ignoring vertex influence with negative joint index\n";
-        //FIXME: Why are there joints with index -1
-      } else {
-        const string joint_id = FROM_FSTRING(controller->GetSkinController()->GetJoint(it->first)->GetId());
-        //TODO: what if the joints have just not been defined yet?
-        if (_joints.count(joint_id) > 0) {
-          if (_joints[joint_id]) {
-            for (pvector<pair<PT_EggVertex, PN_stdfloat> >::iterator vi = it->second.begin(); vi != it->second.end(); ++vi) {
-              _joints[joint_id]->ref_vertex(vi->first, vi->second);
-            }
-          } else {
-            daeegg_cat.warning() << "Unprocessed joint being referenced: '" << joint_id << "'" << endl;
-          }
-        } else {
-          daeegg_cat.warning() << "Unknown joint being referenced: '" << joint_id << "'" << endl;
-        }
-      }
+      process_mesh(parent, mesh, materials);
     }
+  } else {
+    // Add a character for this to the table, the mesh is processed later
+    PT(DaeCharacter) character = new DaeCharacter(parent, instance);
+    _characters.push_back(character);
   }
+
   if (controller->IsMorph()) {
     assert(controller != NULL);
     const FCDMorphController* morph_controller = controller->GetMorphController();
@@ -628,28 +779,25 @@ void DAEToEggConverter::process_controller(PT(EggGroup) parent, const FCDControl
       morph->add_child(target);
     }
   }
-  
-  // Get a <Bundle> for the character and add it to the table
-  PT(DaeCharacter) character = new DaeCharacter(parent->get_name(), instance);
-  _table->add_child(character->as_egg_bundle());
 }
 
-void DAEToEggConverter::process_extra(PT(EggGroup) group, const FCDExtra* extra) {
+void DAEToEggConverter::
+process_extra(EggGroup *group, const FCDExtra* extra) {
   if (extra == NULL) {
     return;
   }
   nassertv(group != NULL);
-  
+
   const FCDEType* etype = extra->GetDefaultType();
   if (etype == NULL) {
     return;
   }
-  
+
   const FCDENode* enode = (const FCDENode*) etype->FindTechnique("PANDA3D");
   if (enode == NULL) {
     return;
   }
-  
+
   FCDENodeList tags;
   enode->FindChildrenNodes("param", tags);
   for (FCDENodeList::iterator it = tags.begin(); it != tags.end(); ++it) {
@@ -660,18 +808,45 @@ void DAEToEggConverter::process_extra(PT(EggGroup) group, const FCDExtra* extra)
   }
 }
 
-LMatrix4d DAEToEggConverter::convert_matrix(const FMMatrix44& matrix) {
-  LMatrix4d result = LMatrix4d::zeros_mat();
-  for (char x = 0; x < 4; ++x) {
-    for (char y = 0; y < 4; ++y) {
-      result(x, y) = matrix[x][y];
-    }
-  }
-  return result;
+LMatrix4d DAEToEggConverter::
+convert_matrix(const FMMatrix44 &matrix) {
+  return LMatrix4d(
+    matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
+    matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
+    matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
+    matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3]);
 }
 
-void DAEToEggConverter::apply_transform(const PT(EggGroup) to, const FCDTransform* from) {
+void DAEToEggConverter::
+apply_transform(EggGroup *to, const FCDTransform* from) {
   assert(from != NULL);
   assert(to != NULL);
-  to->set_transform3d(convert_matrix(from->ToMatrix()) * to->get_transform3d());
+  //to->set_transform3d(convert_matrix(from->ToMatrix()) * to->get_transform3d());
+  switch (from->GetType()) {
+  case FCDTransform::TRANSLATION:
+    {
+      const FCDTTranslation *trans = (const FCDTTranslation *)from;
+      to->add_translate3d(TO_VEC3(trans->GetTranslation()));
+    }
+    break;
+
+  case FCDTransform::ROTATION:
+    {
+      const FCDTRotation *rot = (const FCDTRotation *)from;
+      to->add_rotate3d(rot->GetAngle(), TO_VEC3(rot->GetAxis()));
+    }
+    break;
+
+  case FCDTransform::SCALE:
+    {
+      const FCDTScale *scale = (const FCDTScale *)from;
+      to->add_scale3d(TO_VEC3(scale->GetScale()));
+    }
+    break;
+
+  default:
+    // Either a matrix, or something we can't handle.
+    to->add_matrix4(convert_matrix(from->ToMatrix()));
+    break;
+  }
 }

+ 23 - 20
pandatool/src/daeegg/daeToEggConverter.h

@@ -35,6 +35,7 @@
 #include "FMath/FMMatrix44.h"
 
 #include "daeMaterials.h"
+#include "daeCharacter.h"
 #include "pvector.h" // Include last
 
 ////////////////////////////////////////////////////////////////////
@@ -47,39 +48,41 @@ public:
   DAEToEggConverter();
   DAEToEggConverter(const DAEToEggConverter &copy);
   ~DAEToEggConverter();
-  
+
   virtual SomethingToEggConverter *make_copy();
-  
+
   virtual string get_name() const;
   virtual string get_extension() const;
-  
+
   virtual bool convert_file(const Filename &filename);
+  virtual DistanceUnit get_input_units();
 
   bool _invert_transparency;
 
 private:
+  string _unit_name;
+  double _unit_meters;
   PT(EggTable) _table;
   FCDocument* _document;
   FUErrorSimpleHandler* _error_handler;
-  pmap<const string, PT(EggGroup)> _joints;
-  pmap<const string, PT(EggVertexPool)> _vertex_pools;
-  pvector<string> _skeletons;
-  int _frame_rate;
-  
+  DaeCharacter::JointMap _joints;
+
+  typedef pvector<PT(DaeCharacter)> Characters;
+  Characters _characters;
+
   void process_asset();
-  void preprocess(const FCDSceneNode* node = NULL);
-  void process_node(PT(EggGroupNode) parent, const FCDSceneNode* node, bool forced = false);
-  void process_instance(PT(EggGroup) parent, const FCDEntityInstance* instance);
-  void process_mesh(PT(EggGroup) parent, const FCDGeometryMesh* mesh, PT(DaeMaterials) materials);
-  void process_spline(PT(EggGroup) parent, const string group_name, FCDGeometrySpline* geometry_spline);
-  void process_spline(PT(EggGroup) parent, const FCDSpline* spline);
-  void process_controller(PT(EggGroup) parent, const FCDControllerInstance* instance);
-  //void process_table_joint(PT(EggTable) parent, FCDSceneNode* node);
-  void process_extra(PT(EggGroup) group, const FCDExtra* extra);
-  
+  void process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced = false);
+  void process_instance(EggGroup *parent, const FCDEntityInstance* instance);
+  void process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
+                    DaeMaterials *materials, DaeCharacter *character = NULL);
+  void process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline);
+  void process_spline(EggGroup *parent, const FCDSpline* spline);
+  void process_controller(EggGroup *parent, const FCDControllerInstance* instance);
+  void process_extra(EggGroup *group, const FCDExtra* extra);
+
   static LMatrix4d convert_matrix(const FMMatrix44& matrix);
-  void apply_transform(const PT(EggGroup) to, const FCDTransform* from);
-  
+  void apply_transform(EggGroup *to, const FCDTransform* from);
+
   friend class DaeCharacter;
 };
 

+ 8 - 0
pandatool/src/daeprogs/daeToEgg.cxx

@@ -26,6 +26,7 @@ DAEToEgg::
 DAEToEgg():
   SomethingToEgg("COLLADA", ".dae")
 {
+  add_animation_options();
   add_units_options();
   add_normals_options();
   add_transform_options();
@@ -42,6 +43,7 @@ DAEToEgg():
     ("This program converts .dae files (COLLADA Digital Asset Exchange) to .egg.");
 
   _coordinate_system = CS_yup_right;
+  _animation_convert = AC_both;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -51,6 +53,12 @@ DAEToEgg():
 ////////////////////////////////////////////////////////////////////
 void DAEToEgg::
 run() {
+  if (_animation_convert != AC_both && _animation_convert != AC_none &&
+      _animation_convert != AC_chan && _animation_convert != AC_model) {
+    cerr << "Unsupported animation convert option.\n";
+    exit(1);
+  }
+
   nout << "Reading " << _input_filename << "\n";
 
   _data->set_coordinate_system(_coordinate_system);