Browse Source

merge multitexture support in from branch

David Rose 21 years ago
parent
commit
c7c639797e
100 changed files with 7868 additions and 1803 deletions
  1. 8 4
      panda/src/builder/builderBucket.I
  2. 31 5
      panda/src/builder/builderBucket.cxx
  3. 6 3
      panda/src/builder/builderBucket.h
  4. 33 16
      panda/src/builder/builderFuncs.I
  5. 48 35
      panda/src/builder/builderPrim.cxx
  6. 2 2
      panda/src/builder/builderPrim.h
  7. 53 40
      panda/src/builder/builderPrimTempl.I
  8. 14 2
      panda/src/builder/builderPrimTempl.h
  9. 0 8
      panda/src/builder/builderTypes.cxx
  10. 2 2
      panda/src/builder/builderTypes.h
  11. 9 9
      panda/src/builder/builderVertex.I
  12. 4 4
      panda/src/builder/builderVertex.h
  13. 79 24
      panda/src/builder/builderVertexTempl.I
  14. 14 5
      panda/src/builder/builderVertexTempl.h
  15. 4 4
      panda/src/char/character.cxx
  16. 1 1
      panda/src/char/character.h
  17. 2 2
      panda/src/collide/collisionTraverser.cxx
  18. 1 1
      panda/src/collide/collisionTraverser.h
  19. 22 0
      panda/src/display/graphicsStateGuardian.I
  20. 17 0
      panda/src/display/graphicsStateGuardian.cxx
  21. 5 0
      panda/src/display/graphicsStateGuardian.h
  22. 27 0
      panda/src/distort/projectionScreen.I
  23. 41 5
      panda/src/distort/projectionScreen.cxx
  24. 20 8
      panda/src/distort/projectionScreen.h
  25. 123 10
      panda/src/doc/eggSyntax.txt
  26. 238 0
      panda/src/doc/howto.use_multitexture.txt
  27. 2 2
      panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx
  28. 8 4
      panda/src/egg/Sources.pp
  29. 2 0
      panda/src/egg/config_egg.cxx
  30. 0 44
      panda/src/egg/eggAttributes.I
  31. 0 24
      panda/src/egg/eggAttributes.cxx
  32. 2 9
      panda/src/egg/eggAttributes.h
  33. 14 4
      panda/src/egg/eggPolysetMaker.cxx
  34. 82 19
      panda/src/egg/eggPrimitive.I
  35. 61 39
      panda/src/egg/eggPrimitive.cxx
  36. 12 3
      panda/src/egg/eggPrimitive.h
  37. 365 37
      panda/src/egg/eggTexture.I
  38. 460 10
      panda/src/egg/eggTexture.cxx
  39. 129 5
      panda/src/egg/eggTexture.h
  40. 42 8
      panda/src/egg/eggTextureCollection.cxx
  41. 4 2
      panda/src/egg/eggUtilities.cxx
  42. 112 15
      panda/src/egg/eggVertex.I
  43. 137 18
      panda/src/egg/eggVertex.cxx
  44. 26 0
      panda/src/egg/eggVertex.h
  45. 38 0
      panda/src/egg/eggVertexUV.I
  46. 116 0
      panda/src/egg/eggVertexUV.cxx
  47. 75 0
      panda/src/egg/eggVertexUV.h
  48. 1 0
      panda/src/egg/egg_composite2.cxx
  49. 3 3
      panda/src/egg/lexer.cxx.prebuilt
  50. 607 588
      panda/src/egg/parser.cxx.prebuilt
  51. 172 7
      panda/src/egg/parser.yxx
  52. 42 28
      panda/src/egg2pg/computedVerticesMaker.cxx
  53. 17 5
      panda/src/egg2pg/computedVerticesMaker.h
  54. 0 1
      panda/src/egg2pg/config_egg2pg.cxx
  55. 0 1
      panda/src/egg2pg/config_egg2pg.h
  56. 501 94
      panda/src/egg2pg/eggLoader.cxx
  57. 32 4
      panda/src/egg2pg/eggLoader.h
  58. 1 0
      panda/src/glgsg/glgsg.h
  59. 0 25
      panda/src/glstuff/glGraphicsStateGuardian_src.I
  60. 525 100
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  61. 28 6
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  62. 0 5
      panda/src/glstuff/glmisc_src.cxx
  63. 0 1
      panda/src/glstuff/glmisc_src.h
  64. 2 1
      panda/src/glxdisplay/Sources.pp
  65. 4 13
      panda/src/glxdisplay/glxGraphicsPipe.h
  66. 171 0
      panda/src/glxdisplay/glxGraphicsStateGuardian.cxx
  67. 22 0
      panda/src/glxdisplay/glxGraphicsStateGuardian.h
  68. 622 0
      panda/src/glxdisplay/glxext.h
  69. 8 3
      panda/src/gobj/Sources.pp
  70. 6 0
      panda/src/gobj/config_gobj.cxx
  71. 2 2
      panda/src/gobj/drawable.cxx
  72. 1 1
      panda/src/gobj/drawable.h
  73. 126 30
      panda/src/gobj/geom.I
  74. 603 370
      panda/src/gobj/geom.cxx
  75. 84 51
      panda/src/gobj/geom.h
  76. 1 0
      panda/src/gobj/geomSprite.h
  77. 2 0
      panda/src/gobj/gobj_composite2.cxx
  78. 4 4
      panda/src/gobj/material.h
  79. 49 0
      panda/src/gobj/texCoordName.I
  80. 161 0
      panda/src/gobj/texCoordName.cxx
  81. 92 0
      panda/src/gobj/texCoordName.h
  82. 581 0
      panda/src/gobj/textureStage.I
  83. 451 0
      panda/src/gobj/textureStage.cxx
  84. 227 0
      panda/src/gobj/textureStage.h
  85. 10 0
      panda/src/gsgmisc/geomIssuer.I
  86. 61 0
      panda/src/gsgmisc/geomIssuer.cxx
  87. 15 1
      panda/src/gsgmisc/geomIssuer.h
  88. 2 0
      panda/src/mesadisplay/mesagsg.h
  89. 33 0
      panda/src/mesadisplay/osMesaGraphicsStateGuardian.cxx
  90. 3 0
      panda/src/mesadisplay/osMesaGraphicsStateGuardian.h
  91. 1 1
      panda/src/particlesystem/spriteParticleRenderer.cxx
  92. 6 1
      panda/src/pgraph/Sources.pp
  93. 40 10
      panda/src/pgraph/billboardEffect.cxx
  94. 4 2
      panda/src/pgraph/billboardEffect.h
  95. 2 0
      panda/src/pgraph/colorBlendAttrib.h
  96. 2 0
      panda/src/pgraph/colorWriteAttrib.h
  97. 41 10
      panda/src/pgraph/compassEffect.cxx
  98. 4 2
      panda/src/pgraph/compassEffect.h
  99. 3 0
      panda/src/pgraph/config_pgraph.cxx
  100. 2 0
      panda/src/pgraph/cullFaceAttrib.h

+ 8 - 4
panda/src/builder/builderBucket.I

@@ -73,8 +73,8 @@ get_normals() const {
 //               associated with this bucket.
 //               associated with this bucket.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void BuilderBucket::
 INLINE void BuilderBucket::
-set_texcoords(const PTA_TexCoordf &texcoords) {
-  _texcoords = texcoords;
+set_texcoords(const TexCoordName *name, const PTA_TexCoordf &texcoords) {
+  _texcoords[name] = texcoords;
 }
 }
 
 
 
 
@@ -84,8 +84,12 @@ set_texcoords(const PTA_TexCoordf &texcoords) {
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_TexCoordf BuilderBucket::
 INLINE PTA_TexCoordf BuilderBucket::
-get_texcoords() const {
-  return _texcoords;
+get_texcoords(const TexCoordName *name) const {
+  TexCoords::const_iterator ti = _texcoords.find(name);
+  if (ti != _texcoords.end()) {
+    return (*ti).second;
+  }
+  return PTA_TexCoordf();
 }
 }
 
 
 
 

+ 31 - 5
panda/src/builder/builderBucket.cxx

@@ -62,9 +62,10 @@ operator = (const BuilderBucket &copy) {
   set_name(copy.get_name());
   set_name(copy.get_name());
   set_coords(copy._coords);
   set_coords(copy._coords);
   set_normals(copy._normals);
   set_normals(copy._normals);
-  set_texcoords(copy._texcoords);
   set_colors(copy._colors);
   set_colors(copy._colors);
 
 
+  _texcoords = copy._texcoords;
+
   _node = copy._node;
   _node = copy._node;
   _hidden = copy._hidden;
   _hidden = copy._hidden;
   _state = copy._state;
   _state = copy._state;
@@ -171,11 +172,34 @@ operator < (const BuilderBucket &other) const {
     return _coords < other._coords;
     return _coords < other._coords;
   if (_normals != other._normals)
   if (_normals != other._normals)
     return _normals < other._normals;
     return _normals < other._normals;
-  if (_texcoords != other._texcoords)
-    return _texcoords < other._texcoords;
   if (_colors != other._colors)
   if (_colors != other._colors)
     return _colors < other._colors;
     return _colors < other._colors;
 
 
+  TexCoords::const_iterator ai, bi;
+  ai = _texcoords.begin();
+  bi = other._texcoords.begin();
+  while (ai != _texcoords.end() && bi != other._texcoords.end()) {
+    if ((*ai).first < (*bi).first) {
+      return true;
+    } else if ((*bi).first < (*ai).first) {
+      return false;
+    } else {
+      if ((*ai).second != (*bi).second) {
+        return (*ai).second < (*bi).second;
+      }
+    }
+
+    ++ai;
+    ++bi;
+  }
+
+  if (bi != other._texcoords.end()) {
+    return true;
+  }
+  if (ai != _texcoords.end()) {
+    return false;
+  }
+
   if (_state != other._state) {
   if (_state != other._state) {
     return _state < other._state;
     return _state < other._state;
   }
   }
@@ -209,8 +233,10 @@ output(ostream &out) const {
     out << "_normals = " << (void *)_normals << "\n";
     out << "_normals = " << (void *)_normals << "\n";
   }
   }
 
 
-  if (_texcoords != (TexCoordf *)NULL) {
-    out << "_texcoords = " << (void *)_texcoords << "\n";
+  TexCoords::const_iterator ti;
+  for (ti = _texcoords.begin(); ti != _texcoords.end(); ++ti) {
+    out << "_texcoords[\"" << (*ti).first << "\"] = " 
+        << (void *)(*ti).second << "\n";
   }
   }
 
 
   if (_colors != (Colorf *)NULL) {
   if (_colors != (Colorf *)NULL) {

+ 6 - 3
panda/src/builder/builderBucket.h

@@ -30,6 +30,7 @@
 #include "pta_Normalf.h"
 #include "pta_Normalf.h"
 #include "pta_Colorf.h"
 #include "pta_Colorf.h"
 #include "pta_TexCoordf.h"
 #include "pta_TexCoordf.h"
+#include "texCoordName.h"
 #include "renderState.h"
 #include "renderState.h"
 
 
 #include "stdlib.h"
 #include "stdlib.h"
@@ -76,8 +77,8 @@ public:
   INLINE void set_normals(const PTA_Normalf &normals);
   INLINE void set_normals(const PTA_Normalf &normals);
   INLINE PTA_Normalf get_normals() const;
   INLINE PTA_Normalf get_normals() const;
 
 
-  INLINE void set_texcoords(const PTA_TexCoordf &texcoords);
-  INLINE PTA_TexCoordf get_texcoords() const;
+  INLINE void set_texcoords(const TexCoordName *name, const PTA_TexCoordf &texcoords);
+  INLINE PTA_TexCoordf get_texcoords(const TexCoordName *name) const;
 
 
   INLINE void set_colors(const PTA_Colorf &colors);
   INLINE void set_colors(const PTA_Colorf &colors);
   INLINE PTA_Colorf get_colors() const;
   INLINE PTA_Colorf get_colors() const;
@@ -93,9 +94,11 @@ public:
 protected:
 protected:
   PTA_Vertexf _coords;
   PTA_Vertexf _coords;
   PTA_Normalf _normals;
   PTA_Normalf _normals;
-  PTA_TexCoordf _texcoords;
   PTA_Colorf _colors;
   PTA_Colorf _colors;
 
 
+  typedef pmap<CPT(TexCoordName), PTA_TexCoordf> TexCoords;
+  TexCoords _texcoords;
+
   static BuilderBucket *_default_bucket;
   static BuilderBucket *_default_bucket;
 
 
 private:
 private:

+ 33 - 16
panda/src/builder/builderFuncs.I

@@ -552,12 +552,15 @@ build_geoms(InputIterator first, InputIterator last,
   //
   //
   //  5. If none of the above apply, the binding will be G_OVERALL.
   //  5. If none of the above apply, the binding will be G_OVERALL.
   //
   //
-  // An exception to the above is for texcoords, which is either G_OFF
-  // (by rule 1) or G_PER_VERTEX.
+  // Texcoords are an exception to the above, since they are either
+  // per vertex or not at all, and since there may be a different set
+  // of texcoords for each of a number of TexCoordName objects.
 
 
   GeomBindType bind_normals = G_OVERALL;
   GeomBindType bind_normals = G_OVERALL;
   GeomBindType bind_colors = G_OVERALL;
   GeomBindType bind_colors = G_OVERALL;
-  GeomBindType bind_texcoords = G_PER_VERTEX;
+
+  typedef pset<const TexCoordName *> TexCoordNames;
+  TexCoordNames texcoord_names;
 
 
   NType overall_normal(0);
   NType overall_normal(0);
   CType overall_color(0);
   CType overall_color(0);
@@ -619,14 +622,16 @@ build_geoms(InputIterator first, InputIterator last,
       }
       }
     }
     }
 
 
-    // Texcoords.
-
-    // Test rule 1.
-    if (!(*i).has_any_texcoord()) {
-      bind_texcoords = G_OFF;
+    // Texcoords.  Get the union of all TexCoordNames defined on the
+    // prims.
+    TYPENAME PrimType::tcn_const_iterator tni;
+    for (tni = (*i).tcn_begin(); tni != (*i).tcn_end(); ++tni) {
+      const TexCoordName *name = (*tni);
+      texcoord_names.insert(name);
     }
     }
   }
   }
 
 
+
   // Determine the primitive type and build the lengths array, if needed.
   // Determine the primitive type and build the lengths array, if needed.
   PTA_int lengths;
   PTA_int lengths;
   bool want_lengths = false;
   bool want_lengths = false;
@@ -707,10 +712,16 @@ build_geoms(InputIterator first, InputIterator last,
   }
   }
 
 
   // Now build up some arrays.
   // Now build up some arrays.
-  PTA(VType) coords=PTA(VType)::empty_array(0);
-  PTA(NType) normals=PTA(NType)::empty_array(0);
-  PTA(TType) texcoords=PTA(TType)::empty_array(0);
-  PTA(CType) colors=PTA(CType)::empty_array(0);
+  PTA(VType) coords = PTA(VType)::empty_array(0);
+  PTA(NType) normals = PTA(NType)::empty_array(0);
+  PTA(CType) colors = PTA(CType)::empty_array(0);
+
+  typedef TYPENAME PrimType::TexCoordFill TexCoordFill;
+  TexCoordFill texcoords;
+  TexCoordNames::const_iterator tni;
+  for (tni = texcoord_names.begin(); tni != texcoord_names.end(); ++tni) {
+    texcoords[*tni] = PTA(TType)::empty_array(0);
+  }
 
 
   int total_verts = 0;
   int total_verts = 0;
   int total_components = 0;
   int total_components = 0;
@@ -727,12 +738,18 @@ build_geoms(InputIterator first, InputIterator last,
         if (bind_normals == G_PER_VERTEX) {
         if (bind_normals == G_PER_VERTEX) {
           normals.push_back((*i).get_vertex(v).get_normal());
           normals.push_back((*i).get_vertex(v).get_normal());
         }
         }
-        if (bind_texcoords == G_PER_VERTEX) {
-          texcoords.push_back((*i).get_vertex(v).get_texcoord());
-        }
         if (bind_colors == G_PER_VERTEX) {
         if (bind_colors == G_PER_VERTEX) {
           colors.push_back((*i).get_vertex(v).get_color());
           colors.push_back((*i).get_vertex(v).get_color());
         }
         }
+        TYPENAME TexCoordFill::iterator tci;
+        for (tci = texcoords.begin(); tci != texcoords.end(); ++tci) {
+          const TexCoordName *name = (*tci).first;
+          if ((*i).get_vertex(v).has_texcoord(name)) {
+            (*tci).second.push_back((*i).get_vertex(v).get_texcoord(name));
+          } else {
+            (*tci).second.push_back(TType());
+          }
+        }
       }
       }
 
 
       num_components = (*i).get_num_components();
       num_components = (*i).get_num_components();
@@ -772,8 +789,8 @@ build_geoms(InputIterator first, InputIterator last,
 
 
   PrimType::fill_geom(geom, coords,
   PrimType::fill_geom(geom, coords,
                       bind_normals, normals,
                       bind_normals, normals,
-                      bind_texcoords, texcoords,
                       bind_colors, colors,
                       bind_colors, colors,
+                      texcoords,
                       bucket, num_prims,
                       bucket, num_prims,
                       total_components, total_verts);
                       total_components, total_verts);
 
 

+ 48 - 35
panda/src/builder/builderPrim.cxx

@@ -62,13 +62,15 @@ nonindexed_copy(const BuilderPrimTempl<BuilderVertexI> &copy,
       v.set_normal(cv.get_normal_value(bucket));
       v.set_normal(cv.get_normal_value(bucket));
     }
     }
 
 
-    if (cv.has_texcoord()) {
-      v.set_texcoord(cv.get_texcoord_value(bucket));
-    }
-
     if (cv.has_color()) {
     if (cv.has_color()) {
       v.set_color(cv.get_color_value(bucket));
       v.set_color(cv.get_color_value(bucket));
     }
     }
+    
+    BuilderVertexI::tc_const_iterator tci;
+    for (tci = cv.tc_begin(); tci != cv.tc_end(); ++tci) {
+      const TexCoordName *name = (*tci).first;
+      v.set_texcoord(name, cv.get_texcoord_value(name, bucket));
+    }
 
 
     if (cv.has_pixel_size()) {
     if (cv.has_pixel_size()) {
       v.set_pixel_size(cv.get_pixel_size());
       v.set_pixel_size(cv.get_pixel_size());
@@ -130,8 +132,8 @@ flatten_vertex_properties() {
 void BuilderPrim::
 void BuilderPrim::
 fill_geom(Geom *geom, const PTA_BuilderV &v_array,
 fill_geom(Geom *geom, const PTA_BuilderV &v_array,
           GeomBindType n_attr, const PTA_BuilderN &n_array,
           GeomBindType n_attr, const PTA_BuilderN &n_array,
-          GeomBindType t_attr, const PTA_BuilderTC &t_array,
           GeomBindType c_attr, const PTA_BuilderC &c_array,
           GeomBindType c_attr, const PTA_BuilderC &c_array,
+          const BuilderPrim::TexCoordFill &texcoords,
           const BuilderBucket &, int, int, int) {
           const BuilderBucket &, int, int, int) {
 
 
   // WARNING!  This is questionable practice.  We have a
   // WARNING!  This is questionable practice.  We have a
@@ -145,13 +147,15 @@ fill_geom(Geom *geom, const PTA_BuilderV &v_array,
     geom->set_normals((PTA_Normalf &)n_array, n_attr);
     geom->set_normals((PTA_Normalf &)n_array, n_attr);
   }
   }
 
 
-  if (t_attr != G_OFF) {
-    geom->set_texcoords((PTA_TexCoordf &)t_array, t_attr);
-  }
-
   if (c_attr != G_OFF) {
   if (c_attr != G_OFF) {
     geom->set_colors((PTA_Colorf &)c_array, c_attr);
     geom->set_colors((PTA_Colorf &)c_array, c_attr);
   }
   }
+
+  TexCoordFill::const_iterator tci;
+  for (tci = texcoords.begin(); tci != texcoords.end(); ++tci) {
+    const TexCoordName *name = (*tci).first;
+    geom->set_texcoords(name, (*tci).second);
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -179,13 +183,12 @@ flatten_vertex_properties() {
 void BuilderPrimI::
 void BuilderPrimI::
 fill_geom(Geom *geom, const PTA_ushort &v_array,
 fill_geom(Geom *geom, const PTA_ushort &v_array,
           GeomBindType n_attr, PTA_ushort n_array,
           GeomBindType n_attr, PTA_ushort n_array,
-          GeomBindType t_attr, PTA_ushort t_array,
           GeomBindType c_attr, PTA_ushort c_array,
           GeomBindType c_attr, PTA_ushort c_array,
+          const BuilderPrimI::TexCoordFill &texcoords,
           const BuilderBucket &bucket,
           const BuilderBucket &bucket,
           int num_prims, int num_components, int num_verts) {
           int num_prims, int num_components, int num_verts) {
   PTA_Vertexf v_data = bucket.get_coords();
   PTA_Vertexf v_data = bucket.get_coords();
   PTA_Normalf n_data = bucket.get_normals();
   PTA_Normalf n_data = bucket.get_normals();
-  PTA_TexCoordf t_data = bucket.get_texcoords();
   PTA_Colorf c_data = bucket.get_colors();
   PTA_Colorf c_data = bucket.get_colors();
 
 
   // Make sure the data pointers are NULL if the attribute is off.
   // Make sure the data pointers are NULL if the attribute is off.
@@ -193,10 +196,6 @@ fill_geom(Geom *geom, const PTA_ushort &v_array,
     n_data = NULL;
     n_data = NULL;
     n_array = NULL;
     n_array = NULL;
   }
   }
-  if (t_attr == G_OFF) {
-    t_data = NULL;
-    t_array = NULL;
-  }
   if (c_attr == G_OFF) {
   if (c_attr == G_OFF) {
     c_data = NULL;
     c_data = NULL;
     c_array = NULL;
     c_array = NULL;
@@ -207,11 +206,6 @@ fill_geom(Geom *geom, const PTA_ushort &v_array,
     (n_attr==G_PER_COMPONENT) ? num_components :
     (n_attr==G_PER_COMPONENT) ? num_components :
     (n_attr==G_PER_PRIM) ? num_prims :
     (n_attr==G_PER_PRIM) ? num_prims :
     (n_attr==G_OVERALL) ? 1 : 0;
     (n_attr==G_OVERALL) ? 1 : 0;
-  int t_len =
-    (t_attr==G_PER_VERTEX) ? num_verts :
-    (t_attr==G_PER_COMPONENT) ? num_components :
-    (t_attr==G_PER_PRIM) ? num_prims :
-    (t_attr==G_OVERALL) ? 1 : 0;
   int c_len =
   int c_len =
     (c_attr==G_PER_VERTEX) ? num_verts :
     (c_attr==G_PER_VERTEX) ? num_verts :
     (c_attr==G_PER_COMPONENT) ? num_components :
     (c_attr==G_PER_COMPONENT) ? num_components :
@@ -223,23 +217,12 @@ fill_geom(Geom *geom, const PTA_ushort &v_array,
       memcmp(v_array, n_array, sizeof(ushort) * n_len)==0) {
       memcmp(v_array, n_array, sizeof(ushort) * n_len)==0) {
     n_array = v_array;
     n_array = v_array;
   }
   }
-  if (t_attr != G_OFF) {
-    if (memcmp(v_array, t_array, sizeof(ushort) * t_len)==0) {
-      t_array = v_array;
-    } else if (t_len <= n_len &&
-               memcmp(n_array, t_array, sizeof(ushort) * t_len)==0) {
-      t_array = n_array;
-    }
-  }
   if (c_attr != G_OFF) {
   if (c_attr != G_OFF) {
     if (memcmp(v_array, c_array, sizeof(ushort) * c_len)==0) {
     if (memcmp(v_array, c_array, sizeof(ushort) * c_len)==0) {
       c_array = v_array;
       c_array = v_array;
     } else if (c_len <= n_len &&
     } else if (c_len <= n_len &&
                memcmp(n_array, c_array, sizeof(ushort) * c_len)==0) {
                memcmp(n_array, c_array, sizeof(ushort) * c_len)==0) {
       c_array = n_array;
       c_array = n_array;
-    } else if (c_len <= t_len &&
-               memcmp(t_array, c_array, sizeof(ushort) * c_len)==0) {
-      c_array = t_array;
     }
     }
   }
   }
 
 
@@ -249,12 +232,42 @@ fill_geom(Geom *geom, const PTA_ushort &v_array,
     geom->set_normals(n_data, n_attr, n_array);
     geom->set_normals(n_data, n_attr, n_array);
   }
   }
 
 
-  if (t_attr != G_OFF) {
-    geom->set_texcoords(t_data, t_attr, t_array);
-  }
-
   if (c_attr != G_OFF) {
   if (c_attr != G_OFF) {
     geom->set_colors(c_data, c_attr, c_array);
     geom->set_colors(c_data, c_attr, c_array);
   }
   }
+
+  TexCoordFill::const_iterator tci;
+  for (tci = texcoords.begin(); tci != texcoords.end(); ++tci) {
+    const TexCoordName *name = (*tci).first;
+    PTA_ushort t_array = (*tci).second;
+
+    PTA_TexCoordf t_data = bucket.get_texcoords(name);
+    if (t_data != (TexCoordf *)NULL) {
+      int t_len = num_verts;
+
+      // Can we share the index list with some other property?
+      if (memcmp(v_array, t_array, sizeof(ushort) * t_len)==0) {
+        t_array = v_array;
+      } else if (t_len <= n_len &&
+                 memcmp(n_array, t_array, sizeof(ushort) * t_len)==0) {
+        t_array = n_array;
+      } else if (t_len <= c_len &&
+                 memcmp(c_array, t_array, sizeof(ushort) * t_len)==0) {
+        t_array = c_array;
+        
+      } else {
+        TexCoordFill::const_iterator tci2;
+        for (tci2 = texcoords.begin(); tci2 != tci; ++tci2) {
+          PTA_ushort t_array2 = (*tci).second;
+          if (memcmp(t_array2, t_array, sizeof(ushort) * t_len)==0) {
+            t_array = t_array2;
+            break;
+          }
+        }
+      }
+
+      geom->set_texcoords(name, t_data, t_array);
+    }
+  }
 }
 }
 
 

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

@@ -93,8 +93,8 @@ public:
 
 
   static void fill_geom(Geom *geom, const PTA_BuilderV &v_array,
   static void fill_geom(Geom *geom, const PTA_BuilderV &v_array,
                         GeomBindType n_attr, const PTA_BuilderN &n_array,
                         GeomBindType n_attr, const PTA_BuilderN &n_array,
-                        GeomBindType t_attr, const PTA_BuilderTC &t_array,
                         GeomBindType c_attr, const PTA_BuilderC &c_array,
                         GeomBindType c_attr, const PTA_BuilderC &c_array,
+                        const TexCoordFill &texcoords,
                         const BuilderBucket &bucket,
                         const BuilderBucket &bucket,
                         int num_prims, int num_components, int num_verts);
                         int num_prims, int num_components, int num_verts);
 };
 };
@@ -118,8 +118,8 @@ public:
 
 
   static void fill_geom(Geom *geom, const PTA_ushort &v_array,
   static void fill_geom(Geom *geom, const PTA_ushort &v_array,
                         GeomBindType n_attr, PTA_ushort n_array,
                         GeomBindType n_attr, PTA_ushort n_array,
-                        GeomBindType t_attr, PTA_ushort t_array,
                         GeomBindType c_attr, PTA_ushort c_array,
                         GeomBindType c_attr, PTA_ushort c_array,
+                        const TexCoordFill &texcoords,
                         const BuilderBucket &bucket,
                         const BuilderBucket &bucket,
                         int num_prims, int num_components, int num_verts);
                         int num_prims, int num_components, int num_verts);
 };
 };

+ 53 - 40
panda/src/builder/builderPrimTempl.I

@@ -212,22 +212,6 @@ has_vertex_color() const {
 }
 }
 
 
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: BuilderPrimTempl::has_vertex_texcoord
-//       Access: Public
-//  Description: Returns true if each of the primitive's vertices has
-//               a texcoord value set.
-////////////////////////////////////////////////////////////////////
-template <class VTX>
-INLINE bool BuilderPrimTempl<VTX>::
-has_vertex_texcoord() const {
-  if ((_overall & BAF_overall_updated) == 0) {
-    ((BuilderPrimTempl<VTX> *)this)->update_overall_attrib();
-  }
-  return (_overall & BAF_vertex_texcoord)!=0;
-}
-
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: BuilderPrimTempl::has_vertex_pixel_size
 //     Function: BuilderPrimTempl::has_vertex_pixel_size
 //       Access: Public
 //       Access: Public
@@ -325,20 +309,6 @@ has_any_color() const {
 }
 }
 
 
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: BuilderPrimTempl::has_any_texcoord
-//       Access: Public
-//  Description: Returns true if the prim has a texcoord value set
-//               at any level.  Since texture coordinates can only be
-//               set on vertices, this is the same thing as
-//               has_vertex_texcoord().
-////////////////////////////////////////////////////////////////////
-template <class VTX>
-INLINE bool BuilderPrimTempl<VTX>::
-has_any_texcoord() const {
-  return has_vertex_texcoord();
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: BuilderPrimTempl::has_any_pixel_size
 //     Function: BuilderPrimTempl::has_any_pixel_size
 //       Access: Public
 //       Access: Public
@@ -354,6 +324,43 @@ has_any_pixel_size() const {
     has_component_pixel_size();
     has_component_pixel_size();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderPrimTempl::tcn_begin
+//       Access: Public
+//  Description: Returns an iterator that can be used to walk through
+//               the list of TexCoordNames that are common to all
+//               vertices on this primitive.
+////////////////////////////////////////////////////////////////////
+template <class VTX>
+INLINE TYPENAME BuilderPrimTempl<VTX>::tcn_const_iterator BuilderPrimTempl<VTX>::
+tcn_begin() const {
+  return _texcoord_names.begin();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderPrimTempl::tcn_end
+//       Access: Public
+//  Description: Returns an iterator that can be used to walk through
+//               the list of TexCoordNames that are common to all
+//               vertices on this primitive.
+////////////////////////////////////////////////////////////////////
+template <class VTX>
+INLINE TYPENAME BuilderPrimTempl<VTX>::tcn_const_iterator BuilderPrimTempl<VTX>::
+tcn_end() const {
+  return _texcoord_names.end();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderPrimTempl::tcn_size
+//       Access: Public
+//  Description: Returns the number of TexCoordNames that are common
+//               to all vertices on this primitive.
+////////////////////////////////////////////////////////////////////
+template <class VTX>
+INLINE TYPENAME BuilderPrimTempl<VTX>::tcn_size_type BuilderPrimTempl<VTX>::
+tcn_size() const {
+  return _texcoord_names.size();
+}
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: BuilderPrimTempl::clear
 //     Function: BuilderPrimTempl::clear
@@ -713,7 +720,7 @@ sort_value() const {
   return
   return
     ((has_vertex_normal() ? 1:0) << 5) |
     ((has_vertex_normal() ? 1:0) << 5) |
     ((has_vertex_color() ? 1:0) << 4) |
     ((has_vertex_color() ? 1:0) << 4) |
-    ((has_any_texcoord() ? 1:0) << 3) |
+    ((!_texcoord_names.empty() ? 1:0) << 3) |
     ((has_any_normal() ? 1:0) << 2) |
     ((has_any_normal() ? 1:0) << 2) |
     ((has_any_color() ? 1:0) << 1) |
     ((has_any_color() ? 1:0) << 1) |
     ((has_pixel_size() ? 1:0) << 0);
     ((has_pixel_size() ? 1:0) << 0);
@@ -872,14 +879,25 @@ update_overall_attrib() {
 
 
   bool has_vertex_normal = false;
   bool has_vertex_normal = false;
   bool has_vertex_color = false;
   bool has_vertex_color = false;
-  bool has_vertex_texcoord = false;
   bool has_vertex_pixel_size = false;
   bool has_vertex_pixel_size = false;
 
 
+  _texcoord_names.clear();
+
   int i;
   int i;
   if (num_verts > 0) {
   if (num_verts > 0) {
-    has_vertex_texcoord = true;
-    for (i = 0; i < num_verts && has_vertex_texcoord; i++) {
-      has_vertex_texcoord = get_vertex(i).has_texcoord();
+    // Get the intersection of TexCoordNames that are defined on all
+    // vertices.
+    const Vertex &vtx = get_vertex(0);
+    TYPENAME Vertex::tc_const_iterator tci;
+    for (tci = vtx.tc_begin(); tci != vtx.tc_end(); ++tci) {
+      const TexCoordName *name = (*tci).first;
+      bool has_texcoord = true;
+      for (i = 1; i < num_verts && has_texcoord; i++) {
+        has_texcoord = get_vertex(i).has_texcoord(name);
+      }
+      if (has_texcoord) {
+        _texcoord_names.insert(name);
+      }
     }
     }
 
 
     if (get_vertex(0).has_normal()) {
     if (get_vertex(0).has_normal()) {
@@ -1018,11 +1036,6 @@ update_overall_attrib() {
     _overall |= BAF_overall_color;
     _overall |= BAF_overall_color;
   }
   }
 
 
-  if (has_vertex_texcoord) {
-    // Each vertex has a texture coordinate.
-    _overall |= BAF_vertex_texcoord;
-  }
-
   if (has_common_pixel_size) {
   if (has_common_pixel_size) {
     // The primitive has one overall pixel size, or each of the vertices
     // The primitive has one overall pixel size, or each of the vertices
     // has the same pixel size.
     // has the same pixel size.

+ 14 - 2
panda/src/builder/builderPrimTempl.h

@@ -45,6 +45,12 @@ public:
   typedef TYPENAME VTX::TType TType;
   typedef TYPENAME VTX::TType TType;
   typedef TYPENAME VTX::CType CType;
   typedef TYPENAME VTX::CType CType;
   typedef TYPENAME VTX::Attrib DAttrib;
   typedef TYPENAME VTX::Attrib DAttrib;
+  typedef pset<const TexCoordName *> TexCoordNames;
+  typedef TexCoordNames::const_iterator tcn_const_iterator;
+  typedef TexCoordNames::size_type tcn_size_type;
+
+  // This type is passed to fill_geom() for the texcoords.
+  typedef pmap<const TexCoordName *, PTA(TType) > TexCoordFill;
 
 
   INLINE BuilderPrimTempl();
   INLINE BuilderPrimTempl();
   INLINE BuilderPrimTempl(const BuilderPrimTempl &copy);
   INLINE BuilderPrimTempl(const BuilderPrimTempl &copy);
@@ -78,7 +84,6 @@ public:
   // normal.
   // normal.
   INLINE bool has_vertex_normal() const;
   INLINE bool has_vertex_normal() const;
   INLINE bool has_vertex_color() const;
   INLINE bool has_vertex_color() const;
-  INLINE bool has_vertex_texcoord() const;
   INLINE bool has_vertex_pixel_size() const;
   INLINE bool has_vertex_pixel_size() const;
 
 
   // has_component_normal() can only be true for aggregate primitive
   // has_component_normal() can only be true for aggregate primitive
@@ -96,9 +101,15 @@ public:
 
 
   INLINE bool has_any_normal() const;
   INLINE bool has_any_normal() const;
   INLINE bool has_any_color() const;
   INLINE bool has_any_color() const;
-  INLINE bool has_any_texcoord() const;
   INLINE bool has_any_pixel_size() const;
   INLINE bool has_any_pixel_size() const;
 
 
+  // The following methods iterate through the list of texture
+  // coordinate names that all vertices in the primitive have in
+  // common.
+  INLINE tcn_const_iterator tcn_begin() const;
+  INLINE tcn_const_iterator tcn_end() const;
+  INLINE tcn_size_type tcn_size() const;
+
   INLINE BuilderPrimTempl &clear();
   INLINE BuilderPrimTempl &clear();
   INLINE BuilderPrimTempl &clear_vertices();
   INLINE BuilderPrimTempl &clear_vertices();
 
 
@@ -140,6 +151,7 @@ protected:
 
 
   Verts _verts;
   Verts _verts;
   Components _components;
   Components _components;
+  TexCoordNames _texcoord_names;
   BuilderPrimType _type;
   BuilderPrimType _type;
   int _overall;
   int _overall;
 };
 };

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

@@ -29,10 +29,6 @@ ostream &operator << (ostream &out, BuilderAttribFlags baf) {
     out << space << "normal";
     out << space << "normal";
     space = " ";
     space = " ";
   }
   }
-  if (baf & BAF_texcoord) {
-    out << space << "texcoord";
-    space = " ";
-  }
   if (baf & BAF_color) {
   if (baf & BAF_color) {
     out << space << "color";
     out << space << "color";
     space = " ";
     space = " ";
@@ -61,10 +57,6 @@ ostream &operator << (ostream &out, BuilderAttribFlags baf) {
     out << space << "vertex_normal";
     out << space << "vertex_normal";
     space = " ";
     space = " ";
   }
   }
-  if (baf & BAF_vertex_texcoord) {
-    out << space << "vertex_texcoord";
-    space = " ";
-  }
   if (baf & BAF_vertex_color) {
   if (baf & BAF_vertex_color) {
     out << space << "vertex_color";
     out << space << "vertex_color";
     space = " ";
     space = " ";

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

@@ -39,7 +39,7 @@ typedef PTA_Colorf PTA_BuilderC;
 enum BuilderAttribFlags {
 enum BuilderAttribFlags {
   BAF_coord                  = 0x00001,
   BAF_coord                  = 0x00001,
   BAF_normal                 = 0x00002,
   BAF_normal                 = 0x00002,
-  BAF_texcoord               = 0x00004,
+  //BAF_texcoord               = 0x00004,  No longer used.
   BAF_color                  = 0x00008,
   BAF_color                  = 0x00008,
   BAF_pixel_size             = 0x00010,
   BAF_pixel_size             = 0x00010,
 
 
@@ -49,7 +49,7 @@ enum BuilderAttribFlags {
   BAF_overall_pixel_size     = 0x00800,
   BAF_overall_pixel_size     = 0x00800,
 
 
   BAF_vertex_normal          = 0x01000,
   BAF_vertex_normal          = 0x01000,
-  BAF_vertex_texcoord        = 0x02000,
+  //BAF_vertex_texcoord        = 0x02000,  No longer used.
   BAF_vertex_color           = 0x04000,
   BAF_vertex_color           = 0x04000,
   BAF_vertex_pixel_size      = 0x08000,
   BAF_vertex_pixel_size      = 0x08000,
 
 

+ 9 - 9
panda/src/builder/builderVertex.I

@@ -60,8 +60,8 @@ set_normal_value(const BuilderN *array, ushort index) {
 //               assumes the array is the same one it's indexing on).
 //               assumes the array is the same one it's indexing on).
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void BuilderVertex::
 INLINE void BuilderVertex::
-set_texcoord_value(const BuilderTC *array, ushort index) {
-  set_texcoord(array[index]);
+set_texcoord_value(const TexCoordName *name, const BuilderTC *array, ushort index) {
+  set_texcoord(name, array[index]);
 }
 }
 
 
 
 
@@ -130,8 +130,8 @@ get_normal_value(const BuilderBucket &) const {
 //               get_coord_value().
 //               get_coord_value().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE BuilderTC BuilderVertex::
 INLINE BuilderTC BuilderVertex::
-get_texcoord_value(const BuilderBucket &) const {
-  return get_texcoord();
+get_texcoord_value(const TexCoordName *name, const BuilderBucket &) const {
+  return get_texcoord(name);
 }
 }
 
 
 
 
@@ -191,8 +191,8 @@ set_normal_value(const BuilderN *, ushort index) {
 //               assumes the array is the same one it's indexing on).
 //               assumes the array is the same one it's indexing on).
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void BuilderVertexI::
 INLINE void BuilderVertexI::
-set_texcoord_value(const BuilderTC *, ushort index) {
-  set_texcoord(index);
+set_texcoord_value(const TexCoordName *name,const BuilderTC *, ushort index) {
+  set_texcoord(name, index);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -259,9 +259,9 @@ get_normal_value(const BuilderBucket &bucket) const {
 //               get_coord_value().
 //               get_coord_value().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE BuilderTC BuilderVertexI::
 INLINE BuilderTC BuilderVertexI::
-get_texcoord_value(const BuilderBucket &bucket) const {
-  nassertr(bucket.get_texcoords() != (TexCoordf *)NULL, BuilderTC());
-  return bucket.get_texcoords()[get_texcoord()];
+get_texcoord_value(const TexCoordName *name, const BuilderBucket &bucket) const {
+  nassertr(bucket.get_texcoords(name) != (TexCoordf *)NULL, BuilderTC());
+  return bucket.get_texcoords(name)[get_texcoord(name)];
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 4 - 4
panda/src/builder/builderVertex.h

@@ -80,12 +80,12 @@ public:
 
 
   INLINE void set_coord_value(const BuilderV *array, ushort index);
   INLINE void set_coord_value(const BuilderV *array, ushort index);
   INLINE void set_normal_value(const BuilderN *array, ushort index);
   INLINE void set_normal_value(const BuilderN *array, ushort index);
-  INLINE void set_texcoord_value(const BuilderTC *array, ushort index);
+  INLINE void set_texcoord_value(const TexCoordName *name, const BuilderTC *array, ushort index);
   INLINE void set_color_value(const BuilderC *array, ushort index);
   INLINE void set_color_value(const BuilderC *array, ushort index);
 
 
   INLINE BuilderV get_coord_value(const BuilderBucket &bucket) const;
   INLINE BuilderV get_coord_value(const BuilderBucket &bucket) const;
   INLINE BuilderN get_normal_value(const BuilderBucket &bucket) const;
   INLINE BuilderN get_normal_value(const BuilderBucket &bucket) const;
-  INLINE BuilderTC get_texcoord_value(const BuilderBucket &bucket) const;
+  INLINE BuilderTC get_texcoord_value(const TexCoordName *name, const BuilderBucket &bucket) const;
   INLINE BuilderC get_color_value(const BuilderBucket &bucket) const;
   INLINE BuilderC get_color_value(const BuilderBucket &bucket) const;
 
 
 };
 };
@@ -111,12 +111,12 @@ public:
 
 
   INLINE void set_coord_value(const BuilderV *array, ushort index);
   INLINE void set_coord_value(const BuilderV *array, ushort index);
   INLINE void set_normal_value(const BuilderN *array, ushort index);
   INLINE void set_normal_value(const BuilderN *array, ushort index);
-  INLINE void set_texcoord_value(const BuilderTC *array, ushort index);
+  INLINE void set_texcoord_value(const TexCoordName *name, const BuilderTC *array, ushort index);
   INLINE void set_color_value(const BuilderC *array, ushort index);
   INLINE void set_color_value(const BuilderC *array, ushort index);
 
 
   INLINE BuilderV get_coord_value(const BuilderBucket &bucket) const;
   INLINE BuilderV get_coord_value(const BuilderBucket &bucket) const;
   INLINE BuilderN get_normal_value(const BuilderBucket &bucket) const;
   INLINE BuilderN get_normal_value(const BuilderBucket &bucket) const;
-  INLINE BuilderTC get_texcoord_value(const BuilderBucket &bucket) const;
+  INLINE BuilderTC get_texcoord_value(const TexCoordName *name, const BuilderBucket &bucket) const;
   INLINE BuilderC get_color_value(const BuilderBucket &bucket) const;
   INLINE BuilderC get_color_value(const BuilderBucket &bucket) const;
 };
 };
 
 

+ 79 - 24
panda/src/builder/builderVertexTempl.I

@@ -60,7 +60,7 @@ INLINE BuilderVertexTempl<VT, NT, TT, CT> &BuilderVertexTempl<VT, NT, TT, CT>::
 operator = (const BuilderVertexTempl<VT, NT, TT, CT> &copy) {
 operator = (const BuilderVertexTempl<VT, NT, TT, CT> &copy) {
   BuilderAttribTempl<VT, NT, TT, CT>::operator = (copy);
   BuilderAttribTempl<VT, NT, TT, CT>::operator = (copy);
   _coord = copy._coord;
   _coord = copy._coord;
-  _texcoord = copy._texcoord;
+  _texcoords = copy._texcoords;
   _pixel_size = copy._pixel_size;
   _pixel_size = copy._pixel_size;
 
 
   return *this;
   return *this;
@@ -170,8 +170,25 @@ clear_normal() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 template <class VT, class NT, class TT, class CT>
 template <class VT, class NT, class TT, class CT>
 INLINE bool BuilderVertexTempl<VT, NT, TT, CT>::
 INLINE bool BuilderVertexTempl<VT, NT, TT, CT>::
-has_texcoord() const {
-  return (_flags & BAF_texcoord) != 0;
+has_texcoord(const TexCoordName *name) const {
+  TYPENAME TexCoords::const_iterator ti = _texcoords.find(name);
+  return (ti != _texcoords.end());
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderVertexTempl::get_texcoord
+//       Access: Public
+//  Description: Returns the vertex's texture coordinate.  It is an
+//               error to call this without first verifying that
+//               has_texcoord() is true.
+////////////////////////////////////////////////////////////////////
+template <class VT, class NT, class TT, class CT>
+INLINE TYPENAME BuilderVertexTempl<VT, NT, TT, CT>::TType BuilderVertexTempl<VT, NT, TT, CT>::
+get_texcoord(const TexCoordName *name) const {
+  TYPENAME TexCoords::const_iterator ti = _texcoords.find(name);
+  nassertr(ti != _texcoords.end(), (*ti).second);
+  return (*ti).second;
 }
 }
 
 
 
 
@@ -182,9 +199,8 @@ has_texcoord() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 template <class VT, class NT, class TT, class CT>
 template <class VT, class NT, class TT, class CT>
 INLINE BuilderVertexTempl<VT, NT, TT, CT> &BuilderVertexTempl<VT, NT, TT, CT>::
 INLINE BuilderVertexTempl<VT, NT, TT, CT> &BuilderVertexTempl<VT, NT, TT, CT>::
-set_texcoord(const TType &t) {
-  _flags |= BAF_texcoord;
-  _texcoord = t;
+set_texcoord(const TexCoordName *name, const TType &t) {
+  _texcoords[name] = t;
   return *this;
   return *this;
 }
 }
 
 
@@ -195,24 +211,44 @@ set_texcoord(const TType &t) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 template <class VT, class NT, class TT, class CT>
 template <class VT, class NT, class TT, class CT>
 INLINE BuilderVertexTempl<VT, NT, TT, CT> &BuilderVertexTempl<VT, NT, TT, CT>::
 INLINE BuilderVertexTempl<VT, NT, TT, CT> &BuilderVertexTempl<VT, NT, TT, CT>::
-clear_texcoord() {
-  _flags &= ~BAF_texcoord;
+clear_texcoord(const TexCoordName *name) {
+  _texcoords.erase(name);
   return *this;
   return *this;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderVertexTempl::tc_begin
+//       Access: Public
+//  Description: Returns an iterator that can be used to walk through
+//               the set of TexCoord names and their definitions.
+////////////////////////////////////////////////////////////////////
+template <class VT, class NT, class TT, class CT>
+INLINE TYPENAME BuilderVertexTempl<VT, NT, TT, CT>::tc_const_iterator BuilderVertexTempl<VT, NT, TT, CT>::
+tc_begin() const {
+  return _texcoords.begin();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BuilderVertexTempl::tc_end
+//       Access: Public
+//  Description: Returns an iterator that can be used to walk through
+//               the set of TexCoord names and their definitions.
+////////////////////////////////////////////////////////////////////
+template <class VT, class NT, class TT, class CT>
+INLINE TYPENAME BuilderVertexTempl<VT, NT, TT, CT>::tc_const_iterator BuilderVertexTempl<VT, NT, TT, CT>::
+tc_end() const {
+  return _texcoords.end();
+}
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: BuilderVertexTempl::get_texcoord
+//     Function: BuilderVertexTempl::tc_size
 //       Access: Public
 //       Access: Public
-//  Description: Returns the vertex's texture coordinate.  It is an
-//               error to call this without first verifying that
-//               has_texcoord() is true.
+//  Description: Returns the number of TexCoord definitions.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 template <class VT, class NT, class TT, class CT>
 template <class VT, class NT, class TT, class CT>
-INLINE TYPENAME BuilderVertexTempl<VT, NT, TT, CT>::TType BuilderVertexTempl<VT, NT, TT, CT>::
-get_texcoord() const {
-  nassertr(has_texcoord(), _texcoord);
-  return _texcoord;
+INLINE TYPENAME BuilderVertexTempl<VT, NT, TT, CT>::tc_size_type BuilderVertexTempl<VT, NT, TT, CT>::
+tc_size() const {
+  return _texcoords.size();
 }
 }
 
 
 
 
@@ -336,11 +372,28 @@ compare_to(const BuilderVertexTempl<VT, NT, TT, CT> &other) const {
     }
     }
   }
   }
 
 
-  if (has_texcoord()) {
-    int texcoord_compare = builder_compare(_texcoord, other._texcoord);
-    if (texcoord_compare != 0) {
-      return texcoord_compare;
+  TYPENAME TexCoords::const_iterator ai, bi;
+  ai = _texcoords.begin();
+  bi = other._texcoords.begin();
+  while (ai != _texcoords.end() && bi != other._texcoords.end()) {
+    if ((*ai).first < (*bi).first) {
+      return -1;
+    } else if ((*bi).first < (*ai).first) {
+      return 1;
+    } else {
+      int texcoord_compare = builder_compare((*ai).second, (*bi).second);
+      if (texcoord_compare != 0) {
+        return texcoord_compare;
+      }
     }
     }
+    ++ai;
+    ++bi;
+  }
+  if (bi != other._texcoords.end()) {
+    return -1;
+  }
+  if (ai != _texcoords.end()) {
+    return 1;
   }
   }
 
 
   return BuilderAttribTempl<VT, NT, TT, CT>::compare_to(other);
   return BuilderAttribTempl<VT, NT, TT, CT>::compare_to(other);
@@ -363,14 +416,16 @@ output(ostream &out) const {
       out << " normal " << get_normal();
       out << " normal " << get_normal();
     }
     }
 
 
-    if (has_texcoord()) {
-      out << " texcoord " << get_texcoord();
-    }
-
     if (has_color()) {
     if (has_color()) {
       out << " color " << get_color();
       out << " color " << get_color();
     }
     }
 
 
+    TYPENAME TexCoords::const_iterator ti;
+    for (ti = _texcoords.begin(); ti != _texcoords.end(); ++ti) {
+      const TexCoordName *name = (*ti).first;
+      out << " texcoord \"" << name->get_name() << "\" " << (*ti).second;
+    }
+
     if (has_pixel_size()) {
     if (has_pixel_size()) {
       out << " pixel_size " << get_pixel_size();
       out << " pixel_size " << get_pixel_size();
     }
     }

+ 14 - 5
panda/src/builder/builderVertexTempl.h

@@ -24,10 +24,13 @@
 #include "builderTypes.h"
 #include "builderTypes.h"
 #include "builderAttribTempl.h"
 #include "builderAttribTempl.h"
 #include "builder_compare.h"
 #include "builder_compare.h"
+#include "texCoordName.h"
+#include "pointerTo.h"
 
 
 #include "notify.h"
 #include "notify.h"
 #include "pvector.h"
 #include "pvector.h"
 
 
+
 /////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////
 //       Class : BuilderVertexTempl
 //       Class : BuilderVertexTempl
 // Description : The main body of BuilderVertex and BuilderVertexI.
 // Description : The main body of BuilderVertex and BuilderVertexI.
@@ -42,6 +45,9 @@ public:
   typedef NT NType;
   typedef NT NType;
   typedef TT TType;
   typedef TT TType;
   typedef CT CType;
   typedef CT CType;
+  typedef pmap<CPT(TexCoordName), TType> TexCoords;
+  typedef TYPENAME TexCoords::const_iterator tc_const_iterator;
+  typedef TYPENAME TexCoords::size_type tc_size_type;
 
 
   INLINE BuilderVertexTempl();
   INLINE BuilderVertexTempl();
   INLINE BuilderVertexTempl(const VType &c);
   INLINE BuilderVertexTempl(const VType &c);
@@ -58,10 +64,13 @@ public:
   INLINE BuilderVertexTempl &set_normal(const NType &c);
   INLINE BuilderVertexTempl &set_normal(const NType &c);
   INLINE BuilderVertexTempl &clear_normal();
   INLINE BuilderVertexTempl &clear_normal();
 
 
-  INLINE bool has_texcoord() const;
-  INLINE TType get_texcoord() const;
-  INLINE BuilderVertexTempl &set_texcoord(const TType &t);
-  INLINE BuilderVertexTempl &clear_texcoord();
+  INLINE bool has_texcoord(const TexCoordName *name) const;
+  INLINE TType get_texcoord(const TexCoordName *name) const;
+  INLINE BuilderVertexTempl &set_texcoord(const TexCoordName *name, const TType &t);
+  INLINE BuilderVertexTempl &clear_texcoord(const TexCoordName *name);
+  INLINE tc_const_iterator tc_begin() const;
+  INLINE tc_const_iterator tc_end() const;
+  INLINE tc_size_type tc_size() const;
 
 
   INLINE BuilderVertexTempl &set_color(const CType &c);
   INLINE BuilderVertexTempl &set_color(const CType &c);
   INLINE BuilderVertexTempl &clear_color();
   INLINE BuilderVertexTempl &clear_color();
@@ -78,7 +87,7 @@ public:
 
 
 protected:
 protected:
   VType _coord;
   VType _coord;
-  TType _texcoord;
+  TexCoords _texcoords;
 };
 };
 
 
 template <class VT, class NT, class TT, class CT>
 template <class VT, class NT, class TT, class CT>

+ 4 - 4
panda/src/char/character.cxx

@@ -280,7 +280,7 @@ r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Character::r_copy_char
 //     Function: Character::r_copy_char
 //       Access: Private
 //       Access: Private
-//  Description: Recursively walks the scene graph hiernodehy below the
+//  Description: Recursively walks the scene graph hierarchy below the
 //               Character node, duplicating it while noting the
 //               Character node, duplicating it while noting the
 //               orig:copy node mappings, and also updates any
 //               orig:copy node mappings, and also updates any
 //               GeomNodes found.
 //               GeomNodes found.
@@ -297,7 +297,7 @@ r_copy_char(PandaNode *dest, const PandaNode *source,
     dest_gnode->remove_all_geoms();
     dest_gnode->remove_all_geoms();
     int num_geoms = source_gnode->get_num_geoms();
     int num_geoms = source_gnode->get_num_geoms();
     for (int i = 0; i < num_geoms; i++) {
     for (int i = 0; i < num_geoms; i++) {
-      Geom *geom = source_gnode->get_geom(i);
+      const Geom *geom = source_gnode->get_geom(i);
       const RenderState *state = source_gnode->get_geom_state(i);
       const RenderState *state = source_gnode->get_geom_state(i);
       dest_gnode->add_geom(copy_geom(geom, from), state);
       dest_gnode->add_geom(copy_geom(geom, from), state);
     }
     }
@@ -338,7 +338,7 @@ r_copy_char(PandaNode *dest, const PandaNode *source,
 //               returns the same Geom.
 //               returns the same Geom.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PT(Geom) Character::
 PT(Geom) Character::
-copy_geom(Geom *source, const Character *from) {
+copy_geom(const Geom *source, const Character *from) {
   GeomBindType bind;
   GeomBindType bind;
   PTA_ushort index;
   PTA_ushort index;
 
 
@@ -347,7 +347,7 @@ copy_geom(Geom *source, const Character *from) {
   PTA_Colorf colors;
   PTA_Colorf colors;
   PTA_TexCoordf texcoords;
   PTA_TexCoordf texcoords;
 
 
-  PT(Geom) dest = source;
+  PT(Geom) dest = (Geom *)source;
 
 
   source->get_coords(coords, index);
   source->get_coords(coords, index);
   if ((coords != (void *)NULL) && (coords == (from->_cv._coords))) {
   if ((coords != (void *)NULL) && (coords == (from->_cv._coords))) {

+ 1 - 1
panda/src/char/character.h

@@ -73,7 +73,7 @@ private:
   virtual void r_copy_children(const PandaNode *from, InstanceMap &inst_map);
   virtual void r_copy_children(const PandaNode *from, InstanceMap &inst_map);
   void r_copy_char(PandaNode *dest, const PandaNode *source,
   void r_copy_char(PandaNode *dest, const PandaNode *source,
                    const Character *from, NodeMap &node_map);
                    const Character *from, NodeMap &node_map);
-  PT(Geom) copy_geom(Geom *source, const Character *from);
+  PT(Geom) copy_geom(const Geom *source, const Character *from);
   void copy_node_pointers(const Character *from, const NodeMap &node_map);
   void copy_node_pointers(const Character *from, const NodeMap &node_map);
 
 
   // These are the actual dynamic vertex pools for this Character's
   // These are the actual dynamic vertex pools for this Character's

+ 2 - 2
panda/src/collide/collisionTraverser.cxx

@@ -668,7 +668,7 @@ compare_collider_to_geom_node(CollisionEntry &entry,
     int num_geoms = gnode->get_num_geoms();
     int num_geoms = gnode->get_num_geoms();
     for (int s = 0; s < num_geoms; s++) {
     for (int s = 0; s < num_geoms; s++) {
       entry._into = (CollisionSolid *)NULL;
       entry._into = (CollisionSolid *)NULL;
-      Geom *geom = DCAST(Geom, gnode->get_geom(s));
+      const Geom *geom = DCAST(Geom, gnode->get_geom(s));
       if (geom != (Geom *)NULL) {
       if (geom != (Geom *)NULL) {
         const BoundingVolume &geom_bv = geom->get_bound();
         const BoundingVolume &geom_bv = geom->get_bound();
         const GeometricBoundingVolume *geom_gbv = NULL;
         const GeometricBoundingVolume *geom_gbv = NULL;
@@ -716,7 +716,7 @@ compare_collider_to_solid(CollisionEntry &entry,
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CollisionTraverser::
 void CollisionTraverser::
-compare_collider_to_geom(CollisionEntry &entry, Geom *geom,
+compare_collider_to_geom(CollisionEntry &entry, const Geom *geom,
                          const GeometricBoundingVolume *from_node_gbv,
                          const GeometricBoundingVolume *from_node_gbv,
                          const GeometricBoundingVolume *geom_gbv) {
                          const GeometricBoundingVolume *geom_gbv) {
   bool within_geom_bounds = true;
   bool within_geom_bounds = true;

+ 1 - 1
panda/src/collide/collisionTraverser.h

@@ -102,7 +102,7 @@ private:
   void compare_collider_to_solid(CollisionEntry &entry,
   void compare_collider_to_solid(CollisionEntry &entry,
                                  const GeometricBoundingVolume *from_node_gbv,
                                  const GeometricBoundingVolume *from_node_gbv,
                                  const GeometricBoundingVolume *solid_gbv);
                                  const GeometricBoundingVolume *solid_gbv);
-  void compare_collider_to_geom(CollisionEntry &entry, Geom *geom,
+  void compare_collider_to_geom(CollisionEntry &entry, const Geom *geom,
                                 const GeometricBoundingVolume *from_node_gbv,
                                 const GeometricBoundingVolume *from_node_gbv,
                                 const GeometricBoundingVolume *solid_gbv);
                                 const GeometricBoundingVolume *solid_gbv);
 
 

+ 22 - 0
panda/src/display/graphicsStateGuardian.I

@@ -120,6 +120,26 @@ get_threading_model() const {
   return _threading_model;
   return _threading_model;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_texture_stages
+//       Access: Published
+//  Description: Returns the maximum number of simultaneous textures
+//               that may be applied to geometry with multitexturing,
+//               as supported by this particular GSG.  If you exceed
+//               this number, the lowest-priority texture stages will
+//               not be applied.  Use TextureStage::set_priority() to
+//               adjust the relative importance of the different
+//               texture stages.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_texture_stages() const {
+  return _max_texture_stages;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::set_scene
 //     Function: GraphicsStateGuardian::set_scene
@@ -228,6 +248,7 @@ modify_state(const RenderState *state) {
   }
   }
 #endif
 #endif
   _state = _state->issue_delta_modify(state, this);
   _state = _state->issue_delta_modify(state, this);
+  finish_modify_state();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -251,6 +272,7 @@ set_state(const RenderState *state) {
   }
   }
 #endif
 #endif
   _state = _state->issue_delta_set(state, this);
   _state = _state->issue_delta_set(state, this);
+  finish_modify_state();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 17 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -81,6 +81,11 @@ GraphicsStateGuardian(const FrameBufferProperties &properties) {
   _closing_gsg = false;
   _closing_gsg = false;
   _active = true;
   _active = true;
   _prepared_objects = new PreparedGraphicsObjects;
   _prepared_objects = new PreparedGraphicsObjects;
+
+  // Initially, we set this to 1 (the default--no multitexturing
+  // supported).  A derived GSG may set this differently if it
+  // supports multitexturing.
+  _max_texture_stages = 1;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1150,6 +1155,18 @@ set_blend_mode(ColorWriteAttrib::Mode, ColorBlendAttrib::Mode,
                TransparencyAttrib::Mode) {
                TransparencyAttrib::Mode) {
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::finish_modify_state
+//       Access: Protected, Virtual
+//  Description: Called after the GSG state has been modified via
+//               modify_state() or set_state(), this hook is provided
+//               for the derived class to do any further state setup
+//               work.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+finish_modify_state() {
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::free_pointers
 //     Function: GraphicsStateGuardian::free_pointers
 //       Access: Protected, Virtual
 //       Access: Protected, Virtual

+ 5 - 0
panda/src/display/graphicsStateGuardian.h

@@ -81,6 +81,8 @@ PUBLISHED:
   INLINE GraphicsEngine *get_engine() const;
   INLINE GraphicsEngine *get_engine() const;
   INLINE const GraphicsThreadingModel &get_threading_model() const;
   INLINE const GraphicsThreadingModel &get_threading_model() const;
 
 
+  INLINE int get_max_texture_stages() const;
+
 public:
 public:
   INLINE void set_scene(SceneSetup *scene_setup);
   INLINE void set_scene(SceneSetup *scene_setup);
   INLINE SceneSetup *get_scene() const;
   INLINE SceneSetup *get_scene() const;
@@ -192,6 +194,8 @@ protected:
                                                  CPT(DisplayRegion) dr)=0;
                                                  CPT(DisplayRegion) dr)=0;
   virtual void restore_frame_buffer(SavedFrameBuffer *frame_buffer)=0;
   virtual void restore_frame_buffer(SavedFrameBuffer *frame_buffer)=0;
 
 
+  virtual void finish_modify_state();
+
   virtual void free_pointers();
   virtual void free_pointers();
   virtual void close_gsg();
   virtual void close_gsg();
   void panic_deactivate();
   void panic_deactivate();
@@ -268,6 +272,7 @@ protected:
   bool _active;
   bool _active;
 
 
   PT(PreparedGraphicsObjects) _prepared_objects;
   PT(PreparedGraphicsObjects) _prepared_objects;
+  int _max_texture_stages;
 
 
 public:
 public:
   // Statistics
   // Statistics

+ 27 - 0
panda/src/distort/projectionScreen.I

@@ -30,6 +30,33 @@ get_projector() const {
   return _projector;
   return _projector;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: ProjectionScreen::set_texcoord_name
+//       Access: Published
+//  Description: Specifies the name of the texture coordinates that
+//               are generated by this particular ProjectionScreen.
+//               This can be used in the presence of multitexturing to
+//               compute the UV's for just a subset of all of the
+//               active stages of the multitexture pipeline.
+////////////////////////////////////////////////////////////////////
+INLINE void ProjectionScreen::
+set_texcoord_name(const string &texcoord_name) {
+  _texcoord_name = TexCoordName::make(texcoord_name);
+  _stale = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ProjectionScreen::get_texcoord_name
+//       Access: Published
+//  Description: Returns the name of the texture coordinates that
+//               will be generated by this particular
+//               ProjectionScreen, as set by set_texcoord_name().
+////////////////////////////////////////////////////////////////////
+INLINE const string &ProjectionScreen::
+get_texcoord_name() const {
+  return _texcoord_name->get_name();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: ProjectionScreen::set_invert_uvs
 //     Function: ProjectionScreen::set_invert_uvs
 //       Access: Published
 //       Access: Published

+ 41 - 5
panda/src/distort/projectionScreen.cxx

@@ -34,6 +34,8 @@ TypeHandle ProjectionScreen::_type_handle;
 ProjectionScreen::
 ProjectionScreen::
 ProjectionScreen(const string &name) : PandaNode(name)
 ProjectionScreen(const string &name) : PandaNode(name)
 {
 {
+  _texcoord_name = TexCoordName::get_default();
+
   _invert_uvs = project_invert_uvs;
   _invert_uvs = project_invert_uvs;
   _vignette_on = false;
   _vignette_on = false;
   _vignette_color.set(0.0f, 0.0f, 0.0f, 1.0f);
   _vignette_color.set(0.0f, 0.0f, 0.0f, 1.0f);
@@ -61,6 +63,7 @@ ProjectionScreen(const ProjectionScreen &copy) :
   PandaNode(copy),
   PandaNode(copy),
   _projector(copy._projector),
   _projector(copy._projector),
   _projector_node(copy._projector_node),
   _projector_node(copy._projector_node),
+  _texcoord_name(copy._texcoord_name),
   _vignette_on(copy._vignette_on),
   _vignette_on(copy._vignette_on),
   _vignette_color(copy._vignette_color),
   _vignette_color(copy._vignette_color),
   _frame_color(copy._frame_color)
   _frame_color(copy._frame_color)
@@ -465,6 +468,12 @@ recompute_child(const WorkingNodePath &np, LMatrix4f &rel_mat,
     // the relative matrix from this point.
     // the relative matrix from this point.
     LMatrix4f new_rel_mat;
     LMatrix4f new_rel_mat;
     bool computed_new_rel_mat = false;
     bool computed_new_rel_mat = false;
+
+    if (distort_cat.is_spam()) {
+      distort_cat.spam()
+        << "Saving rel_mat " << (void *)&new_rel_mat << " at " << np << "\n";
+    }
+
     recompute_node(np, new_rel_mat, computed_new_rel_mat);
     recompute_node(np, new_rel_mat, computed_new_rel_mat);
     
     
   } else {
   } else {
@@ -488,11 +497,23 @@ recompute_geom_node(const WorkingNodePath &np, LMatrix4f &rel_mat,
     NodePath true_np = np.get_node_path();
     NodePath true_np = np.get_node_path();
     rel_mat = true_np.get_mat(_projector);
     rel_mat = true_np.get_mat(_projector);
     computed_rel_mat = true;
     computed_rel_mat = true;
+
+    if (distort_cat.is_spam()) {
+      distort_cat.spam()
+        << "Computing rel_mat " << (void *)&rel_mat << " at " << np << "\n";
+      distort_cat.spam()
+        << "  " << rel_mat << "\n";
+    }
+  } else {
+    if (distort_cat.is_spam()) {
+      distort_cat.spam()
+        << "Applying rel_mat " << (void *)&rel_mat << " to " << np << "\n";
+    }
   }
   }
 
 
   int num_geoms = node->get_num_geoms();
   int num_geoms = node->get_num_geoms();
   for (int i = 0; i < num_geoms; i++) {
   for (int i = 0; i < num_geoms; i++) {
-    Geom *geom = node->get_geom(i);
+    Geom *geom = node->get_unique_geom(i);
     recompute_geom(geom, rel_mat);
     recompute_geom(geom, rel_mat);
   }
   }
 }
 }
@@ -543,8 +564,23 @@ recompute_geom(Geom *geom, const LMatrix4f &rel_mat) {
     }
     }
   }
   }
 
 
-  // Now set the UV's.
-  geom->set_texcoords(uvs, G_PER_VERTEX);
+  // Now set the UV's.  If the geom already has indexed UV's, we need
+  // to make a new index for the geom.
+  geom->remove_texcoords(_texcoord_name);
+  if (!geom->are_texcoords_indexed()) {
+    // Simple case: no indexing needed.
+    geom->set_texcoords(_texcoord_name, uvs);
+
+  } else {
+    // Harder case: we need to make up an index.  But this isn't
+    // terribly hard.
+    PTA_ushort index = PTA_ushort::empty_array(num_vertices);
+    for (int i = 0; i < num_vertices; i++) {
+      index[i] = i;
+    }
+    geom->set_texcoords(_texcoord_name, uvs, index);
+  }
+
 
 
   if (_vignette_on) {
   if (_vignette_on) {
     geom->set_colors(_colors, G_PER_VERTEX, color_index);
     geom->set_colors(_colors, G_PER_VERTEX, color_index);
@@ -645,7 +681,7 @@ make_mesh_geom_node(const WorkingNodePath &np, const NodePath &camera,
 
 
   int num_geoms = node->get_num_geoms();
   int num_geoms = node->get_num_geoms();
   for (int i = 0; i < num_geoms; i++) {
   for (int i = 0; i < num_geoms; i++) {
-    Geom *geom = node->get_geom(i);
+    const Geom *geom = node->get_geom(i);
     PT(Geom) new_geom = 
     PT(Geom) new_geom = 
       make_mesh_geom(geom, lens_node->get_lens(), rel_mat);
       make_mesh_geom(geom, lens_node->get_lens(), rel_mat);
     if (new_geom != (Geom *)NULL) {
     if (new_geom != (Geom *)NULL) {
@@ -665,7 +701,7 @@ make_mesh_geom_node(const WorkingNodePath &np, const NodePath &camera,
 //               that involves an unprojectable vertex is eliminated.
 //               that involves an unprojectable vertex is eliminated.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PT(Geom) ProjectionScreen::
 PT(Geom) ProjectionScreen::
-make_mesh_geom(Geom *geom, Lens *lens, LMatrix4f &rel_mat) {
+make_mesh_geom(const Geom *geom, Lens *lens, LMatrix4f &rel_mat) {
   Geom *new_geom = geom->make_copy();
   Geom *new_geom = geom->make_copy();
   PT(Geom) result = new_geom;
   PT(Geom) result = new_geom;
 
 

+ 20 - 8
panda/src/distort/projectionScreen.h

@@ -25,6 +25,8 @@
 #include "lensNode.h"
 #include "lensNode.h"
 #include "geomNode.h"
 #include "geomNode.h"
 #include "nodePath.h"
 #include "nodePath.h"
+#include "texCoordName.h"
+#include "pointerTo.h"
 
 
 class Geom;
 class Geom;
 class WorkingNodePath;
 class WorkingNodePath;
@@ -35,15 +37,21 @@ class WorkingNodePath;
 //               projective texturing.  The ProjectionScreen node is
 //               projective texturing.  The ProjectionScreen node is
 //               the parent of a hierarchy of geometry that is
 //               the parent of a hierarchy of geometry that is
 //               considered a "screen"; the ProjectionScreen will
 //               considered a "screen"; the ProjectionScreen will
-//               automatically recompute all the UV's on its
-//               subordinate geometry according to the relative
-//               position and lens parameters of the indicated
-//               LensNode.
+//               automatically recompute all the UV's (for a
+//               particular texture stage) on its subordinate geometry
+//               according to the relative position and lens
+//               parameters of the indicated LensNode.
+//
+//               All this does is recompute UV's; the caller is
+//               responsible for applying the appropriate texture(s)
+//               to the geometry.
 //
 //
 //               This does not take advantage of any hardware-assisted
 //               This does not take advantage of any hardware-assisted
-//               projective texturing; nor does it presently support
-//               multitexturing.  However, it does support any kind of
-//               lens, linear or nonlinear, that might be defined
+//               projective texturing; all of the UV's are computed in
+//               the CPU.  (Use NodePath::project_texture() to enable
+//               hardware-assisted projective texturing.)  However,
+//               the ProjectionScreen interface does support any kind
+//               of lens, linear or nonlinear, that might be defined
 //               using the Lens interface, including fisheye and
 //               using the Lens interface, including fisheye and
 //               cylindrical lenses.
 //               cylindrical lenses.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -73,6 +81,9 @@ PUBLISHED:
                          float fill_ratio);
                          float fill_ratio);
   PT(PandaNode) make_flat_mesh(const NodePath &this_np, const NodePath &camera);
   PT(PandaNode) make_flat_mesh(const NodePath &this_np, const NodePath &camera);
 
 
+  INLINE void set_texcoord_name(const string &texcoord_name);
+  INLINE const string &get_texcoord_name() const;
+
   INLINE void set_invert_uvs(bool invert_uvs);
   INLINE void set_invert_uvs(bool invert_uvs);
   INLINE bool get_invert_uvs() const;
   INLINE bool get_invert_uvs() const;
 
 
@@ -108,11 +119,12 @@ private:
                                    const NodePath &camera,
                                    const NodePath &camera,
                                    LMatrix4f &rel_mat,
                                    LMatrix4f &rel_mat,
                                    bool &computed_rel_mat);
                                    bool &computed_rel_mat);
-  PT(Geom) make_mesh_geom(Geom *geom, Lens *lens, LMatrix4f &rel_mat);
+  PT(Geom) make_mesh_geom(const Geom *geom, Lens *lens, LMatrix4f &rel_mat);
 
 
 
 
   NodePath _projector;
   NodePath _projector;
   PT(LensNode) _projector_node;
   PT(LensNode) _projector_node;
+  CPT(TexCoordName) _texcoord_name;
   bool _invert_uvs;
   bool _invert_uvs;
   bool _vignette_on;
   bool _vignette_on;
   Colorf _vignette_color;
   Colorf _vignette_color;

+ 123 - 10
panda/src/doc/eggSyntax.txt

@@ -71,15 +71,14 @@ appear before they are referenced.
 
 
 <CoordinateSystem> { string }
 <CoordinateSystem> { string }
 
 
-  This is a new kind of node introduced for the new player.  The old
-  player used a Y-up coordinate system, so all modeling was done with
-  Y-up and egg files used Y-up implicitly.  Since we have adopted a
-  Z-up coordinate system with the new player, it becomes necessary to
-  make a distinction.  The contents of this entry are either the
-  string "Y-up" or "Z-up" to indicate the coordinate system used in
-  the egg file; the egg loader will automatically make a conversion if
-  necessary.  By convention, this entry should only appear at the
-  beginning of the file.  If it is omitted, Y-up is assumed.
+  This entry indicates the coordinate system used in the egg file; the
+  egg loader will automatically make a conversion if necessary.  The
+  following strings are valid: Y-up, Z-up, Y-up-right, Z-up-right,
+  Y-up-left, or Z-up-left.  (Y-up is the same as Y-up-right, and Z-up
+  is the same as Z-up-right.)
+
+  By convention, this entry should only appear at the beginning of the
+  file.  If it is omitted, Y-up is assumed.
 
 
 
 
 <Texture> name { filename [scalars] }
 <Texture> name { filename [scalars] }
@@ -154,6 +153,115 @@ appear before they are referenced.
 
 
       MODULATE
       MODULATE
       DECAL
       DECAL
+      BLEND
+      REPLACE
+      ADD
+
+  <Scalar> combine-rgb { combine-mode }
+  <Scalar> combine-alpha { combine-mode }
+  <Scalar> combine-rgb-source0 { combine-source }
+  <Scalar> combine-rgb-operand0 { combine-operand }
+  <Scalar> combine-rgb-source1 { combine-source }
+  <Scalar> combine-rgb-operand1 { combine-operand }
+  <Scalar> combine-rgb-source2 { combine-source }
+  <Scalar> combine-rgb-operand2 { combine-operand }
+  <Scalar> combine-alpha-source0 { combine-source }
+  <Scalar> combine-alpha-operand0 { combine-operand }
+  <Scalar> combine-alpha-source1 { combine-source }
+  <Scalar> combine-alpha-operand1 { combine-operand }
+  <Scalar> combine-alpha-source2 { combine-source }
+  <Scalar> combine-alpha-operand2 { combine-operand }
+
+    These options replace the envtype and specify the texture combiner
+    mode, which is usually used for multitexturing.  This specifies
+    how the texture combines with the base color and/or the other
+    textures applied previously.  You must specify both an rgb and an
+    alpha combine mode.  Some combine-modes use one source/operand
+    pair, and some use all three; most use just two.
+
+    combine-mode may be one of:
+
+      REPLACE
+      MODULATE
+      ADD
+      ADD-SIGNED
+      INTERPOLATE
+      SUBTRACT
+      DOT3-RGB
+      DOT3-RGBA
+
+    combine-source may be one of:
+
+      TEXTURE
+      CONSTANT
+      PRIMARY-COLOR
+      PREVIOUS
+
+    combine-operand may be one of:
+
+      SRC-COLOR
+      ONE-MINUS-SRC-COLOR
+      SRC-ALPHA
+      ONE-MINUS-SRC-ALPHA
+
+    The default values if any of these are omitted are:
+      <Scalar> combine-rgb { modulate }
+      <Scalar> combine-alpha { modulate }
+      <Scalar> combine-rgb-source0 { previous }
+      <Scalar> combine-rgb-operand0 { src-color }
+      <Scalar> combine-rgb-source1 { texture }
+      <Scalar> combine-rgb-operand1 { src-color }
+      <Scalar> combine-rgb-source2 { constant }
+      <Scalar> combine-rgb-operand2 { src-alpha }
+      <Scalar> combine-alpha-source0 { previous }
+      <Scalar> combine-alpha-operand0 { src-alpha }
+      <Scalar> combine-alpha-source1 { texture }
+      <Scalar> combine-alpha-operand1 { src-alpha }
+      <Scalar> combine-alpha-source2 { constant }
+      <Scalar> combine-alpha-operand2 { src-alpha }
+
+  <Scalar> tex-gen { mode }
+
+    This specifies that texture coordinates for the primitives that
+    reference this texture should be dynamically computed at runtime,
+    for instance to apply a reflection map or some other effect.  The
+    valid values for mode are:
+
+      SPHERE_MAP
+      CUBE_MAP
+      WORLD_POSITION
+      OBJECT_POSITION
+      EYE_POSITION
+
+  <Scalar> stage-name { name }
+
+    Specifies the name of the TextureStage object that is created to
+    render this texture.  If this is omitted, a custom TextureStage is
+    created for this texture if it is required (e.g. because some
+    other multitexturing parameter has been specified), or the system
+    default TextureStage is used if multitexturing is not required.
+
+  <Scalar> priority { priority-value }
+
+    Specifies an integer sort value to rank this texture in priority
+    among other textures that are applied to the same geometry.  This
+    is only used to eliminate low-priority textures in case more
+    textures are requested for a particular piece of geometry than the
+    graphics hardware can render.
+
+  <Scalar> blendr { red-value }
+  <Scalar> blendg { green-value }
+  <Scalar> blendb { blue-value }
+  <Scalar> blenda { alpha-value }
+
+    Specifies a four-component color that is applied with the color in
+    case the envtype, above, is "blend".
+
+  <Scalar> uv-name { name }
+
+    Specifies the name of the texture coordinates that are to be
+    associated with this texture.  If this is omitted, the default
+    texture coordinates are used.
 
 
   <Scalar> alpha { alpha-type }
   <Scalar> alpha { alpha-type }
 
 
@@ -339,12 +447,17 @@ appear before they are referenced.
     (1 1 1 1).  The morph-list is an optional list of <DRGBA> entries.
     (1 1 1 1).  The morph-list is an optional list of <DRGBA> entries.
 
 
 
 
-    <UV> { u v [morph-list] }
+    <UV> name { u v [morph-list] }
 
 
     This gives the texture coordinates of the vertex.  This must be
     This gives the texture coordinates of the vertex.  This must be
     specified if a texture is to be mapped onto this geometry.  As
     specified if a texture is to be mapped onto this geometry.  As
     before, morph-list is an optional list of <DUV> entries.
     before, morph-list is an optional list of <DUV> entries.
 
 
+    Unlike the other kinds of attributes, there may be multiple sets
+    of UV's on each vertex, each with a unique name; this provides
+    support for multitexturing.  The name may be omitted to specify
+    the default UV's.
+
 
 
     In addition to the above, when the vertex serves as a control
     In addition to the above, when the vertex serves as a control
     vertex for a parametric curve the following attributes may be
     vertex for a parametric curve the following attributes may be

+ 238 - 0
panda/src/doc/howto.use_multitexture.txt

@@ -0,0 +1,238 @@
+MULTITEXTURE OVERVIEW
+
+Modern graphics cards are capable of applying more than one texture
+image at once to geometry as they render polygons.  This capability is
+referred to as multitexture.
+
+The textures are applied in a pipeline fashion, where the output of
+each texturing operation is used as the input to the next.  A
+particular graphics card will have a certain number of texture units
+dedicated to this function, which limits the number of textures that
+may be pipelined in this way.
+
+To apply a texture in Panda, you must have a Texture object (which you
+might have loaded from disk, or extracted from a model) and a
+TextureStage object (which you can create on-the-fly).  The primary
+call to add a texture to the pipeline is:
+
+  nodepath.set_texture(texture_stage, texture);
+
+This adds the indicated texture into the pipeline for all the geometry
+at nodepath level and below, associating it with the indicated
+TextureStage object.
+
+The purpose of the TextureStage object is to represent a single stage
+in the texture pipeline.  You can create as many TextureStage objects
+as you like; each one can associate a different texture, and each of
+those textures will be applied together (within the limits of your
+hardware).  If you want to change out a particular texture within the
+pipeline without disturbing the others, keep a handle to the
+TextureStage object that you used for that stage, and issue a
+set_texture() call using the same TextureStage object and a different
+texture--this replaces the texture that you assigned previously.  (You
+may do this on the same NodePath, or on a lower NodePath level to
+override the texture specified from above.)
+
+To undo a set_texture() call for a particular stage or for all stages,
+do:
+
+  nodepath.clear_texture(texture_stage)
+  nodepath.clear_texture()
+
+Don't confuse this with the calls to actively disable a particular
+texture stage or to disable texturing altogether, which are:
+
+  nodepath.set_texture_off(texture_stage)
+  nodepath.set_texture_off()
+
+The difference between the two is that set_texture_off() inserts a
+command into the scene graph to specifically turn off the texture
+associated with the indicated texture stage, while clear_texture()
+simply removes the texture stage from this node's list of assigned
+textures.  Use clear_texture() to undo a previous call to
+set_texture() on a given node.  You need set_texture_off() more
+rarely; you might use this when you want to override a particular
+setting from above to turn off just one particular stage of the
+pipeline (for instance, you may have a set_texture() applied at the
+root of a scene to apply a particular effect to everything in the
+scene, but use set_texture_off() on one particular model for which you
+don't want that effect applied).
+
+There is also a default TextureStage object that is used for all of
+the old single-texture Panda interfaces (like
+nodepath.set_texture(texture)).  It is also the TextureStage that will
+be used to apply Textures onto models (e.g. egg files and/or bam
+files) that do not specify the use of multitexturing.  This default
+TextureStage can be accessed by TextureStage::get_default().
+
+There are a number of different blend modes that you may specify for
+each texture stage in the pipeline; these are specified with
+texture_stage.set_mode().  The mode may be one of:
+
+     TextureStage::M_modulate
+       Multiplies the incoming color by the texture color.  This
+       allows the texture to darken, but not brighten, the incoming
+       color.
+
+     TextureStage::M_add
+       Adds the incoming color and the texture color.  This allows the
+       texture to brighten, but not darken, the incoming color, and
+       tends to lead to bright, desaturated colors.
+
+     TextureStage::M_decal
+       Shows the texture color where the texture is alpha = 1, and the
+       incoming color where the texture is alpha = 0.  This can be
+       used to paint a texture on top of the existing texture.
+
+     TextureStage::M_blend
+       Defined for grayscale textures only.  You can specify an
+       arbitrary color as a separate parameter with
+       texture_stage.set_color(), and then the result of M_blend is to
+       produced the specified color where the texture is white, and
+       the incoming color where the texture is black.  This can be
+       used to paint arbitrary color stripes or a similar effect over
+       an existing texture.
+
+     TextureStage::M_replace
+       Completely replaces the incoming color with the texture color;
+       probably not terribly useful in a multitexture environment,
+       except for the first texture stage.
+
+     TextureStage::M_combine
+       This mode supercedes most of the above with a more powerful
+       collection of options, including signed add and/or subtract,
+       and linear interpolation between two different colors using a
+       third parameter.  You can specify the input(s) as one or more
+       combinations of a specified constant color, or the previous
+       texture in the pipeline, or the incoming color.  However, very
+       old graphics drivers may not support this mode.
+
+       Since combine mode has a number of associated parameters, you
+       enable this mode by calling set_combine_rgb() and
+       set_combine_alpha() with the appropriate parameters; it's not
+       necessary to call set_mode(M_combine).  A complete description
+       of this mode is not given here.
+
+Some of the above modes are very order-dependent.  For this reason,
+you may use texture_stage.set_sort() to specify the order in which
+textures should be applied, using an integer sort parameter.  When
+Panda collects the textures together for rendering a particular piece
+of geometry, it will sort them in order from lowest sort value to
+highest sort value.  The default sort value is 0.  Thus, you can
+specify a large positive number to apply a texture on top of existing
+textures, or a large negative number to apply it beneath existing
+textures.  
+
+The egg loader will create texture stages automatically in the
+presence of a multitexturing specification in the egg file, and it
+will assign to these stages sort values in multiples of 10: the lowest
+texture stage will have a sort value of 0, the next 10, the next 20,
+and so on.
+
+Since the number of texture units available on the hardware is
+limited, and is usually a small number (and some hardware doesn't
+support multitexturing at all, so effectively has only one texture
+unit), Panda needs some rule for selecting the subset of textures to
+render when you have requested more texture stages than are available.
+For this Panda relies on the texture_stage.set_priority() value, which
+is an integer value that represents the importance of this particular
+texture.  If the requested textures will not fit on the available
+number of texture units, Panda will select the n textures with the
+highest priority (and then sort them into order by the set_sort()
+parameter).  Between two textures with the same priority, Panda will
+prefer the one with the lower sort value.  The default priority is 0.
+
+If you need to know the actual limit, you can query your available
+number of texture stages from the GraphicsStateGuardian, with the call
+gsg->get_max_texture_stages() (e.g. from Python, call
+base.win.getGsg().getMaxTextureStages()).
+
+
+TEXTURE COORDINATES
+
+In many cases, all of the texture stages need to use the same set of
+texture coordinates, which is the default behavior.  You can also
+apply a different texture matrix on some texture stages to apply a
+linear transformation to the texture coordinates (for instance, to
+position a decal on the surface).
+
+  nodepath.set_tex_offset(texture_stage, u_offset, v_offset);
+  nodepath.set_tex_scale(texture_stage, u_scale, v_scale);
+  nodepath.set_tex_rotate(texture_stage, degrees);
+  nodepath.set_tex_transform(texture_stage, general_transform);
+
+These operations accumulate through nested nodes just like standard
+scene graph transforms.  In fact, you can get and set relative texture
+transforms:
+
+  rel_offset = nodepath.get_tex_offset(other, texture_stage);
+  nodepath.set_tex_scale(other, texture_stage, u_scale, v_scale);
+  (etc.)
+
+You may create LerpIntervals to lerp texture matrices.  There are no
+interval types that operate directly on a texture matrix, but you can
+set up a TexProjectorEffect to bind a node's transform to the texture
+matrix:
+
+  nodepath.set_tex_projector(texture_stage, from, to)
+
+Where "from" and "to" are arbitrary NodePaths.  The TexProjectorEffect
+will measure the relative transform between "from" and "to" each frame
+and apply it to the nodepath's texture matrix.  Once this is in place,
+you may create a LerpPosInterval, or any other Panda construct, to
+adjust either the "from" or the "to" NodePath, which will thus
+indirectly adjust the texture matrix by the same amount.
+
+
+Sometimes, a texture stage may need to use a completely different set
+of texture coordinates, for instance as provided by the artist who
+generated the model.  Panda allows a model to store any number of
+different sets of texture coordinates on its vertices, each with a
+unique name.  You can associate any texture stage with any set of
+texture coordinates you happen to have available on your model:
+
+  texture_stage.set_texcoord_name(name)
+
+
+Finally, you may need to generate texture coordinates for a particular
+texture stage on the fly.  This is particularly useful, for instance,
+to apply reflection maps, e.g. sphere maps or cube maps.  To enable
+this effect, use:
+
+  nodepath.set_tex_gen(texture_stage, mode)
+
+Where mode is one of the enumerated types named by TexGenAttrib::Mode;
+at the present, this may be any of M_world_position,
+M_object_position, M_eye_position, or M_sphere_map.  The first three
+modes simply apply the X, Y, Z coordinates of the vertex to its U, V
+texture coordinates (a texture matrix may then be applied to transform
+the generated texture coordinates into the particular U, V coordinate
+space that you require).  The remaining modes generate texture
+coordinates appropriate to a reflection map of the corresponding type,
+based on the position and normal of each vertex, relative to the
+camera.
+
+
+The texture generation mode and the tex projector mode may be combined
+to provide hardware-assisted projective texturing, where a texture is
+applied to geometry as if it were projected from a particular point in
+space, like a slide projector.  This is particularly useful for
+applying shadow maps or flashlight effects, for instance.  There is a
+convenience function on NodePath that automatically makes the three
+separate calls needed to enable projective texturing:
+
+  nodepath.project_texture(texture_stage, texture, projector);
+
+Where projector is a NodePath that references a LensNode.  The
+indicated texture is applied to the geometry at nodepath and below, as
+if it were projected from the indicated projector.  The lens
+properties such as field of view may be adjusted on the fly to adjust
+the projection.
+
+(Note that Panda also provides a ProjectionScreen object, which
+performs an effect very similar to the project_texture() call, except
+that it is performed entirely in the CPU, whereas project_texture()
+will offload the work onto the graphics card if the card supports
+this.  This may or may not result in a performance improvement over
+ProjectionScreen, depending on the nature of your scene and your CPU
+load versus your graphics card load.)

+ 2 - 2
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -3496,7 +3496,7 @@ issue_tex_gen(const TexGenAttrib *attrib) {
    * Use the wrap mode from the texture coordinate set at index 1.
    * Use the wrap mode from the texture coordinate set at index 1.
    */
    */
   DO_PSTATS_STUFF(_texture_state_pcollector.add_level(1));
   DO_PSTATS_STUFF(_texture_state_pcollector.add_level(1));
-  if (attrib->is_off()) {
+  if (attrib->is_empty()) {
 
 
     //enable_texturing(false);
     //enable_texturing(false);
     // reset the texcoordindex lookup to 0
     // reset the texcoordindex lookup to 0
@@ -3504,7 +3504,7 @@ issue_tex_gen(const TexGenAttrib *attrib) {
     _pD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, 0);
     _pD3DDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, 0);
     _pD3DDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0);
     _pD3DDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0);
 
 
-  } else if (attrib->get_mode() == TexGenAttrib::M_spherical) {
+  } else if (attrib->get_mode(TextureStage::get_default()) == TexGenAttrib::M_sphere_map) {
 
 
 #if 0
 #if 0
     // best reflection on a sphere is achieved by camera space normals in directx
     // best reflection on a sphere is achieved by camera space normals in directx

+ 8 - 4
panda/src/egg/Sources.pp

@@ -33,8 +33,10 @@
      eggTexture.h eggTextureCollection.I eggTextureCollection.h  \
      eggTexture.h eggTextureCollection.I eggTextureCollection.h  \
      eggTransform3d.I eggTransform3d.h \
      eggTransform3d.I eggTransform3d.h \
      eggUserData.I eggUserData.h \
      eggUserData.I eggUserData.h \
-     eggUtilities.I eggUtilities.h eggVertex.I eggVertex.h  \
-     eggVertexPool.I eggVertexPool.h eggXfmAnimData.I  \
+     eggUtilities.I eggUtilities.h \
+     eggVertex.I eggVertex.h eggVertexPool.I eggVertexPool.h \
+     eggVertexUV.I eggVertexUV.h \
+     eggXfmAnimData.I  \
      eggXfmAnimData.h eggXfmSAnim.I eggXfmSAnim.h parserDefs.h  \
      eggXfmAnimData.h eggXfmSAnim.I eggXfmSAnim.h parserDefs.h  \
      parser.yxx lexerDefs.h lexer.lxx pt_EggMaterial.h  \
      parser.yxx lexerDefs.h lexer.lxx pt_EggMaterial.h  \
      vector_PT_EggMaterial.h pt_EggTexture.h  \
      vector_PT_EggMaterial.h pt_EggTexture.h  \
@@ -55,7 +57,7 @@
      eggTable.cxx eggTexture.cxx eggTextureCollection.cxx  \
      eggTable.cxx eggTexture.cxx eggTextureCollection.cxx  \
      eggTransform3d.cxx \
      eggTransform3d.cxx \
      eggUserData.cxx \
      eggUserData.cxx \
-     eggUtilities.cxx eggVertex.cxx eggVertexPool.cxx  \
+     eggUtilities.cxx eggVertex.cxx eggVertexPool.cxx eggVertexUV.cxx \
      eggXfmAnimData.cxx eggXfmSAnim.cxx xx xx pt_EggMaterial.cxx  \
      eggXfmAnimData.cxx eggXfmSAnim.cxx xx xx pt_EggMaterial.cxx  \
      vector_PT_EggMaterial.cxx pt_EggTexture.cxx  \
      vector_PT_EggMaterial.cxx pt_EggTexture.cxx  \
      vector_PT_EggTexture.cxx pt_EggVertex.cxx  \
      vector_PT_EggTexture.cxx pt_EggVertex.cxx  \
@@ -84,7 +86,9 @@
     eggTransform3d.I eggTransform3d.h \
     eggTransform3d.I eggTransform3d.h \
     eggUserData.I eggUserData.h \
     eggUserData.I eggUserData.h \
     eggUtilities.I eggUtilities.h eggVertex.I eggVertex.h \
     eggUtilities.I eggUtilities.h eggVertex.I eggVertex.h \
-    eggVertexPool.I eggVertexPool.h eggXfmAnimData.I eggXfmAnimData.h \
+    eggVertexPool.I eggVertexPool.h \
+    eggVertexUV.I eggVertexUV.h \
+    eggXfmAnimData.I eggXfmAnimData.h \
     eggXfmSAnim.I eggXfmSAnim.h \
     eggXfmSAnim.I eggXfmSAnim.h \
     pt_EggMaterial.h vector_PT_EggMaterial.h \
     pt_EggMaterial.h vector_PT_EggMaterial.h \
     pt_EggTexture.h vector_PT_EggTexture.h \
     pt_EggTexture.h vector_PT_EggTexture.h \

+ 2 - 0
panda/src/egg/config_egg.cxx

@@ -49,6 +49,7 @@
 #include "eggUserData.h"
 #include "eggUserData.h"
 #include "eggVertex.h"
 #include "eggVertex.h"
 #include "eggVertexPool.h"
 #include "eggVertexPool.h"
+#include "eggVertexUV.h"
 #include "eggXfmAnimData.h"
 #include "eggXfmAnimData.h"
 #include "eggXfmSAnim.h"
 #include "eggXfmSAnim.h"
 
 
@@ -122,6 +123,7 @@ init_libegg() {
   EggUserData::init_type();
   EggUserData::init_type();
   EggVertex::init_type();
   EggVertex::init_type();
   EggVertexPool::init_type();
   EggVertexPool::init_type();
+  EggVertexUV::init_type();
   EggXfmAnimData::init_type();
   EggXfmAnimData::init_type();
   EggXfmSAnim::init_type();
   EggXfmSAnim::init_type();
 }
 }

+ 0 - 44
panda/src/egg/eggAttributes.I

@@ -16,8 +16,6 @@
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
-#include "notify.h"
-
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggAttributes::has_normal
 //     Function: EggAttributes::has_normal
@@ -61,48 +59,6 @@ clear_normal() {
   _flags &= ~F_has_normal;
   _flags &= ~F_has_normal;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: EggAttributes::has_uv
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE bool EggAttributes::
-has_uv() const {
-  return (_flags & F_has_uv) != 0;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggAttributes::get_uv
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE const TexCoordd &EggAttributes::
-get_uv() const {
-  nassertr(has_uv(), _uv);
-  return _uv;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggAttributes::set_uv
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void EggAttributes::
-set_uv(const TexCoordd &uv) {
-  _uv = uv;
-  _flags |= F_has_uv;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggAttributes::clear_uv
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void EggAttributes::
-clear_uv() {
-  _flags &= ~F_has_uv;
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggAttributes::has_color
 //     Function: EggAttributes::has_color
 //       Access: Public
 //       Access: Public

+ 0 - 24
panda/src/egg/eggAttributes.cxx

@@ -56,10 +56,8 @@ EggAttributes &EggAttributes::
 operator = (const EggAttributes &copy) {
 operator = (const EggAttributes &copy) {
   _flags = copy._flags;
   _flags = copy._flags;
   _normal = copy._normal;
   _normal = copy._normal;
-  _uv = copy._uv;
   _color = copy._color;
   _color = copy._color;
   _dnormals = copy._dnormals;
   _dnormals = copy._dnormals;
-  _duvs = copy._duvs;
   _drgbas = copy._drgbas;
   _drgbas = copy._drgbas;
   return *this;
   return *this;
 }
 }
@@ -93,17 +91,6 @@ write(ostream &out, int indent_level) const {
       indent(out, indent_level) << "}\n";
       indent(out, indent_level) << "}\n";
     }
     }
   }
   }
-  if (has_uv()) {
-    if (_duvs.empty()) {
-      indent(out, indent_level)
-        << "<UV> { " << get_uv() << " }\n";
-    } else {
-      indent(out, indent_level) << "<UV> {\n";
-      indent(out, indent_level+2) << get_uv() << "\n";
-      _duvs.write(out, indent_level+2);
-      indent(out, indent_level) << "}\n";
-    }
-  }
   if (has_color()) {
   if (has_color()) {
     if (_drgbas.empty()) {
     if (_drgbas.empty()) {
       indent(out, indent_level)
       indent(out, indent_level)
@@ -142,17 +129,6 @@ sorts_less_than(const EggAttributes &other) const {
     }
     }
   }
   }
 
 
-  if (has_uv()) {
-    int compare =
-      _uv.compare_to(other._uv, egg_parameters->_uv_threshold);
-    if (compare != 0) {
-      return compare < 0;
-    }
-    if (_duvs != other._duvs) {
-      return _duvs < other._duvs;
-    }
-  }
-
   if (has_color()) {
   if (has_color()) {
     int compare =
     int compare =
       _color.compare_to(other._color, egg_parameters->_color_threshold);
       _color.compare_to(other._color, egg_parameters->_color_threshold);

+ 2 - 9
panda/src/egg/eggAttributes.h

@@ -25,6 +25,7 @@
 
 
 #include "typedObject.h"
 #include "typedObject.h"
 #include "luse.h"
 #include "luse.h"
+#include "notify.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : EggAttributes
 //       Class : EggAttributes
@@ -49,11 +50,6 @@ PUBLISHED:
   INLINE void set_normal(const Normald &normal);
   INLINE void set_normal(const Normald &normal);
   INLINE void clear_normal();
   INLINE void clear_normal();
 
 
-  INLINE bool has_uv() const;
-  INLINE const TexCoordd &get_uv() const;
-  INLINE void set_uv(const TexCoordd &texCoord);
-  INLINE void clear_uv();
-
   INLINE bool has_color() const;
   INLINE bool has_color() const;
   INLINE Colorf get_color() const;
   INLINE Colorf get_color() const;
   INLINE void set_color(const Colorf &Color);
   INLINE void set_color(const Colorf &Color);
@@ -65,19 +61,16 @@ PUBLISHED:
   void transform(const LMatrix4d &mat);
   void transform(const LMatrix4d &mat);
 
 
   EggMorphNormalList _dnormals;
   EggMorphNormalList _dnormals;
-  EggMorphTexCoordList _duvs;
   EggMorphColorList _drgbas;
   EggMorphColorList _drgbas;
 
 
 private:
 private:
   enum Flags {
   enum Flags {
     F_has_normal = 0x001,
     F_has_normal = 0x001,
-    F_has_uv     = 0x002,
-    F_has_color  = 0x004,
+    F_has_color  = 0x002,
   };
   };
 
 
   int _flags;
   int _flags;
   Normald _normal;
   Normald _normal;
-  TexCoordd _uv;
   Colorf _color;
   Colorf _color;
 
 
 
 

+ 14 - 4
panda/src/egg/eggPolysetMaker.cxx

@@ -72,13 +72,23 @@ sorts_less(int bin_number, const EggNode *a, const EggNode *b) {
   const EggPolygon *pb = DCAST(EggPolygon, b);
   const EggPolygon *pb = DCAST(EggPolygon, b);
 
 
   if ((_properties & (P_has_texture | P_texture)) != 0) {
   if ((_properties & (P_has_texture | P_texture)) != 0) {
-    if (pa->has_texture() != pb->has_texture()) {
-      return ((int)pa->has_texture() < (int)pb->has_texture());
+    bool a_has_texture = (pa->get_num_textures() > 0);
+    bool b_has_texture = (pb->get_num_textures() > 0);
+    if (a_has_texture != b_has_texture) {
+      return ((int)a_has_texture < (int)b_has_texture);
     }
     }
   }
   }
   if ((_properties & (P_texture)) != 0) {
   if ((_properties & (P_texture)) != 0) {
-    if (pa->has_texture()) {
-      return (pa->get_texture()->sorts_less_than(*pb->get_texture(), ~EggTexture::E_tref_name));
+    int num_textures = min(pa->get_num_textures(), pb->get_num_textures());
+    for (int i = 0; i < num_textures; i++) {
+      EggTexture *a_texture = pa->get_texture(i);
+      EggTexture *b_texture = pb->get_texture(i);
+      if (a_texture != b_texture) {
+        return (a_texture->sorts_less_than(*b_texture, ~EggTexture::E_tref_name));
+      }
+    }
+    if (pa->get_num_textures() != pb->get_num_textures()) {
+      return (pa->get_num_textures() < pb->get_num_textures());
     }
     }
   }
   }
   if ((_properties & (P_has_material | P_material)) != 0) {
   if ((_properties & (P_has_material | P_material)) != 0) {

+ 82 - 19
panda/src/egg/eggPrimitive.I

@@ -36,7 +36,7 @@ INLINE EggPrimitive::
 EggPrimitive(const EggPrimitive &copy) :
 EggPrimitive(const EggPrimitive &copy) :
   EggNode(copy),
   EggNode(copy),
   EggAttributes(copy),
   EggAttributes(copy),
-  _texture(copy._texture),
+  _textures(copy._textures),
   _material(copy._material),
   _material(copy._material),
   _bface(copy._bface)
   _bface(copy._bface)
 {
 {
@@ -53,7 +53,7 @@ operator = (const EggPrimitive &copy) {
   EggNode::operator = (copy);
   EggNode::operator = (copy);
   EggAttributes::operator = (copy);
   EggAttributes::operator = (copy);
   copy_vertices(copy);
   copy_vertices(copy);
-  _texture = copy._texture;
+  _textures = copy._textures;
   _material = copy._material;
   _material = copy._material;
   _bface = copy._bface;
   _bface = copy._bface;
   return *this;
   return *this;
@@ -72,47 +72,110 @@ INLINE EggPrimitive::
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::set_texture
 //     Function: EggPrimitive::set_texture
 //       Access: Published
 //       Access: Published
-//  Description: Applies the indicated texture to the primitive.
+//  Description: Replaces the current list of textures with the
+//               indicated texture.
+//
+//               This method is deprecated and is used in support of
+//               single-texturing only.  New code should be written to
+//               use the multitexture variants instead.q
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggPrimitive::
 INLINE void EggPrimitive::
 set_texture(EggTexture *texture) {
 set_texture(EggTexture *texture) {
-  _texture = texture;
+  clear_texture();
+  add_texture(texture);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: EggPrimitive::clear_texture
+//     Function: EggPrimitive::has_texture
 //       Access: Published
 //       Access: Published
-//  Description: Removes any texturing from the primitive.
+//  Description: Returns true if the primitive has any textures
+//               specified, false otherwise.
+//
+//               This method is deprecated and is used in support of
+//               single-texturing only.  New code should be written to
+//               use the multitexture variants instead.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE void EggPrimitive::
-clear_texture() {
-  _texture = (EggTexture *)NULL;
+INLINE bool EggPrimitive::
+has_texture() const {
+  return get_num_textures() > 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::has_texture
+//       Access: Published
+//  Description: Returns true if the primitive has the particular
+//               indicated texture, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggPrimitive::
+has_texture(EggTexture *texture) const {
+  PT_EggTexture t = texture;
+  return (find(_textures.begin(), _textures.end(), t) != _textures.end());
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::get_texture
 //     Function: EggPrimitive::get_texture
 //       Access: Published
 //       Access: Published
-//  Description: Returns a pointer to the applied texture, or NULL if
-//               there is no texture applied.
+//  Description: Returns the first texture on the primitive, if any,
+//               or NULL if there are no textures on the primitive.
+//
+//               This method is deprecated and is used in support of
+//               single-texturing only.  New code should be written to
+//               use the multitexture variants instead.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE EggTexture *EggPrimitive::
 INLINE EggTexture *EggPrimitive::
 get_texture() const {
 get_texture() const {
-  return _texture;
+  return has_texture() ? get_texture(0) : (EggTexture *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::add_texture
+//       Access: Published
+//  Description: Applies the indicated texture to the primitive.
+//
+//               Note that, in the case of multiple textures being
+//               applied to a single primitive, the order in which the
+//               textures are applied does not affect the rendering
+//               order; use EggTexture::set_sort() to specify that.
+////////////////////////////////////////////////////////////////////
+INLINE void EggPrimitive::
+add_texture(EggTexture *texture) {
+  _textures.push_back(texture);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::clear_texture
+//       Access: Published
+//  Description: Removes any texturing from the primitive.
+////////////////////////////////////////////////////////////////////
+INLINE void EggPrimitive::
+clear_texture() {
+  _textures.clear();
+}
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: EggPrimitive::has_texture
+//     Function: EggPrimitive::get_num_textures
 //       Access: Published
 //       Access: Published
-//  Description: Returns true if the primitive is textured (and
-//               get_texture() will return a real pointer), false
-//               otherwise (and get_texture() will return NULL).
+//  Description: Returns the number of textures applied to the
+//               primitive.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE bool EggPrimitive::
-has_texture() const {
-  return _texture != (EggTexture*)NULL;
+INLINE int EggPrimitive::
+get_num_textures() const {
+  return _textures.size();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::get_texture
+//       Access: Published
+//  Description: Returns the nth texture that has been applied to the
+//               primitive.
+////////////////////////////////////////////////////////////////////
+INLINE EggTexture *EggPrimitive::
+get_texture(int n) const {
+  nassertr(n >= 0 && n < (int)_textures.size(), NULL);
+  return _textures[n];
+}
+
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::set_material
 //     Function: EggPrimitive::set_material
 //       Access: Published
 //       Access: Published

+ 61 - 39
panda/src/egg/eggPrimitive.cxx

@@ -45,8 +45,11 @@ determine_alpha_mode() {
 
 
   EggRenderMode *result = EggNode::determine_alpha_mode();
   EggRenderMode *result = EggNode::determine_alpha_mode();
   if (result == (EggRenderMode *)NULL) {
   if (result == (EggRenderMode *)NULL) {
-    if (has_texture() && get_texture()->get_alpha_mode() != AM_unspecified) {
-      result = get_texture();
+    int num_textures = get_num_textures();
+    for (int i = 0; i < num_textures && result == (EggRenderMode *)NULL; i++) {
+      if (get_texture(i)->get_alpha_mode() != AM_unspecified) {
+        result = get_texture(i);
+      }
     }
     }
   }
   }
   return result;
   return result;
@@ -69,9 +72,11 @@ determine_depth_write_mode() {
 
 
   EggRenderMode *result = EggNode::determine_depth_write_mode();
   EggRenderMode *result = EggNode::determine_depth_write_mode();
   if (result == (EggRenderMode *)NULL) {
   if (result == (EggRenderMode *)NULL) {
-    if (has_texture() &&
-        get_texture()->get_depth_write_mode() != DWM_unspecified) {
-      result = get_texture();
+    int num_textures = get_num_textures();
+    for (int i = 0; i < num_textures && result == (EggRenderMode *)NULL; i++) {
+      if (get_texture(i)->get_depth_write_mode() != DWM_unspecified) {
+        result = get_texture(i);
+      }
     }
     }
   }
   }
   return result;
   return result;
@@ -94,9 +99,11 @@ determine_depth_test_mode() {
 
 
   EggRenderMode *result = EggNode::determine_depth_test_mode();
   EggRenderMode *result = EggNode::determine_depth_test_mode();
   if (result == (EggRenderMode *)NULL) {
   if (result == (EggRenderMode *)NULL) {
-    if (has_texture() &&
-        get_texture()->get_depth_test_mode() != DTM_unspecified) {
-      result = get_texture();
+    int num_textures = get_num_textures();
+    for (int i = 0; i < num_textures && result == (EggRenderMode *)NULL; i++) {
+      if (get_texture(i)->get_depth_test_mode() != DTM_unspecified) {
+        result = get_texture(i);
+      }
     }
     }
   }
   }
   return result;
   return result;
@@ -119,9 +126,11 @@ determine_visibility_mode() {
 
 
   EggRenderMode *result = EggNode::determine_visibility_mode();
   EggRenderMode *result = EggNode::determine_visibility_mode();
   if (result == (EggRenderMode *)NULL) {
   if (result == (EggRenderMode *)NULL) {
-    if (has_texture() &&
-        get_texture()->get_visibility_mode() != VM_unspecified) {
-      result = get_texture();
+    int num_textures = get_num_textures();
+    for (int i = 0; i < num_textures && result == (EggRenderMode *)NULL; i++) {
+      if (get_texture(i)->get_visibility_mode() != VM_unspecified) {
+        result = get_texture(i);
+      }
     }
     }
   }
   }
   return result;
   return result;
@@ -144,8 +153,11 @@ determine_draw_order() {
 
 
   EggRenderMode *result = EggNode::determine_draw_order();
   EggRenderMode *result = EggNode::determine_draw_order();
   if (result == (EggRenderMode *)NULL) {
   if (result == (EggRenderMode *)NULL) {
-    if (has_texture() && get_texture()->has_draw_order()) {
-      result = get_texture();
+    int num_textures = get_num_textures();
+    for (int i = 0; i < num_textures && result == (EggRenderMode *)NULL; i++) {
+      if (get_texture(i)->has_draw_order()) {
+        result = get_texture(i);
+      }
     }
     }
   }
   }
   return result;
   return result;
@@ -168,8 +180,11 @@ determine_bin() {
 
 
   EggRenderMode *result = EggNode::determine_bin();
   EggRenderMode *result = EggNode::determine_bin();
   if (result == (EggRenderMode *)NULL) {
   if (result == (EggRenderMode *)NULL) {
-    if (has_texture() && get_texture()->has_bin()) {
-      result = get_texture();
+    int num_textures = get_num_textures();
+    for (int i = 0; i < num_textures && result == (EggRenderMode *)NULL; i++) {
+      if (get_texture(i)->has_bin()) {
+        result = get_texture(i);
+      }
     }
     }
   }
   }
   return result;
   return result;
@@ -184,7 +199,7 @@ determine_bin() {
 void EggPrimitive::
 void EggPrimitive::
 copy_attributes(const EggPrimitive &other) {
 copy_attributes(const EggPrimitive &other) {
   EggAttributes::operator = (other);
   EggAttributes::operator = (other);
-  set_texture(other.get_texture());
+  _textures = other._textures;
   set_material(other.get_material());
   set_material(other.get_material());
   set_bface_flag(other.get_bface_flag());
   set_bface_flag(other.get_bface_flag());
 }
 }
@@ -581,11 +596,9 @@ write_body(ostream &out, int indent_level) const {
   EggAttributes::write(out, indent_level);
   EggAttributes::write(out, indent_level);
   EggRenderMode::write(out, indent_level);
   EggRenderMode::write(out, indent_level);
 
 
-  if (has_texture()) {
-    EggTexture *texture = get_texture();
-
-    // Make sure the texture is named.
-    nassertv(texture->has_name());
+  int num_textures = get_num_textures();
+  for (int i = 0; i < num_textures; i++) {
+    EggTexture *texture = get_texture(i);
 
 
     indent(out, indent_level) << "<TRef> { ";
     indent(out, indent_level) << "<TRef> { ";
     enquote_string(out, texture->get_name())
     enquote_string(out, texture->get_name())
@@ -595,9 +608,6 @@ write_body(ostream &out, int indent_level) const {
   if (has_material()) {
   if (has_material()) {
     EggMaterial *material = get_material();
     EggMaterial *material = get_material();
 
 
-    // Make sure the material is named.
-    nassertv(material->has_name());
-
     indent(out, indent_level) << "<MRef> { ";
     indent(out, indent_level) << "<MRef> { ";
     enquote_string(out, material->get_name())
     enquote_string(out, material->get_name())
       << " }\n";
       << " }\n";
@@ -726,15 +736,21 @@ r_flatten_transforms() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void EggPrimitive::
 void EggPrimitive::
 r_apply_texmats(EggTextureCollection &textures) {
 r_apply_texmats(EggTextureCollection &textures) {
-  if (has_texture()) {
-    EggTexture *texture = get_texture();
-    if (texture->has_transform()) {
-      if (texture->transform_is_identity()) {
-        // Now, what's the point of a texture with an identity
-        // transform?
-        texture->clear_transform();
-        return;
-      }
+  Textures new_textures;
+  Textures::const_iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    EggTexture *texture = (*ti);
+
+    if (!texture->has_transform()) {
+      new_textures.push_back(texture);
+
+    } else if (texture->transform_is_identity()) {
+      // Now, what's the point of a texture with an identity
+      // transform?
+      texture->clear_transform();
+      new_textures.push_back(texture);
+
+    } else {
 
 
       // We've got a texture with a matrix applied.  Save the matrix,
       // We've got a texture with a matrix applied.  Save the matrix,
       // and get a new texture without the matrix.
       // and get a new texture without the matrix.
@@ -743,7 +759,8 @@ r_apply_texmats(EggTextureCollection &textures) {
       new_texture.clear_transform();
       new_texture.clear_transform();
       EggTexture *unique = textures.create_unique_texture(new_texture, ~0);
       EggTexture *unique = textures.create_unique_texture(new_texture, ~0);
 
 
-      set_texture(unique);
+      new_textures.push_back(unique);
+      string uv_name = unique->get_uv_name();
 
 
       // Now apply the matrix to the vertex UV's.  Create new vertices
       // Now apply the matrix to the vertex UV's.  Create new vertices
       // as necessary.
       // as necessary.
@@ -751,17 +768,22 @@ r_apply_texmats(EggTextureCollection &textures) {
       for (size_t i = 0; i < num_vertices; i++) {
       for (size_t i = 0; i < num_vertices; i++) {
         EggVertex *vertex = get_vertex(i);
         EggVertex *vertex = get_vertex(i);
 
 
-        if (vertex->has_uv()) {
-          EggVertexPool *pool = vertex->get_pool();
-
+        EggVertexUV *uv_obj = vertex->get_uv_obj(uv_name);
+        if (uv_obj != (EggVertexUV *)NULL) {
           EggVertex new_vertex(*vertex);
           EggVertex new_vertex(*vertex);
-          new_vertex.set_uv(vertex->get_uv() * mat);
+          PT(EggVertexUV) new_uv_obj = new EggVertexUV(*uv_obj);
+          new_uv_obj->set_uv(uv_obj->get_uv() * mat);
+          new_vertex.set_uv_obj(new_uv_obj);
+          
+          EggVertexPool *pool = vertex->get_pool();
           EggVertex *unique = pool->create_unique_vertex(new_vertex);
           EggVertex *unique = pool->create_unique_vertex(new_vertex);
           unique->copy_grefs_from(*vertex);
           unique->copy_grefs_from(*vertex);
-
+          
           set_vertex(i, unique);
           set_vertex(i, unique);
         }
         }
       }
       }
     }
     }
   }
   }
+
+  _textures.swap(new_textures);
 }
 }

+ 12 - 3
panda/src/egg/eggPrimitive.h

@@ -30,6 +30,7 @@
 #include "pt_EggTexture.h"
 #include "pt_EggTexture.h"
 #include "pt_EggMaterial.h"
 #include "pt_EggMaterial.h"
 #include "vector_PT_EggVertex.h"
 #include "vector_PT_EggVertex.h"
+#include "vector_PT_EggTexture.h"
 
 
 #include "pointerTo.h"
 #include "pointerTo.h"
 #include "pvector.h"
 #include "pvector.h"
@@ -78,9 +79,14 @@ PUBLISHED:
   virtual EggRenderMode *determine_bin();
   virtual EggRenderMode *determine_bin();
 
 
   INLINE void set_texture(EggTexture *texture);
   INLINE void set_texture(EggTexture *texture);
-  INLINE void clear_texture();
-  INLINE EggTexture *get_texture() const;
   INLINE bool has_texture() const;
   INLINE bool has_texture() const;
+  INLINE bool has_texture(EggTexture *texture) const;
+  INLINE EggTexture *get_texture() const;
+
+  INLINE void add_texture(EggTexture *texture);
+  INLINE void clear_texture();
+  INLINE int get_num_textures() const;
+  INLINE EggTexture *get_texture(int n) const;
 
 
   INLINE void set_material(EggMaterial *material);
   INLINE void set_material(EggMaterial *material);
   INLINE void clear_material();
   INLINE void clear_material();
@@ -181,7 +187,8 @@ protected:
 
 
 
 
 private:
 private:
-  PT_EggTexture _texture;
+  typedef vector_PT_EggTexture Textures;
+  Textures _textures;
   PT_EggMaterial _material;
   PT_EggMaterial _material;
   bool _bface;
   bool _bface;
 
 
@@ -206,6 +213,8 @@ public:
 
 
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
+
+  friend class EggTextureCollection;
 };
 };
 
 
 #include "eggPrimitive.I"
 #include "eggPrimitive.I"

+ 365 - 37
panda/src/egg/eggTexture.I

@@ -19,7 +19,7 @@
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_format
 //     Function: EggTexture::set_format
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -29,7 +29,7 @@ set_format(Format format) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_format
 //     Function: EggTexture::get_format
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE EggTexture::Format EggTexture::
 INLINE EggTexture::Format EggTexture::
@@ -39,7 +39,7 @@ get_format() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_wrap_mode
 //     Function: EggTexture::set_wrap_mode
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -49,7 +49,7 @@ set_wrap_mode(WrapMode mode) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_wrap_mode
 //     Function: EggTexture::get_wrap_mode
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE EggTexture::WrapMode EggTexture::
 INLINE EggTexture::WrapMode EggTexture::
@@ -59,7 +59,7 @@ get_wrap_mode() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_wrap_u
 //     Function: EggTexture::set_wrap_u
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -69,7 +69,7 @@ set_wrap_u(WrapMode mode) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_wrap_u
 //     Function: EggTexture::get_wrap_u
-//       Access: Public
+//       Access: Published
 //  Description: Returns the amount specified for U wrap.  This may be
 //  Description: Returns the amount specified for U wrap.  This may be
 //               unspecified, even if there is an overall wrap value.
 //               unspecified, even if there is an overall wrap value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -80,7 +80,7 @@ get_wrap_u() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::determine_wrap_u
 //     Function: EggTexture::determine_wrap_u
-//       Access: Public
+//       Access: Published
 //  Description: Determines the appropriate wrap in the U direction.
 //  Description: Determines the appropriate wrap in the U direction.
 //               This is different from get_wrap_u() in that if the U
 //               This is different from get_wrap_u() in that if the U
 //               wrap is unspecified, it returns the overall wrap
 //               wrap is unspecified, it returns the overall wrap
@@ -93,7 +93,7 @@ determine_wrap_u() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_wrap_v
 //     Function: EggTexture::set_wrap_v
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -103,7 +103,7 @@ set_wrap_v(WrapMode mode) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_wrap_v
 //     Function: EggTexture::get_wrap_v
-//       Access: Public
+//       Access: Published
 //  Description: Returns the amount specified for V wrap.  This may be
 //  Description: Returns the amount specified for V wrap.  This may be
 //               unspecified, even if there is an overall wrap value.
 //               unspecified, even if there is an overall wrap value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -114,7 +114,7 @@ get_wrap_v() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::determine_wrap_v
 //     Function: EggTexture::determine_wrap_v
-//       Access: Public
+//       Access: Published
 //  Description: Determines the appropriate wrap in the V direction.
 //  Description: Determines the appropriate wrap in the V direction.
 //               This is different from get_wrap_v() in that if the U
 //               This is different from get_wrap_v() in that if the U
 //               wrap is unspecified, it returns the overall wrap
 //               wrap is unspecified, it returns the overall wrap
@@ -127,7 +127,7 @@ determine_wrap_v() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_minfilter
 //     Function: EggTexture::set_minfilter
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -137,7 +137,7 @@ set_minfilter(FilterType type) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_minfilter
 //     Function: EggTexture::get_minfilter
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE EggTexture::FilterType EggTexture::
 INLINE EggTexture::FilterType EggTexture::
@@ -147,7 +147,7 @@ get_minfilter() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_magfilter
 //     Function: EggTexture::set_magfilter
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -157,7 +157,7 @@ set_magfilter(FilterType type) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_magfilter
 //     Function: EggTexture::get_magfilter
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE EggTexture::FilterType EggTexture::
 INLINE EggTexture::FilterType EggTexture::
@@ -167,7 +167,7 @@ get_magfilter() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_anisotropic_degree
 //     Function: EggTexture::set_anisotropic_degree
-//       Access: Public
+//       Access: Published
 //  Description: Sets the degree of anisotropic filtering for this
 //  Description: Sets the degree of anisotropic filtering for this
 //               texture.  1 is off; higher levels indicate filtering
 //               texture.  1 is off; higher levels indicate filtering
 //               in effect.
 //               in effect.
@@ -180,7 +180,7 @@ set_anisotropic_degree(int anisotropic_degree) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::clear_anisotropic_degree
 //     Function: EggTexture::clear_anisotropic_degree
-//       Access: Public
+//       Access: Published
 //  Description: Removes the specification of anisotropic filtering
 //  Description: Removes the specification of anisotropic filtering
 //               from the texture.
 //               from the texture.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -192,7 +192,7 @@ clear_anisotropic_degree() {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::has_anisotropic_degree
 //     Function: EggTexture::has_anisotropic_degree
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if a value for the anisotropic filtering
 //  Description: Returns true if a value for the anisotropic filtering
 //               degree has been specified for this texture, false
 //               degree has been specified for this texture, false
 //               otherwise.
 //               otherwise.
@@ -204,7 +204,7 @@ has_anisotropic_degree() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_anisotropic_degree
 //     Function: EggTexture::get_anisotropic_degree
-//       Access: Public
+//       Access: Published
 //  Description: Returns the anisotropic filtering degree that has
 //  Description: Returns the anisotropic filtering degree that has
 //               been specified for this texture, or 0 if nothing has
 //               been specified for this texture, or 0 if nothing has
 //               been specified.
 //               been specified.
@@ -218,7 +218,7 @@ get_anisotropic_degree() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_env_type
 //     Function: EggTexture::set_env_type
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -228,7 +228,7 @@ set_env_type(EnvType type) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_env_type
 //     Function: EggTexture::get_env_type
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE EggTexture::EnvType EggTexture::
 INLINE EggTexture::EnvType EggTexture::
@@ -236,10 +236,300 @@ get_env_type() const {
   return _env_type;
   return _env_type;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_combine_mode
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_combine_mode(CombineChannel channel, CombineMode cm) {
+  nassertv((int)channel >= 0 && (int)channel < (int)CC_num_channels);
+  _combiner[channel]._mode = cm;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_combine_mode
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE EggTexture::CombineMode EggTexture::
+get_combine_mode(CombineChannel channel) const {
+  nassertr((int)channel >= 0 && (int)channel < (int)CC_num_channels, CM_unspecified);
+  return _combiner[channel]._mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_combine_source
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_combine_source(CombineChannel channel, int n, CombineSource cs) {
+  nassertv((int)channel >= 0 && (int)channel < (int)CC_num_channels);
+  nassertv(n >= 0 && n < (int)CI_num_indices);
+  _combiner[channel]._ops[n]._source = cs;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_combine_source
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE EggTexture::CombineSource EggTexture::
+get_combine_source(CombineChannel channel, int n) const {
+  nassertr((int)channel >= 0 && (int)channel < (int)CC_num_channels, CS_unspecified);
+  nassertr(n >= 0 && n < (int)CI_num_indices, CS_unspecified);
+  return _combiner[channel]._ops[n]._source;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_combine_operand
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_combine_operand(CombineChannel channel, int n, CombineOperand co) {
+  nassertv((int)channel >= 0 && (int)channel < (int)CC_num_channels);
+  nassertv(n >= 0 && n < (int)CI_num_indices);
+  _combiner[channel]._ops[n]._operand = co;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_combine_operand
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE EggTexture::CombineOperand EggTexture::
+get_combine_operand(CombineChannel channel, int n) const {
+  nassertr((int)channel >= 0 && (int)channel < (int)CC_num_channels, CO_unspecified);
+  nassertr(n >= 0 && n < (int)CI_num_indices, CO_unspecified);
+  return _combiner[channel]._ops[n]._operand;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_tex_gen
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_tex_gen(TexGen tex_gen) {
+  _tex_gen = tex_gen;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_tex_gen
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE EggTexture::TexGen EggTexture::
+get_tex_gen() const {
+  return _tex_gen;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_stage_name
+//       Access: Published
+//  Description: Specifies the particular TextureStage this texture
+//               will be rendered on by name.  If this is omitted, the
+//               texture will be rendered on the default TextureStage,
+//               unless some other stage-specific property is
+//               specificied, in which case the texture will be
+//               rendered on a TextureStage with the same name as the
+//               tref.  This is in support of multitexturing.
+//
+//               Each different TextureStage in the world must be
+//               uniquely named.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_stage_name(const string &stage_name) {
+  _stage_name = stage_name;
+  _flags |= F_has_stage_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::clear_stage_name
+//       Access: Published
+//  Description: Removes the named TextureStage specification.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+clear_stage_name() {
+  _stage_name = string();
+  _flags &= ~F_has_stage_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::has_stage_name
+//       Access: Published
+//  Description: Returns true if a stage name has been explicitly
+//               specified for this texture, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggTexture::
+has_stage_name() const {
+  return (_flags & F_has_stage_name) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_stage_name
+//       Access: Published
+//  Description: Returns the stage name that has been specified for
+//               this texture, or the tref name if no texture stage
+//               has explicitly been specified.
+////////////////////////////////////////////////////////////////////
+INLINE const string &EggTexture::
+get_stage_name() const {
+  return has_stage_name() ? _stage_name : get_name();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_priority
+//       Access: Published
+//  Description: Sets the importance of this texture with respect to
+//               other textures also applied on the same geometry.
+//               This is only meaningful in the presence of
+//               multitexturing.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_priority(int priority) {
+  _priority = priority;
+  _flags |= F_has_priority;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::clear_priority
+//       Access: Published
+//  Description: Removes the specification of multitexture priority
+//               from the texture.  The default priority value is 0.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+clear_priority() {
+  _priority = 0;
+  _flags &= ~F_has_priority;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::has_priority
+//       Access: Published
+//  Description: Returns true if a priority value for multitexture
+//               importance has been specified for the texture, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggTexture::
+has_priority() const {
+  return (_flags & F_has_priority) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_priority
+//       Access: Published
+//  Description: Returns the multitexture importance value that has been
+//               specified for the texture, or 0 if no priority value has
+//               been specified.
+////////////////////////////////////////////////////////////////////
+INLINE int EggTexture::
+get_priority() const {
+  return _priority;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_color(const Colorf &color) {
+  _color = color;
+  _flags |= F_has_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::clear_color
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+clear_color() {
+  _color.set(0.0f, 0.0f, 0.0f, 1.0f);
+  _flags &= ~F_has_color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::has_color
+//       Access: Published
+//  Description: Returns true if a blend color has been
+//               specified for the texture.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggTexture::
+has_color() const {
+  return (_flags & F_has_color) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_color
+//       Access: Published
+//  Description: Returns the blend color if one has been
+//               specified, or (0, 0, 0, 1) otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE const Colorf &EggTexture::
+get_color() const {
+  return _color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::set_uv_name
+//       Access: Published
+//  Description: Specifies the named set of texture coordinates that
+//               this texture will use when it is applied to geometry.
+//               Geometry may have multiple sets of texture
+//               coordinates defined, by name.
+//
+//               If this is not specified for a particular texture,
+//               the default set of texture coordinates will be used.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+set_uv_name(const string &uv_name) {
+  _uv_name = uv_name;
+  _flags |= F_has_uv_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::clear_uv_name
+//       Access: Published
+//  Description: Removes the restriction to a particular named set of
+//               texture coordinates and restores the texture to using
+//               the default texture coordinates.
+////////////////////////////////////////////////////////////////////
+INLINE void EggTexture::
+clear_uv_name() {
+  _uv_name = string();
+  _flags &= ~F_has_uv_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::has_uv_name
+//       Access: Published
+//  Description: Returns true if a texcoord name has been explicitly
+//               specified for this texture, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggTexture::
+has_uv_name() const {
+  return (_flags & F_has_uv_name) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::get_uv_name
+//       Access: Published
+//  Description: Returns the texcoord name that has been specified for
+//               this texture, or the empty string if no texcoord name
+//               has explicitly been specified.
+////////////////////////////////////////////////////////////////////
+INLINE const string &EggTexture::
+get_uv_name() const {
+  return _uv_name;
+}
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_transform
 //     Function: EggTexture::set_transform
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -250,7 +540,7 @@ set_transform(const LMatrix3d &transform) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::clear_transform
 //     Function: EggTexture::clear_transform
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -261,7 +551,7 @@ clear_transform() {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::has_transform
 //     Function: EggTexture::has_transform
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if a texture matrix transform has been
 //  Description: Returns true if a texture matrix transform has been
 //               specified for the texture (even if the transform is
 //               specified for the texture (even if the transform is
 //               identity).
 //               identity).
@@ -273,7 +563,7 @@ has_transform() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_transform
 //     Function: EggTexture::get_transform
-//       Access: Public
+//       Access: Published
 //  Description: Returns the texture matrix transform if one has been
 //  Description: Returns the texture matrix transform if one has been
 //               specified, or identity matrix otherwise.
 //               specified, or identity matrix otherwise.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -284,7 +574,7 @@ get_transform() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::transform_is_identity()
 //     Function: EggTexture::transform_is_identity()
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if no texture matrix transform has been
 //  Description: Returns true if no texture matrix transform has been
 //               specified, or if the one specified is the identity
 //               specified, or if the one specified is the identity
 //               transform.  Returns false only if a nonidentity
 //               transform.  Returns false only if a nonidentity
@@ -298,7 +588,7 @@ transform_is_identity() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_alpha_filename
 //     Function: EggTexture::set_alpha_filename
-//       Access: Public
+//       Access: Published
 //  Description: Specifies a separate file that will be loaded in with
 //  Description: Specifies a separate file that will be loaded in with
 //               the 1- or 3-component texture and applied as the
 //               the 1- or 3-component texture and applied as the
 //               alpha channel.  This is useful when loading textures
 //               alpha channel.  This is useful when loading textures
@@ -314,7 +604,7 @@ set_alpha_filename(const Filename &alpha_filename) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::clear_alpha_filename
 //     Function: EggTexture::clear_alpha_filename
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void EggTexture::
 INLINE void EggTexture::
@@ -326,7 +616,7 @@ clear_alpha_filename() {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::has_alpha_filename
 //     Function: EggTexture::has_alpha_filename
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if a separate file for the alpha
 //  Description: Returns true if a separate file for the alpha
 //               component has been applied, false otherwise.  See
 //               component has been applied, false otherwise.  See
 //               set_alpha_filename().
 //               set_alpha_filename().
@@ -338,7 +628,7 @@ has_alpha_filename() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_alpha_filename
 //     Function: EggTexture::get_alpha_filename
-//       Access: Public
+//       Access: Published
 //  Description: Returns the separate file assigned for the alpha
 //  Description: Returns the separate file assigned for the alpha
 //               channel.  It is an error to call this unless
 //               channel.  It is an error to call this unless
 //               has_alpha_filename() returns true.  See set_alpha_filename().
 //               has_alpha_filename() returns true.  See set_alpha_filename().
@@ -351,7 +641,7 @@ get_alpha_filename() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_alpha_fullpath
 //     Function: EggTexture::get_alpha_fullpath
-//       Access: Public
+//       Access: Published
 //  Description: Returns the full pathname to the alpha file, if it is
 //  Description: Returns the full pathname to the alpha file, if it is
 //               known; otherwise, returns the same thing as
 //               known; otherwise, returns the same thing as
 //               get_alpha_filename().
 //               get_alpha_filename().
@@ -371,7 +661,7 @@ get_alpha_fullpath() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_alpha_fullpath
 //     Function: EggTexture::set_alpha_fullpath
-//       Access: Public
+//       Access: Published
 //  Description: Records the full pathname to the file, for the
 //  Description: Records the full pathname to the file, for the
 //               benefit of get_alpha_fullpath().
 //               benefit of get_alpha_fullpath().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -382,7 +672,7 @@ set_alpha_fullpath(const Filename &alpha_fullpath) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::set_alpha_file_channel
 //     Function: EggTexture::set_alpha_file_channel
-//       Access: Public
+//       Access: Published
 //  Description: If a separate alpha-file is specified, this indicates
 //  Description: If a separate alpha-file is specified, this indicates
 //               which channel number should be extracted from this
 //               which channel number should be extracted from this
 //               file to derive the alpha channel for the final image.
 //               file to derive the alpha channel for the final image.
@@ -400,7 +690,7 @@ set_alpha_file_channel(int alpha_file_channel) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::clear_alpha_file_channel
 //     Function: EggTexture::clear_alpha_file_channel
-//       Access: Public
+//       Access: Published
 //  Description: Removes the specification of a particular channel to
 //  Description: Removes the specification of a particular channel to
 //               use from the alpha-file image.
 //               use from the alpha-file image.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -412,7 +702,7 @@ clear_alpha_file_channel() {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::has_alpha_file_channel
 //     Function: EggTexture::has_alpha_file_channel
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if a particular channel has been
 //  Description: Returns true if a particular channel has been
 //               specified for the alpha-file image, false otherwise.
 //               specified for the alpha-file image, false otherwise.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -423,7 +713,7 @@ has_alpha_file_channel() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::get_alpha_file_channel
 //     Function: EggTexture::get_alpha_file_channel
-//       Access: Public
+//       Access: Published
 //  Description: Returns the particular channel that has been
 //  Description: Returns the particular channel that has been
 //               specified for the alpha-file image, or 0 if no
 //               specified for the alpha-file image, or 0 if no
 //               channel has been specified.  See
 //               channel has been specified.  See
@@ -435,8 +725,46 @@ get_alpha_file_channel() const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: UniqueEggTextures::Constructor
+//     Function: EggTexture::get_multitexture_sort
+//       Access: Published
+//  Description: Returns an integer that represents the depth to which
+//               this texture is layered on all other textures in the
+//               egg file.  In general, if texture A is layered over
+//               texture B, then sort(A) > sort(B).  If texture A is
+//               never layered over any other texture, then sort(A) ==
+//               0.  More than that is difficult to guarantee.
+////////////////////////////////////////////////////////////////////
+INLINE int EggTexture::
+get_multitexture_sort() const {
+  return _multitexture_sort;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::SourceAndOperand::Constructor
 //       Access: Public
 //       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE EggTexture::SourceAndOperand::
+SourceAndOperand() :
+  _source(CS_unspecified),
+  _operand(CO_unspecified)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::Combiner::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE EggTexture::Combiner::
+Combiner() :
+  _mode(CM_unspecified)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: UniqueEggTextures::Constructor
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE UniqueEggTextures::
 INLINE UniqueEggTextures::
@@ -445,7 +773,7 @@ UniqueEggTextures(int eq) : _eq(eq) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: UniqueEggTextures::Function operator
 //     Function: UniqueEggTextures::Function operator
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool UniqueEggTextures::
 INLINE bool UniqueEggTextures::

+ 460 - 10
panda/src/egg/eggTexture.cxx

@@ -28,7 +28,7 @@ TypeHandle EggTexture::_type_handle;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::Constructor
 //     Function: EggTexture::Constructor
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 EggTexture::
 EggTexture::
@@ -43,14 +43,18 @@ EggTexture(const string &tref_name, const string &filename)
   _magfilter = FT_unspecified;
   _magfilter = FT_unspecified;
   _anisotropic_degree = 0;
   _anisotropic_degree = 0;
   _env_type = ET_unspecified;
   _env_type = ET_unspecified;
+  _tex_gen = TG_unspecified;
+  _priority = 0;
+  _color.set(0.0f, 0.0f, 0.0f, 1.0f);
   _flags = 0;
   _flags = 0;
   _transform = LMatrix3d::ident_mat();
   _transform = LMatrix3d::ident_mat();
   _alpha_file_channel = 0;
   _alpha_file_channel = 0;
+  _multitexture_sort = 0;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::Copy constructor
 //     Function: EggTexture::Copy constructor
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 EggTexture::
 EggTexture::
@@ -60,11 +64,13 @@ EggTexture(const EggTexture &copy) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::Copy assignment operator
 //     Function: EggTexture::Copy assignment operator
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 EggTexture &EggTexture::
 EggTexture &EggTexture::
 operator = (const EggTexture &copy) {
 operator = (const EggTexture &copy) {
+  clear_multitexture();
+
   EggFilenameNode::operator = (copy);
   EggFilenameNode::operator = (copy);
   EggRenderMode::operator = (copy);
   EggRenderMode::operator = (copy);
 
 
@@ -76,15 +82,33 @@ operator = (const EggTexture &copy) {
   _magfilter = copy._magfilter;
   _magfilter = copy._magfilter;
   _anisotropic_degree = copy._anisotropic_degree;
   _anisotropic_degree = copy._anisotropic_degree;
   _env_type = copy._env_type;
   _env_type = copy._env_type;
+  _tex_gen = copy._tex_gen;
+  _stage_name = copy._stage_name;
+  _priority = copy._priority;
+  _color = copy._color;
+  _uv_name = copy._uv_name;
   _flags = copy._flags;
   _flags = copy._flags;
   _transform = copy._transform;
   _transform = copy._transform;
   _alpha_filename = copy._alpha_filename;
   _alpha_filename = copy._alpha_filename;
   _alpha_fullpath = copy._alpha_fullpath;
   _alpha_fullpath = copy._alpha_fullpath;
   _alpha_file_channel = copy._alpha_file_channel;
   _alpha_file_channel = copy._alpha_file_channel;
+  _multitexture_sort = 0;
+  _combiner[0] = copy._combiner[0];
+  _combiner[1] = copy._combiner[1];
 
 
   return *this;
   return *this;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::Destructor
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggTexture::
+~EggTexture() {
+  clear_multitexture();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::write
 //     Function: EggTexture::write
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -149,6 +173,58 @@ write(ostream &out, int indent_level) const {
       << "<Scalar> envtype { " << get_env_type() << " }\n";
       << "<Scalar> envtype { " << get_env_type() << " }\n";
   }
   }
 
 
+  for (int ci = 0; ci < (int)CC_num_channels; ci++) {
+    CombineChannel channel = (CombineChannel)ci;
+    if (get_combine_mode(channel) != CM_unspecified) {
+      indent(out, indent_level + 2)
+        << "<Scalar> combine-" << channel 
+        << " { " << get_combine_mode(channel) << " }\n";
+    }
+    for (int i = 0; i < (int)CI_num_indices; i++) {
+      if (get_combine_source(channel, i) != CS_unspecified) {
+        indent(out, indent_level + 2)
+          << "<Scalar> combine-" << channel << "-source" << i
+          << " { " << get_combine_source(channel, i) << " }\n";
+      }
+      if (get_combine_operand(channel, i) != CO_unspecified) {
+        indent(out, indent_level + 2)
+          << "<Scalar> combine-" << channel << "-operand" << i
+          << " { " << get_combine_operand(channel, i) << " }\n";
+      }
+    }
+  }
+
+  if (get_tex_gen() != TG_unspecified) {
+    indent(out, indent_level + 2)
+      << "<Scalar> tex-gen { " << get_tex_gen() << " }\n";
+  }
+
+  if (has_stage_name()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> stage-name { " << get_stage_name() << " }\n";
+  }
+
+  if (has_priority()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> priority { " << get_priority() << " }\n";
+  }
+
+  if (has_color()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> blendr { " << _color[0] << " }\n";
+    indent(out, indent_level + 2)
+      << "<Scalar> blendg { " << _color[1] << " }\n";
+    indent(out, indent_level + 2)
+      << "<Scalar> blendb { " << _color[2] << " }\n";
+    indent(out, indent_level + 2)
+      << "<Scalar> blenda { " << _color[3] << " }\n";
+  }
+
+  if (has_uv_name()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> uv-name { " << get_uv_name() << " }\n";
+  }
+
   EggRenderMode::write(out, indent_level + 2);
   EggRenderMode::write(out, indent_level + 2);
 
 
   if (has_transform()) {
   if (has_transform()) {
@@ -160,7 +236,7 @@ write(ostream &out, int indent_level) const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::is_equivalent_to
 //     Function: EggTexture::is_equivalent_to
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if the two textures are equivalent in
 //  Description: Returns true if the two textures are equivalent in
 //               all relevant properties (according to eq), false
 //               all relevant properties (according to eq), false
 //               otherwise.
 //               otherwise.
@@ -258,7 +334,7 @@ is_equivalent_to(const EggTexture &other, int eq) const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::sorts_less_than
 //     Function: EggTexture::sorts_less_than
-//       Access: Public
+//       Access: Published
 //  Description: An ordering operator to compare two textures for
 //  Description: An ordering operator to compare two textures for
 //               sorting order.  This imposes an arbitrary ordering
 //               sorting order.  This imposes an arbitrary ordering
 //               useful to identify unique textures, according to the
 //               useful to identify unique textures, according to the
@@ -348,7 +424,7 @@ sorts_less_than(const EggTexture &other, int eq) const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::has_alpha_channel
 //     Function: EggTexture::has_alpha_channel
-//       Access: Public
+//       Access: Published
 //  Description: Given the number of color components (channels) in
 //  Description: Given the number of color components (channels) in
 //               the image file as actually read from the disk, return
 //               the image file as actually read from the disk, return
 //               true if this texture seems to have an alpha channel
 //               true if this texture seems to have an alpha channel
@@ -391,9 +467,79 @@ has_alpha_channel(int num_components) const {
   return false;
   return false;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::clear_multitexture
+//       Access: Published
+//  Description: Resets the multitexture flags set by
+//               multitexture_over().  After this call,
+//               get_multitexture() will return false, and
+//               get_multitexture_sort() will return 0.
+////////////////////////////////////////////////////////////////////
+void EggTexture::
+clear_multitexture() {
+  _multitexture_sort = 0;
+
+  // Now empty out the _over_textures and _under_textures sets.  This
+  // requires a bit of care so we don't end up in mutual recursion or
+  // iterating through self-modifying structures.  To avoid this, we
+  // empty the sets first, and then walk through their original
+  // contents.
+  MultiTextures orig_over_textures, orig_under_textures;
+  orig_over_textures.swap(_over_textures);
+  orig_under_textures.swap(_under_textures);
+
+  MultiTextures::iterator mti;
+  for (mti = orig_over_textures.begin(); 
+       mti != orig_over_textures.end(); 
+       ++mti) {
+    EggTexture *other = (*mti);
+    other->_under_textures.erase(this);
+  }
+  for (mti = orig_under_textures.begin(); 
+       mti != orig_under_textures.end(); 
+       ++mti) {
+    EggTexture *other = (*mti);
+    other->_over_textures.erase(this);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::multitexture_over
+//       Access: Published
+//  Description: Indicates that this texture should be layered on top
+//               of the other texture.  This will guarantee that
+//               this->get_multitexture_sort() >
+//               other->get_multitexture_sort(), at least until
+//               clear_multitexture() is called on either one.
+//
+//               The return value is true if successful, or false if
+//               there is a failure because the other texture was
+//               already layered on top of this one (or there is a
+//               three- or more-way cycle).
+////////////////////////////////////////////////////////////////////
+bool EggTexture::
+multitexture_over(EggTexture *other) {
+  if (get_multitexture_sort() <= other->get_multitexture_sort()) {
+    MultiTextures cycle_detector;
+    if (!r_min_multitexture_sort(other->get_multitexture_sort() + 1,
+                                 cycle_detector)) {
+      // Found a cycle right off the bat!
+      return false;
+    }
+  }
+
+  if (_over_textures.insert(other).second) {
+    bool inserted_under = other->_under_textures.insert(this).second;
+    nassertr(inserted_under, false);
+  }
+  nassertr(get_multitexture_sort() > other->get_multitexture_sort(), false);
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::string_format
 //     Function: EggTexture::string_format
-//       Access: Public
+//       Access: Published, Static
 //  Description: Returns the Format value associated with the given
 //  Description: Returns the Format value associated with the given
 //               string representation, or F_unspecified if the string
 //               string representation, or F_unspecified if the string
 //               does not match any known Format value.
 //               does not match any known Format value.
@@ -444,7 +590,7 @@ string_format(const string &string) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::string_wrap_mode
 //     Function: EggTexture::string_wrap_mode
-//       Access: Public
+//       Access: Published, Static
 //  Description: Returns the WrapMode value associated with the given
 //  Description: Returns the WrapMode value associated with the given
 //               string representation, or WM_unspecified if the string
 //               string representation, or WM_unspecified if the string
 //               does not match any known WrapMode value.
 //               does not match any known WrapMode value.
@@ -462,7 +608,7 @@ string_wrap_mode(const string &string) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::string_filter_type
 //     Function: EggTexture::string_filter_type
-//       Access: Public
+//       Access: Published, Static
 //  Description: Returns the FilterType value associated with the given
 //  Description: Returns the FilterType value associated with the given
 //               string representation, or FT_unspecified if the string
 //               string representation, or FT_unspecified if the string
 //               does not match any known FilterType value.
 //               does not match any known FilterType value.
@@ -508,7 +654,7 @@ string_filter_type(const string &string) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::string_env_type
 //     Function: EggTexture::string_env_type
-//       Access: Public
+//       Access: Published, Static
 //  Description: Returns the EnvType value associated with the given
 //  Description: Returns the EnvType value associated with the given
 //               string representation, or ET_unspecified if the string
 //               string representation, or ET_unspecified if the string
 //               does not match any known EnvType value.
 //               does not match any known EnvType value.
@@ -517,13 +663,146 @@ EggTexture::EnvType EggTexture::
 string_env_type(const string &string) {
 string_env_type(const string &string) {
   if (cmp_nocase_uh(string, "modulate") == 0) {
   if (cmp_nocase_uh(string, "modulate") == 0) {
     return ET_modulate;
     return ET_modulate;
+
   } else if (cmp_nocase_uh(string, "decal") == 0) {
   } else if (cmp_nocase_uh(string, "decal") == 0) {
     return ET_decal;
     return ET_decal;
+
+  } else if (cmp_nocase_uh(string, "blend") == 0) {
+    return ET_blend;
+
+  } else if (cmp_nocase_uh(string, "replace") == 0) {
+    return ET_replace;
+
+  } else if (cmp_nocase_uh(string, "add") == 0) {
+    return ET_add;
+
   } else {
   } else {
     return ET_unspecified;
     return ET_unspecified;
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::string_combine_mode
+//       Access: Published, Static
+//  Description: Returns the CombineMode value associated with the given
+//               string representation, or CM_unspecified if the string
+//               does not match any known CombineMode value.
+////////////////////////////////////////////////////////////////////
+EggTexture::CombineMode EggTexture::
+string_combine_mode(const string &string) {
+  if (cmp_nocase_uh(string, "replace") == 0) {
+    return CM_replace;
+
+  } else if (cmp_nocase_uh(string, "modulate") == 0) {
+    return CM_modulate;
+
+  } else if (cmp_nocase_uh(string, "add") == 0) {
+    return CM_add;
+
+  } else if (cmp_nocase_uh(string, "add_signed") == 0) {
+    return CM_add_signed;
+
+  } else if (cmp_nocase_uh(string, "interpolate") == 0) {
+    return CM_interpolate;
+
+  } else if (cmp_nocase_uh(string, "subtract") == 0) {
+    return CM_subtract;
+
+  } else if (cmp_nocase_uh(string, "dot3_rgb") == 0) {
+    return CM_dot3_rgb;
+
+  } else if (cmp_nocase_uh(string, "dot3_rgba") == 0) {
+    return CM_dot3_rgba;
+
+  } else {
+    return CM_unspecified;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::string_combine_source
+//       Access: Published, Static
+//  Description: Returns the CombineSource value associated with the given
+//               string representation, or CS_unspecified if the string
+//               does not match any known CombineSource value.
+////////////////////////////////////////////////////////////////////
+EggTexture::CombineSource EggTexture::
+string_combine_source(const string &string) {
+  if (cmp_nocase_uh(string, "texture") == 0) {
+    return CS_texture;
+
+  } else if (cmp_nocase_uh(string, "constant") == 0) {
+    return CS_constant;
+
+  } else if (cmp_nocase_uh(string, "primary_color") == 0) {
+    return CS_primary_color;
+
+  } else if (cmp_nocase_uh(string, "previous") == 0) {
+    return CS_previous;
+
+  } else {
+    return CS_unspecified;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::string_combine_operand
+//       Access: Published, Static
+//  Description: Returns the CombineOperand value associated with the given
+//               string representation, or CO_unspecified if the string
+//               does not match any known CombineOperand value.
+////////////////////////////////////////////////////////////////////
+EggTexture::CombineOperand EggTexture::
+string_combine_operand(const string &string) {
+  if (cmp_nocase_uh(string, "src_color") == 0) {
+    return CO_src_color;
+
+  } else if (cmp_nocase_uh(string, "one_minus_src_color") == 0) {
+    return CO_one_minus_src_color;
+
+  } else if (cmp_nocase_uh(string, "src_alpha") == 0) {
+    return CO_src_alpha;
+
+  } else if (cmp_nocase_uh(string, "one_minus_src_alpha") == 0) {
+    return CO_one_minus_src_alpha;
+
+  } else {
+    return CO_unspecified;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::string_tex_gen
+//       Access: Published, Static
+//  Description: Returns the TexGen value associated with the given
+//               string representation, or ET_unspecified if the string
+//               does not match any known TexGen value.
+////////////////////////////////////////////////////////////////////
+EggTexture::TexGen EggTexture::
+string_tex_gen(const string &string) {
+  if (cmp_nocase_uh(string, "unspecified") == 0) {
+    return TG_unspecified;
+
+  } else if (cmp_nocase_uh(string, "sphere_map") == 0) {
+    return TG_sphere_map;
+
+  } else if (cmp_nocase_uh(string, "cube_map") == 0) {
+    return TG_cube_map;
+
+  } else if (cmp_nocase_uh(string, "world_position") == 0) {
+    return TG_world_position;
+
+  } else if (cmp_nocase_uh(string, "object_position") == 0) {
+    return TG_object_position;
+
+  } else if (cmp_nocase_uh(string, "eye_position") == 0) {
+    return TG_eye_position;
+
+  } else {
+    return TG_unspecified;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTexture::egg_start_parse_body
 //     Function: EggTexture::egg_start_parse_body
 //       Access: Protected, Virtual
 //       Access: Protected, Virtual
@@ -539,6 +818,45 @@ egg_start_parse_body() {
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggTexture::r_min_multitexture_sort
+//       Access: Private
+//  Description: Ensures that our multitexture_sort is at least the
+//               indicated value.
+////////////////////////////////////////////////////////////////////
+bool EggTexture::
+r_min_multitexture_sort(int sort, EggTexture::MultiTextures &cycle_detector) {
+  if (_multitexture_sort >= sort) {
+    // No problem.
+    return true;
+  }
+
+  if (!cycle_detector.insert(this).second) {
+    // Oops, we just hit a cycle!
+    return false;
+  }
+
+  _multitexture_sort = sort;
+
+  // Now we also have to increment all of the textures that we are
+  // under.
+  bool no_cycles = true;
+
+  MultiTextures::iterator mti;
+  for (mti = _under_textures.begin();
+       mti != _under_textures.end();
+       ++mti) {
+    EggTexture *other = (*mti);
+    if (!other->r_min_multitexture_sort(sort + 1, cycle_detector)) {
+      // Oops, found a cycle!
+      no_cycles = false;
+    }
+  }
+
+  return no_cycles;
+}
+
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Format output operator
 //     Function: Format output operator
 //  Description:
 //  Description:
@@ -646,12 +964,144 @@ ostream &operator << (ostream &out, EggTexture::EnvType type) {
   switch (type) {
   switch (type) {
   case EggTexture::ET_unspecified:
   case EggTexture::ET_unspecified:
     return out << "unspecified";
     return out << "unspecified";
+
   case EggTexture::ET_modulate:
   case EggTexture::ET_modulate:
     return out << "modulate";
     return out << "modulate";
+
   case EggTexture::ET_decal:
   case EggTexture::ET_decal:
     return out << "decal";
     return out << "decal";
+
+  case EggTexture::ET_blend:
+    return out << "blend";
+
+  case EggTexture::ET_replace:
+    return out << "replace";
+
+  case EggTexture::ET_add:
+    return out << "add";
   }
   }
 
 
   nassertr(false, out);
   nassertr(false, out);
   return out << "(**invalid**)";
   return out << "(**invalid**)";
 }
 }
+
+ostream &
+operator << (ostream &out, EggTexture::CombineMode cm) {
+  switch (cm) {
+  case EggTexture::CM_unspecified:
+    return out << "unspecified";
+
+  case EggTexture::CM_replace:
+    return out << "replace";
+
+  case EggTexture::CM_modulate:
+    return out << "modulate";
+
+  case EggTexture::CM_add:
+    return out << "add";
+
+  case EggTexture::CM_add_signed:
+    return out << "add_signed";
+
+  case EggTexture::CM_interpolate:
+    return out << "interpolate";
+
+  case EggTexture::CM_subtract:
+    return out << "subtract";
+
+  case EggTexture::CM_dot3_rgb:
+    return out << "dot3_rgb";
+
+  case EggTexture::CM_dot3_rgba:
+    return out << "dot3_rgba";
+  }
+
+  return out << "**invalid CombineMode(" << (int)cm << ")**";
+}
+
+ostream &
+operator << (ostream &out, EggTexture::CombineChannel cm) {
+  switch (cm) {
+  case EggTexture::CC_rgb:
+    return out << "rgb";
+
+  case EggTexture::CC_alpha:
+    return out << "alpha";
+
+  case EggTexture::CC_num_channels:
+    // This case is here just to prevent a compiler warning.  Fall out
+    // of the switch and return the error message.
+    break;
+  }
+
+  return out << "**invalid CombineChannel(" << (int)cm << ")**";
+}
+
+ostream &
+operator << (ostream &out, EggTexture::CombineSource cs) {
+  switch (cs) {
+  case EggTexture::CS_unspecified:
+    return out << "unspecified";
+
+  case EggTexture::CS_texture:
+    return out << "texture";
+
+  case EggTexture::CS_constant:
+    return out << "constant";
+
+  case EggTexture::CS_primary_color:
+    return out << "primary_color";
+
+  case EggTexture::CS_previous:
+    return out << "previous";
+  }
+
+  return out << "**invalid CombineSource(" << (int)cs << ")**";
+}
+
+ostream &
+operator << (ostream &out, EggTexture::CombineOperand co) {
+  switch (co) {
+  case EggTexture::CO_unspecified:
+    return out << "unspecified";
+
+  case EggTexture::CO_src_color:
+    return out << "src_color";
+
+  case EggTexture::CO_one_minus_src_color:
+    return out << "one_minus_src_color";
+
+  case EggTexture::CO_src_alpha:
+    return out << "src_alpha";
+
+  case EggTexture::CO_one_minus_src_alpha:
+    return out << "one_minus_src_alpha";
+  }
+
+  return out << "**invalid CombineOperand(" << (int)co << ")**";
+}
+
+ostream &
+operator << (ostream &out, EggTexture::TexGen tex_gen) {
+  switch (tex_gen) {
+  case EggTexture::TG_unspecified:
+    return out << "unspecified";
+
+  case EggTexture::TG_sphere_map:
+    return out << "sphere_map";
+
+  case EggTexture::TG_cube_map:
+    return out << "cube_map";
+
+  case EggTexture::TG_world_position:
+    return out << "world_position";
+
+  case EggTexture::TG_object_position:
+    return out << "object_position";
+
+  case EggTexture::TG_eye_position:
+    return out << "eye_position";
+  }
+
+  return out << "**invalid TexGen(" << (int)tex_gen << ")**";
+}

+ 129 - 5
panda/src/egg/eggTexture.h

@@ -24,6 +24,7 @@
 #include "eggRenderMode.h"
 #include "eggRenderMode.h"
 #include "eggFilenameNode.h"
 #include "eggFilenameNode.h"
 
 
+#include "pset.h"
 #include "luse.h"
 #include "luse.h"
 
 
 
 
@@ -37,6 +38,7 @@ PUBLISHED:
   EggTexture(const string &tref_name, const string &filename);
   EggTexture(const string &tref_name, const string &filename);
   EggTexture(const EggTexture &copy);
   EggTexture(const EggTexture &copy);
   EggTexture &operator = (const EggTexture &copy);
   EggTexture &operator = (const EggTexture &copy);
+  virtual ~EggTexture(); 
 
 
   virtual void write(ostream &out, int indent_level) const;
   virtual void write(ostream &out, int indent_level) const;
 
 
@@ -83,7 +85,53 @@ PUBLISHED:
     FT_linear_mipmap_linear,     // "mipmap trilinear"
     FT_linear_mipmap_linear,     // "mipmap trilinear"
   };
   };
   enum EnvType {
   enum EnvType {
-    ET_unspecified, ET_modulate, ET_decal
+    ET_unspecified, 
+    ET_modulate, 
+    ET_decal,
+    ET_blend,
+    ET_replace,
+    ET_add,
+  };
+  enum CombineMode {
+    CM_unspecified,
+    CM_replace,
+    CM_modulate,
+    CM_add,
+    CM_add_signed,
+    CM_interpolate,
+    CM_subtract,
+    CM_dot3_rgb,
+    CM_dot3_rgba,
+  };
+  enum CombineChannel {
+    CC_rgb = 0,
+    CC_alpha = 1,
+    CC_num_channels = 2,
+  };
+  enum CombineIndex {
+    CI_num_indices = 3
+  };
+  enum CombineSource {
+    CS_unspecified,
+    CS_texture,
+    CS_constant,
+    CS_primary_color,
+    CS_previous,
+  };
+  enum CombineOperand {
+    CO_unspecified,
+    CO_src_color,
+    CO_one_minus_src_color,
+    CO_src_alpha,
+    CO_one_minus_src_alpha,
+  };
+  enum TexGen {
+    TG_unspecified,
+    TG_sphere_map,
+    TG_cube_map,
+    TG_world_position,
+    TG_object_position,
+    TG_eye_position,
   };
   };
 
 
   INLINE void set_format(Format format);
   INLINE void set_format(Format format);
@@ -114,6 +162,36 @@ PUBLISHED:
   INLINE void set_env_type(EnvType type);
   INLINE void set_env_type(EnvType type);
   INLINE EnvType get_env_type() const;
   INLINE EnvType get_env_type() const;
 
 
+  INLINE void set_combine_mode(CombineChannel channel, CombineMode cm);
+  INLINE CombineMode get_combine_mode(CombineChannel channel) const;
+  INLINE void set_combine_source(CombineChannel channel, int n, CombineSource cs);
+  INLINE CombineSource get_combine_source(CombineChannel channel, int n) const;
+  INLINE void set_combine_operand(CombineChannel channel, int n, CombineOperand co);
+  INLINE CombineOperand get_combine_operand(CombineChannel channel, int n) const;
+
+  INLINE void set_tex_gen(TexGen tex_gen);
+  INLINE TexGen get_tex_gen() const;
+
+  INLINE void set_stage_name(const string &stage_name);
+  INLINE void clear_stage_name();
+  INLINE bool has_stage_name() const;
+  INLINE const string &get_stage_name() const;
+
+  INLINE void set_priority(int priority);
+  INLINE void clear_priority();
+  INLINE bool has_priority() const;
+  INLINE int get_priority() const;
+
+  INLINE void set_color(const Colorf &color);
+  INLINE void clear_color();
+  INLINE bool has_color() const;
+  INLINE const Colorf &get_color() const;
+
+  INLINE void set_uv_name(const string &uv_name);
+  INLINE void clear_uv_name();
+  INLINE bool has_uv_name() const;
+  INLINE const string &get_uv_name() const;
+
   INLINE void set_transform(const LMatrix3d &transform);
   INLINE void set_transform(const LMatrix3d &transform);
   INLINE void clear_transform();
   INLINE void clear_transform();
   INLINE bool has_transform() const;
   INLINE bool has_transform() const;
@@ -133,20 +211,35 @@ PUBLISHED:
   INLINE bool has_alpha_file_channel() const;
   INLINE bool has_alpha_file_channel() const;
   INLINE int get_alpha_file_channel() const;
   INLINE int get_alpha_file_channel() const;
 
 
+  void clear_multitexture();
+  bool multitexture_over(EggTexture *other);
+  INLINE int get_multitexture_sort() const;
+
   static Format string_format(const string &string);
   static Format string_format(const string &string);
   static WrapMode string_wrap_mode(const string &string);
   static WrapMode string_wrap_mode(const string &string);
   static FilterType string_filter_type(const string &string);
   static FilterType string_filter_type(const string &string);
   static EnvType string_env_type(const string &string);
   static EnvType string_env_type(const string &string);
+  static CombineMode string_combine_mode(const string &string);
+  static CombineSource string_combine_source(const string &string);
+  static CombineOperand string_combine_operand(const string &string);
+  static TexGen string_tex_gen(const string &string);
 
 
 protected:
 protected:
   virtual bool egg_start_parse_body();
   virtual bool egg_start_parse_body();
 
 
 private:
 private:
+  typedef pset<EggTexture *> MultiTextures;
+  bool r_min_multitexture_sort(int sort, MultiTextures &cycle_detector);
+
   enum Flags {
   enum Flags {
     F_has_transform          = 0x0001,
     F_has_transform          = 0x0001,
     F_has_alpha_filename     = 0x0002,
     F_has_alpha_filename     = 0x0002,
     F_has_anisotropic_degree = 0x0004,
     F_has_anisotropic_degree = 0x0004,
     F_has_alpha_file_channel = 0x0008,
     F_has_alpha_file_channel = 0x0008,
+    F_has_stage_name         = 0x0010,
+    F_has_uv_name            = 0x0020,
+    F_has_priority           = 0x0040,
+    F_has_color              = 0x0080,
   };
   };
 
 
   Format _format;
   Format _format;
@@ -154,12 +247,38 @@ private:
   FilterType _minfilter, _magfilter;
   FilterType _minfilter, _magfilter;
   int _anisotropic_degree;
   int _anisotropic_degree;
   EnvType _env_type;
   EnvType _env_type;
+  TexGen _tex_gen;
+  string _stage_name;
+  int _priority;
+  Colorf _color;
+  string _uv_name;
   int _flags;
   int _flags;
   LMatrix3d _transform;
   LMatrix3d _transform;
   Filename _alpha_filename;
   Filename _alpha_filename;
   Filename _alpha_fullpath;
   Filename _alpha_fullpath;
   int _alpha_file_channel;
   int _alpha_file_channel;
+  int _multitexture_sort;
+
+  class SourceAndOperand {
+  public:
+    INLINE SourceAndOperand();
+    CombineSource _source;
+    CombineOperand _operand;
+  };
+
+  class Combiner {
+  public:
+    INLINE Combiner();
+    CombineMode _mode;
+    SourceAndOperand _ops[CI_num_indices];
+  };
+
+  Combiner _combiner[CC_num_channels];
 
 
+  // This is the set of all of the textures that are multitextured on
+  // top of (and under) this one.  This is filled in by
+  // multitexture_over().
+  MultiTextures _over_textures, _under_textures;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
@@ -200,10 +319,15 @@ INLINE ostream &operator << (ostream &out, const EggTexture &n) {
   return out << n.get_filename();
   return out << n.get_filename();
 }
 }
 
 
-ostream EXPCL_PANDAEGG &operator << (ostream &out, EggTexture::Format format);
-ostream EXPCL_PANDAEGG &operator << (ostream &out, EggTexture::WrapMode mode);
-ostream EXPCL_PANDAEGG &operator << (ostream &out, EggTexture::FilterType type);
-ostream EXPCL_PANDAEGG &operator << (ostream &out, EggTexture::EnvType type);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::Format format);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::WrapMode mode);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::FilterType type);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::EnvType type);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::CombineMode cm);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::CombineChannel cc);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::CombineSource cs);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::CombineOperand co);
+EXPCL_PANDAEGG ostream &operator << (ostream &out, EggTexture::TexGen tex_gen);
 
 
 #include "eggTexture.I"
 #include "eggTexture.I"
 
 

+ 42 - 8
panda/src/egg/eggTextureCollection.cxx

@@ -20,6 +20,7 @@
 #include "eggGroupNode.h"
 #include "eggGroupNode.h"
 #include "eggPrimitive.h"
 #include "eggPrimitive.h"
 #include "eggTexture.h"
 #include "eggTexture.h"
+#include "pt_EggTexture.h"
 
 
 #include "nameUniquifier.h"
 #include "nameUniquifier.h"
 
 
@@ -152,6 +153,13 @@ insert_textures(EggGroupNode *node, EggGroupNode::iterator position) {
 //               each time a texture reference is encountered.  This
 //               each time a texture reference is encountered.  This
 //               side effect is taken advantage of by
 //               side effect is taken advantage of by
 //               remove_unused_textures().
 //               remove_unused_textures().
+//
+//               And one more side effect: this function identifies
+//               the presence of multitexturing in the egg file, and
+//               calls multitexture_over() on each texture
+//               appropriately so that, after this call, you may
+//               expect get_multitexture_sort() to return a reasonable
+//               value for each texture.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int EggTextureCollection::
 int EggTextureCollection::
 find_used_textures(EggNode *node) {
 find_used_textures(EggNode *node) {
@@ -159,12 +167,17 @@ find_used_textures(EggNode *node) {
 
 
   if (node->is_of_type(EggPrimitive::get_class_type())) {
   if (node->is_of_type(EggPrimitive::get_class_type())) {
     EggPrimitive *primitive = DCAST(EggPrimitive, node);
     EggPrimitive *primitive = DCAST(EggPrimitive, node);
-    if (primitive->has_texture()) {
-      EggTexture *tex = primitive->get_texture();
+
+    bool found_any_new = false;
+    int num_textures = primitive->get_num_textures();
+    for (int i = 0; i < num_textures; i++) {
+      EggTexture *tex = primitive->get_texture(i);
+
       Textures::iterator ti = _textures.find(tex);
       Textures::iterator ti = _textures.find(tex);
       if (ti == _textures.end()) {
       if (ti == _textures.end()) {
         // Here's a new texture!
         // Here's a new texture!
         num_found++;
         num_found++;
+        found_any_new = true;
         _textures.insert(Textures::value_type(tex, 1));
         _textures.insert(Textures::value_type(tex, 1));
         _ordered_textures.push_back(tex);
         _ordered_textures.push_back(tex);
       } else {
       } else {
@@ -172,6 +185,20 @@ find_used_textures(EggNode *node) {
         // usage count.
         // usage count.
         (*ti).second++;
         (*ti).second++;
       }
       }
+
+      // Get the multitexture ordering right.
+      for (int j = 0; j < i; j++) {
+        // The return value of this function will be false if there is
+        // some cycle in the texture layout order; e.g. A layers over
+        // B on one primitive, but B layers over A on another
+        // primitive.  In that case the Egg Loader won't be able to
+        // assign a unique ordering between A and B, so it's probably
+        // an error worth reporting to the user--but we don't report
+        // it here, because this is a much lower-level function that
+        // gets called in other contexts too.  That means it doesn't
+        // get reported at all, but too bad.
+        tex->multitexture_over(primitive->get_texture(j));
+      }
     }
     }
 
 
   } else if (node->is_of_type(EggGroupNode::get_class_type())) {
   } else if (node->is_of_type(EggGroupNode::get_class_type())) {
@@ -252,7 +279,7 @@ int EggTextureCollection::
 collapse_equivalent_textures(int eq, EggTextureCollection::TextureReplacement &removed) {
 collapse_equivalent_textures(int eq, EggTextureCollection::TextureReplacement &removed) {
   int num_collapsed = 0;
   int num_collapsed = 0;
 
 
-  typedef pset<PT(EggTexture), UniqueEggTextures> Collapser;
+  typedef pset<PT_EggTexture, UniqueEggTextures> Collapser;
   UniqueEggTextures uet(eq);
   UniqueEggTextures uet(eq);
   Collapser collapser(uet);
   Collapser collapser(uet);
 
 
@@ -303,15 +330,22 @@ replace_textures(EggGroupNode *node,
     EggNode *child = *ci;
     EggNode *child = *ci;
     if (child->is_of_type(EggPrimitive::get_class_type())) {
     if (child->is_of_type(EggPrimitive::get_class_type())) {
       EggPrimitive *primitive = DCAST(EggPrimitive, child);
       EggPrimitive *primitive = DCAST(EggPrimitive, child);
-      if (primitive->has_texture()) {
-        PT(EggTexture) tex = primitive->get_texture();
+      EggPrimitive::Textures new_textures;
+      EggPrimitive::Textures::const_iterator ti;
+      for (ti = primitive->_textures.begin(); 
+           ti != primitive->_textures.end();
+           ++ti) {
+        PT_EggTexture tex = (*ti);
         TextureReplacement::const_iterator ri;
         TextureReplacement::const_iterator ri;
         ri = replace.find(tex);
         ri = replace.find(tex);
         if (ri != replace.end()) {
         if (ri != replace.end()) {
           // Here's a texture we want to replace.
           // Here's a texture we want to replace.
-          primitive->set_texture((*ri).second);
+          new_textures.push_back((*ri).second);
+        } else {
+          new_textures.push_back(tex);
         }
         }
       }
       }
+      primitive->_textures.swap(new_textures);
 
 
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {
       EggGroupNode *group_child = DCAST(EggGroupNode, child);
       EggGroupNode *group_child = DCAST(EggGroupNode, child);
@@ -365,7 +399,7 @@ bool EggTextureCollection::
 add_texture(EggTexture *texture) {
 add_texture(EggTexture *texture) {
   nassertr(_textures.size() == _ordered_textures.size(), false);
   nassertr(_textures.size() == _ordered_textures.size(), false);
 
 
-  PT(EggTexture) new_tex = texture;
+  PT_EggTexture new_tex = texture;
 
 
   Textures::const_iterator ti;
   Textures::const_iterator ti;
   ti = _textures.find(new_tex);
   ti = _textures.find(new_tex);
@@ -402,7 +436,7 @@ remove_texture(EggTexture *texture) {
   _textures.erase(ti);
   _textures.erase(ti);
 
 
   OrderedTextures::iterator oti;
   OrderedTextures::iterator oti;
-  PT(EggTexture) ptex = texture;
+  PT_EggTexture ptex = texture;
   oti = find(_ordered_textures.begin(), _ordered_textures.end(), ptex);
   oti = find(_ordered_textures.begin(), _ordered_textures.end(), ptex);
   nassertr(oti != _ordered_textures.end(), false);
   nassertr(oti != _ordered_textures.end(), false);
 
 

+ 4 - 2
panda/src/egg/eggUtilities.cxx

@@ -19,6 +19,7 @@
 #include "eggUtilities.h"
 #include "eggUtilities.h"
 #include "eggPrimitive.h"
 #include "eggPrimitive.h"
 #include "eggGroupNode.h"
 #include "eggGroupNode.h"
+#include "pt_EggTexture.h"
 
 
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -35,8 +36,9 @@ get_textures_by_filename(const EggNode *node, EggTextureFilenames &result) {
   if (node->is_of_type(EggPrimitive::get_class_type())) {
   if (node->is_of_type(EggPrimitive::get_class_type())) {
     const EggPrimitive *prim = DCAST(EggPrimitive, node);
     const EggPrimitive *prim = DCAST(EggPrimitive, node);
 
 
-    if (prim->has_texture()) {
-      PT(EggTexture) tex = prim->get_texture();
+    int num_textures = prim->get_num_textures();
+    for (int i = 0; i < num_textures; i++) {
+      PT_EggTexture tex = prim->get_texture(i);
       result[tex->get_filename()].insert(tex);
       result[tex->get_filename()].insert(tex);
     }
     }
 
 

+ 112 - 15
panda/src/egg/eggVertex.I

@@ -20,7 +20,7 @@
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_pool
 //     Function: EggVertex::get_pool
-//       Access: Public
+//       Access: Published
 //  Description: Returns the vertex pool this vertex belongs in.  This
 //  Description: Returns the vertex pool this vertex belongs in.  This
 //               may be NULL if the vertex has not been added to a
 //               may be NULL if the vertex has not been added to a
 //               pool.
 //               pool.
@@ -32,7 +32,7 @@ get_pool() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::is_forward_reference
 //     Function: EggVertex::is_forward_reference
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if the vertex is a forward reference to
 //  Description: Returns true if the vertex is a forward reference to
 //               some vertex that hasn't been defined yet.  In this
 //               some vertex that hasn't been defined yet.  In this
 //               case, the vertex may not have any properties filled
 //               case, the vertex may not have any properties filled
@@ -50,7 +50,7 @@ is_forward_reference() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::set_pos
 //     Function: EggVertex::set_pos
-//       Access: Public
+//       Access: Published
 //  Description: Sets the vertex position.  This variant sets the
 //  Description: Sets the vertex position.  This variant sets the
 //               vertex to a one-dimensional value.
 //               vertex to a one-dimensional value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -63,7 +63,7 @@ set_pos(double pos) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::set_pos
 //     Function: EggVertex::set_pos
-//       Access: Public
+//       Access: Published
 //  Description: Sets the vertex position.  This variant sets the
 //  Description: Sets the vertex position.  This variant sets the
 //               vertex to a two-dimensional value.
 //               vertex to a two-dimensional value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -76,7 +76,7 @@ set_pos(const LPoint2d &pos) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::set_pos
 //     Function: EggVertex::set_pos
-//       Access: Public
+//       Access: Published
 //  Description: Sets the vertex position.  This variant sets the
 //  Description: Sets the vertex position.  This variant sets the
 //               vertex to a three-dimensional value.
 //               vertex to a three-dimensional value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -89,7 +89,7 @@ set_pos(const LPoint3d &pos) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::set_pos
 //     Function: EggVertex::set_pos
-//       Access: Public
+//       Access: Published
 //  Description: Sets the vertex position.  This variant sets the
 //  Description: Sets the vertex position.  This variant sets the
 //               vertex to a four-dimensional value.
 //               vertex to a four-dimensional value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -102,7 +102,7 @@ set_pos(const LPoint4d &pos) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::set_pos4
 //     Function: EggVertex::set_pos4
-//       Access: Public
+//       Access: Published
 //  Description: This special flavor of set_pos() sets the vertex as a
 //  Description: This special flavor of set_pos() sets the vertex as a
 //               four-component value, but does not change the set
 //               four-component value, but does not change the set
 //               number of dimensions.  It's handy for retrieving the
 //               number of dimensions.  It's handy for retrieving the
@@ -118,7 +118,7 @@ set_pos4(const LPoint4d &pos) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_num_dimensions
 //     Function: EggVertex::get_num_dimensions
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of dimensions the vertex uses.
 //  Description: Returns the number of dimensions the vertex uses.
 //               Usually this will be 3, but it may be 1, 2, 3, or 4.
 //               Usually this will be 3, but it may be 1, 2, 3, or 4.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -130,7 +130,7 @@ get_num_dimensions() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_pos1
 //     Function: EggVertex::get_pos1
-//       Access: Public
+//       Access: Published
 //  Description: Only valid if get_num_dimensions() returns 1.
 //  Description: Only valid if get_num_dimensions() returns 1.
 //               Returns the position as a one-dimensional value.
 //               Returns the position as a one-dimensional value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -143,7 +143,7 @@ get_pos1() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_pos2
 //     Function: EggVertex::get_pos2
-//       Access: Public
+//       Access: Published
 //  Description: Only valid if get_num_dimensions() returns 2.
 //  Description: Only valid if get_num_dimensions() returns 2.
 //               Returns the position as a two-dimensional value.
 //               Returns the position as a two-dimensional value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -156,7 +156,7 @@ get_pos2() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_pos3
 //     Function: EggVertex::get_pos3
-//       Access: Public
+//       Access: Published
 //  Description: Valid if get_num_dimensions() returns 3 or 4.
 //  Description: Valid if get_num_dimensions() returns 3 or 4.
 //               Returns the position as a three-dimensional value.
 //               Returns the position as a three-dimensional value.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -170,7 +170,7 @@ get_pos3() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_pos4
 //     Function: EggVertex::get_pos4
-//       Access: Public
+//       Access: Published
 //  Description: This is always valid, regardless of the value of
 //  Description: This is always valid, regardless of the value of
 //               get_num_dimensions.  It returns the position as a
 //               get_num_dimensions.  It returns the position as a
 //               four-dimensional value.  If the pos has fewer than
 //               four-dimensional value.  If the pos has fewer than
@@ -183,10 +183,107 @@ get_pos4() const {
   return _pos;
   return _pos;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::has_uv
+//       Access: Published
+//  Description: Returns true if the vertex has an unnamed UV
+//               coordinate pair, false otherwise.
+//
+//               This is the more restrictive interface, and is
+//               generally useful only in the absence of
+//               multitexturing; see has_uv(name) for the interface
+//               that supports multitexturing.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggVertex::
+has_uv() const {
+  return has_uv("");
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::get_uv
+//       Access: Published
+//  Description: Returns the unnamed UV coordinate pair on the
+//               vertex.  It is an error to call this if has_uv() has
+//               returned false.
+//
+//               This is the more restrictive interface, and is
+//               generally useful only in the absence of
+//               multitexturing; see get_uv(name) for the interface
+//               that supports multitexturing.
+////////////////////////////////////////////////////////////////////
+INLINE const TexCoordd &EggVertex::
+get_uv() const {
+  nassertr(has_uv(), TexCoordd::zero());
+  return get_uv("");
+}
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: EggVertex::get_index
+//     Function: EggVertex::set_uv
+//       Access: Published
+//  Description: Replaces the unnamed UV coordinate pair on the vertex
+//               with the indicated value.
+//
+//               This is the more restrictive interface, and is
+//               generally useful only in the absence of
+//               multitexturing; see set_uv(name, uv) for the
+//               interface that supports multitexturing.
+////////////////////////////////////////////////////////////////////
+INLINE void EggVertex::
+set_uv(const TexCoordd &uv) {
+  set_uv("", uv);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::clear_uv
+//       Access: Published
+//  Description: Removes all UV coordinate pairs from the vertex.
+////////////////////////////////////////////////////////////////////
+INLINE void EggVertex::
+clear_uv() {
+  _uv_map.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::uv_begin
+//       Access: Public
+//  Description: Returns an iterator that allows walking through the
+//               complete set of named UV's on the vertex.
+//
+//               This interface is not safe to use outside of
+//               PANDAEGG.DLL.
+////////////////////////////////////////////////////////////////////
+INLINE EggVertex::const_uv_iterator EggVertex::
+uv_begin() const {
+  return _uv_map.begin();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::uv_end
 //       Access: Public
 //       Access: Public
+//  Description: Returns an iterator that allows walking through the
+//               complete set of named UV's on the vertex.
+//
+//               This interface is not safe to use outside of
+//               PANDAEGG.DLL.
+////////////////////////////////////////////////////////////////////
+INLINE EggVertex::const_uv_iterator EggVertex::
+uv_end() const {
+  return _uv_map.end();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::uv_size
+//       Access: Public
+//  Description: Returns the number of named UV's on the vertex.
+////////////////////////////////////////////////////////////////////
+INLINE EggVertex::uv_size_type EggVertex::
+uv_size() const {
+  return _uv_map.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::get_index
+//       Access: Published
 //  Description: Returns the index number of the vertex within its
 //  Description: Returns the index number of the vertex within its
 //               pool.
 //               pool.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -197,7 +294,7 @@ get_index() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::set_external_index
 //     Function: EggVertex::set_external_index
-//       Access: Public
+//       Access: Published
 //  Description: Sets a special index number that is associated with
 //  Description: Sets a special index number that is associated with
 //               the EggVertex (but is not written to the egg file).
 //               the EggVertex (but is not written to the egg file).
 //               This number is not interpreted by any egg code; it is
 //               This number is not interpreted by any egg code; it is
@@ -216,7 +313,7 @@ set_external_index(int external_index) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_external_index
 //     Function: EggVertex::get_external_index
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number set by set_external_index().  See
 //  Description: Returns the number set by set_external_index().  See
 //               set_external_index().
 //               set_external_index().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 137 - 18
panda/src/egg/eggVertex.cxx

@@ -36,7 +36,7 @@ TypeHandle EggVertex::_type_handle;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::Constructor
 //     Function: EggVertex::Constructor
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 EggVertex::
 EggVertex::
@@ -52,7 +52,7 @@ EggVertex() {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::Copy constructor
 //     Function: EggVertex::Copy constructor
-//       Access: Public
+//       Access: Published
 //  Description: Copies all properties of the vertex except its vertex
 //  Description: Copies all properties of the vertex except its vertex
 //               pool, index number, and group membership.
 //               pool, index number, and group membership.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -62,7 +62,8 @@ EggVertex(const EggVertex &copy)
     _dxyzs(copy._dxyzs),
     _dxyzs(copy._dxyzs),
     _external_index(copy._external_index),
     _external_index(copy._external_index),
     _pos(copy._pos),
     _pos(copy._pos),
-    _num_dimensions(copy._num_dimensions)
+    _num_dimensions(copy._num_dimensions),
+    _uv_map(copy._uv_map)
 {
 {
   _pool = NULL;
   _pool = NULL;
   _forward_reference = false;
   _forward_reference = false;
@@ -74,7 +75,7 @@ EggVertex(const EggVertex &copy)
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::Copy assignment operator
 //     Function: EggVertex::Copy assignment operator
-//       Access: Public
+//       Access: Published
 //  Description: Copies all properties of the vertex except its vertex
 //  Description: Copies all properties of the vertex except its vertex
 //               pool, index number, and group membership.
 //               pool, index number, and group membership.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -82,10 +83,11 @@ EggVertex &EggVertex::
 operator = (const EggVertex &copy) {
 operator = (const EggVertex &copy) {
   EggObject::operator = (copy);
   EggObject::operator = (copy);
   EggAttributes::operator = (copy);
   EggAttributes::operator = (copy);
+  _dxyzs = copy._dxyzs;
   _external_index = copy._external_index;
   _external_index = copy._external_index;
   _pos = copy._pos;
   _pos = copy._pos;
   _num_dimensions = copy._num_dimensions;
   _num_dimensions = copy._num_dimensions;
-  _dxyzs = copy._dxyzs;
+  _uv_map = copy._uv_map;
 
 
   test_pref_integrity();
   test_pref_integrity();
   test_gref_integrity();
   test_gref_integrity();
@@ -95,7 +97,7 @@ operator = (const EggVertex &copy) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::Destructor
 //     Function: EggVertex::Destructor
-//       Access: Public, Virtual
+//       Access: Published, Virtual
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 EggVertex::
 EggVertex::
@@ -111,6 +113,91 @@ EggVertex::
   nassertv(_pref.empty());
   nassertv(_pref.empty());
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::has_uv
+//       Access: Published
+//  Description: Returns true if the vertex has the named UV
+//               coordinate pair, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggVertex::
+has_uv(const string &name) const {
+  UVMap::const_iterator ui = _uv_map.find(name);
+  return (ui != _uv_map.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::get_uv
+//       Access: Published
+//  Description: Returns the named UV coordinate pair on the vertex.
+//               vertex.  It is an error to call this if has_uv(name)
+//               returned false.
+////////////////////////////////////////////////////////////////////
+const TexCoordd &EggVertex::
+get_uv(const string &name) const {
+  UVMap::const_iterator ui = _uv_map.find(name);
+  nassertr(ui != _uv_map.end(), TexCoordd::zero());
+  return (*ui).second->get_uv();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::set_uv
+//       Access: Published
+//  Description: Sets the indicated UV coordinate pair on the vertex.
+//               This replaces any UV coordinate pair with the same
+//               name already on the vertex, but preserves UV morphs.
+////////////////////////////////////////////////////////////////////
+void EggVertex::
+set_uv(const string &name, const TexCoordd &uv) {
+  PT(EggVertexUV) &uv_obj = _uv_map[name];
+
+  if (uv_obj.is_null()) {
+    uv_obj = new EggVertexUV(name, uv);
+
+  } else {
+    uv_obj = new EggVertexUV(*uv_obj);
+    uv_obj->set_uv(uv);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::get_uv_obj
+//       Access: Published
+//  Description: Returns the named EggVertexUV object, which defines
+//               both the UV coordinate pair for this name and the UV
+//               morphs.
+////////////////////////////////////////////////////////////////////
+EggVertexUV *EggVertex::
+get_uv_obj(const string &name) const {
+  UVMap::const_iterator ui = _uv_map.find(name);
+  if (ui != _uv_map.end()) {
+    return (*ui).second;
+  }
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::set_uv_obj
+//       Access: Published
+//  Description: Sets the indicated EggVertexUV on the vertex.
+//               This replaces any UV coordinate pair with the same
+//               name already on the vertex, including UV morphs.
+////////////////////////////////////////////////////////////////////
+void EggVertex::
+set_uv_obj(EggVertexUV *uv) {
+  _uv_map[uv->get_name()] = uv;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertex::clear_uv
+//       Access: Published
+//  Description: Removes the named UV coordinate pair from the vertex,
+//               along with any UV morphs.
+///////////////////////////////////////////////////////////////////
+void EggVertex::
+clear_uv(const string &name) {
+  _uv_map.erase(name);
+}
+
 
 
 
 
 ///////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////
@@ -142,7 +229,7 @@ INLINE ostream &operator << (ostream &out, const GroupRefEntry &gre) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::write
 //     Function: EggVertex::write
-//       Access: Public
+//       Access: Published
 //  Description: Writes the vertex to the indicated output stream in
 //  Description: Writes the vertex to the indicated output stream in
 //               Egg format.
 //               Egg format.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -162,6 +249,11 @@ write(ostream &out, int indent_level) const {
   }
   }
   out << "\n";
   out << "\n";
 
 
+  UVMap::const_iterator ui;
+  for (ui = _uv_map.begin(); ui != _uv_map.end(); ++ui) {
+    (*ui).second->write(out, indent_level + 2);
+  }
+
   EggAttributes::write(out, indent_level+2);
   EggAttributes::write(out, indent_level+2);
 
 
   _dxyzs.write(out, indent_level+2);
   _dxyzs.write(out, indent_level+2);
@@ -189,7 +281,7 @@ write(ostream &out, int indent_level) const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::sorts_less_than
 //     Function: EggVertex::sorts_less_than
-//       Access: Public
+//       Access: Published
 //  Description: An ordering operator to compare two vertices for
 //  Description: An ordering operator to compare two vertices for
 //               sorting order.  This imposes an arbitrary ordering
 //               sorting order.  This imposes an arbitrary ordering
 //               useful to identify unique vertices.
 //               useful to identify unique vertices.
@@ -226,12 +318,39 @@ sorts_less_than(const EggVertex &other) const {
     return _dxyzs < other._dxyzs;
     return _dxyzs < other._dxyzs;
   }
   }
 
 
+  // Merge-compare the uv maps.
+  UVMap::const_iterator ai, bi;
+  ai = _uv_map.begin();
+  bi = other._uv_map.end();
+  while (ai != _uv_map.end() && bi != other._uv_map.end()) {
+    if ((*ai).first < (*bi).first) {
+      return true;
+
+    } else if ((*bi).first < (*ai).first) {
+      return false;
+
+    } else {
+      int compare = (*ai).second->compare_to(*(*bi).second);
+      if (compare != 0) {
+        return compare < 0;
+      }
+    }
+    ++ai;
+    ++bi;
+  }
+  if (bi != _uv_map.end()) {
+    return true;
+  }
+  if (ai != _uv_map.end()) {
+    return false;
+  }
+
   return EggAttributes::sorts_less_than(other);
   return EggAttributes::sorts_less_than(other);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_num_local_coord
 //     Function: EggVertex::get_num_local_coord
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of primitives that own this vertex
 //  Description: Returns the number of primitives that own this vertex
 //               whose vertices are interpreted to be in a local
 //               whose vertices are interpreted to be in a local
 //               coordinate system.
 //               coordinate system.
@@ -252,7 +371,7 @@ get_num_local_coord() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::get_num_global_coord
 //     Function: EggVertex::get_num_global_coord
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of primitives that own this vertex
 //  Description: Returns the number of primitives that own this vertex
 //               whose vertices are interpreted in the global
 //               whose vertices are interpreted in the global
 //               coordinate system.
 //               coordinate system.
@@ -274,7 +393,7 @@ get_num_global_coord() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::transform
 //     Function: EggVertex::transform
-//       Access: Public, Virtual
+//       Access: Published, Virtual
 //  Description: Applies the indicated transformation matrix to the
 //  Description: Applies the indicated transformation matrix to the
 //               vertex.
 //               vertex.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -344,7 +463,7 @@ gref_size() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::has_gref
 //     Function: EggVertex::has_gref
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if the indicated group references this
 //  Description: Returns true if the indicated group references this
 //               vertex, false otherwise.
 //               vertex, false otherwise.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -355,7 +474,7 @@ has_gref(const EggGroup *group) const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::copy_grefs_from
 //     Function: EggVertex::copy_grefs_from
-//       Access: Public
+//       Access: Published
 //  Description: Copies all the group references from the other vertex
 //  Description: Copies all the group references from the other vertex
 //               onto this one.  This assigns the current vertex to
 //               onto this one.  This assigns the current vertex to
 //               exactly the same groups, with exactly the same
 //               exactly the same groups, with exactly the same
@@ -389,7 +508,7 @@ copy_grefs_from(const EggVertex &other) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::clear_grefs
 //     Function: EggVertex::clear_grefs
-//       Access: Public
+//       Access: Published
 //  Description: Removes all group references from the vertex, so that
 //  Description: Removes all group references from the vertex, so that
 //               it is not assigned to any group.
 //               it is not assigned to any group.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -455,7 +574,7 @@ pref_size() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::has_pref
 //     Function: EggVertex::has_pref
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of times the vertex appears in the
 //  Description: Returns the number of times the vertex appears in the
 //               indicated primitive, or 0 if it does not appear.
 //               indicated primitive, or 0 if it does not appear.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -468,7 +587,7 @@ has_pref(const EggPrimitive *prim) const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::test_gref_integrity
 //     Function: EggVertex::test_gref_integrity
-//       Access: Public
+//       Access: Published
 //  Description: Verifies that the gref list is correct and that all
 //  Description: Verifies that the gref list is correct and that all
 //               the groups included actually exist and do reference
 //               the groups included actually exist and do reference
 //               the vertex.
 //               the vertex.
@@ -491,7 +610,7 @@ test_gref_integrity() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::test_pref_integrity
 //     Function: EggVertex::test_pref_integrity
-//       Access: Public
+//       Access: Published
 //  Description: Verifies that the pref list is correct and that all
 //  Description: Verifies that the pref list is correct and that all
 //               the primitives included actually exist and do
 //               the primitives included actually exist and do
 //               reference the vertex.
 //               reference the vertex.
@@ -517,7 +636,7 @@ test_pref_integrity() const {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertex::output
 //     Function: EggVertex::output
-//       Access: Public
+//       Access: Published
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void EggVertex::
 void EggVertex::

+ 26 - 0
panda/src/egg/eggVertex.h

@@ -24,10 +24,12 @@
 #include "eggObject.h"
 #include "eggObject.h"
 #include "eggAttributes.h"
 #include "eggAttributes.h"
 #include "eggMorphList.h"
 #include "eggMorphList.h"
+#include "eggVertexUV.h"
 
 
 #include "referenceCount.h"
 #include "referenceCount.h"
 #include "luse.h"
 #include "luse.h"
 #include "pset.h"
 #include "pset.h"
+#include "iterator_types.h"
 
 
 class EggVertexPool;
 class EggVertexPool;
 class EggGroup;
 class EggGroup;
@@ -43,6 +45,11 @@ class EXPCL_PANDAEGG EggVertex : public EggObject, public EggAttributes {
 public:
 public:
   typedef pset<EggGroup *> GroupRef;
   typedef pset<EggGroup *> GroupRef;
   typedef pmultiset<EggPrimitive *> PrimitiveRef;
   typedef pmultiset<EggPrimitive *> PrimitiveRef;
+  typedef pmap< string, PT(EggVertexUV) > UVMap;
+
+  typedef second_of_pair_iterator<UVMap::const_iterator> uv_iterator;
+  typedef uv_iterator const_uv_iterator;
+  typedef UVMap::size_type uv_size_type;
 
 
 PUBLISHED:
 PUBLISHED:
   EggVertex();
   EggVertex();
@@ -74,6 +81,23 @@ PUBLISHED:
   INLINE Vertexd get_pos3() const;
   INLINE Vertexd get_pos3() const;
   INLINE LPoint4d get_pos4() const;
   INLINE LPoint4d get_pos4() const;
 
 
+  INLINE bool has_uv() const;
+  INLINE const TexCoordd &get_uv() const;
+  INLINE void set_uv(const TexCoordd &texCoord);
+  INLINE void clear_uv();
+  bool has_uv(const string &name) const;
+  const TexCoordd &get_uv(const string &name) const;
+  void set_uv(const string &name, const TexCoordd &texCoord);
+  EggVertexUV *get_uv_obj(const string &name) const;
+  void set_uv_obj(EggVertexUV *vertex_uv);
+  void clear_uv(const string &name);
+
+public:
+  INLINE const_uv_iterator uv_begin() const;
+  INLINE const_uv_iterator uv_end() const;
+  INLINE uv_size_type uv_size() const;
+
+PUBLISHED:
   INLINE int get_index() const;
   INLINE int get_index() const;
 
 
   INLINE void set_external_index(int external_index);
   INLINE void set_external_index(int external_index);
@@ -126,6 +150,8 @@ private:
   GroupRef _gref;
   GroupRef _gref;
   PrimitiveRef _pref;
   PrimitiveRef _pref;
 
 
+  UVMap _uv_map;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 38 - 0
panda/src/egg/eggVertexUV.I

@@ -0,0 +1,38 @@
+// Filename: eggVertexUV.I
+// Created by:  drose (20Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::get_uv
+//       Access: Published
+//  Description: Returns the texture coordinate pair.
+////////////////////////////////////////////////////////////////////
+INLINE const TexCoordd &EggVertexUV::
+get_uv() const {
+  return _uv;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::set_uv
+//       Access: Published
+//  Description: Sets the texture coordinate pair.
+////////////////////////////////////////////////////////////////////
+INLINE void EggVertexUV::
+set_uv(const TexCoordd &uv) {
+  _uv = uv;
+}

+ 116 - 0
panda/src/egg/eggVertexUV.cxx

@@ -0,0 +1,116 @@
+// Filename: eggVertexUV.cxx
+// Created by:  drose (20Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "eggVertexUV.h"
+#include "eggParameters.h"
+
+#include "indent.h"
+
+TypeHandle EggVertexUV::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+EggVertexUV::
+EggVertexUV(const string &name, const TexCoordd &uv) :
+  EggNamedObject(name),
+  _uv(uv)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::Copy Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+EggVertexUV::
+EggVertexUV(const EggVertexUV &copy) :
+  EggNamedObject(copy),
+  _duvs(copy._duvs),
+  _uv(copy._uv)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::Copy Assignment Operator
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+EggVertexUV &EggVertexUV::
+operator = (const EggVertexUV &copy) {
+  EggNamedObject::operator = (copy);
+  _duvs = copy._duvs;
+  _uv = copy._uv;
+
+  return (*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::Destructor
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+EggVertexUV::
+~EggVertexUV() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::write
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void EggVertexUV::
+write(ostream &out, int indent_level) const {
+  string inline_name = get_name();
+  if (!inline_name.empty()) {
+    inline_name += ' ';
+  }
+
+  if (_duvs.empty()) {
+    indent(out, indent_level)
+      << "<UV> " << inline_name << "{ " << get_uv() << " }\n";
+  } else {
+    indent(out, indent_level) << "<UV> " << inline_name << "{\n";
+    indent(out, indent_level+2) << get_uv() << "\n";
+    _duvs.write(out, indent_level+2);
+    indent(out, indent_level) << "}\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexUV::compare_to
+//       Access: Public
+//  Description: An ordering operator to compare two vertices for
+//               sorting order.  This imposes an arbitrary ordering
+//               useful to identify unique vertices.
+////////////////////////////////////////////////////////////////////
+int EggVertexUV::
+compare_to(const EggVertexUV &other) const {
+  int compare =
+    _uv.compare_to(other._uv, egg_parameters->_uv_threshold);
+  if (compare != 0) {
+    return compare;
+  }
+  if (_duvs != other._duvs) {
+    return _duvs < other._duvs ? -1 : 1;
+  }
+
+  return 0;
+}

+ 75 - 0
panda/src/egg/eggVertexUV.h

@@ -0,0 +1,75 @@
+// Filename: eggVertexUV.h
+// Created by:  drose (20Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef EGGVERTEXUV_H
+#define EGGVERTEXUV_H
+
+#include "pandabase.h"
+
+#include "eggMorphList.h"
+#include "eggNamedObject.h"
+
+#include "luse.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : EggVertexUV
+// Description : The set of UV's that may or may not be assigned to a
+//               vertex.  To support multitexturing, there may be
+//               multiple sets of UV's on a particular vertex, each
+//               with its own name.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEGG EggVertexUV : public EggNamedObject {
+PUBLISHED:
+  EggVertexUV(const string &name, const TexCoordd &uv);
+  EggVertexUV(const EggVertexUV &copy);
+  EggVertexUV &operator = (const EggVertexUV &copy);
+  virtual ~EggVertexUV();
+
+  INLINE const TexCoordd &get_uv() const;
+  INLINE void set_uv(const TexCoordd &texCoord);
+
+  void write(ostream &out, int indent_level) const;
+  int compare_to(const EggVertexUV &other) const;
+
+  EggMorphTexCoordList _duvs;
+
+private:
+  TexCoordd _uv;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    EggNamedObject::init_type();
+    register_type(_type_handle, "EggVertexUV",
+                  EggNamedObject::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "eggVertexUV.I"
+
+#endif
+

+ 1 - 0
panda/src/egg/egg_composite2.cxx

@@ -17,6 +17,7 @@
 #include "eggUtilities.cxx"
 #include "eggUtilities.cxx"
 #include "eggVertex.cxx"
 #include "eggVertex.cxx"
 #include "eggVertexPool.cxx"
 #include "eggVertexPool.cxx"
+#include "eggVertexUV.cxx"
 #include "eggXfmAnimData.cxx"
 #include "eggXfmAnimData.cxx"
 #include "eggXfmSAnim.cxx"
 #include "eggXfmSAnim.cxx"
 #include "pt_EggMaterial.cxx"
 #include "pt_EggMaterial.cxx"

+ 3 - 3
panda/src/egg/lexer.cxx.prebuilt

@@ -710,11 +710,11 @@ char *yytext;
 #include "config_egg.h"
 #include "config_egg.h"
 #include "parser.h"
 #include "parser.h"
 
 
-#include "indent.h"
-#include "notify.h"
+#include <indent.h>
+#include <notify.h>
 
 
 #include <math.h>
 #include <math.h>
-#include "pandabase.h"
+#include <pandabase.h>
 
 
 extern "C" int eggyywrap(void);  // declared below.
 extern "C" int eggyywrap(void);  // declared below.
 
 

File diff suppressed because it is too large
+ 607 - 588
panda/src/egg/parser.cxx.prebuilt


+ 172 - 7
panda/src/egg/parser.yxx

@@ -13,6 +13,7 @@
 #include "eggGroup.h"
 #include "eggGroup.h"
 #include "eggVertex.h"
 #include "eggVertex.h"
 #include "eggVertexPool.h"
 #include "eggVertexPool.h"
+#include "eggVertexUV.h"
 #include "eggPolygon.h"
 #include "eggPolygon.h"
 #include "eggPoint.h"
 #include "eggPoint.h"
 #include "eggLine.h"
 #include "eggLine.h"
@@ -381,6 +382,7 @@ texture_body:
     } else {
     } else {
       texture->set_magfilter(f);
       texture->set_magfilter(f);
     }
     }
+
   } else if (cmp_nocase_uh(name, "anisotropic_degree") == 0) {
   } else if (cmp_nocase_uh(name, "anisotropic_degree") == 0) {
     texture->set_anisotropic_degree((int)value);
     texture->set_anisotropic_degree((int)value);
 
 
@@ -392,6 +394,155 @@ texture_body:
       texture->set_env_type(e);
       texture->set_env_type(e);
     }
     }
 
 
+  } else if (cmp_nocase_uh(name, "combine-rgb") == 0) {
+    EggTexture::CombineMode cm = EggTexture::string_combine_mode(strval);
+    if (cm == EggTexture::CM_unspecified) {
+      eggyywarning("Unknown combine mode " + strval);
+    } else {
+      texture->set_combine_mode(EggTexture::CC_rgb, cm);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-rgb-source0") == 0) {
+    EggTexture::CombineSource cs = EggTexture::string_combine_source(strval);
+    if (cs == EggTexture::CS_unspecified) {
+      eggyywarning("Unknown combine source " + strval);
+    } else {
+      texture->set_combine_source(EggTexture::CC_rgb, 0, cs);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-rgb-operand0") == 0) {
+    EggTexture::CombineOperand co = EggTexture::string_combine_operand(strval);
+    if (co == EggTexture::CO_unspecified) {
+      eggyywarning("Unknown combine operand " + strval);
+    } else {
+      texture->set_combine_operand(EggTexture::CC_rgb, 0, co);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-rgb-source1") == 0) {
+    EggTexture::CombineSource cs = EggTexture::string_combine_source(strval);
+    if (cs == EggTexture::CS_unspecified) {
+      eggyywarning("Unknown combine source " + strval);
+    } else {
+      texture->set_combine_source(EggTexture::CC_rgb, 1, cs);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-rgb-operand1") == 0) {
+    EggTexture::CombineOperand co = EggTexture::string_combine_operand(strval);
+    if (co == EggTexture::CO_unspecified) {
+      eggyywarning("Unknown combine operand " + strval);
+    } else {
+      texture->set_combine_operand(EggTexture::CC_rgb, 1, co);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-rgb-source2") == 0) {
+    EggTexture::CombineSource cs = EggTexture::string_combine_source(strval);
+    if (cs == EggTexture::CS_unspecified) {
+      eggyywarning("Unknown combine source " + strval);
+    } else {
+      texture->set_combine_source(EggTexture::CC_rgb, 2, cs);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-rgb-operand2") == 0) {
+    EggTexture::CombineOperand co = EggTexture::string_combine_operand(strval);
+    if (co == EggTexture::CO_unspecified) {
+      eggyywarning("Unknown combine operand " + strval);
+    } else {
+      texture->set_combine_operand(EggTexture::CC_rgb, 2, co);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-alpha") == 0) {
+    EggTexture::CombineMode cm = EggTexture::string_combine_mode(strval);
+    if (cm == EggTexture::CM_unspecified) {
+      eggyywarning("Unknown combine mode " + strval);
+    } else {
+      texture->set_combine_mode(EggTexture::CC_alpha, cm);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-alpha-source0") == 0) {
+    EggTexture::CombineSource cs = EggTexture::string_combine_source(strval);
+    if (cs == EggTexture::CS_unspecified) {
+      eggyywarning("Unknown combine source " + strval);
+    } else {
+      texture->set_combine_source(EggTexture::CC_alpha, 0, cs);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-alpha-operand0") == 0) {
+    EggTexture::CombineOperand co = EggTexture::string_combine_operand(strval);
+    if (co == EggTexture::CO_unspecified) {
+      eggyywarning("Unknown combine operand " + strval);
+    } else {
+      texture->set_combine_operand(EggTexture::CC_alpha, 0, co);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-alpha-source1") == 0) {
+    EggTexture::CombineSource cs = EggTexture::string_combine_source(strval);
+    if (cs == EggTexture::CS_unspecified) {
+      eggyywarning("Unknown combine source " + strval);
+    } else {
+      texture->set_combine_source(EggTexture::CC_alpha, 1, cs);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-alpha-operand1") == 0) {
+    EggTexture::CombineOperand co = EggTexture::string_combine_operand(strval);
+    if (co == EggTexture::CO_unspecified) {
+      eggyywarning("Unknown combine operand " + strval);
+    } else {
+      texture->set_combine_operand(EggTexture::CC_alpha, 1, co);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-alpha-source2") == 0) {
+    EggTexture::CombineSource cs = EggTexture::string_combine_source(strval);
+    if (cs == EggTexture::CS_unspecified) {
+      eggyywarning("Unknown combine source " + strval);
+    } else {
+      texture->set_combine_source(EggTexture::CC_alpha, 2, cs);
+    }
+
+  } else if (cmp_nocase_uh(name, "combine-alpha-operand2") == 0) {
+    EggTexture::CombineOperand co = EggTexture::string_combine_operand(strval);
+    if (co == EggTexture::CO_unspecified) {
+      eggyywarning("Unknown combine operand " + strval);
+    } else {
+      texture->set_combine_operand(EggTexture::CC_alpha, 2, co);
+    }
+
+  } else if (cmp_nocase_uh(name, "tex_gen") == 0) {
+    EggTexture::TexGen tex_gen = EggTexture::string_tex_gen(strval);
+    if (tex_gen == EggTexture::TG_unspecified) {
+      eggyywarning("Unknown texture env type " + strval);
+    } else {
+      texture->set_tex_gen(tex_gen);
+    }
+
+  } else if (cmp_nocase_uh(name, "stage_name") == 0) {
+    texture->set_stage_name(strval);
+
+  } else if (cmp_nocase_uh(name, "priority") == 0) {
+    texture->set_priority((int)value);
+
+  } else if (cmp_nocase_uh(name, "blendr") == 0) {
+    Colorf color = texture->get_color();
+    color[0] = value;
+    texture->set_color(value);
+
+  } else if (cmp_nocase_uh(name, "blendg") == 0) {
+    Colorf color = texture->get_color();
+    color[1] = value;
+    texture->set_color(value);
+
+  } else if (cmp_nocase_uh(name, "blendb") == 0) {
+    Colorf color = texture->get_color();
+    color[2] = value;
+    texture->set_color(value);
+
+  } else if (cmp_nocase_uh(name, "blenda") == 0) {
+    Colorf color = texture->get_color();
+    color[3] = value;
+    texture->set_color(value);
+
+  } else if (cmp_nocase_uh(name, "uv_name") == 0) {
+    texture->set_uv_name(strval);
+
   } else if (cmp_nocase_uh(name, "alpha") == 0) {
   } else if (cmp_nocase_uh(name, "alpha") == 0) {
     EggRenderMode::AlphaMode a = EggRenderMode::string_alpha_mode(strval);
     EggRenderMode::AlphaMode a = EggRenderMode::string_alpha_mode(strval);
     if (a == EggRenderMode::AM_unspecified) {
     if (a == EggRenderMode::AM_unspecified) {
@@ -725,7 +876,21 @@ vertex_body:
 {
 {
   DCAST(EggVertex, egg_stack.back())->set_pos(LPoint4d($1, $2, $3, $4));
   DCAST(EggVertex, egg_stack.back())->set_pos(LPoint4d($1, $2, $3, $4));
 }
 }
-        | vertex_body UV '{' vertex_uv_body '}'
+        | vertex_body UV optional_name '{'
+{
+  EggVertex *vertex = DCAST(EggVertex, egg_stack.back());
+  EggVertexUV *uv = new EggVertexUV($3, TexCoordd::zero());
+  egg_stack.push_back(uv);
+  if (vertex->has_uv($3)) {
+    eggyywarning("Ignoring repeated UV name " + $3);
+  } else {
+    vertex->set_uv_obj(uv);
+  }
+}
+	 vertex_uv_body '}'
+{
+  egg_stack.pop_back();
+}
         | vertex_body NORMAL '{' vertex_normal_body '}'
         | vertex_body NORMAL '{' vertex_normal_body '}'
         | vertex_body RGBA '{' vertex_color_body '}'
         | vertex_body RGBA '{' vertex_color_body '}'
         | vertex_body DXYZ string '{' real real real '}'
         | vertex_body DXYZ string '{' real real real '}'
@@ -757,19 +922,19 @@ vertex_body:
 vertex_uv_body:
 vertex_uv_body:
         real real
         real real
 {
 {
-  DCAST(EggVertex, egg_stack.back())->set_uv(TexCoordd($1, $2));
+  DCAST(EggVertexUV, egg_stack.back())->set_uv(TexCoordd($1, $2));
 }
 }
         | vertex_uv_body DUV string '{' real real '}'
         | vertex_uv_body DUV string '{' real real '}'
 {
 {
-  bool inserted = DCAST(EggVertex, egg_stack.back())->_duvs.
+  bool inserted = DCAST(EggVertexUV, egg_stack.back())->_duvs.
     insert(EggMorphTexCoord($3, LVector2d($5, $6))).second;
     insert(EggMorphTexCoord($3, LVector2d($5, $6))).second;
   if (!inserted) {
   if (!inserted) {
     eggyywarning("Ignoring repeated morph name " + $3);
     eggyywarning("Ignoring repeated morph name " + $3);
   }
   }
 }
 }
-        | vertex_uv_body DUV '{' string real real '}'
+       | vertex_uv_body DUV '{' string real real '}'
 {
 {
-  bool inserted = DCAST(EggVertex, egg_stack.back())->_duvs.
+  bool inserted = DCAST(EggVertexUV, egg_stack.back())->_duvs.
     insert(EggMorphTexCoord($4, LVector2d($5, $6))).second;
     insert(EggMorphTexCoord($4, LVector2d($5, $6))).second;
   if (!inserted) {
   if (!inserted) {
     eggyywarning("Ignoring repeated morph name " + $4);
     eggyywarning("Ignoring repeated morph name " + $4);
@@ -1724,7 +1889,7 @@ primitive_tref_body:
 {
 {
   if ($1 != (EggTexture *)NULL) {
   if ($1 != (EggTexture *)NULL) {
     EggTexture *texture = DCAST(EggTexture, $1);
     EggTexture *texture = DCAST(EggTexture, $1);
-    DCAST(EggPrimitive, egg_stack.back())->set_texture(texture);
+    DCAST(EggPrimitive, egg_stack.back())->add_texture(texture);
   }
   }
 }
 }
         ;
         ;
@@ -1765,7 +1930,7 @@ primitive_texture_body:
   }
   }
 
 
   nassertr(texture != NULL, 0);
   nassertr(texture != NULL, 0);
-  DCAST(EggPrimitive, egg_stack.back())->set_texture(texture);
+  DCAST(EggPrimitive, egg_stack.back())->add_texture(texture);
 }
 }
         ;
         ;
 
 

+ 42 - 28
panda/src/egg2pg/computedVerticesMaker.cxx

@@ -25,6 +25,7 @@
 #include "eggNode.h"
 #include "eggNode.h"
 #include "eggGroup.h"
 #include "eggGroup.h"
 #include "eggVertex.h"
 #include "eggVertex.h"
+#include "texCoordName.h"
 
 
 #include <algorithm>
 #include <algorithm>
 
 
@@ -35,10 +36,9 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 ComputedVerticesMaker::
 ComputedVerticesMaker::
 ComputedVerticesMaker() {
 ComputedVerticesMaker() {
-  _coords= PTA_Vertexf::empty_array(0);
-  _norms= PTA_Normalf::empty_array(0);
-  _colors= PTA_Colorf::empty_array(0);
-  _texcoords= PTA_TexCoordf::empty_array(0);
+  _coords = PTA_Vertexf::empty_array(0);
+  _norms = PTA_Normalf::empty_array(0);
+  _colors = PTA_Colorf::empty_array(0);
   _current_vc = NULL;
   _current_vc = NULL;
 }
 }
 
 
@@ -242,17 +242,20 @@ add_normal(const Normald &normal, const EggMorphNormalList &morphs,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: ComputedVerticesMaker::add_texcoord
 //     Function: ComputedVerticesMaker::add_texcoord
 //       Access: Public
 //       Access: Public
-//  Description: Adds a texcoord value to the array (texture
+//  Description: Adds a texcoord value to the named array (texture
 //               coordinates are unrelated to the current transform
 //               coordinates are unrelated to the current transform
 //               space), and returns its index number within the
 //               space), and returns its index number within the
 //               array.
 //               array.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int ComputedVerticesMaker::
 int ComputedVerticesMaker::
-add_texcoord(const TexCoordd &texcoord, const EggMorphTexCoordList &morphs,
+add_texcoord(const TexCoordName *name,
+             const TexCoordd &texcoord, const EggMorphTexCoordList &morphs,
              const LMatrix3d &transform) {
              const LMatrix3d &transform) {
+  TexCoordDef &def = _tdefmap[name];
+
   TexCoordf ttc = LCAST(float, texcoord * transform);
   TexCoordf ttc = LCAST(float, texcoord * transform);
-  int index = _tmap.add_value(ttc, morphs, _texcoords);
-  _tindex.insert(index);
+  int index = def._tmap.add_value(ttc, morphs, _texcoords[name]);
+  def._tindex.insert(index);
 
 
   // Now create any morph sliders.
   // Now create any morph sliders.
   EggMorphTexCoordList::const_iterator mli;
   EggMorphTexCoordList::const_iterator mli;
@@ -263,13 +266,14 @@ add_texcoord(const TexCoordd &texcoord, const EggMorphTexCoordList &morphs,
       MorphList &mlist = _morphs[morph.get_name()];
       MorphList &mlist = _morphs[morph.get_name()];
 
 
       // Have we already morphed this texcoord?
       // Have we already morphed this texcoord?
-      TexCoordMorphList::iterator vmi = mlist._tmorphs.find(index);
-      if (vmi != mlist._tmorphs.end()) {
+      TexCoordMorphList &tmorphs = mlist._tmorphs[name];
+      TexCoordMorphList::iterator vmi = tmorphs.find(index);
+      if (vmi != tmorphs.end()) {
         // Yes, we have.
         // Yes, we have.
         nassertr((*vmi).second.almost_equal(LCAST(float, offset), 0.001), index);
         nassertr((*vmi).second.almost_equal(LCAST(float, offset), 0.001), index);
       } else {
       } else {
         // No, we haven't yet; morph it now.
         // No, we haven't yet; morph it now.
-        mlist._tmorphs[index] = LCAST(float, offset);
+        tmorphs[index] = LCAST(float, offset);
       }
       }
     }
     }
   }
   }
@@ -373,7 +377,10 @@ make_computed_vertices(Character *character, CharacterMaker &char_maker) {
   character->_cv._coords = _coords;
   character->_cv._coords = _coords;
   character->_cv._norms = _norms;
   character->_cv._norms = _norms;
   character->_cv._colors = _colors;
   character->_cv._colors = _colors;
-  character->_cv._texcoords = _texcoords;
+
+  // Temporary: the ComputedVertices object currently doesn't support
+  // multitexture.
+  character->_cv._texcoords = _texcoords[TexCoordName::get_default()];
 
 
   // Finally, add in all the morph definitions.
   // Finally, add in all the morph definitions.
   Morphs::const_iterator mi;
   Morphs::const_iterator mi;
@@ -414,20 +421,6 @@ make_computed_vertices(Character *character, CharacterMaker &char_maker) {
       }
       }
     }
     }
 
 
-    if (!mlist._tmorphs.empty()) {
-      comp_verts->_texcoord_morphs.push_back(ComputedVerticesMorphTexCoord());
-      ComputedVerticesMorphTexCoord &mv = comp_verts->_texcoord_morphs.back();
-      mv._slider_index = slider_index;
-
-      TexCoordMorphList::const_iterator vmi;
-      for (vmi = mlist._tmorphs.begin();
-           vmi != mlist._tmorphs.end();
-           ++vmi) {
-        mv._morphs.push_back(ComputedVerticesMorphValue2((*vmi).first,
-                                                         (*vmi).second));
-      }
-    }
-
     if (!mlist._cmorphs.empty()) {
     if (!mlist._cmorphs.empty()) {
       comp_verts->_color_morphs.push_back(ComputedVerticesMorphColor());
       comp_verts->_color_morphs.push_back(ComputedVerticesMorphColor());
       ComputedVerticesMorphColor &mv = comp_verts->_color_morphs.back();
       ComputedVerticesMorphColor &mv = comp_verts->_color_morphs.back();
@@ -441,6 +434,26 @@ make_computed_vertices(Character *character, CharacterMaker &char_maker) {
                                                          (*vmi).second));
                                                          (*vmi).second));
       }
       }
     }
     }
+
+    TexCoordMorphMap::const_iterator mmi;
+    for (mmi = mlist._tmorphs.begin(); mmi != mlist._tmorphs.end(); ++mmi) {
+      const TexCoordName *name = (*mmi).first;
+      const TexCoordMorphList &tmorphs = (*mmi).second;
+
+      // Temporary check: the ComputedVertices object currently
+      // doesn't support multitexture.
+      if (name == TexCoordName::get_default()) {
+        comp_verts->_texcoord_morphs.push_back(ComputedVerticesMorphTexCoord());
+        ComputedVerticesMorphTexCoord &mv = comp_verts->_texcoord_morphs.back();
+        mv._slider_index = slider_index;
+        
+        TexCoordMorphList::const_iterator vmi;
+        for (vmi = tmorphs.begin(); vmi != tmorphs.end(); ++vmi) {
+          mv._morphs.push_back(ComputedVerticesMorphValue2((*vmi).first,
+                                                           (*vmi).second));
+        }
+      }
+    }
   }
   }
 
 
   comp_verts->make_orig(character);
   comp_verts->make_orig(character);
@@ -459,8 +472,9 @@ write(ostream &out) const {
       << _transforms.size() << " transform spaces, "
       << _transforms.size() << " transform spaces, "
       << _coords.size() << " vertices, "
       << _coords.size() << " vertices, "
       << _norms.size() << " normals, "
       << _norms.size() << " normals, "
-      << _texcoords.size() << " uvs, "
-      << _colors.size() << " colors.\n";
+      << _colors.size() << " colors, "
+      << _texcoords.size() << " named uv sets.\n";
+
   TransformSpaces::const_iterator tsi;
   TransformSpaces::const_iterator tsi;
   for (tsi = _transforms.begin(); tsi != _transforms.end(); ++tsi) {
   for (tsi = _transforms.begin(); tsi != _transforms.end(); ++tsi) {
     const JointWeights &jw = (*tsi).first;
     const JointWeights &jw = (*tsi).first;

+ 17 - 5
panda/src/egg2pg/computedVerticesMaker.h

@@ -40,6 +40,7 @@ class ComputedVertices;
 class CharacterMaker;
 class CharacterMaker;
 class EggNode;
 class EggNode;
 class EggVertex;
 class EggVertex;
+class TexCoordName;
 
 
 ///////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////
 //       Class : ComputedVerticesMaker
 //       Class : ComputedVerticesMaker
@@ -73,7 +74,8 @@ public:
                  const LMatrix4d &transform);
                  const LMatrix4d &transform);
   int add_normal(const Normald &normal, const EggMorphNormalList &morphs,
   int add_normal(const Normald &normal, const EggMorphNormalList &morphs,
                  const LMatrix4d &transform);
                  const LMatrix4d &transform);
-  int add_texcoord(const TexCoordd &texcoord,
+  int add_texcoord(const TexCoordName *name,
+                   const TexCoordd &texcoord,
                    const EggMorphTexCoordList &morphs,
                    const EggMorphTexCoordList &morphs,
                    const LMatrix3d &transform);
                    const LMatrix3d &transform);
   int add_color(const Colorf &color, const EggMorphColorList &morphs);
   int add_color(const Colorf &color, const EggMorphColorList &morphs);
@@ -87,19 +89,22 @@ public:
   PTA_Vertexf _coords;
   PTA_Vertexf _coords;
   PTA_Normalf _norms;
   PTA_Normalf _norms;
   PTA_Colorf _colors;
   PTA_Colorf _colors;
-  PTA_TexCoordf _texcoords;
+
+  typedef pmap<const TexCoordName *, PTA_TexCoordf> TexCoords;
+  TexCoords _texcoords;
 
 
 protected:
 protected:
   typedef pmap<int, LVector3f> VertexMorphList;
   typedef pmap<int, LVector3f> VertexMorphList;
   typedef pmap<int, LVector3f> NormalMorphList;
   typedef pmap<int, LVector3f> NormalMorphList;
   typedef pmap<int, LVector2f> TexCoordMorphList;
   typedef pmap<int, LVector2f> TexCoordMorphList;
   typedef pmap<int, LVector4f> ColorMorphList;
   typedef pmap<int, LVector4f> ColorMorphList;
+  typedef pmap<const TexCoordName *, TexCoordMorphList> TexCoordMorphMap;
   class MorphList {
   class MorphList {
   public:
   public:
     VertexMorphList _vmorphs;
     VertexMorphList _vmorphs;
     NormalMorphList _nmorphs;
     NormalMorphList _nmorphs;
-    TexCoordMorphList _tmorphs;
     ColorMorphList _cmorphs;
     ColorMorphList _cmorphs;
+    TexCoordMorphMap _tmorphs;
   };
   };
 
 
   typedef pmap<string, MorphList> Morphs;
   typedef pmap<string, MorphList> Morphs;
@@ -108,9 +113,16 @@ protected:
   typedef pset<int> Vertices;
   typedef pset<int> Vertices;
 
 
   Vertices _cindex;
   Vertices _cindex;
-  Vertices _tindex;
 
 
-  ComputedVerticesMakerTexCoordMap _tmap;
+  class TexCoordDef {
+  public:
+    Vertices _tindex;
+    ComputedVerticesMakerTexCoordMap _tmap;
+  };
+
+  typedef pmap<const TexCoordName *, TexCoordDef> TexCoordDefMap;
+  TexCoordDefMap _tdefmap;
+
   ComputedVerticesMakerColorMap _cmap;
   ComputedVerticesMakerColorMap _cmap;
 
 
 #ifdef WIN32_VC
 #ifdef WIN32_VC

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

@@ -43,7 +43,6 @@ double egg_coplanar_threshold = config_egg2pg.GetDouble("egg-coplanar-threshold"
 bool egg_ignore_mipmaps = config_egg2pg.GetBool("egg-ignore-mipmaps", false);
 bool egg_ignore_mipmaps = config_egg2pg.GetBool("egg-ignore-mipmaps", false);
 bool egg_ignore_filters = config_egg2pg.GetBool("egg-ignore-filters", false);
 bool egg_ignore_filters = config_egg2pg.GetBool("egg-ignore-filters", false);
 bool egg_ignore_clamp = config_egg2pg.GetBool("egg-ignore-clamp", false);
 bool egg_ignore_clamp = config_egg2pg.GetBool("egg-ignore-clamp", false);
-bool egg_always_decal_textures = config_egg2pg.GetBool("egg-always-decal-textures", false);
 bool egg_ignore_decals = config_egg2pg.GetBool("egg-ignore-decals", false);
 bool egg_ignore_decals = config_egg2pg.GetBool("egg-ignore-decals", false);
 bool egg_flatten = config_egg2pg.GetBool("egg-flatten", true);
 bool egg_flatten = config_egg2pg.GetBool("egg-flatten", true);
 
 

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

@@ -47,7 +47,6 @@ extern EXPCL_PANDAEGG CoordinateSystem egg_coordinate_system;
 extern EXPCL_PANDAEGG bool egg_ignore_mipmaps;
 extern EXPCL_PANDAEGG bool egg_ignore_mipmaps;
 extern EXPCL_PANDAEGG bool egg_ignore_filters;
 extern EXPCL_PANDAEGG bool egg_ignore_filters;
 extern EXPCL_PANDAEGG bool egg_ignore_clamp;
 extern EXPCL_PANDAEGG bool egg_ignore_clamp;
-extern EXPCL_PANDAEGG bool egg_always_decal_textures;
 extern EXPCL_PANDAEGG bool egg_ignore_decals;
 extern EXPCL_PANDAEGG bool egg_ignore_decals;
 extern EXPCL_PANDAEGG bool egg_flatten;
 extern EXPCL_PANDAEGG bool egg_flatten;
 extern EXPCL_PANDAEGG bool egg_flatten_siblings;
 extern EXPCL_PANDAEGG bool egg_flatten_siblings;

+ 501 - 94
panda/src/egg2pg/eggLoader.cxx

@@ -60,6 +60,7 @@
 #include "eggTable.h"
 #include "eggTable.h"
 #include "eggBinner.h"
 #include "eggBinner.h"
 #include "eggVertexPool.h"
 #include "eggVertexPool.h"
+#include "pt_EggTexture.h"
 #include "characterMaker.h"
 #include "characterMaker.h"
 #include "character.h"
 #include "character.h"
 #include "animBundleMaker.h"
 #include "animBundleMaker.h"
@@ -239,7 +240,8 @@ void EggLoader::
 make_nonindexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
 make_nonindexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
                           const LMatrix4d *transform) {
                           const LMatrix4d *transform) {
   BuilderBucket bucket;
   BuilderBucket bucket;
-  setup_bucket(bucket, parent, egg_prim);
+  BakeInUVs bake_in_uvs;
+  setup_bucket(bucket, bake_in_uvs, parent, egg_prim);
   if (bucket._hidden && egg_suppress_hidden) {
   if (bucket._hidden && egg_suppress_hidden) {
     // Eat this primitive.
     // Eat this primitive.
     return;
     return;
@@ -302,14 +304,25 @@ make_nonindexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
           // vertex colors.
           // vertex colors.
           has_vert_color = false;
           has_vert_color = false;
         }
         }
-        if (egg_vert->has_uv()) {
-          TexCoordd uv = egg_vert->get_uv();
-          if (egg_prim->has_texture() &&
-              egg_prim->get_texture()->has_transform()) {
-            // If we have a texture matrix, apply it.
-            uv = uv * egg_prim->get_texture()->get_transform();
+
+        EggVertex::const_uv_iterator ui;
+        for (ui = egg_vert->uv_begin(); ui != egg_vert->uv_end(); ++ui) {
+          EggVertexUV *uv_obj = (*ui);
+          TexCoordd uv = uv_obj->get_uv();
+          CPT(TexCoordName) uv_name;
+          if (uv_obj->has_name()) {
+            uv_name = TexCoordName::make(uv_obj->get_name());
+          } else {
+            uv_name = TexCoordName::get_default();
           }
           }
-          bvert.set_texcoord(LCAST(float, uv));
+
+          BakeInUVs::const_iterator buv = bake_in_uvs.find(uv_name);
+          if (buv != bake_in_uvs.end()) {
+            // If we are to bake in a texture matrix, do so now.
+            uv = uv * (*buv).second->get_transform();
+          }
+
+          bvert.set_texcoord(uv_name, LCAST(float, uv));
         }
         }
         
         
         bprim.add_vertex(bvert);
         bprim.add_vertex(bvert);
@@ -336,7 +349,8 @@ make_indexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
                        const LMatrix4d *transform,
                        const LMatrix4d *transform,
                        ComputedVerticesMaker &_comp_verts_maker) {
                        ComputedVerticesMaker &_comp_verts_maker) {
   BuilderBucket bucket;
   BuilderBucket bucket;
-  setup_bucket(bucket, parent, egg_prim);
+  BakeInUVs bake_in_uvs;
+  setup_bucket(bucket, bake_in_uvs, parent, egg_prim);
   if (bucket._hidden && egg_suppress_hidden) {
   if (bucket._hidden && egg_suppress_hidden) {
     // Eat this primitive.
     // Eat this primitive.
     return;
     return;
@@ -344,9 +358,16 @@ make_indexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
 
 
   bucket.set_coords(_comp_verts_maker._coords);
   bucket.set_coords(_comp_verts_maker._coords);
   bucket.set_normals(_comp_verts_maker._norms);
   bucket.set_normals(_comp_verts_maker._norms);
-  bucket.set_texcoords(_comp_verts_maker._texcoords);
   bucket.set_colors(_comp_verts_maker._colors);
   bucket.set_colors(_comp_verts_maker._colors);
 
 
+  ComputedVerticesMaker::TexCoords::const_iterator tci;
+  for (tci = _comp_verts_maker._texcoords.begin();
+       tci != _comp_verts_maker._texcoords.end();
+       ++tci) {
+    const TexCoordName *name = (*tci).first;
+    bucket.set_texcoords(name, (*tci).second);
+  }
+
   LMatrix4d mat;
   LMatrix4d mat;
 
 
   if (transform != NULL) {
   if (transform != NULL) {
@@ -429,21 +450,28 @@ make_indexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
         has_vert_color = false;
         has_vert_color = false;
       }
       }
 
 
-      if (egg_vert->has_uv()) {
-        TexCoordd uv = egg_vert->get_uv();
-        LMatrix3d mat;
-        
-        if (egg_prim->has_texture() &&
-            egg_prim->get_texture()->has_transform()) {
-          // If we have a texture matrix, apply it.
-          mat = egg_prim->get_texture()->get_transform();
+      EggVertex::const_uv_iterator ui;
+      for (ui = egg_vert->uv_begin(); ui != egg_vert->uv_end(); ++ui) {
+        EggVertexUV *uv_obj = (*ui);
+        TexCoordd uv = uv_obj->get_uv();
+        CPT(TexCoordName) uv_name;
+        if (uv_obj->has_name()) {
+          uv_name = TexCoordName::make(uv_obj->get_name());
         } else {
         } else {
-          mat = LMatrix3d::ident_mat();
+          uv_name = TexCoordName::get_default();
+        }
+
+        LMatrix3d mat = LMatrix3d::ident_mat();
+
+        BakeInUVs::const_iterator buv = bake_in_uvs.find(uv_name);
+        if (buv != bake_in_uvs.end()) {
+          // If we are to bake in a texture matrix, do so now.
+          mat = (*buv).second->get_transform();
         }
         }
         
         
         int tindex =
         int tindex =
-          _comp_verts_maker.add_texcoord(uv, egg_vert->_duvs, mat);
-        bvert.set_texcoord(tindex);
+          _comp_verts_maker.add_texcoord(uv_name, uv, uv_obj->_duvs, mat);
+        bvert.set_texcoord(uv_name, tindex);
       }
       }
       
       
       bprim.add_vertex(bvert);
       bprim.add_vertex(bvert);
@@ -519,31 +547,17 @@ make_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
   // but all we do with this bucket is immediately extract the state
   // but all we do with this bucket is immediately extract the state
   // from it.
   // from it.
   BuilderBucket bucket;
   BuilderBucket bucket;
-  setup_bucket(bucket, parent, egg_curve);
+  BakeInUVs bake_in_uvs;
+  setup_bucket(bucket, bake_in_uvs, parent, egg_curve);
   if (bucket._hidden && egg_suppress_hidden) {
   if (bucket._hidden && egg_suppress_hidden) {
     // Eat this primitive.
     // Eat this primitive.
     return;
     return;
   }
   }
+  nassertv(bake_in_uvs.empty());
 
 
   rope->set_state(bucket._state);
   rope->set_state(bucket._state);
+  rope->set_uv_mode(RopeNode::UV_parametric);
 
 
-  // If we have a texture matrix, we have to apply that explicitly
-  // (the UV's are computed on the fly, so we can't precompute the
-  // texture matrix into them).
-  if (egg_curve->has_texture()) {
-    rope->set_uv_mode(RopeNode::UV_parametric);
-
-    PT(EggTexture) egg_tex = egg_curve->get_texture();
-    if (egg_tex->has_transform()) {
-      // Expand the 2-d matrix to a 3-d matrix.
-      const LMatrix3d &mat3 = egg_tex->get_transform();
-      LMatrix4f mat4(mat3(0, 0), mat3(0, 1), 0.0f, mat3(0, 2),
-                     mat3(1, 0), mat3(1, 1), 0.0f, mat3(1, 2),
-                     0.0f, 0.0f, 1.0f, 0.0f,
-                     mat3(2, 0), mat3(2, 1), 0.0f, mat3(2, 2));
-      rope->set_attrib(TexMatrixAttrib::make(mat4));
-    }
-  }
   if (egg_curve->has_vertex_color()) {
   if (egg_curve->has_vertex_color()) {
     // If the curve had individual vertex color, enable it.
     // If the curve had individual vertex color, enable it.
     rope->set_use_vertex_color(true);
     rope->set_use_vertex_color(true);
@@ -674,30 +688,16 @@ make_nurbs_surface(EggNurbsSurface *egg_surface, PandaNode *parent,
   // but all we do with this bucket is immediately extract the state
   // but all we do with this bucket is immediately extract the state
   // from it.
   // from it.
   BuilderBucket bucket;
   BuilderBucket bucket;
-  setup_bucket(bucket, parent, egg_surface);
+  BakeInUVs bake_in_uvs;
+  setup_bucket(bucket, bake_in_uvs, parent, egg_surface);
   if (bucket._hidden && egg_suppress_hidden) {
   if (bucket._hidden && egg_suppress_hidden) {
     // Eat this primitive.
     // Eat this primitive.
     return;
     return;
   }
   }
+  nassertv(bake_in_uvs.empty());
 
 
   sheet->set_state(bucket._state);
   sheet->set_state(bucket._state);
 
 
-  // If we have a texture matrix, we have to apply that explicitly
-  // (the UV's are computed on the fly, so we can't precompute the
-  // texture matrix into them).
-  if (egg_surface->has_texture()) {
-    PT(EggTexture) egg_tex = egg_surface->get_texture();
-    if (egg_tex->has_transform()) {
-      // Expand the 2-d matrix to a 3-d matrix.
-      const LMatrix3d &mat3 = egg_tex->get_transform();
-      LMatrix4f mat4(mat3(0, 0), mat3(0, 1), 0.0f, mat3(0, 2),
-                     mat3(1, 0), mat3(1, 1), 0.0f, mat3(1, 2),
-                     0.0f, 0.0f, 1.0f, 0.0f,
-                     mat3(2, 0), mat3(2, 1), 0.0f, mat3(2, 2));
-      sheet->set_attrib(TexMatrixAttrib::make(mat4));
-    }
-  }
-
   if (egg_surface->has_vertex_color()) {
   if (egg_surface->has_vertex_color()) {
     // If the surface had individual vertex color, enable it.
     // If the surface had individual vertex color, enable it.
     sheet->set_use_vertex_color(true);
     sheet->set_use_vertex_color(true);
@@ -728,7 +728,7 @@ load_textures() {
 
 
   EggTextureCollection::iterator ti;
   EggTextureCollection::iterator ti;
   for (ti = tc.begin(); ti != tc.end(); ++ti) {
   for (ti = tc.begin(); ti != tc.end(); ++ti) {
-    PT(EggTexture) egg_tex = (*ti);
+    PT_EggTexture egg_tex = (*ti);
 
 
     TextureDef def;
     TextureDef def;
     if (load_texture(def, egg_tex)) {
     if (load_texture(def, egg_tex)) {
@@ -742,8 +742,8 @@ load_textures() {
   // the same pointers as the others.
   // the same pointers as the others.
   EggTextureCollection::TextureReplacement::const_iterator ri;
   EggTextureCollection::TextureReplacement::const_iterator ri;
   for (ri = replace.begin(); ri != replace.end(); ++ri) {
   for (ri = replace.begin(); ri != replace.end(); ++ri) {
-    PT(EggTexture) orig = (*ri).first;
-    PT(EggTexture) repl = (*ri).second;
+    PT_EggTexture orig = (*ri).first;
+    PT_EggTexture repl = (*ri).second;
 
 
     _textures[orig] = _textures[repl];
     _textures[orig] = _textures[repl];
   }
   }
@@ -823,10 +823,12 @@ load_texture(TextureDef &def, const EggTexture *egg_tex) {
   }
   }
 
 
   apply_texture_attributes(tex, egg_tex);
   apply_texture_attributes(tex, egg_tex);
-  CPT(RenderAttrib) apply = get_texture_apply_attributes(egg_tex);
 
 
-  def._texture = TextureAttrib::make(tex);
-  def._apply = apply;
+  // Make a texture stage for the texture.
+  PT(TextureStage) stage = make_texture_stage(egg_tex);
+  def._texture = DCAST(TextureAttrib, TextureAttrib::make())->add_on_stage(stage, tex);
+  def._stage = stage;
+  def._egg_tex = egg_tex;
 
 
   return true;
   return true;
 }
 }
@@ -1123,37 +1125,143 @@ apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: EggLoader::apply_texture_apply_attributes
+//     Function: EggLoader::make_texture_stage
 //       Access: Private
 //       Access: Private
-//  Description:
-////////////////////////////////////////////////////////////////////
-CPT(RenderAttrib) EggLoader::
-get_texture_apply_attributes(const EggTexture *egg_tex) {
-  CPT(RenderAttrib) result = TextureApplyAttrib::make(TextureApplyAttrib::M_modulate);
-  if (egg_always_decal_textures) {
-    result = TextureApplyAttrib::make(TextureApplyAttrib::M_decal);
+//  Description: Creates a TextureStage object suitable for rendering
+//               the indicated texture.
+////////////////////////////////////////////////////////////////////
+PT(TextureStage) EggLoader::
+make_texture_stage(const EggTexture *egg_tex) {
+  // If the egg texture specifies any relevant TextureStage
+  // properties, or if it is multitextured on top of anything else, it
+  // gets its own texture stage; otherwise, it gets the default
+  // texture stage.
+  if (!egg_tex->has_stage_name() &&
+      !egg_tex->has_uv_name() &&
+      !egg_tex->has_color() && 
+      egg_tex->get_env_type() == EggTexture::ET_unspecified &&
+      egg_tex->get_env_type() == EggTexture::ET_modulate &&
+      egg_tex->get_combine_mode(EggTexture::CC_rgb) == EggTexture::CM_unspecified &&
+      egg_tex->get_combine_mode(EggTexture::CC_alpha) == EggTexture::CM_unspecified &&
+
+      !egg_tex->has_priority() &&
+      egg_tex->get_multitexture_sort() == 0) {
+    return TextureStage::get_default();
+  }
+
+  PT(TextureStage) stage = new TextureStage(egg_tex->get_stage_name());
+
+  switch (egg_tex->get_env_type()) {
+  case EggTexture::ET_modulate:
+    stage->set_mode(TextureStage::M_modulate);
+    break;
+    
+  case EggTexture::ET_decal:
+    stage->set_mode(TextureStage::M_decal);
+    break;
+    
+  case EggTexture::ET_blend:
+    stage->set_mode(TextureStage::M_blend);
+    break;
+    
+  case EggTexture::ET_replace:
+    stage->set_mode(TextureStage::M_replace);
+    break;
+    
+  case EggTexture::ET_add:
+    stage->set_mode(TextureStage::M_add);
+    break;
 
 
-  } else {
-    switch (egg_tex->get_env_type()) {
-    case EggTexture::ET_modulate:
-      result = TextureApplyAttrib::make(TextureApplyAttrib::M_modulate);
-      break;
+  case EggTexture::ET_unspecified:
+    break;
+  }
 
 
-    case EggTexture::ET_decal:
-      result = TextureApplyAttrib::make(TextureApplyAttrib::M_decal);
-      break;
+  switch (egg_tex->get_combine_mode(EggTexture::CC_rgb)) {
+  case EggTexture::CM_replace:
+    stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb),
+                           get_combine_source(egg_tex, EggTexture::CC_rgb, 0),
+                           get_combine_operand(egg_tex, EggTexture::CC_rgb, 0));
+    break;
 
 
-    case EggTexture::ET_unspecified:
-      break;
+  case EggTexture::CM_modulate:
+  case EggTexture::CM_add:
+  case EggTexture::CM_add_signed:
+  case EggTexture::CM_subtract:
+  case EggTexture::CM_dot3_rgb:
+  case EggTexture::CM_dot3_rgba:
+    stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb),
+                           get_combine_source(egg_tex, EggTexture::CC_rgb, 0),
+                           get_combine_operand(egg_tex, EggTexture::CC_rgb, 0),
+                           get_combine_source(egg_tex, EggTexture::CC_rgb, 1),
+                           get_combine_operand(egg_tex, EggTexture::CC_rgb, 1));
+    break;
 
 
-    default:
-      egg2pg_cat.warning()
-        << "Invalid texture environment "
-        << (int)egg_tex->get_env_type() << "\n";
-    }
+  case EggTexture::CM_interpolate:
+    stage->set_combine_rgb(get_combine_mode(egg_tex, EggTexture::CC_rgb),
+                           get_combine_source(egg_tex, EggTexture::CC_rgb, 0),
+                           get_combine_operand(egg_tex, EggTexture::CC_rgb, 0),
+                           get_combine_source(egg_tex, EggTexture::CC_rgb, 1),
+                           get_combine_operand(egg_tex, EggTexture::CC_rgb, 1),
+                           get_combine_source(egg_tex, EggTexture::CC_rgb, 2),
+                           get_combine_operand(egg_tex, EggTexture::CC_rgb, 2));
+    break;
+
+  case EggTexture::CM_unspecified:
+    break;
+  }
+
+  switch (egg_tex->get_combine_mode(EggTexture::CC_alpha)) {
+  case EggTexture::CM_replace:
+    stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha),
+                             get_combine_source(egg_tex, EggTexture::CC_alpha, 0),
+                             get_combine_operand(egg_tex, EggTexture::CC_alpha, 0));
+    break;
+
+  case EggTexture::CM_modulate:
+  case EggTexture::CM_add:
+  case EggTexture::CM_add_signed:
+  case EggTexture::CM_subtract:
+    stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha),
+                             get_combine_source(egg_tex, EggTexture::CC_alpha, 0),
+                             get_combine_operand(egg_tex, EggTexture::CC_alpha, 0),
+                             get_combine_source(egg_tex, EggTexture::CC_alpha, 1),
+                             get_combine_operand(egg_tex, EggTexture::CC_alpha, 1));
+    break;
+    
+  case EggTexture::CM_interpolate:
+    stage->set_combine_alpha(get_combine_mode(egg_tex, EggTexture::CC_alpha),
+                             get_combine_source(egg_tex, EggTexture::CC_alpha, 0),
+                             get_combine_operand(egg_tex, EggTexture::CC_alpha, 0),
+                             get_combine_source(egg_tex, EggTexture::CC_alpha, 1),
+                             get_combine_operand(egg_tex, EggTexture::CC_alpha, 1),
+                             get_combine_source(egg_tex, EggTexture::CC_alpha, 2),
+                             get_combine_operand(egg_tex, EggTexture::CC_alpha, 2));
+    break;
+
+  case EggTexture::CM_unspecified:
+  case EggTexture::CM_dot3_rgb:
+  case EggTexture::CM_dot3_rgba:
+    break;
   }
   }
 
 
-  return result;
+
+  if (egg_tex->has_uv_name() && !egg_tex->get_uv_name().empty()) {
+    CPT(TexCoordName) name = 
+      TexCoordName::make(egg_tex->get_uv_name());
+    stage->set_texcoord_name(name);
+  }
+
+  stage->set_sort(egg_tex->get_multitexture_sort() * 10);
+
+  if (egg_tex->has_priority()) {
+    stage->set_sort(egg_tex->get_priority());
+  }
+
+  if (egg_tex->has_color()) {
+    stage->set_color(egg_tex->get_color());
+  }
+
+  return stage;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1218,8 +1326,8 @@ get_material_attrib(const EggMaterial *egg_mat, bool bface) {
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void EggLoader::
 void EggLoader::
-setup_bucket(BuilderBucket &bucket, PandaNode *parent,
-             EggPrimitive *egg_prim) {
+setup_bucket(BuilderBucket &bucket, EggLoader::BakeInUVs &bake_in_uvs,
+             PandaNode *parent, EggPrimitive *egg_prim) {
   bucket._node = parent;
   bucket._node = parent;
   bucket._mesh = egg_mesh;
   bucket._mesh = egg_mesh;
   bucket._retesselate_coplanar = egg_retesselate_coplanar;
   bucket._retesselate_coplanar = egg_retesselate_coplanar;
@@ -1289,29 +1397,151 @@ setup_bucket(BuilderBucket &bucket, PandaNode *parent,
   }
   }
 
 
   bucket.add_attrib(TextureAttrib::make_off());
   bucket.add_attrib(TextureAttrib::make_off());
-  if (egg_prim->has_texture()) {
-    PT(EggTexture) egg_tex = egg_prim->get_texture();
+  int num_textures = egg_prim->get_num_textures();
+  CPT(RenderAttrib) texture_attrib = NULL;
+  CPT(RenderAttrib) tex_gen_attrib = NULL;
+  CPT(RenderAttrib) tex_mat_attrib = NULL;
+  TexMats tex_mats;
+
+  for (int i = 0; i < num_textures; i++) {
+    PT_EggTexture egg_tex = egg_prim->get_texture(i);
 
 
     const TextureDef &def = _textures[egg_tex];
     const TextureDef &def = _textures[egg_tex];
     if (def._texture != (const RenderAttrib *)NULL) {
     if (def._texture != (const RenderAttrib *)NULL) {
-      bucket.add_attrib(def._texture);
-      bucket.add_attrib(def._apply);
+      if (texture_attrib == (RenderAttrib *)NULL) {
+        texture_attrib = def._texture;
+      } else {
+        texture_attrib = texture_attrib->compose(def._texture);
+      }
 
 
       // If neither the primitive nor the texture specified an alpha
       // If neither the primitive nor the texture specified an alpha
       // mode, assume it should be alpha'ed if the texture has an
       // mode, assume it should be alpha'ed if the texture has an
-      // alpha channel.
+      // alpha channel (unless the texture environment type is one
+      // that doesn't apply its alpha to the result).
       if (am == EggRenderMode::AM_unspecified) {
       if (am == EggRenderMode::AM_unspecified) {
         const TextureAttrib *tex_attrib = DCAST(TextureAttrib, def._texture);
         const TextureAttrib *tex_attrib = DCAST(TextureAttrib, def._texture);
         Texture *tex = tex_attrib->get_texture();
         Texture *tex = tex_attrib->get_texture();
         nassertv(tex != (Texture *)NULL);
         nassertv(tex != (Texture *)NULL);
         int num_components = tex->_pbuffer->get_num_components();
         int num_components = tex->_pbuffer->get_num_components();
         if (egg_tex->has_alpha_channel(num_components)) {
         if (egg_tex->has_alpha_channel(num_components)) {
-          implicit_alpha = true;
+          switch (egg_tex->get_env_type()) {
+          case EggTexture::ET_decal:
+          case EggTexture::ET_add:
+            break;
+
+          default:
+            implicit_alpha = true;
+          }
+        }
+      }
+
+      // Check for a texgen attrib.
+      bool has_tex_gen = false;
+      if (egg_tex->get_tex_gen() != EggTexture::TG_unspecified) {
+        has_tex_gen = true;
+        if (tex_gen_attrib == (const RenderAttrib *)NULL) {
+          tex_gen_attrib = TexGenAttrib::make();
+        }
+        tex_gen_attrib = DCAST(TexGenAttrib, tex_gen_attrib)->
+          add_stage(def._stage, get_tex_gen(egg_tex));
+      }
+
+      // Record the texture's associated texture matrix, so we can see
+      // if we can safely bake it into the UV's.  (We need to get the
+      // complete list of textures that share this same set of UV's
+      // per each unique texture matrix.  Whew!)
+      CPT(TexCoordName) uv_name;
+      if (egg_tex->has_uv_name()) {
+        uv_name = TexCoordName::make(egg_tex->get_uv_name());
+      } else {
+        uv_name = TexCoordName::get_default();
+      }
+
+      if (has_tex_gen) {
+        // If the texture has a texgen mode, we will always apply its
+        // texture transform, never bake it in.  In fact, we don't
+        // even care about its UV's in this case, since we won't be
+        // using them.
+        tex_mat_attrib = apply_tex_mat(tex_mat_attrib, def._stage, egg_tex);
+
+      } else {
+        // Otherwise, we need to record that there is at least one
+        // texture on this particular UV name and with this particular
+        // texture matrix.  If there are no other textures, or if all
+        // of the other textures use the same texture matrix, then
+        // tex_mats[uv_name].size() will remain 1 (which tells us we
+        // can bake in the texture matrix to the UV's).  On the other
+        // hand, if there is another texture on the same uv name but
+        // with a different transform, it will increase
+        // tex_mats[uv_name].size() to at least 2, indicating we can't
+        // bake in the texture matrix.
+        tex_mats[uv_name][egg_tex->get_transform()].push_back(&def);
+      }
+    }
+  }
+
+  // These parametric primitive types can't have their UV's baked in,
+  // so if we have one of these we always need to apply the texture
+  // matrix as a separate attribute, regardless of how many textures
+  // share the particular UV set.
+  bool needs_tex_mat = (egg_prim->is_of_type(EggCurve::get_class_type()) ||
+                        egg_prim->is_of_type(EggSurface::get_class_type()));
+
+  // Now that we've visited all of the textures in the above loop, we
+  // can go back and see how many of them share the same UV name and
+  // texture matrix.
+  TexMats::const_iterator tmi;
+  for (tmi = tex_mats.begin(); tmi != tex_mats.end(); ++tmi) {
+    const TexCoordName *uv_name = (*tmi).first;
+    const TexMatTransforms &tmt = (*tmi).second;
+
+    if (tmt.size() == 1 && !needs_tex_mat) {
+      // Only one unique transform sharing this set of UV's.  We can
+      // bake in the transform!
+      const TexMatTextures &tmtex = (*tmt.begin()).second;
+
+      // The first EggTexture on the list is sufficient, since we know
+      // they all have the same transform.
+      nassertv(!tmtex.empty());
+      TexMatTextures::const_iterator tmtexi = tmtex.begin();
+      const EggTexture *egg_tex = (*tmtexi)->_egg_tex;
+      if (egg_tex->has_transform()) {
+        // If there's no transform, it's an identity matrix; don't
+        // bother recording it.  Of course, it would do no harm to
+        // record it if we felt like it.
+        bake_in_uvs[uv_name] = egg_tex;
+      }
+
+    } else {
+      // Multiple transforms on this UV set, or a geometry type that
+      // doesn't support baking in UV's.  We have to apply the
+      // texture matrix to each stage.
+      TexMatTransforms::const_iterator tmti;
+      for (tmti = tmt.begin(); tmti != tmt.end(); ++tmti) {
+        const TexMatTextures &tmtex = (*tmti).second;
+        TexMatTextures::const_iterator tmtexi;
+        for (tmtexi = tmtex.begin(); tmtexi != tmtex.end(); ++tmtexi) {
+          const EggTexture *egg_tex = (*tmtexi)->_egg_tex;
+          TextureStage *stage = (*tmtexi)->_stage;
+          
+          tex_mat_attrib = apply_tex_mat(tex_mat_attrib, stage, egg_tex);
         }
         }
       }
       }
     }
     }
   }
   }
 
 
+  if (texture_attrib != (RenderAttrib *)NULL) {
+    bucket.add_attrib(texture_attrib);
+  }
+
+  if (tex_gen_attrib != (RenderAttrib *)NULL) {
+    bucket.add_attrib(tex_gen_attrib);
+  }
+
+  if (tex_mat_attrib != (RenderAttrib *)NULL) {
+    bucket.add_attrib(tex_mat_attrib);
+  }
+
   if (egg_prim->has_material()) {
   if (egg_prim->has_material()) {
     CPT(RenderAttrib) mt =
     CPT(RenderAttrib) mt =
       get_material_attrib(egg_prim->get_material(),
       get_material_attrib(egg_prim->get_material(),
@@ -2695,3 +2925,180 @@ make_transform(const EggTransform3d *egg_transform) {
 
 
   return ts;
   return ts;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::get_combine_mode
+//       Access: Private, Static
+//  Description: Extracts the combine_mode from the given egg texture,
+//               and returns its corresponding TextureStage value.
+////////////////////////////////////////////////////////////////////
+TextureStage::CombineMode EggLoader::
+get_combine_mode(const EggTexture *egg_tex, 
+                 EggTexture::CombineChannel channel) {
+  switch (egg_tex->get_combine_mode(channel)) {
+  case EggTexture::CM_unspecified:
+    // fall through
+
+  case EggTexture::CM_modulate:
+    return TextureStage::CM_modulate;
+
+  case EggTexture::CM_replace:
+    return TextureStage::CM_replace;
+
+  case EggTexture::CM_add:
+    return TextureStage::CM_add;
+
+  case EggTexture::CM_add_signed:
+    return TextureStage::CM_add_signed;
+
+  case EggTexture::CM_interpolate:
+    return TextureStage::CM_interpolate;
+
+  case EggTexture::CM_subtract:
+    return TextureStage::CM_subtract;
+
+  case EggTexture::CM_dot3_rgb:
+    return TextureStage::CM_dot3_rgb;
+
+  case EggTexture::CM_dot3_rgba:
+    return TextureStage::CM_dot3_rgba;
+  };
+
+  return TextureStage::CM_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::get_combine_source
+//       Access: Private, Static
+//  Description: Extracts the combine_source from the given egg texture,
+//               and returns its corresponding TextureStage value.
+////////////////////////////////////////////////////////////////////
+TextureStage::CombineSource EggLoader::
+get_combine_source(const EggTexture *egg_tex, 
+                   EggTexture::CombineChannel channel, int n) {
+  switch (egg_tex->get_combine_source(channel, n)) {
+  case EggTexture::CS_unspecified:
+    // The default source if it is unspecified is based on the
+    // parameter index.
+    switch (n) {
+    case 0:
+      return TextureStage::CS_previous;
+    case 1:
+      return TextureStage::CS_texture;
+    case 2:
+      return TextureStage::CS_constant;
+    }
+    // Otherwise, fall through
+
+  case EggTexture::CS_texture:
+    return TextureStage::CS_texture;
+
+  case EggTexture::CS_constant:
+    return TextureStage::CS_constant;
+
+  case EggTexture::CS_primary_color:
+    return TextureStage::CS_primary_color;
+
+  case EggTexture::CS_previous:
+    return TextureStage::CS_previous;
+  };
+
+  return TextureStage::CS_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::get_combine_operand
+//       Access: Private, Static
+//  Description: Extracts the combine_operand from the given egg texture,
+//               and returns its corresponding TextureStage value.
+////////////////////////////////////////////////////////////////////
+TextureStage::CombineOperand EggLoader::
+get_combine_operand(const EggTexture *egg_tex, 
+                    EggTexture::CombineChannel channel, int n) {
+  switch (egg_tex->get_combine_operand(channel, n)) {
+  case EggTexture::CS_unspecified:
+    if (channel == EggTexture::CC_rgb) {
+      // The default operand for RGB is src_color, except for the
+      // third parameter, which defaults to src_alpha.
+      return n < 2 ? TextureStage::CO_src_color : TextureStage::CO_src_alpha;
+    } else {
+      // The default operand for alpha is always src_alpha.
+      return TextureStage::CO_src_alpha;
+    }
+
+  case EggTexture::CO_src_color:
+    return TextureStage::CO_src_color;
+
+  case EggTexture::CO_one_minus_src_color:
+    return TextureStage::CO_one_minus_src_color;
+
+  case EggTexture::CO_src_alpha:
+    return TextureStage::CO_src_alpha;
+
+  case EggTexture::CO_one_minus_src_alpha:
+    return TextureStage::CO_one_minus_src_alpha;
+  };
+
+  return TextureStage::CO_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::get_tex_gen
+//       Access: Private, Static
+//  Description: Extracts the tex_gen from the given egg texture,
+//               and returns its corresponding TexGenAttrib mode.
+////////////////////////////////////////////////////////////////////
+TexGenAttrib::Mode EggLoader::
+get_tex_gen(const EggTexture *egg_tex) {
+  switch (egg_tex->get_tex_gen()) {
+  case EggTexture::TG_unspecified:
+    return TexGenAttrib::M_off;
+
+  case EggTexture::TG_sphere_map:
+    return TexGenAttrib::M_sphere_map;
+
+  case EggTexture::TG_cube_map:
+    return TexGenAttrib::M_cube_map;
+
+  case EggTexture::TG_world_position:
+    return TexGenAttrib::M_world_position;
+
+  case EggTexture::TG_object_position:
+    return TexGenAttrib::M_object_position;
+
+  case EggTexture::TG_eye_position:
+    return TexGenAttrib::M_eye_position;
+  };
+
+  return TexGenAttrib::M_off;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::apply_tex_mat
+//       Access: Private, Static
+//  Description: Applies the texture matrix from the indicated egg
+//               texture to the given TexMatrixAttrib, and returns the
+//               new attrib.
+////////////////////////////////////////////////////////////////////
+CPT(RenderAttrib) EggLoader::
+apply_tex_mat(CPT(RenderAttrib) tex_mat_attrib, 
+              TextureStage *stage, const EggTexture *egg_tex) {
+  if (egg_tex->has_transform()) {
+    const LMatrix3d &tex_mat = egg_tex->get_transform();
+    LMatrix4f mat4(tex_mat(0, 0), tex_mat(0, 1), 0.0f, tex_mat(0, 2),
+                   tex_mat(1, 0), tex_mat(1, 1), 0.0f, tex_mat(1, 2),
+                   0.0f, 0.0f, 1.0f, 0.0f,
+                   tex_mat(2, 0), tex_mat(2, 1), 0.0f, tex_mat(2, 2));
+    CPT(TransformState) transform = TransformState::make_mat(mat4);
+  
+    if (tex_mat_attrib == (const RenderAttrib *)NULL) {
+      tex_mat_attrib = TexMatrixAttrib::make();
+    }
+    tex_mat_attrib = DCAST(TexMatrixAttrib, tex_mat_attrib)->
+      add_stage(stage, transform);
+  }
+    
+  return tex_mat_attrib;
+}
+
+  

+ 32 - 4
panda/src/egg2pg/eggLoader.h

@@ -35,6 +35,8 @@
 #include "lmatrix.h"
 #include "lmatrix.h"
 #include "indirectCompareTo.h"
 #include "indirectCompareTo.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
+#include "textureStage.h"
+#include "texGenAttrib.h"
 #include "eggTransform3d.h"
 #include "eggTransform3d.h"
 
 
 class EggNode;
 class EggNode;
@@ -81,9 +83,18 @@ private:
   class TextureDef {
   class TextureDef {
   public:
   public:
     CPT(RenderAttrib) _texture;
     CPT(RenderAttrib) _texture;
-    CPT(RenderAttrib) _apply;
+    PT(TextureStage) _stage;
+    const EggTexture *_egg_tex;
   };
   };
 
 
+  // This structure is used internally in setup_bucket().
+  typedef pvector<const TextureDef *> TexMatTextures;
+  typedef pmap<LMatrix3d, TexMatTextures> TexMatTransforms;
+  typedef pmap<CPT(TexCoordName), TexMatTransforms> TexMats;
+
+  // This structure is returned by setup_bucket().
+  typedef pmap<CPT(TexCoordName), const EggTexture *> BakeInUVs;
+  
   void make_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
   void make_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
                         const LMatrix4d &mat);
                         const LMatrix4d &mat);
   void make_old_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
   void make_old_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
@@ -94,13 +105,13 @@ private:
   void load_textures();
   void load_textures();
   bool load_texture(TextureDef &def, const EggTexture *egg_tex);
   bool load_texture(TextureDef &def, const EggTexture *egg_tex);
   void apply_texture_attributes(Texture *tex, const EggTexture *egg_tex);
   void apply_texture_attributes(Texture *tex, const EggTexture *egg_tex);
-  CPT(RenderAttrib) get_texture_apply_attributes(const EggTexture *egg_tex);
+  PT(TextureStage) make_texture_stage(const EggTexture *egg_tex);
 
 
   CPT(RenderAttrib) get_material_attrib(const EggMaterial *egg_mat,
   CPT(RenderAttrib) get_material_attrib(const EggMaterial *egg_mat,
                                         bool bface);
                                         bool bface);
 
 
-  void setup_bucket(BuilderBucket &bucket, PandaNode *parent,
-                    EggPrimitive *egg_prim);
+  void setup_bucket(BuilderBucket &bucket, BakeInUVs &bake_in_uvs,
+                    PandaNode *parent, EggPrimitive *egg_prim);
 
 
   PandaNode *make_node(EggNode *egg_node, PandaNode *parent);
   PandaNode *make_node(EggNode *egg_node, PandaNode *parent);
   PandaNode *make_node(EggPrimitive *egg_prim, PandaNode *parent);
   PandaNode *make_node(EggPrimitive *egg_prim, PandaNode *parent);
@@ -146,6 +157,23 @@ private:
 
 
   CPT(TransformState) make_transform(const EggTransform3d *egg_transform);
   CPT(TransformState) make_transform(const EggTransform3d *egg_transform);
 
 
+  static TextureStage::CombineMode 
+  get_combine_mode(const EggTexture *egg_tex, 
+                   EggTexture::CombineChannel channel);
+
+  static TextureStage::CombineSource
+  get_combine_source(const EggTexture *egg_tex, 
+                     EggTexture::CombineChannel channel, int n);
+
+  static TextureStage::CombineOperand
+  get_combine_operand(const EggTexture *egg_tex, 
+                      EggTexture::CombineChannel channel, int n);
+  static TexGenAttrib::Mode get_tex_gen(const EggTexture *egg_tex);
+
+  static CPT(RenderAttrib)
+  apply_tex_mat(CPT(RenderAttrib) tex_mat_attrib, 
+                TextureStage *stage, const EggTexture *egg_tex);
+
   Builder _builder;
   Builder _builder;
 
 
   typedef pmap<PT_EggTexture, TextureDef> Textures;
   typedef pmap<PT_EggTexture, TextureDef> Textures;

+ 1 - 0
panda/src/glgsg/glgsg.h

@@ -29,6 +29,7 @@
 #define GLP(name) gl##name
 #define GLP(name) gl##name
 #define GLUP(name) glu##name
 #define GLUP(name) glu##name
 #define CLP(name) GL##name
 #define CLP(name) GL##name
+#define GLPREFIX_QUOTED "gl"
 #define CLASSPREFIX_QUOTED "GL"
 #define CLASSPREFIX_QUOTED "GL"
 #define CONFIGOBJ config_glgsg
 #define CONFIGOBJ config_glgsg
 #define GLCAT glgsg_cat
 #define GLCAT glgsg_cat

+ 0 - 25
panda/src/glstuff/glGraphicsStateGuardian_src.I

@@ -714,31 +714,6 @@ enable_stencil_test(bool val) {
   }
   }
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: CLP(GraphicsStateGuardian)::enable_texturing
-//       Access:
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void CLP(GraphicsStateGuardian)::
-enable_texturing(bool val) {
-  if (_texturing_enabled != val) {
-    _texturing_enabled = val;
-    if (val) {
-#ifdef GSG_VERBOSE
-      GLCAT.spam()
-        << "glEnable(GL_TEXTURE_2D)" << endl;
-#endif
-      GLP(Enable)(GL_TEXTURE_2D);
-    } else {
-#ifdef GSG_VERBOSE
-      GLCAT.spam()
-        << "glDisable(GL_TEXTURE_2D)" << endl;
-#endif
-      GLP(Disable)(GL_TEXTURE_2D);
-    }
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::enable_scissor
 //     Function: CLP(GraphicsStateGuardian)::enable_scissor
 //       Access:
 //       Access:

+ 525 - 100
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -81,13 +81,28 @@ issue_normal_gl(const Geom *geom, Geom::NormalIterator &niterator,
 }
 }
 
 
 static void
 static void
-issue_texcoord_gl(const Geom *geom, Geom::TexCoordIterator &tciterator, 
-                GraphicsStateGuardianBase *) {
-  const TexCoordf &texcoord = geom->get_next_texcoord(tciterator);
-  //  GLCAT.spam() << "Issuing texcoord " << texcoord << "\n";
+issue_texcoord_single_gl(const Geom *geom, 
+                         Geom::MultiTexCoordIterator &tciterator, 
+                         GraphicsStateGuardianBase *) {
+  const TexCoordf &texcoord = geom->get_next_multitexcoord(tciterator, 0);
+  // GLCAT.spam() << "Issuing texcoord " << texcoord << " on unit 0 (single-texture mode)\n";
   GLP(TexCoord2fv)(texcoord.get_data());
   GLP(TexCoord2fv)(texcoord.get_data());
 }
 }
 
 
+static void
+issue_texcoord_multi_gl(const Geom *geom, 
+                        Geom::MultiTexCoordIterator &tciterator, 
+                        GraphicsStateGuardianBase *gsgbase) {
+  CLP(GraphicsStateGuardian) *gsg;
+  DCAST_INTO_V(gsg, gsgbase);
+  for (int i = 0; i < tciterator._num_stages; i++) {
+    const TexCoordf &texcoord = geom->get_next_multitexcoord(tciterator, i);
+    int stage_index = tciterator._stage_index[i];
+    // GLCAT.spam() << "Issuing texcoord " << texcoord << " on unit " << stage_index << "\n";
+    gsg->_glMultiTexCoord2fv(GL_TEXTURE0 + stage_index, texcoord.get_data());
+  }
+}
+
 static void
 static void
 issue_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
 issue_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
                GraphicsStateGuardianBase *) {
                GraphicsStateGuardianBase *) {
@@ -104,6 +119,16 @@ issue_transformed_color_gl(const Geom *geom, Geom::ColorIterator &citerator,
   glgsg->issue_transformed_color(color);
   glgsg->issue_transformed_color(color);
 }
 }
 
 
+// This noop function is assigned to _glActiveTexture in case we don't
+// have multitexturing support, so it will always be safe to call
+// _glActiveTexture().
+static void APIENTRY
+null_glActiveTexture(GLenum gl_texture_stage) {
+  // If we don't support multitexture, we'd better not try to request
+  // a texture beyond the first texture stage.
+  nassertv(gl_texture_stage == GL_TEXTURE0);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: uchar_bgr_to_rgb
 //     Function: uchar_bgr_to_rgb
 //  Description: Recopies the given array of pixels, converting from
 //  Description: Recopies the given array of pixels, converting from
@@ -268,18 +293,46 @@ reset() {
   GraphicsStateGuardian::reset();
   GraphicsStateGuardian::reset();
 
 
   // Output the vendor and version strings.
   // Output the vendor and version strings.
-  show_gl_string("GL_VENDOR", GL_VENDOR);
-  show_gl_string("GL_RENDERER", GL_RENDERER);
   get_gl_version();
   get_gl_version();
 
 
   // Save the extensions tokens.
   // Save the extensions tokens.
-  save_extensions((const char *)glGetString(GL_EXTENSIONS));
+  save_extensions((const char *)GLP(GetString)(GL_EXTENSIONS));
   get_extra_extensions();
   get_extra_extensions();
   report_extensions();
   report_extensions();
 
 
   _supports_bgr = has_extension("GL_EXT_bgra");
   _supports_bgr = has_extension("GL_EXT_bgra");
   _supports_multisample = has_extension("GL_ARB_multisample");
   _supports_multisample = has_extension("GL_ARB_multisample");
 
 
+  _supports_multitexture = false;
+    
+  if (is_at_least_version(1, 3)) {
+    _supports_multitexture = true;
+
+    _glActiveTexture = (PFNGLACTIVETEXTUREPROC)
+      get_extension_func(GLPREFIX_QUOTED, "ActiveTexture");
+    _glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVPROC)
+      get_extension_func(GLPREFIX_QUOTED, "MultiTexCoord2fv");
+
+  } else if (has_extension("GL_ARB_multitexture")) {
+    _supports_multitexture = true;
+
+    _glActiveTexture = (PFNGLACTIVETEXTUREPROC)
+      get_extension_func(GLPREFIX_QUOTED, "ActiveTextureARB");
+    _glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVPROC)
+      get_extension_func(GLPREFIX_QUOTED, "MultiTexCoord2fvARB");
+  }
+
+  if (_supports_multitexture) {
+    if (_glActiveTexture == NULL || _glMultiTexCoord2fv == NULL) {
+      GLCAT.warning()
+        << "Multitexture advertised as supported by OpenGL runtime, but could not get pointers to extension functions.\n";
+      _supports_multitexture = false;
+    }
+  }
+  if (!_supports_multitexture) {
+    _glActiveTexture = null_glActiveTexture;
+  }
+
   _edge_clamp = GL_CLAMP;
   _edge_clamp = GL_CLAMP;
   if (has_extension("GL_SGIS_texture_edge_clamp") ||
   if (has_extension("GL_SGIS_texture_edge_clamp") ||
       is_at_least_version(1, 2)) {
       is_at_least_version(1, 2)) {
@@ -368,7 +421,6 @@ reset() {
   _line_smooth_enabled = false;
   _line_smooth_enabled = false;
   _point_smooth_enabled = false;
   _point_smooth_enabled = false;
   _scissor_enabled = false;
   _scissor_enabled = false;
-  _texturing_enabled = false;
   _stencil_test_enabled = false;
   _stencil_test_enabled = false;
   _multisample_alpha_one_enabled = false;
   _multisample_alpha_one_enabled = false;
   _multisample_alpha_mask_enabled = false;
   _multisample_alpha_mask_enabled = false;
@@ -383,6 +435,8 @@ reset() {
   GLP(Disable)(GL_DITHER);
   GLP(Disable)(GL_DITHER);
   _dithering_enabled = false;
   _dithering_enabled = false;
 
 
+  _texgen_forced_normal = false;
+
   // Antialiasing.
   // Antialiasing.
   enable_line_smooth(false);
   enable_line_smooth(false);
   enable_multisample(true);
   enable_multisample(true);
@@ -409,6 +463,17 @@ reset() {
   _current_projection_mat = LMatrix4f::ident_mat();
   _current_projection_mat = LMatrix4f::ident_mat();
   _projection_mat_stack_count = 0;
   _projection_mat_stack_count = 0;
 
 
+  if (_supports_multitexture) {
+    GLint max_texture_stages;
+    GLP(GetIntegerv)(GL_MAX_TEXTURE_UNITS, &max_texture_stages);
+    _max_texture_stages = max_texture_stages;
+  }
+  _current_texture = DCAST(TextureAttrib, TextureAttrib::make_all_off());
+  _current_tex_mat = DCAST(TexMatrixAttrib, TexMatrixAttrib::make());
+  _needs_tex_mat = false;
+  _current_tex_gen = DCAST(TexGenAttrib, TexGenAttrib::make());
+  _needs_tex_gen = false;
+
   report_my_gl_errors();
   report_my_gl_errors();
 
 
   // Make sure the GL state matches all of our initial attribute
   // Make sure the GL state matches all of our initial attribute
@@ -680,7 +745,9 @@ draw_point(GeomPoint *geom, GeomContext *gc) {
   int nprims = geom->get_num_prims();
   int nprims = geom->get_num_prims();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -694,8 +761,10 @@ draw_point(GeomPoint *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // Draw overall
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
   issuer.issue_color(G_OVERALL, ci);
@@ -745,7 +814,9 @@ draw_line(GeomLine *geom, GeomContext *gc) {
   int nprims = geom->get_num_prims();
   int nprims = geom->get_num_prims();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -759,8 +830,10 @@ draw_line(GeomLine *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
   // Otherwise we want flat shading for performance reasons.
@@ -824,7 +897,9 @@ draw_linestrip(GeomLinestrip *geom, GeomContext *gc) {
   const int *plen = geom->get_lengths();
   const int *plen = geom->get_lengths();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -838,8 +913,10 @@ draw_linestrip(GeomLinestrip *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
   // Otherwise we want flat shading for performance reasons.
@@ -1198,7 +1275,9 @@ draw_polygon(GeomPolygon *geom, GeomContext *gc) {
   const int *plen = geom->get_lengths();
   const int *plen = geom->get_lengths();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -1212,8 +1291,10 @@ draw_polygon(GeomPolygon *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
   // Otherwise we want flat shading for performance reasons.
@@ -1280,7 +1361,9 @@ draw_tri(GeomTri *geom, GeomContext *gc) {
   int nprims = geom->get_num_prims();
   int nprims = geom->get_num_prims();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -1294,8 +1377,10 @@ draw_tri(GeomTri *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
   // Otherwise we want flat shading for performance reasons.
@@ -1360,7 +1445,9 @@ draw_quad(GeomQuad *geom, GeomContext *gc) {
   int nprims = geom->get_num_prims();
   int nprims = geom->get_num_prims();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -1374,8 +1461,10 @@ draw_quad(GeomQuad *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
   // Otherwise we want flat shading for performance reasons.
@@ -1440,7 +1529,9 @@ draw_tristrip(GeomTristrip *geom, GeomContext *gc) {
   const int *plen = geom->get_lengths();
   const int *plen = geom->get_lengths();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -1454,8 +1545,10 @@ draw_tristrip(GeomTristrip *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
   // Otherwise we want flat shading for performance reasons.
@@ -1542,7 +1635,9 @@ draw_trifan(GeomTrifan *geom, GeomContext *gc) {
   const int *plen = geom->get_lengths();
   const int *plen = geom->get_lengths();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
   Geom::NormalIterator ni = geom->make_normal_iterator();
-  Geom::TexCoordIterator ti = geom->make_texcoord_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -1556,8 +1651,10 @@ draw_trifan(GeomTrifan *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   // If we have per-vertex colors or normals, we need smooth shading.
   // If we have per-vertex colors or normals, we need smooth shading.
   // Otherwise we want flat shading for performance reasons.
   // Otherwise we want flat shading for performance reasons.
@@ -1641,6 +1738,9 @@ draw_sphere(GeomSphere *geom, GeomContext *gc) {
 
 
   int nprims = geom->get_num_prims();
   int nprims = geom->get_num_prims();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
   Geom::VertexIterator vi = geom->make_vertex_iterator();
+  Geom::MultiTexCoordIterator ti;
+  geom->setup_multitexcoord_iterator(ti, _current_texture->get_on_stages(),
+                                     _current_tex_gen->get_no_texcoords());
   Geom::ColorIterator ci = geom->make_color_iterator();
   Geom::ColorIterator ci = geom->make_color_iterator();
 
 
   GeomIssuer::IssueColor *issue_color;
   GeomIssuer::IssueColor *issue_color;
@@ -1654,8 +1754,10 @@ draw_sphere(GeomSphere *geom, GeomContext *gc) {
   GeomIssuer issuer(geom, this,
   GeomIssuer issuer(geom, this,
                     issue_vertex_gl,
                     issue_vertex_gl,
                     issue_normal_gl,
                     issue_normal_gl,
-                    issue_texcoord_gl,
-                    issue_color);
+                    issue_color,
+                    issue_texcoord_single_gl,
+                    issue_texcoord_multi_gl,
+                    ti);
 
 
   if (wants_normals()) {
   if (wants_normals()) {
     call_glShadeModel(GL_SMOOTH);
     call_glShadeModel(GL_SMOOTH);
@@ -1823,9 +1925,8 @@ prepare_geom(Geom *geom) {
 
 
   // We need to temporarily force normals and UV's on, so the display
   // We need to temporarily force normals and UV's on, so the display
   // list will have them built in.
   // list will have them built in.
-  bool old_texturing_enabled = _texturing_enabled;
+  //force_texcoords(); 
   force_normals();
   force_normals();
-  _texturing_enabled = true;
 
 
 #ifdef DO_PSTATS
 #ifdef DO_PSTATS
   // Count up the number of vertices we're about to render, by
   // Count up the number of vertices we're about to render, by
@@ -1854,7 +1955,7 @@ prepare_geom(Geom *geom) {
 #endif
 #endif
 
 
   undo_force_normals();
   undo_force_normals();
-  _texturing_enabled = old_texturing_enabled;
+  //undo_force_texcoords();
 
 
   report_my_gl_errors();
   report_my_gl_errors();
   return ggc;
   return ggc;
@@ -2198,9 +2299,13 @@ issue_transform(const TransformState *transform) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
 issue_tex_matrix(const TexMatrixAttrib *attrib) {
 issue_tex_matrix(const TexMatrixAttrib *attrib) {
-  GLP(MatrixMode)(GL_TEXTURE);
-  GLP(LoadMatrixf)(attrib->get_mat().get_data());
-  report_my_gl_errors();
+  // We don't apply the texture matrix right away, since we might yet
+  // get a TextureAttrib that changes the set of TextureStages we have
+  // active.  Instead, we simply set a flag that indicates we need to
+  // re-issue the texture matrix after all of the other attribs are
+  // done being issued.
+  _current_tex_mat = attrib;
+  _needs_tex_mat = true;
 }
 }
 
 
 
 
@@ -2253,39 +2358,13 @@ issue_cg_shader_bind(const CgShaderAttrib *attrib) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
 issue_tex_gen(const TexGenAttrib *attrib) {
 issue_tex_gen(const TexGenAttrib *attrib) {
-  DO_PSTATS_STUFF(_texture_state_pcollector.add_level(1));
-  static bool forced_normal = false;
-  if (attrib->is_off()) {
-    //enable_texturing(false);
-    glDisable(GL_TEXTURE_GEN_S);
-    glDisable(GL_TEXTURE_GEN_T);
-
-    if (forced_normal) {
-      undo_force_normals();
-      forced_normal = false;
-    }
-  }
-  else if (attrib->get_mode() == TexGenAttrib::M_spherical) {
-#if 0
-    Texture *tex = attrib->get_texture();
-    nassertv(tex != (Texture *)NULL);
-    TextureContext *tc = tex->prepare_now(_prepared_objects, this);
-    apply_texture(tc);
-#else
-    // Set The Texture Generation Mode For S To Sphere Mapping
-    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
-    // Set The Texture Generation Mode For T To Sphere Mapping
-    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
-    glEnable(GL_TEXTURE_GEN_S); //Enable Texture Coord Generation For S
-    glEnable(GL_TEXTURE_GEN_T);	// Enable Texture Coord Generation For T
-
-    if (!forced_normal) {
-      force_normals();
-      forced_normal = true;
-    }
-#endif
-  }
-  report_my_gl_errors();
+  // We don't apply the texture coordinate generation commands right
+  // away, since we might yet get a TextureAttrib that changes the set
+  // of TextureStages we have active.  Instead, we simply set a flag
+  // that indicates we need to re-issue the TexGenAttrib after all of
+  // the other attribs are done being issued.
+  _current_tex_gen = attrib;
+  _needs_tex_gen = true;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -2296,16 +2375,117 @@ issue_tex_gen(const TexGenAttrib *attrib) {
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
 issue_texture(const TextureAttrib *attrib) {
 issue_texture(const TextureAttrib *attrib) {
   DO_PSTATS_STUFF(_texture_state_pcollector.add_level(1));
   DO_PSTATS_STUFF(_texture_state_pcollector.add_level(1));
-  if (attrib->is_off()) {
-    enable_texturing(false);
-  } else {
-    enable_texturing(true);
-    Texture *tex = attrib->get_texture();
-    nassertv(tex != (Texture *)NULL);
 
 
-    TextureContext *tc = tex->prepare_now(_prepared_objects, this);
-    apply_texture(tc);
+  CPT(TextureAttrib) new_texture = attrib->filter_to_max(_max_texture_stages);
+  
+  int num_stages = new_texture->get_num_on_stages();
+  int num_old_stages = _current_texture->get_num_on_stages();
+
+  nassertv(num_stages <= _max_texture_stages && 
+           num_old_stages <= _max_texture_stages);
+
+  int i;
+  for (i = 0; i < num_stages; i++) {
+    TextureStage *stage = new_texture->get_on_stage(i);
+    Texture *texture = new_texture->get_on_texture(stage);
+    nassertv(texture != (Texture *)NULL);
+    
+    if (i >= num_old_stages ||
+        stage != _current_texture->get_on_stage(i) ||
+        texture != _current_texture->get_on_texture(stage)) {
+      // Stage i has changed.  Issue the texture on this stage.
+      _glActiveTexture(GL_TEXTURE0 + i);
+
+      GLP(Enable)(GL_TEXTURE_2D);
+      
+      TextureContext *tc = texture->prepare_now(_prepared_objects, this);
+      apply_texture(tc);
+      
+      GLint glmode = get_texture_apply_mode_type(stage->get_mode());
+      GLP(TexEnvi)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, glmode);
+      GLP(TexEnvfv)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, stage->get_color().get_data());
+
+      if (stage->get_mode() == TextureStage::M_combine) {
+        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_COMBINE_RGB, 
+                     get_texture_combine_type(stage->get_combine_rgb_mode()));
+
+        switch (stage->get_num_combine_rgb_operands()) {
+        case 3:
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC2_RGB, 
+                       get_texture_src_type(stage->get_combine_rgb_source2()));
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND2_RGB, 
+                       get_texture_operand_type(stage->get_combine_rgb_operand2()));
+          // fall through
+
+        case 2:
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC1_RGB, 
+                       get_texture_src_type(stage->get_combine_rgb_source1()));
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND1_RGB, 
+                       get_texture_operand_type(stage->get_combine_rgb_operand1()));
+          // fall through
+
+        case 1:
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC0_RGB, 
+                       get_texture_src_type(stage->get_combine_rgb_source0()));
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND0_RGB, 
+                       get_texture_operand_type(stage->get_combine_rgb_operand0()));
+          // fall through
+
+        default:
+          break;
+        }
+        GLP(TexEnvi)(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, 
+                     get_texture_combine_type(stage->get_combine_alpha_mode()));
+
+        switch (stage->get_num_combine_alpha_operands()) {
+        case 3:
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC2_ALPHA, 
+                       get_texture_src_type(stage->get_combine_alpha_source2()));
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, 
+                       get_texture_operand_type(stage->get_combine_alpha_operand2()));
+          // fall through
+
+        case 2:
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC1_ALPHA, 
+                       get_texture_src_type(stage->get_combine_alpha_source1()));
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, 
+                       get_texture_operand_type(stage->get_combine_alpha_operand1()));
+          // fall through
+
+        case 1:
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_SRC0_ALPHA, 
+                       get_texture_src_type(stage->get_combine_alpha_source0()));
+          GLP(TexEnvi)(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, 
+                       get_texture_operand_type(stage->get_combine_alpha_operand0()));
+          // fall through
+
+        default:
+          break;
+        }
+      }
+
+      GLP(MatrixMode)(GL_TEXTURE);
+      if (_current_tex_mat->has_stage(stage)) {
+        GLP(LoadMatrixf)(_current_tex_mat->get_mat(stage).get_data());
+      } else {
+        GLP(LoadIdentity)();
+      }
+    }
+  }
+    
+  // Disable the texture stages that are no longer used.
+  for (i = num_stages; i < num_old_stages; i++) {
+    _glActiveTexture(GL_TEXTURE0 + i);
+    GLP(Disable)(GL_TEXTURE_2D);
   }
   }
+
+  _current_texture = new_texture;
+
+  // Changing the set of texture stages will require us to reissue the
+  // texgen and texmat attribs.
+  _needs_tex_gen = true;
+  _needs_tex_mat = true;
+
   report_my_gl_errors();
   report_my_gl_errors();
 }
 }
 
 
@@ -2359,10 +2539,10 @@ issue_render_mode(const RenderModeAttrib *attrib) {
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
-issue_texture_apply(const TextureApplyAttrib *attrib) {
-  GLint glmode = get_texture_apply_mode_type(attrib->get_mode());
-  GLP(TexEnvi)(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, glmode);
-  report_my_gl_errors();
+issue_texture_apply(const TextureApplyAttrib *) {
+  // This attrib is no longer used; it is replaced by the parameters
+  // within TextureStage.
+  return;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -2646,7 +2826,7 @@ bind_light(Spotlight *light, int light_id) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool CLP(GraphicsStateGuardian)::
 bool CLP(GraphicsStateGuardian)::
 wants_texcoords() const {
 wants_texcoords() const {
-  return _texturing_enabled;
+  return true;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -2754,11 +2934,14 @@ show_gl_string(const string &name, GLenum id) {
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::get_gl_version
 //     Function: GLGraphicsStateGuardian::get_gl_version
-//       Access: Protected
+//       Access: Protected, Virtual
 //  Description: Queries the runtime version of OpenGL in use.
 //  Description: Queries the runtime version of OpenGL in use.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
 void CLP(GraphicsStateGuardian)::
 get_gl_version() {
 get_gl_version() {
+  show_gl_string("GL_VENDOR", GL_VENDOR);
+  show_gl_string("GL_RENDERER", GL_RENDERER);
+
   _gl_version_major = 0;
   _gl_version_major = 0;
   _gl_version_minor = 0;
   _gl_version_minor = 0;
   _gl_version_release = 0;
   _gl_version_release = 0;
@@ -2876,6 +3059,21 @@ is_at_least_version(int major_version, int minor_version,
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::get_extension_func
+//       Access: Public, Virtual
+//  Description: Returns the pointer to the GL extension function with
+//               the indicated name.  It is the responsibility of the
+//               caller to ensure that the required extension is
+//               defined in the OpenGL runtime prior to calling this;
+//               it is an error to call this for a function that is
+//               not defined.
+////////////////////////////////////////////////////////////////////
+void *CLP(GraphicsStateGuardian)::
+get_extension_func(const char *, const char *) {
+  return NULL;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::set_draw_buffer
 //     Function: CLP(GraphicsStateGuardian)::set_draw_buffer
@@ -3595,26 +3793,91 @@ get_internal_image_format(PixelBuffer::Format format) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::get_texture_apply_mode_type
 //     Function: CLP(GraphicsStateGuardian)::get_texture_apply_mode_type
 //       Access: Protected
 //       Access: Protected
-//  Description: Maps from the texture environment's mode types
-//       to the corresponding OpenGL ids
+//  Description: Maps from the texture stage's mode types
+//               to the corresponding OpenGL ids
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 GLint CLP(GraphicsStateGuardian)::
 GLint CLP(GraphicsStateGuardian)::
-get_texture_apply_mode_type(TextureApplyAttrib::Mode am) const {
-  if (CLP(always_decal_textures)) {
-    return GL_DECAL;
-  }
+get_texture_apply_mode_type(TextureStage::Mode am) const {
   switch (am) {
   switch (am) {
-  case TextureApplyAttrib::M_modulate: return GL_MODULATE;
-  case TextureApplyAttrib::M_decal: return GL_DECAL;
-  case TextureApplyAttrib::M_blend: return GL_BLEND;
-  case TextureApplyAttrib::M_replace: return GL_REPLACE;
-  case TextureApplyAttrib::M_add: return GL_ADD;
+  case TextureStage::M_modulate: return GL_MODULATE;
+  case TextureStage::M_decal: return GL_DECAL;
+  case TextureStage::M_blend: return GL_BLEND;
+  case TextureStage::M_replace: return GL_REPLACE;
+  case TextureStage::M_add: return GL_ADD;
+  case TextureStage::M_combine: return GL_COMBINE;
   }
   }
+
   GLCAT.error()
   GLCAT.error()
-    << "Invalid TextureApplyAttrib::Mode value" << endl;
+    << "Invalid TextureStage::Mode value" << endl;
   return GL_MODULATE;
   return GL_MODULATE;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::get_texture_combine_type
+//       Access: Protected
+//  Description: Maps from the texture stage's CombineMode types
+//               to the corresponding OpenGL ids
+////////////////////////////////////////////////////////////////////
+GLint CLP(GraphicsStateGuardian)::
+get_texture_combine_type(TextureStage::CombineMode cm) const {
+  switch (cm) {
+  case TextureStage::CM_undefined: // fall through
+  case TextureStage::CM_replace: return GL_REPLACE;
+  case TextureStage::CM_modulate: return GL_MODULATE;
+  case TextureStage::CM_add: return GL_ADD;
+  case TextureStage::CM_add_signed: return GL_ADD_SIGNED;
+  case TextureStage::CM_interpolate: return GL_INTERPOLATE;
+  case TextureStage::CM_subtract: return GL_SUBTRACT;
+  case TextureStage::CM_dot3_rgb: return GL_DOT3_RGB;
+  case TextureStage::CM_dot3_rgba: return GL_DOT3_RGBA;
+  }
+  GLCAT.error()
+    << "Invalid TextureStage::CombineMode value" << endl;
+  return GL_REPLACE;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::get_texture_src_type
+//       Access: Protected
+//  Description: Maps from the texture stage's CombineSource types
+//               to the corresponding OpenGL ids
+////////////////////////////////////////////////////////////////////
+GLint CLP(GraphicsStateGuardian)::
+get_texture_src_type(TextureStage::CombineSource cs) const {
+  switch (cs) {
+  case TextureStage::CS_undefined: // fall through
+  case TextureStage::CS_texture: return GL_TEXTURE;
+  case TextureStage::CS_constant: return GL_CONSTANT;
+  case TextureStage::CS_primary_color: return GL_PRIMARY_COLOR;
+  case TextureStage::CS_previous: return GL_PREVIOUS;
+  }
+
+  GLCAT.error()
+    << "Invalid TextureStage::CombineSource value" << endl;
+  return GL_TEXTURE;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::get_texture_operand_type
+//       Access: Protected
+//  Description: Maps from the texture stage's CombineOperand types
+//               to the corresponding OpenGL ids
+////////////////////////////////////////////////////////////////////
+GLint CLP(GraphicsStateGuardian)::
+get_texture_operand_type(TextureStage::CombineOperand co) const {
+  switch (co) {
+  case TextureStage::CO_undefined: // fall through
+  case TextureStage::CO_src_alpha: return GL_SRC_ALPHA;
+  case TextureStage::CO_one_minus_src_alpha: return GL_ONE_MINUS_SRC_ALPHA;
+  case TextureStage::CO_src_color: return GL_SRC_COLOR;
+  case TextureStage::CO_one_minus_src_color: return GL_ONE_MINUS_SRC_COLOR;
+  }
+
+  GLCAT.error()
+    << "Invalid TextureStage::CombineOperand value" << endl;
+  return GL_SRC_COLOR;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::get_fog_mode_type
 //     Function: CLP(GraphicsStateGuardian)::get_fog_mode_type
 //       Access: Protected
 //       Access: Protected
@@ -4007,6 +4270,169 @@ set_blend_mode(ColorWriteAttrib::Mode color_write_mode,
   enable_blend(false);
   enable_blend(false);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GLGraphicsStateGuardian::finish_modify_state
+//       Access: Protected, Virtual
+//  Description: Called after the GSG state has been modified via
+//               modify_state() or set_state(), this hook is provided
+//               for the derived class to do any further state setup
+//               work.
+////////////////////////////////////////////////////////////////////
+void CLP(GraphicsStateGuardian)::
+finish_modify_state() {
+  // Apply the texture matrix, if needed.
+  if (_needs_tex_mat) {
+    _needs_tex_mat = false;
+
+    int num_stages = _current_texture->get_num_on_stages();
+    nassertv(num_stages <= _max_texture_stages);
+    
+    for (int i = 0; i < num_stages; i++) {
+      TextureStage *stage = _current_texture->get_on_stage(i);
+      _glActiveTexture(GL_TEXTURE0 + i);
+      
+      GLP(MatrixMode)(GL_TEXTURE);
+      if (_current_tex_mat->has_stage(stage)) {
+        GLP(LoadMatrixf)(_current_tex_mat->get_mat(stage).get_data());
+      } else {
+        // For some reason, the glLoadIdentity() call doesn't work on
+        // my Dell laptop's IBM OpenGL driver, when used in
+        // conjunction with glTexGen(), below.  But explicitly loading
+        // an identity matrix does work.
+        //        GLP(LoadIdentity)();
+        GLP(LoadMatrixf)(LMatrix4f::ident_mat().get_data());
+      }
+    }
+    report_my_gl_errors();
+  }
+
+  if (_needs_tex_gen) {
+    _needs_tex_gen = false;
+    bool force_normal = false;
+
+    int num_stages = _current_texture->get_num_on_stages();
+    nassertv(num_stages <= _max_texture_stages);
+    
+    // These are passed in for the four OBJECT_PLANE or EYE_PLANE
+    // values; they effectively define an identity matrix that maps
+    // the spatial coordinates one-for-one to UV's.  If you want a
+    // mapping other than identity, use a TexMatrixAttrib (or a
+    // TexProjectorEffect).
+    static const float s_data[4] = { 1, 0, 0, 0 };
+    static const float t_data[4] = { 0, 1, 0, 0 };
+    static const float r_data[4] = { 0, 0, 1, 0 };
+    static const float q_data[4] = { 0, 0, 0, 1 };
+    
+    for (int i = 0; i < num_stages; i++) {
+      TextureStage *stage = _current_texture->get_on_stage(i);
+      _glActiveTexture(GL_TEXTURE0 + i);
+      
+      switch (_current_tex_gen->get_mode(stage)) {
+      case TexGenAttrib::M_off:
+      case TexGenAttrib::M_cube_map:
+        GLP(Disable)(GL_TEXTURE_GEN_S);
+        GLP(Disable)(GL_TEXTURE_GEN_T);
+        GLP(Disable)(GL_TEXTURE_GEN_R);
+        GLP(Disable)(GL_TEXTURE_GEN_Q);
+        break;
+        
+      case TexGenAttrib::M_sphere_map:
+        GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+        GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+        GLP(Enable)(GL_TEXTURE_GEN_S);
+        GLP(Enable)(GL_TEXTURE_GEN_T);
+        GLP(Disable)(GL_TEXTURE_GEN_R);
+        GLP(Disable)(GL_TEXTURE_GEN_Q);
+        force_normal = true;
+        break;
+
+      case TexGenAttrib::M_object_position:
+        GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+        GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+        GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+        GLP(TexGeni)(GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+        
+        GLP(TexGenfv)(GL_S, GL_OBJECT_PLANE, s_data);
+        GLP(TexGenfv)(GL_T, GL_OBJECT_PLANE, t_data);
+        GLP(TexGenfv)(GL_R, GL_OBJECT_PLANE, r_data);
+        GLP(TexGenfv)(GL_Q, GL_OBJECT_PLANE, q_data);
+        
+        GLP(Enable)(GL_TEXTURE_GEN_S);
+        GLP(Enable)(GL_TEXTURE_GEN_T);
+        GLP(Enable)(GL_TEXTURE_GEN_R);
+        GLP(Enable)(GL_TEXTURE_GEN_Q);
+        break;
+
+      case TexGenAttrib::M_eye_position:
+        // To represent eye position correctly, we need to temporarily
+        // load the coordinate-system transform.
+        GLP(MatrixMode)(GL_MODELVIEW);
+        GLP(PushMatrix)();
+        GLP(LoadMatrixf)(_scene_setup->get_cs_transform()->get_mat().get_data());
+
+        GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        GLP(TexGeni)(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        
+        GLP(TexGenfv)(GL_S, GL_EYE_PLANE, s_data);
+        GLP(TexGenfv)(GL_T, GL_EYE_PLANE, t_data);
+        GLP(TexGenfv)(GL_R, GL_EYE_PLANE, r_data);
+        GLP(TexGenfv)(GL_Q, GL_EYE_PLANE, q_data);
+        
+        GLP(Enable)(GL_TEXTURE_GEN_S);
+        GLP(Enable)(GL_TEXTURE_GEN_T);
+        GLP(Enable)(GL_TEXTURE_GEN_R);
+        GLP(Enable)(GL_TEXTURE_GEN_Q);
+
+        GLP(MatrixMode)(GL_MODELVIEW);
+        GLP(PopMatrix)();
+        break;
+
+      case TexGenAttrib::M_world_position:
+        // We achieve world position coordinates by using the eye
+        // position mode, and loading the transform of the root
+        // node--thus putting the "eye" at the root.
+        GLP(MatrixMode)(GL_MODELVIEW);
+        GLP(PushMatrix)();
+        CPT(TransformState) root_transform = _scene_setup->get_render_transform();
+        GLP(LoadMatrixf)(root_transform->get_mat().get_data());
+        GLP(TexGeni)(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        GLP(TexGeni)(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        GLP(TexGeni)(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        GLP(TexGeni)(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
+        
+        GLP(TexGenfv)(GL_S, GL_EYE_PLANE, s_data);
+        GLP(TexGenfv)(GL_T, GL_EYE_PLANE, t_data);
+        GLP(TexGenfv)(GL_R, GL_EYE_PLANE, r_data);
+        GLP(TexGenfv)(GL_Q, GL_EYE_PLANE, q_data);
+        
+        GLP(Enable)(GL_TEXTURE_GEN_S);
+        GLP(Enable)(GL_TEXTURE_GEN_T);
+        GLP(Enable)(GL_TEXTURE_GEN_R);
+        GLP(Enable)(GL_TEXTURE_GEN_Q);
+
+        GLP(MatrixMode)(GL_MODELVIEW);
+        GLP(PopMatrix)();
+        break;
+      }
+    }
+
+    // Certain texgen modes (sphere_map, cube_map) require forcing the
+    // normal to be sent to the GL while the texgen mode is in effect.
+    if (force_normal != _texgen_forced_normal) {
+      if (force_normal) {
+        force_normals();
+      } else  {
+        undo_force_normals();
+      }
+      _texgen_forced_normal = force_normal;
+    }
+
+    report_my_gl_errors();
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::free_pointers
 //     Function: CLP(GraphicsStateGuardian)::free_pointers
 //       Access: Protected, Virtual
 //       Access: Protected, Virtual
@@ -4285,7 +4711,6 @@ dump_state(void)
       dump << "\t\t" << "GL_POINT_SMOOTH " << _point_smooth_enabled << " " << (bool)GLP(IsEnabled)(GL_POINT_SMOOTH) << "\n";
       dump << "\t\t" << "GL_POINT_SMOOTH " << _point_smooth_enabled << " " << (bool)GLP(IsEnabled)(GL_POINT_SMOOTH) << "\n";
       dump << "\t\t" << "GL_LIGHTING " << _lighting_enabled << " " << (bool)GLP(IsEnabled)(GL_LIGHTING) << "\n";
       dump << "\t\t" << "GL_LIGHTING " << _lighting_enabled << " " << (bool)GLP(IsEnabled)(GL_LIGHTING) << "\n";
       dump << "\t\t" << "GL_SCISSOR_TEST " << _scissor_enabled << " " << (bool)GLP(IsEnabled)(GL_SCISSOR_TEST) << "\n";
       dump << "\t\t" << "GL_SCISSOR_TEST " << _scissor_enabled << " " << (bool)GLP(IsEnabled)(GL_SCISSOR_TEST) << "\n";
-      dump << "\t\t" << "GL_TEXTURE_2D " << _texturing_enabled << " " << (bool)GLP(IsEnabled)(GL_TEXTURE_2D) << "\n";
       dump << "\t\t" << "GL_STENCIL_TEST " << " " << (bool)GLP(IsEnabled)(GL_STENCIL_TEST) << "\n";
       dump << "\t\t" << "GL_STENCIL_TEST " << " " << (bool)GLP(IsEnabled)(GL_STENCIL_TEST) << "\n";
       dump << "\t\t" << "GL_BLEND " << _blend_enabled << " " << (bool)GLP(IsEnabled)(GL_BLEND) << "\n";
       dump << "\t\t" << "GL_BLEND " << _blend_enabled << " " << (bool)GLP(IsEnabled)(GL_BLEND) << "\n";
       dump << "\t\t" << "GL_DEPTH_TEST " << _depth_test_enabled << " " << (bool)GLP(IsEnabled)(GL_DEPTH_TEST) << "\n";
       dump << "\t\t" << "GL_DEPTH_TEST " << _depth_test_enabled << " " << (bool)GLP(IsEnabled)(GL_DEPTH_TEST) << "\n";

+ 28 - 6
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -25,6 +25,10 @@
 #include "displayRegion.h"
 #include "displayRegion.h"
 #include "material.h"
 #include "material.h"
 #include "depthTestAttrib.h"
 #include "depthTestAttrib.h"
+#include "textureAttrib.h"
+#include "texMatrixAttrib.h"
+#include "texGenAttrib.h"
+#include "textureStage.h"
 #include "textureApplyAttrib.h"
 #include "textureApplyAttrib.h"
 #include "pointerToArray.h"
 #include "pointerToArray.h"
 #include "fog.h"
 #include "fog.h"
@@ -134,12 +138,13 @@ protected:
   static bool report_errors_loop(int line, const char *source_file, 
   static bool report_errors_loop(int line, const char *source_file, 
                                  GLenum error_code, int &error_count);
                                  GLenum error_code, int &error_count);
   void show_gl_string(const string &name, GLenum id);
   void show_gl_string(const string &name, GLenum id);
-  void get_gl_version();
+  virtual void get_gl_version();
   void save_extensions(const char *extensions);
   void save_extensions(const char *extensions);
   virtual void get_extra_extensions();
   virtual void get_extra_extensions();
   void report_extensions() const;
   void report_extensions() const;
   bool has_extension(const string &extension) const;
   bool has_extension(const string &extension) const;
   bool is_at_least_version(int major_version, int minor_version, int release_version = 0) const;
   bool is_at_least_version(int major_version, int minor_version, int release_version = 0) const;
+  virtual void *get_extension_func(const char *prefix, const char *name);
 
 
   virtual bool slot_new_light(int light_id);
   virtual bool slot_new_light(int light_id);
   virtual void enable_lighting(bool enable);
   virtual void enable_lighting(bool enable);
@@ -158,6 +163,8 @@ protected:
                               ColorBlendAttrib::Mode color_blend_mode,
                               ColorBlendAttrib::Mode color_blend_mode,
                               TransparencyAttrib::Mode transparency_mode);
                               TransparencyAttrib::Mode transparency_mode);
 
 
+  virtual void finish_modify_state();
+
   virtual void free_pointers();
   virtual void free_pointers();
   virtual PT(SavedFrameBuffer) save_frame_buffer(const RenderBuffer &buffer,
   virtual PT(SavedFrameBuffer) save_frame_buffer(const RenderBuffer &buffer,
                                                  CPT(DisplayRegion) dr);
                                                  CPT(DisplayRegion) dr);
@@ -195,7 +202,6 @@ protected:
   INLINE void enable_multisample(bool val);
   INLINE void enable_multisample(bool val);
   INLINE void enable_line_smooth(bool val);
   INLINE void enable_line_smooth(bool val);
   INLINE void enable_point_smooth(bool val);
   INLINE void enable_point_smooth(bool val);
-  INLINE void enable_texturing(bool val);
   INLINE void enable_scissor(bool val);
   INLINE void enable_scissor(bool val);
   INLINE void enable_stencil_test(bool val);
   INLINE void enable_stencil_test(bool val);
   INLINE void enable_multisample_alpha_one(bool val);
   INLINE void enable_multisample_alpha_one(bool val);
@@ -230,7 +236,10 @@ protected:
   GLenum get_image_type(PixelBuffer::Type type);
   GLenum get_image_type(PixelBuffer::Type type);
   GLenum get_external_image_format(PixelBuffer::Format format);
   GLenum get_external_image_format(PixelBuffer::Format format);
   GLenum get_internal_image_format(PixelBuffer::Format format);
   GLenum get_internal_image_format(PixelBuffer::Format format);
-  GLint get_texture_apply_mode_type(TextureApplyAttrib::Mode am) const;
+  GLint get_texture_apply_mode_type(TextureStage::Mode am) const;
+  GLint get_texture_combine_type(TextureStage::CombineMode cm) const;
+  GLint get_texture_src_type(TextureStage::CombineSource cs) const;
+  GLint get_texture_operand_type(TextureStage::CombineOperand co) const;
   GLenum get_fog_mode_type(Fog::Mode m) const;
   GLenum get_fog_mode_type(Fog::Mode m) const;
 
 
   static CPT(RenderState) get_untextured_state();
   static CPT(RenderState) get_untextured_state();
@@ -279,7 +288,6 @@ protected:
   bool _line_smooth_enabled;
   bool _line_smooth_enabled;
   bool _point_smooth_enabled;
   bool _point_smooth_enabled;
   bool _scissor_enabled;
   bool _scissor_enabled;
-  bool _texturing_enabled;
   bool _stencil_test_enabled;
   bool _stencil_test_enabled;
   bool _multisample_alpha_one_enabled;
   bool _multisample_alpha_one_enabled;
   bool _multisample_alpha_mask_enabled;
   bool _multisample_alpha_mask_enabled;
@@ -291,12 +299,19 @@ protected:
   int _decal_level;
   int _decal_level;
 
 
   bool _dithering_enabled;
   bool _dithering_enabled;
+  bool _texgen_forced_normal;
 
 
   int _max_lights;
   int _max_lights;
   int _max_clip_planes;
   int _max_clip_planes;
 
 
   LMatrix4f _current_projection_mat;
   LMatrix4f _current_projection_mat;
   int _projection_mat_stack_count;
   int _projection_mat_stack_count;
+  
+  CPT(TextureAttrib) _current_texture;
+  CPT(TexMatrixAttrib) _current_tex_mat;
+  bool _needs_tex_mat;
+  CPT(TexGenAttrib) _current_tex_gen;
+  bool _needs_tex_gen;
 
 
   CPT(DisplayRegion) _actual_display_region;
   CPT(DisplayRegion) _actual_display_region;
 #ifdef HAVE_CGGL
 #ifdef HAVE_CGGL
@@ -306,14 +321,21 @@ protected:
 #endif
 #endif
 
 
   int _pass_number;
   int _pass_number;
+  
+  int _error_count;
 
 
   int _gl_version_major, _gl_version_minor, _gl_version_release;
   int _gl_version_major, _gl_version_minor, _gl_version_release;
   pset<string> _extensions;
   pset<string> _extensions;
+
+public:
   bool _supports_bgr;
   bool _supports_bgr;
   bool _supports_multisample;
   bool _supports_multisample;
-  GLenum _edge_clamp;
 
 
-  int _error_count;
+  bool _supports_multitexture;
+  PFNGLACTIVETEXTUREPROC _glActiveTexture;
+  PFNGLMULTITEXCOORD2FVPROC _glMultiTexCoord2fv;
+
+  GLenum _edge_clamp;
 
 
 public:
 public:
   static GraphicsStateGuardian *
   static GraphicsStateGuardian *

+ 0 - 5
panda/src/glstuff/glmisc_src.cxx

@@ -21,11 +21,6 @@
 // possible mode.
 // possible mode.
 bool CLP(cheap_textures) = CONFIGOBJ.GetBool("gl-cheap-textures", false);
 bool CLP(cheap_textures) = CONFIGOBJ.GetBool("gl-cheap-textures", false);
 
 
-// Configure this true to ignore texture modes like modulate that
-// blend texture color with polygon color (a little cheaper for
-// software renderers).
-bool CLP(always_decal_textures) = CONFIGOBJ.GetBool("gl-always-decal-textures", false);
-
 // Configure this true to disable texture clamp mode (all textures
 // Configure this true to disable texture clamp mode (all textures
 // repeat, a little cheaper for software renderers).
 // repeat, a little cheaper for software renderers).
 bool CLP(ignore_clamp) = CONFIGOBJ.GetBool("gl-ignore-clamp", false);
 bool CLP(ignore_clamp) = CONFIGOBJ.GetBool("gl-ignore-clamp", false);

+ 0 - 1
panda/src/glstuff/glmisc_src.h

@@ -21,7 +21,6 @@
 //#define GSG_VERBOSE 1
 //#define GSG_VERBOSE 1
 
 
 extern bool CLP(cheap_textures);
 extern bool CLP(cheap_textures);
-extern bool CLP(always_decal_textures);
 extern bool CLP(ignore_clamp);
 extern bool CLP(ignore_clamp);
 extern bool CLP(ignore_filters);
 extern bool CLP(ignore_filters);
 extern bool CLP(ignore_mipmaps);
 extern bool CLP(ignore_mipmaps);

+ 2 - 1
panda/src/glxdisplay/Sources.pp

@@ -15,7 +15,8 @@
     glxGraphicsPipe.I glxGraphicsPipe.cxx glxGraphicsPipe.h \
     glxGraphicsPipe.I glxGraphicsPipe.cxx glxGraphicsPipe.h \
     glxGraphicsWindow.h glxGraphicsWindow.I glxGraphicsWindow.cxx \
     glxGraphicsWindow.h glxGraphicsWindow.I glxGraphicsWindow.cxx \
     glxGraphicsStateGuardian.h glxGraphicsStateGuardian.I \
     glxGraphicsStateGuardian.h glxGraphicsStateGuardian.I \
-    glxGraphicsStateGuardian.cxx
+    glxGraphicsStateGuardian.cxx \
+    glxext.h
 
 
   #define INSTALL_HEADERS \
   #define INSTALL_HEADERS \
     glxGraphicsBuffer.I glxGraphicsBuffer.h \
     glxGraphicsBuffer.I glxGraphicsBuffer.h \

+ 4 - 13
panda/src/glxdisplay/glxGraphicsPipe.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 #include "graphicsWindow.h"
 #include "graphicsWindow.h"
 #include "graphicsPipe.h"
 #include "graphicsPipe.h"
+#include "glgsg.h"
 
 
 class FrameBufferProperties;
 class FrameBufferProperties;
 
 
@@ -41,19 +42,9 @@ typedef int XIC;
 #include <X11/Xlib.h>
 #include <X11/Xlib.h>
 #include <GL/glx.h>
 #include <GL/glx.h>
 
 
-#ifndef GLX_VERSION_1_3
-  // Pre-glx 1.3, these GLXFBConfig definitions might have been
-  // defined as SGI extensions.
-  #define GLX_RGBA_TYPE GLX_RGBA_TYPE_SGIX
-  #define GLXFBConfig GLXFBConfigSGIX
-  #define GLXPbuffer GLXPbufferSGIX
-  #define glXChooseFBConfig glXChooseFBConfigSGIX
-  #define glXCreateNewContext glXCreateContextWithConfigSGIX
-  #define glXGetVisualFromFBConfig glXGetVisualFromFBConfigSGIX
-  #define glXGetFBConfigAttrib glXGetFBConfigAttribSGIX
-  #define glXCreatePbuffer glXCreateGLXPbufferSGIX
-  #define glXDestroyPbuffer glXDestroyGLXPbufferSGIX
-#endif // GLX_VERSION_1_3
+// This must be included after we have included glgsg.h (which
+// includes gl.h).
+#include "glxext.h"
 
 
 #endif  // CPPPARSER
 #endif  // CPPPARSER
 
 

+ 171 - 0
panda/src/glxdisplay/glxGraphicsStateGuardian.cxx

@@ -17,6 +17,10 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "glxGraphicsStateGuardian.h"
 #include "glxGraphicsStateGuardian.h"
+#include "config_glxdisplay.h"
+#include "config_glgsg.h"
+
+#include <dlfcn.h>
 
 
 
 
 TypeHandle glxGraphicsStateGuardian::_type_handle;
 TypeHandle glxGraphicsStateGuardian::_type_handle;
@@ -41,6 +45,9 @@ glxGraphicsStateGuardian(const FrameBufferProperties &properties,
   if (share_with != (glxGraphicsStateGuardian *)NULL) {
   if (share_with != (glxGraphicsStateGuardian *)NULL) {
     _prepared_objects = share_with->get_prepared_objects();
     _prepared_objects = share_with->get_prepared_objects();
   }
   }
+  
+  _libgl_handle = NULL;
+  _checked_get_proc_address = false;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -57,6 +64,50 @@ glxGraphicsStateGuardian::
     glXDestroyContext(_display, _context);
     glXDestroyContext(_display, _context);
     _context = (GLXContext)NULL;
     _context = (GLXContext)NULL;
   }
   }
+  if (_libgl_handle != (void *)NULL) {
+    dlclose(_libgl_handle);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: glxGraphicsStateGuardian::glx_is_at_least_version
+//       Access: Public
+//  Description: Returns true if the runtime GLX version number is at
+//               least the indicated value, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool glxGraphicsStateGuardian::
+glx_is_at_least_version(int major_version, int minor_version) const {
+  if (_glx_version_major < major_version) {
+    return false;
+  }
+  if (_glx_version_minor < minor_version) {
+    return false;
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: glxGraphicsStateGuardian::get_gl_version
+//       Access: Protected, Virtual
+//  Description: Queries the runtime version of OpenGL in use.
+////////////////////////////////////////////////////////////////////
+void glxGraphicsStateGuardian::
+get_gl_version() {
+  GLGraphicsStateGuardian::get_gl_version();
+
+  show_glx_client_string("GLX_VENDOR", GLX_VENDOR);
+  show_glx_client_string("GLX_VERSION", GLX_VERSION);
+  show_glx_server_string("GLX_VENDOR", GLX_VENDOR);
+  show_glx_server_string("GLX_VERSION", GLX_VERSION);
+
+  glXQueryVersion(_display, &_glx_version_major, &_glx_version_minor);
+
+  // We output to glgsg_cat instead of glxdisplay_cat, since this is
+  // where the GL version has been output, and it's nice to see the
+  // two of these together.
+  glgsg_cat.debug()
+    << "GLX_VERSION = " << _glx_version_major << "." << _glx_version_minor 
+    << "\n";
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -71,3 +122,123 @@ void glxGraphicsStateGuardian::
 get_extra_extensions() {
 get_extra_extensions() {
   save_extensions(glXQueryExtensionsString(_display, _screen));
   save_extensions(glXQueryExtensionsString(_display, _screen));
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: glxGraphicsStateGuardian::get_extension_func
+//       Access: Public, Virtual
+//  Description: Returns the pointer to the GL extension function with
+//               the indicated name.  It is the responsibility of the
+//               caller to ensure that the required extension is
+//               defined in the OpenGL runtime prior to calling this;
+//               it is an error to call this for a function that is
+//               not defined.
+////////////////////////////////////////////////////////////////////
+void *glxGraphicsStateGuardian::
+get_extension_func(const char *prefix, const char *name) {
+  if (!_checked_get_proc_address) {
+    // First, check if we have glxGetProcAddress available.  This will
+    // be superior if we can get it.
+    const char *funcName = NULL;
+
+    if (glx_is_at_least_version(1, 4)) {
+      funcName = "glXGetProcAddress";
+
+    } else if (has_extension("GLX_ARB_get_proc_address")) {
+      funcName = "glXGetProcAddressARB";
+    }
+
+    if (funcName != NULL) {
+      _glxGetProcAddress = (PFNGLXGETPROCADDRESSPROC)get_system_func(funcName);
+      if (_glxGetProcAddress == NULL) {
+        glxdisplay_cat.warning()
+          << "Couldn't load function " << funcName
+          << ", GL extensions may be unavailable.\n";
+      }
+    }
+
+    _checked_get_proc_address = true;
+  }
+
+  string fullname = string(prefix) + string(name);
+
+  // Use glxGetProcAddress() if we've got it; it should be more robust.
+  if (_glxGetProcAddress != NULL) {
+    return (void *)_glxGetProcAddress((const GLubyte *)fullname.c_str());
+  }
+
+  // Otherwise, fall back to the OS-provided calls.
+  return get_system_func(fullname.c_str());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: glxGraphicsStateGuardian::get_system_func
+//       Access: Private
+//  Description: Support for get_extension_func(), above, that uses
+//               system calls to find a GL or GLX function (in the
+//               absence of a working glxGetProcAddress() function to
+//               call).
+////////////////////////////////////////////////////////////////////
+void *glxGraphicsStateGuardian::
+get_system_func(const char *name) {
+  if (_libgl_handle == (void *)NULL) {
+    // We open the current executable, rather than naming a particular
+    // library.  Presumably libGL.so (or whatever the library should
+    // be called) is already available in the current executable
+    // address space, so this is more portable than insisting on a
+    // particular shared library name.
+    _libgl_handle = dlopen(NULL, RTLD_LAZY);
+    nassertr(_libgl_handle != (void *)NULL, NULL);
+
+    // If that doesn't locate the symbol we expected, then fall back
+    // to loading the GL library by its usual name.
+    if (dlsym(_libgl_handle, name) == NULL) {
+      dlclose(_libgl_handle);
+      glxdisplay_cat.warning()
+        << name << " not found in executable; looking in libGL.so instead.\n";
+      _libgl_handle = dlopen("libGL.so", RTLD_LAZY);
+      nassertr(_libgl_handle != (void *)NULL, NULL);
+    }
+  }
+
+  return dlsym(_libgl_handle, name);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: glxGraphicsStateGuardian::show_glx_client_string
+//       Access: Protected
+//  Description: Outputs the result of glxGetClientString() on the
+//               indicated tag.
+////////////////////////////////////////////////////////////////////
+void glxGraphicsStateGuardian::
+show_glx_client_string(const string &name, int id) {
+  if (glgsg_cat.is_debug()) {
+    const char *text = glXGetClientString(_display, id);
+    if (text == (const char *)NULL) {
+      glgsg_cat.debug()
+        << "Unable to query " << name << " (client)\n";
+    } else {
+      glgsg_cat.debug()
+        << name << " (client) = " << (const char *)text << "\n";
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: glxGraphicsStateGuardian::show_glx_server_string
+//       Access: Protected
+//  Description: Outputs the result of glxQueryServerString() on the
+//               indicated tag.
+////////////////////////////////////////////////////////////////////
+void glxGraphicsStateGuardian::
+show_glx_server_string(const string &name, int id) {
+  if (glgsg_cat.is_debug()) {
+    const char *text = glXQueryServerString(_display, _screen, id);
+    if (text == (const char *)NULL) {
+      glgsg_cat.debug()
+        << "Unable to query " << name << " (server)\n";
+    } else {
+      glgsg_cat.debug()
+        << name << " (server) = " << (const char *)text << "\n";
+    }
+  }
+}

+ 22 - 0
panda/src/glxdisplay/glxGraphicsStateGuardian.h

@@ -24,6 +24,12 @@
 #include "glgsg.h"
 #include "glgsg.h"
 #include "glxGraphicsPipe.h"
 #include "glxGraphicsPipe.h"
 
 
+#include <GL/glx.h>
+
+// This must be included after we have included glgsg.h (which
+// includes gl.h).
+#include "glxext.h"
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : glxGraphicsStateGuardian
 //       Class : glxGraphicsStateGuardian
 // Description : A tiny specialization on GLGraphicsStateGuardian to
 // Description : A tiny specialization on GLGraphicsStateGuardian to
@@ -37,6 +43,8 @@ public:
                            XVisualInfo *visual, Display *display, int screen);
                            XVisualInfo *visual, Display *display, int screen);
   virtual ~glxGraphicsStateGuardian();
   virtual ~glxGraphicsStateGuardian();
 
 
+  bool glx_is_at_least_version(int major_version, int minor_version) const;
+
   GLXContext _context;
   GLXContext _context;
   GLXFBConfig _fbconfig;
   GLXFBConfig _fbconfig;
   XVisualInfo *_visual;
   XVisualInfo *_visual;
@@ -44,7 +52,21 @@ public:
   int _screen;
   int _screen;
 
 
 protected:
 protected:
+  virtual void get_gl_version();
   virtual void get_extra_extensions();
   virtual void get_extra_extensions();
+  virtual void *get_extension_func(const char *prefix, const char *name);
+
+private:
+  void *get_system_func(const char *name);
+  void show_glx_client_string(const string &name, int id);
+  void show_glx_server_string(const string &name, int id);
+
+
+  int _glx_version_major, _glx_version_minor;
+
+  void *_libgl_handle;
+  bool _checked_get_proc_address;
+  PFNGLXGETPROCADDRESSPROC _glxGetProcAddress;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 622 - 0
panda/src/glxdisplay/glxext.h

@@ -0,0 +1,622 @@
+#ifndef __glxext_h_
+#define __glxext_h_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** License Applicability. Except to the extent portions of this file are
+** made subject to an alternative license as permitted in the SGI Free
+** Software License B, Version 1.1 (the "License"), the contents of this
+** file are subject only to the provisions of the License. You may not use
+** this file except in compliance with the License. You may obtain a copy
+** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
+** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
+** 
+** http://oss.sgi.com/projects/FreeB
+** 
+** Note that, as provided in the License, the Software is distributed on an
+** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
+** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
+** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
+** PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
+** 
+** Original Code. The Original Code is: OpenGL Sample Implementation,
+** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
+** Inc. The Original Code is Copyright (c) 1991-2002 Silicon Graphics, Inc.
+** Copyright in any portions created by third parties is as indicated
+** elsewhere herein. All Rights Reserved.
+** 
+** Additional Notice Provisions: This software was created using the
+** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has
+** not been independently verified as being compliant with the OpenGL(R)
+** version 1.2.1 Specification.
+*/
+
+#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
+#define WIN32_LEAN_AND_MEAN 1
+#include <windows.h>
+#endif
+
+#ifndef APIENTRY
+#define APIENTRY
+#endif
+#ifndef APIENTRYP
+#define APIENTRYP APIENTRY *
+#endif
+#ifndef GLAPI
+#define GLAPI extern
+#endif
+
+/*************************************************************/
+
+/* Header file version number, required by OpenGL ABI for Linux */
+/* glxext.h last updated 2002/03/22 */
+/* Current version at http://oss.sgi.com/projects/ogl-sample/registry/ */
+#define GLX_GLXEXT_VERSION 5
+
+#ifndef GLX_VERSION_1_3
+#define GLX_WINDOW_BIT                     0x00000001
+#define GLX_PIXMAP_BIT                     0x00000002
+#define GLX_PBUFFER_BIT                    0x00000004
+#define GLX_RGBA_BIT                       0x00000001
+#define GLX_COLOR_INDEX_BIT                0x00000002
+#define GLX_PBUFFER_CLOBBER_MASK           0x08000000
+#define GLX_FRONT_LEFT_BUFFER_BIT          0x00000001
+#define GLX_FRONT_RIGHT_BUFFER_BIT         0x00000002
+#define GLX_BACK_LEFT_BUFFER_BIT           0x00000004
+#define GLX_BACK_RIGHT_BUFFER_BIT          0x00000008
+#define GLX_AUX_BUFFERS_BIT                0x00000010
+#define GLX_DEPTH_BUFFER_BIT               0x00000020
+#define GLX_STENCIL_BUFFER_BIT             0x00000040
+#define GLX_ACCUM_BUFFER_BIT               0x00000080
+#define GLX_CONFIG_CAVEAT                  0x20
+#define GLX_X_VISUAL_TYPE                  0x22
+#define GLX_TRANSPARENT_TYPE               0x23
+#define GLX_TRANSPARENT_INDEX_VALUE        0x24
+#define GLX_TRANSPARENT_RED_VALUE          0x25
+#define GLX_TRANSPARENT_GREEN_VALUE        0x26
+#define GLX_TRANSPARENT_BLUE_VALUE         0x27
+#define GLX_TRANSPARENT_ALPHA_VALUE        0x28
+#define GLX_DONT_CARE                      0xFFFFFFFF
+#define GLX_NONE                           0x8000
+#define GLX_SLOW_CONFIG                    0x8001
+#define GLX_TRUE_COLOR                     0x8002
+#define GLX_DIRECT_COLOR                   0x8003
+#define GLX_PSEUDO_COLOR                   0x8004
+#define GLX_STATIC_COLOR                   0x8005
+#define GLX_GRAY_SCALE                     0x8006
+#define GLX_STATIC_GRAY                    0x8007
+#define GLX_TRANSPARENT_RGB                0x8008
+#define GLX_TRANSPARENT_INDEX              0x8009
+#define GLX_VISUAL_ID                      0x800B
+#define GLX_SCREEN                         0x800C
+#define GLX_NON_CONFORMANT_CONFIG          0x800D
+#define GLX_DRAWABLE_TYPE                  0x8010
+#define GLX_RENDER_TYPE                    0x8011
+#define GLX_X_RENDERABLE                   0x8012
+#define GLX_FBCONFIG_ID                    0x8013
+#define GLX_RGBA_TYPE                      0x8014
+#define GLX_COLOR_INDEX_TYPE               0x8015
+#define GLX_MAX_PBUFFER_WIDTH              0x8016
+#define GLX_MAX_PBUFFER_HEIGHT             0x8017
+#define GLX_MAX_PBUFFER_PIXELS             0x8018
+#define GLX_PRESERVED_CONTENTS             0x801B
+#define GLX_LARGEST_PBUFFER                0x801C
+#define GLX_WIDTH                          0x801D
+#define GLX_HEIGHT                         0x801E
+#define GLX_EVENT_MASK                     0x801F
+#define GLX_DAMAGED                        0x8020
+#define GLX_SAVED                          0x8021
+#define GLX_WINDOW                         0x8022
+#define GLX_PBUFFER                        0x8023
+#define GLX_PBUFFER_HEIGHT                 0x8040
+#define GLX_PBUFFER_WIDTH                  0x8041
+#endif
+
+#ifndef GLX_VERSION_1_4
+#define GLX_SAMPLE_BUFFERS                 100000
+#define GLX_SAMPLES                        100001
+#endif
+
+#ifndef GLX_ARB_get_proc_address
+#endif
+
+#ifndef GLX_ARB_multisample
+#define GLX_SAMPLE_BUFFERS_ARB             100000
+#define GLX_SAMPLES_ARB                    100001
+#endif
+
+#ifndef GLX_SGIS_multisample
+#define GLX_SAMPLE_BUFFERS_SGIS            100000
+#define GLX_SAMPLES_SGIS                   100001
+#endif
+
+#ifndef GLX_EXT_visual_info
+#define GLX_X_VISUAL_TYPE_EXT              0x22
+#define GLX_TRANSPARENT_TYPE_EXT           0x23
+#define GLX_TRANSPARENT_INDEX_VALUE_EXT    0x24
+#define GLX_TRANSPARENT_RED_VALUE_EXT      0x25
+#define GLX_TRANSPARENT_GREEN_VALUE_EXT    0x26
+#define GLX_TRANSPARENT_BLUE_VALUE_EXT     0x27
+#define GLX_TRANSPARENT_ALPHA_VALUE_EXT    0x28
+#define GLX_NONE_EXT                       0x8000
+#define GLX_TRUE_COLOR_EXT                 0x8002
+#define GLX_DIRECT_COLOR_EXT               0x8003
+#define GLX_PSEUDO_COLOR_EXT               0x8004
+#define GLX_STATIC_COLOR_EXT               0x8005
+#define GLX_GRAY_SCALE_EXT                 0x8006
+#define GLX_STATIC_GRAY_EXT                0x8007
+#define GLX_TRANSPARENT_RGB_EXT            0x8008
+#define GLX_TRANSPARENT_INDEX_EXT          0x8009
+#endif
+
+#ifndef GLX_SGI_swap_control
+#endif
+
+#ifndef GLX_SGI_video_sync
+#endif
+
+#ifndef GLX_SGI_make_current_read
+#endif
+
+#ifndef GLX_SGIX_video_source
+#endif
+
+#ifndef GLX_EXT_visual_rating
+#define GLX_VISUAL_CAVEAT_EXT              0x20
+#define GLX_SLOW_VISUAL_EXT                0x8001
+#define GLX_NON_CONFORMANT_VISUAL_EXT      0x800D
+/* reuse GLX_NONE_EXT */
+#endif
+
+#ifndef GLX_EXT_import_context
+#define GLX_SHARE_CONTEXT_EXT              0x800A
+#define GLX_VISUAL_ID_EXT                  0x800B
+#define GLX_SCREEN_EXT                     0x800C
+#endif
+
+#ifndef GLX_SGIX_fbconfig
+#define GLX_WINDOW_BIT_SGIX                0x00000001
+#define GLX_PIXMAP_BIT_SGIX                0x00000002
+#define GLX_RGBA_BIT_SGIX                  0x00000001
+#define GLX_COLOR_INDEX_BIT_SGIX           0x00000002
+#define GLX_DRAWABLE_TYPE_SGIX             0x8010
+#define GLX_RENDER_TYPE_SGIX               0x8011
+#define GLX_X_RENDERABLE_SGIX              0x8012
+#define GLX_FBCONFIG_ID_SGIX               0x8013
+#define GLX_RGBA_TYPE_SGIX                 0x8014
+#define GLX_COLOR_INDEX_TYPE_SGIX          0x8015
+/* reuse GLX_SCREEN_EXT */
+#endif
+
+#ifndef GLX_SGIX_pbuffer
+#define GLX_PBUFFER_BIT_SGIX               0x00000004
+#define GLX_BUFFER_CLOBBER_MASK_SGIX       0x08000000
+#define GLX_FRONT_LEFT_BUFFER_BIT_SGIX     0x00000001
+#define GLX_FRONT_RIGHT_BUFFER_BIT_SGIX    0x00000002
+#define GLX_BACK_LEFT_BUFFER_BIT_SGIX      0x00000004
+#define GLX_BACK_RIGHT_BUFFER_BIT_SGIX     0x00000008
+#define GLX_AUX_BUFFERS_BIT_SGIX           0x00000010
+#define GLX_DEPTH_BUFFER_BIT_SGIX          0x00000020
+#define GLX_STENCIL_BUFFER_BIT_SGIX        0x00000040
+#define GLX_ACCUM_BUFFER_BIT_SGIX          0x00000080
+#define GLX_SAMPLE_BUFFERS_BIT_SGIX        0x00000100
+#define GLX_MAX_PBUFFER_WIDTH_SGIX         0x8016
+#define GLX_MAX_PBUFFER_HEIGHT_SGIX        0x8017
+#define GLX_MAX_PBUFFER_PIXELS_SGIX        0x8018
+#define GLX_OPTIMAL_PBUFFER_WIDTH_SGIX     0x8019
+#define GLX_OPTIMAL_PBUFFER_HEIGHT_SGIX    0x801A
+#define GLX_PRESERVED_CONTENTS_SGIX        0x801B
+#define GLX_LARGEST_PBUFFER_SGIX           0x801C
+#define GLX_WIDTH_SGIX                     0x801D
+#define GLX_HEIGHT_SGIX                    0x801E
+#define GLX_EVENT_MASK_SGIX                0x801F
+#define GLX_DAMAGED_SGIX                   0x8020
+#define GLX_SAVED_SGIX                     0x8021
+#define GLX_WINDOW_SGIX                    0x8022
+#define GLX_PBUFFER_SGIX                   0x8023
+#endif
+
+#ifndef GLX_SGI_cushion
+#endif
+
+#ifndef GLX_SGIX_video_resize
+#define GLX_SYNC_FRAME_SGIX                0x00000000
+#define GLX_SYNC_SWAP_SGIX                 0x00000001
+#endif
+
+#ifndef GLX_SGIX_dmbuffer
+#define GLX_DIGITAL_MEDIA_PBUFFER_SGIX     0x8024
+#endif
+
+#ifndef GLX_SGIX_swap_group
+#endif
+
+#ifndef GLX_SGIX_swap_barrier
+#endif
+
+#ifndef GLX_SGIS_blended_overlay
+#define GLX_BLENDED_RGBA_SGIS              0x8025
+#endif
+
+#ifndef GLX_SGIS_shared_multisample
+#define GLX_MULTISAMPLE_SUB_RECT_WIDTH_SGIS 0x8026
+#define GLX_MULTISAMPLE_SUB_RECT_HEIGHT_SGIS 0x8027
+#endif
+
+#ifndef GLX_SUN_get_transparent_index
+#endif
+
+#ifndef GLX_3DFX_multisample
+#define GLX_SAMPLE_BUFFERS_3DFX            0x8050
+#define GLX_SAMPLES_3DFX                   0x8051
+#endif
+
+#ifndef GLX_MESA_copy_sub_buffer
+#endif
+
+#ifndef GLX_MESA_pixmap_colormap
+#endif
+
+#ifndef GLX_MESA_release_buffers
+#endif
+
+#ifndef GLX_MESA_set_3dfx_mode
+#define GLX_3DFX_WINDOW_MODE_MESA          0x1
+#define GLX_3DFX_FULLSCREEN_MODE_MESA      0x2
+#endif
+
+#ifndef GLX_SGIX_visual_select_group
+#define GLX_VISUAL_SELECT_GROUP_SGIX       0x8028
+#endif
+
+#ifndef GLX_OML_swap_method
+#define GLX_SWAP_METHOD_OML                0x8060
+#define GLX_SWAP_EXCHANGE_OML              0x8061
+#define GLX_SWAP_COPY_OML                  0x8062
+#define GLX_SWAP_UNDEFINED_OML             0x8063
+#endif
+
+#ifndef GLX_OML_sync_control
+#endif
+
+
+/*************************************************************/
+
+/* drose: glxext.h seems to have an error in that it assumes
+   __GLXextFuncPtr will be typedeffed if GLX_ARB_get_proc_address is
+   defined, which does not seem to be the case (at least it is not so
+   on my redhat 8.0 box).  So we work around this by typedeffing it
+   explicitly; and we use #define in case it is already typedeffed. */
+#define __GLXextFuncPtr panda__GLXextFuncPtr
+typedef void (*__GLXextFuncPtr)(void);
+
+  /*
+#ifndef GLX_ARB_get_proc_address
+typedef void (*__GLXextFuncPtr)(void);
+#endif
+  */
+
+#ifndef GLX_SGIX_video_source
+typedef XID GLXVideoSourceSGIX;
+#endif
+
+#ifndef GLX_SGIX_fbconfig
+typedef XID GLXFBConfigIDSGIX;
+typedef struct __GLXFBConfigRec *GLXFBConfigSGIX;
+#endif
+
+#ifndef GLX_SGIX_pbuffer
+typedef XID GLXPbufferSGIX;
+typedef struct {
+    int type;
+    unsigned long serial;	  /* # of last request processed by server */
+    Bool send_event;		  /* true if this came for SendEvent request */
+    Display *display;		  /* display the event was read from */
+    GLXDrawable drawable;	  /* i.d. of Drawable */
+    int event_type;		  /* GLX_DAMAGED_SGIX or GLX_SAVED_SGIX */
+    int draw_type;		  /* GLX_WINDOW_SGIX or GLX_PBUFFER_SGIX */
+    unsigned int mask;	  /* mask indicating which buffers are affected*/
+    int x, y;
+    int width, height;
+    int count;		  /* if nonzero, at least this many more */
+} GLXBufferClobberEventSGIX;
+#endif
+
+#ifndef GLX_VERSION_1_3
+#define GLX_VERSION_1_3 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern GLXFBConfig * glXGetFBConfigs (Display *, int, int *);
+extern GLXFBConfig * glXChooseFBConfig (Display *, int, const int *, int *);
+extern int glXGetFBConfigAttrib (Display *, GLXFBConfig, int, int *);
+extern XVisualInfo * glXGetVisualFromFBConfig (Display *, GLXFBConfig);
+extern GLXWindow glXCreateWindow (Display *, GLXFBConfig, Window, const int *);
+extern void glXDestroyWindow (Display *, GLXWindow);
+extern GLXPixmap glXCreatePixmap (Display *, GLXFBConfig, Pixmap, const int *);
+extern void glXDestroyPixmap (Display *, GLXPixmap);
+extern GLXPbuffer glXCreatePbuffer (Display *, GLXFBConfig, const int *);
+extern void glXDestroyPbuffer (Display *, GLXPbuffer);
+extern void glXQueryDrawable (Display *, GLXDrawable, int, unsigned int *);
+extern GLXContext glXCreateNewContext (Display *, GLXFBConfig, int, GLXContext, Bool);
+extern Bool glXMakeContextCurrent (Display *, GLXDrawable, GLXDrawable, GLXContext);
+extern GLXDrawable glXGetCurrentReadDrawable (void);
+extern Display * glXGetCurrentDisplay (void);
+extern int glXQueryContext (Display *, GLXContext, int, int *);
+extern void glXSelectEvent (Display *, GLXDrawable, unsigned long);
+extern void glXGetSelectedEvent (Display *, GLXDrawable, unsigned long *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+  /* drose: all of the following PFN... typedefs seemed to be
+     incorrect.  I put a * just inside the leading parenthesis. */
+typedef GLXFBConfig * (* PFNGLXGETFBCONFIGSPROC) (Display *dpy, int screen, int *nelements);
+typedef GLXFBConfig * (* PFNGLXCHOOSEFBCONFIGPROC) (Display *dpy, int screen, const int *attrib_list, int *nelements);
+typedef int (* PFNGLXGETFBCONFIGATTRIBPROC) (Display *dpy, GLXFBConfig config, int attribute, int *value);
+typedef XVisualInfo * (* PFNGLXGETVISUALFROMFBCONFIGPROC) (Display *dpy, GLXFBConfig config);
+typedef GLXWindow (* PFNGLXCREATEWINDOWPROC) (Display *dpy, GLXFBConfig config, Window win, const int *attrib_list);
+typedef void (* PFNGLXDESTROYWINDOWPROC) (Display *dpy, GLXWindow win);
+typedef GLXPixmap (* PFNGLXCREATEPIXMAPPROC) (Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list);
+typedef void (* PFNGLXDESTROYPIXMAPPROC) (Display *dpy, GLXPixmap pixmap);
+typedef GLXPbuffer (* PFNGLXCREATEPBUFFERPROC) (Display *dpy, GLXFBConfig config, const int *attrib_list);
+typedef void (* PFNGLXDESTROYPBUFFERPROC) (Display *dpy, GLXPbuffer pbuf);
+typedef void (* PFNGLXQUERYDRAWABLEPROC) (Display *dpy, GLXDrawable draw, int attribute, unsigned int *value);
+typedef GLXContext (* PFNGLXCREATENEWCONTEXTPROC) (Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct);
+typedef Bool (* PFNGLXMAKECONTEXTCURRENTPROC) (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx);
+typedef GLXDrawable (* PFNGLXGETCURRENTREADDRAWABLEPROC) (void);
+typedef Display * (* PFNGLXGETCURRENTDISPLAYPROC) (void);
+typedef int (* PFNGLXQUERYCONTEXTPROC) (Display *dpy, GLXContext ctx, int attribute, int *value);
+typedef void (* PFNGLXSELECTEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long event_mask);
+typedef void (* PFNGLXGETSELECTEDEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long *event_mask);
+#endif
+
+#ifndef GLX_VERSION_1_4
+#define GLX_VERSION_1_4 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern __GLXextFuncPtr glXGetProcAddress (const GLubyte *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef __GLXextFuncPtr (* PFNGLXGETPROCADDRESSPROC) (const GLubyte *procName);
+#endif
+
+#ifndef GLX_ARB_get_proc_address
+#define GLX_ARB_get_proc_address 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern __GLXextFuncPtr glXGetProcAddressARB (const GLubyte *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef __GLXextFuncPtr (* PFNGLXGETPROCADDRESSARBPROC) (const GLubyte *procName);
+#endif
+
+#ifndef GLX_ARB_multisample
+#define GLX_ARB_multisample 1
+#endif
+
+#ifndef GLX_SGIS_multisample
+#define GLX_SGIS_multisample 1
+#endif
+
+#ifndef GLX_EXT_visual_info
+#define GLX_EXT_visual_info 1
+#endif
+
+#ifndef GLX_SGI_swap_control
+#define GLX_SGI_swap_control 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern int glXSwapIntervalSGI (int);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef int (* PFNGLXSWAPINTERVALSGIPROC) (int interval);
+#endif
+
+#ifndef GLX_SGI_video_sync
+#define GLX_SGI_video_sync 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern int glXGetVideoSyncSGI (unsigned int *);
+extern int glXWaitVideoSyncSGI (int, int, unsigned int *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef int (* PFNGLXGETVIDEOSYNCSGIPROC) (unsigned int *count);
+typedef int (* PFNGLXWAITVIDEOSYNCSGIPROC) (int divisor, int remainder, unsigned int *count);
+#endif
+
+#ifndef GLX_SGI_make_current_read
+#define GLX_SGI_make_current_read 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern Bool glXMakeCurrentReadSGI (Display *, GLXDrawable, GLXDrawable, GLXContext);
+extern GLXDrawable glXGetCurrentReadDrawableSGI (void);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef Bool (* PFNGLXMAKECURRENTREADSGIPROC) (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx);
+typedef GLXDrawable (* PFNGLXGETCURRENTREADDRAWABLESGIPROC) (void);
+#endif
+
+#ifndef GLX_SGIX_video_source
+#define GLX_SGIX_video_source 1
+#ifdef _VL_H
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern GLXVideoSourceSGIX glXCreateGLXVideoSourceSGIX (Display *, int, VLServer, VLPath, int, VLNode);
+extern void glXDestroyGLXVideoSourceSGIX (Display *, GLXVideoSourceSGIX);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef GLXVideoSourceSGIX (* PFNGLXCREATEGLXVIDEOSOURCESGIXPROC) (Display *display, int screen, VLServer server, VLPath path, int nodeClass, VLNode drainNode);
+typedef void (* PFNGLXDESTROYGLXVIDEOSOURCESGIXPROC) (Display *dpy, GLXVideoSourceSGIX glxvideosource);
+#endif
+
+#endif /* _VL_H */
+#ifndef GLX_EXT_visual_rating
+#define GLX_EXT_visual_rating 1
+#endif
+
+#ifndef GLX_EXT_import_context
+#define GLX_EXT_import_context 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern Display * glXGetCurrentDisplayEXT (void);
+extern int glXQueryContextInfoEXT (Display *, GLXContext, int, int *);
+extern GLXContextID glXGetContextIDEXT (const GLXContext);
+extern GLXContext glXImportContextEXT (Display *, GLXContextID);
+extern void glXFreeContextEXT (Display *, GLXContext);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef Display * (* PFNGLXGETCURRENTDISPLAYEXTPROC) (void);
+typedef int (* PFNGLXQUERYCONTEXTINFOEXTPROC) (Display *dpy, GLXContext context, int attribute, int *value);
+typedef GLXContextID (* PFNGLXGETCONTEXTIDEXTPROC) (const GLXContext context);
+typedef GLXContext (* PFNGLXIMPORTCONTEXTEXTPROC) (Display *dpy, GLXContextID contextID);
+typedef void (* PFNGLXFREECONTEXTEXTPROC) (Display *dpy, GLXContext context);
+#endif
+
+#ifndef GLX_SGIX_fbconfig
+#define GLX_SGIX_fbconfig 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern int glXGetFBConfigAttribSGIX (Display *, GLXFBConfigSGIX, int, int *);
+extern GLXFBConfigSGIX * glXChooseFBConfigSGIX (Display *, int, int *, int *);
+extern GLXPixmap glXCreateGLXPixmapWithConfigSGIX (Display *, GLXFBConfigSGIX, Pixmap);
+extern GLXContext glXCreateContextWithConfigSGIX (Display *, GLXFBConfigSGIX, int, GLXContext, Bool);
+extern XVisualInfo * glXGetVisualFromFBConfigSGIX (Display *, GLXFBConfigSGIX);
+extern GLXFBConfigSGIX glXGetFBConfigFromVisualSGIX (Display *, XVisualInfo *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef int (* PFNGLXGETFBCONFIGATTRIBSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, int attribute, int *value);
+typedef GLXFBConfigSGIX * (* PFNGLXCHOOSEFBCONFIGSGIXPROC) (Display *dpy, int screen, int *attrib_list, int *nelements);
+typedef GLXPixmap (* PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, Pixmap pixmap);
+typedef GLXContext (* PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, int render_type, GLXContext share_list, Bool direct);
+typedef XVisualInfo * (* PFNGLXGETVISUALFROMFBCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config);
+typedef GLXFBConfigSGIX (* PFNGLXGETFBCONFIGFROMVISUALSGIXPROC) (Display *dpy, XVisualInfo *vis);
+#endif
+
+#ifndef GLX_SGIX_pbuffer
+#define GLX_SGIX_pbuffer 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern GLXPbufferSGIX glXCreateGLXPbufferSGIX (Display *, GLXFBConfigSGIX, unsigned int, unsigned int, int *);
+extern void glXDestroyGLXPbufferSGIX (Display *, GLXPbufferSGIX);
+extern int glXQueryGLXPbufferSGIX (Display *, GLXPbufferSGIX, int, unsigned int *);
+extern void glXSelectEventSGIX (Display *, GLXDrawable, unsigned long);
+extern void glXGetSelectedEventSGIX (Display *, GLXDrawable, unsigned long *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef GLXPbufferSGIX (* PFNGLXCREATEGLXPBUFFERSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list);
+typedef void (* PFNGLXDESTROYGLXPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuf);
+typedef int (* PFNGLXQUERYGLXPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuf, int attribute, unsigned int *value);
+typedef void (* PFNGLXSELECTEVENTSGIXPROC) (Display *dpy, GLXDrawable drawable, unsigned long mask);
+typedef void (* PFNGLXGETSELECTEDEVENTSGIXPROC) (Display *dpy, GLXDrawable drawable, unsigned long *mask);
+#endif
+
+#ifndef GLX_SGI_cushion
+#define GLX_SGI_cushion 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern void glXCushionSGI (Display *, Window, float);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef void (* PFNGLXCUSHIONSGIPROC) (Display *dpy, Window window, float cushion);
+#endif
+
+#ifndef GLX_SGIX_video_resize
+#define GLX_SGIX_video_resize 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern int glXBindChannelToWindowSGIX (Display *, int, int, Window);
+extern int glXChannelRectSGIX (Display *, int, int, int, int, int, int);
+extern int glXQueryChannelRectSGIX (Display *, int, int, int *, int *, int *, int *);
+extern int glXQueryChannelDeltasSGIX (Display *, int, int, int *, int *, int *, int *);
+extern int glXChannelRectSyncSGIX (Display *, int, int, GLenum);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef int (* PFNGLXBINDCHANNELTOWINDOWSGIXPROC) (Display *display, int screen, int channel, Window window);
+typedef int (* PFNGLXCHANNELRECTSGIXPROC) (Display *display, int screen, int channel, int x, int y, int w, int h);
+typedef int (* PFNGLXQUERYCHANNELRECTSGIXPROC) (Display *display, int screen, int channel, int *dx, int *dy, int *dw, int *dh);
+typedef int (* PFNGLXQUERYCHANNELDELTASSGIXPROC) (Display *display, int screen, int channel, int *x, int *y, int *w, int *h);
+typedef int (* PFNGLXCHANNELRECTSYNCSGIXPROC) (Display *display, int screen, int channel, GLenum synctype);
+#endif
+
+#ifndef GLX_SGIX_dmbuffer
+#define GLX_SGIX_dmbuffer 1
+#ifdef _DM_BUFFER_H_
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern Bool glXAssociateDMPbufferSGIX (Display *, GLXPbufferSGIX, DMparams *, DMbuffer);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef Bool (* PFNGLXASSOCIATEDMPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuffer, DMparams *params, DMbuffer dmbuffer);
+#endif
+
+#endif /* _DM_BUFFER_H_ */
+#ifndef GLX_SGIX_swap_group
+#define GLX_SGIX_swap_group 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern void glXJoinSwapGroupSGIX (Display *, GLXDrawable, GLXDrawable);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef void (* PFNGLXJOINSWAPGROUPSGIXPROC) (Display *dpy, GLXDrawable drawable, GLXDrawable member);
+#endif
+
+#ifndef GLX_SGIX_swap_barrier
+#define GLX_SGIX_swap_barrier 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern void glXBindSwapBarrierSGIX (Display *, GLXDrawable, int);
+extern Bool glXQueryMaxSwapBarriersSGIX (Display *, int, int *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef void (* PFNGLXBINDSWAPBARRIERSGIXPROC) (Display *dpy, GLXDrawable drawable, int barrier);
+typedef Bool (* PFNGLXQUERYMAXSWAPBARRIERSSGIXPROC) (Display *dpy, int screen, int *max);
+#endif
+
+#ifndef GLX_SUN_get_transparent_index
+#define GLX_SUN_get_transparent_index 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern Status glXGetTransparentIndexSUN (Display *, Window, Window, long *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef Status (* PFNGLXGETTRANSPARENTINDEXSUNPROC) (Display *dpy, Window overlay, Window underlay, long *pTransparentIndex);
+#endif
+
+#ifndef GLX_MESA_copy_sub_buffer
+#define GLX_MESA_copy_sub_buffer 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern void glXCopySubBufferMESA (Display *, GLXDrawable, int, int, int, int);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef void (* PFNGLXCOPYSUBBUFFERMESAPROC) (Display *dpy, GLXDrawable drawable, int x, int y, int width, int height);
+#endif
+
+#ifndef GLX_MESA_pixmap_colormap
+#define GLX_MESA_pixmap_colormap 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern GLXPixmap glXCreateGLXPixmapMESA (Display *, XVisualInfo *, Pixmap, Colormap);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef GLXPixmap (* PFNGLXCREATEGLXPIXMAPMESAPROC) (Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap);
+#endif
+
+#ifndef GLX_MESA_release_buffers
+#define GLX_MESA_release_buffers 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern Bool glXReleaseBuffersMESA (Display *, GLXDrawable);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef Bool (* PFNGLXRELEASEBUFFERSMESAPROC) (Display *dpy, GLXDrawable drawable);
+#endif
+
+#ifndef GLX_MESA_set_3dfx_mode
+#define GLX_MESA_set_3dfx_mode 1
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern Bool glXSet3DfxModeMESA (int);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef Bool (* PFNGLXSET3DFXMODEMESAPROC) (int mode);
+#endif
+
+#ifndef GLX_SGIX_visual_select_group
+#define GLX_SGIX_visual_select_group 1
+#endif
+
+#ifndef GLX_OML_swap_method
+#define GLX_OML_swap_method 1
+#endif
+
+#ifndef GLX_OML_sync_control
+#define GLX_OML_sync_control 1
+#if defined(__STDC_VERSION__)
+#if __STDC_VERSION__ >= 199901L
+/* Include ISO C99 integer types for OML_sync_control; need a better test */
+#include <inttypes.h>
+
+#ifdef GLX_GLXEXT_PROTOTYPES
+extern Bool glXGetSyncValuesOML (Display *, GLXDrawable, int64_t *, int64_t *, int64_t *);
+extern Bool glXGetMscRateOML (Display *, GLXDrawable, int32_t *, int32_t *);
+extern int64_t glXSwapBuffersMscOML (Display *, GLXDrawable, int64_t, int64_t, int64_t);
+extern Bool glXWaitForMscOML (Display *, GLXDrawable, int64_t, int64_t, int64_t, int64_t *, int64_t *, int64_t *);
+extern Bool glXWaitForSbcOML (Display *, GLXDrawable, int64_t, int64_t *, int64_t *, int64_t *);
+#endif /* GLX_GLXEXT_PROTOTYPES */
+typedef Bool (* PFNGLXGETSYNCVALUESOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t *ust, int64_t *msc, int64_t *sbc);
+typedef Bool (* PFNGLXGETMSCRATEOMLPROC) (Display *dpy, GLXDrawable drawable, int32_t *numerator, int32_t *denominator);
+typedef int64_t (* PFNGLXSWAPBUFFERSMSCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder);
+typedef Bool (* PFNGLXWAITFORMSCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t *ust, int64_t *msc, int64_t *sbc);
+typedef Bool (* PFNGLXWAITFORSBCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_sbc, int64_t *ust, int64_t *msc, int64_t *sbc);
+#endif
+
+#endif /* C99 version test */
+#endif /* STDC test */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 8 - 3
panda/src/gobj/Sources.pp

@@ -28,7 +28,9 @@
     savedContext.I savedContext.h \
     savedContext.I savedContext.h \
     texture.I texture.h \
     texture.I texture.h \
     textureContext.I textureContext.h \
     textureContext.I textureContext.h \
-    texturePool.I texturePool.h
+    texturePool.I texturePool.h \
+    texCoordName.I texCoordName.h \
+    textureStage.I textureStage.h
     
     
   #define INCLUDED_SOURCES \
   #define INCLUDED_SOURCES \
     LOD.cxx \
     LOD.cxx \
@@ -42,7 +44,8 @@
     perspectiveLens.cxx pixelBuffer.cxx \
     perspectiveLens.cxx pixelBuffer.cxx \
     preparedGraphicsObjects.cxx \
     preparedGraphicsObjects.cxx \
     lens.cxx  \
     lens.cxx  \
-    savedContext.cxx texture.cxx textureContext.cxx texturePool.cxx
+    savedContext.cxx texture.cxx textureContext.cxx texturePool.cxx \
+    texCoordName.cxx textureStage.cxx
 
 
   #define INSTALL_HEADERS \
   #define INSTALL_HEADERS \
     LOD.I LOD.h \
     LOD.I LOD.h \
@@ -62,7 +65,9 @@
     savedContext.I savedContext.h \
     savedContext.I savedContext.h \
     texture.I texture.h \
     texture.I texture.h \
     textureContext.I textureContext.h \
     textureContext.I textureContext.h \
-    texturePool.I texturePool.h
+    texturePool.I texturePool.h \
+    texCoordName.I texCoordName.h \
+    textureStage.I textureStage.h
 
 
   #define IGATESCAN all
   #define IGATESCAN all
 
 

+ 6 - 0
panda/src/gobj/config_gobj.cxx

@@ -30,6 +30,8 @@
 #include "pixelBuffer.h"
 #include "pixelBuffer.h"
 #include "lens.h"
 #include "lens.h"
 #include "texture.h"
 #include "texture.h"
+#include "textureStage.h"
+#include "texCoordName.h"
 
 
 #include "dconfig.h"
 #include "dconfig.h"
 #include "string_utils.h"
 #include "string_utils.h"
@@ -197,6 +199,8 @@ ConfigureFn(config_gobj) {
   Lens::init_type();
   Lens::init_type();
   Texture::init_type();
   Texture::init_type();
   dDrawable::init_type();
   dDrawable::init_type();
+  TextureStage::init_type();
+  TexCoordName::init_type();
 
 
   //Registration of writeable object's creation
   //Registration of writeable object's creation
   //functions with BamReader's factory
   //functions with BamReader's factory
@@ -215,4 +219,6 @@ ConfigureFn(config_gobj) {
   MatrixLens::register_with_read_factory();
   MatrixLens::register_with_read_factory();
   PerspectiveLens::register_with_read_factory();
   PerspectiveLens::register_with_read_factory();
   Texture::register_with_read_factory();
   Texture::register_with_read_factory();
+  TextureStage::register_with_read_factory();
+  TexCoordName::register_with_read_factory();
 }
 }

+ 2 - 2
panda/src/gobj/drawable.cxx

@@ -48,9 +48,9 @@ dDrawable::
 //               At this level, this doesn't do very much.
 //               At this level, this doesn't do very much.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void dDrawable::
 void dDrawable::
-draw(GraphicsStateGuardianBase *) { 
+draw(GraphicsStateGuardianBase *) const { 
   if (is_dirty()) {
   if (is_dirty()) {
-    config(); 
+    ((dDrawable *)this)->config(); 
   }
   }
 }
 }
 
 

+ 1 - 1
panda/src/gobj/drawable.h

@@ -50,7 +50,7 @@ public:
   dDrawable();
   dDrawable();
   virtual ~dDrawable();
   virtual ~dDrawable();
 
 
-  virtual void draw(GraphicsStateGuardianBase *);
+  virtual void draw(GraphicsStateGuardianBase *) const;
   virtual bool is_dynamic() const;
   virtual bool is_dynamic() const;
 
 
 protected:
 protected:

+ 126 - 30
panda/src/gobj/geom.I

@@ -29,6 +29,30 @@ get_binding(int attr) const {
   return _bind[attr];
   return _bind[attr];
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::has_any_texcoords
+//       Access: Published
+//  Description: Returns true if the Geom defines any texture
+//               coordinates at all, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool Geom::
+has_any_texcoords() const {
+  return !_texcoords_by_name.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::has_texcoords
+//       Access: Published
+//  Description: Returns true if the Geom defines a set of texture
+//               coordinates for the indicated multitexture stage
+//               name, or false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool Geom::
+has_texcoords(const TexCoordName *name) const {
+  TexCoordsByName::const_iterator tci = _texcoords_by_name.find(name);
+  return (tci != _texcoords_by_name.end());
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::get_coords_array
 //     Function: Geom::get_coords_array
 //       Access: Published
 //       Access: Published
@@ -77,10 +101,34 @@ get_colors_array() const {
 //               or G_OFF.  It may either be indexed or nonindexed,
 //               or G_OFF.  It may either be indexed or nonindexed,
 //               depending on whether get_texcoords_index() returns a
 //               depending on whether get_texcoords_index() returns a
 //               NULL array.
 //               NULL array.
+//
+//               This method is used in the single-texture case: it
+//               returns the default texture coordinates only.  In the
+//               presence of multitexturing, use the version of
+//               get_texcoords_array() method that takes a
+//               TexCoordName parameter.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_TexCoordf Geom::
 INLINE PTA_TexCoordf Geom::
 get_texcoords_array() const {
 get_texcoords_array() const {
-  return _texcoords;
+  return get_texcoords_array(TexCoordName::get_default());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::get_texcoords_array
+//       Access: Published
+//  Description: Returns the texcoords array in the geom with the
+//               indicated name.  This allows multitexturing support
+//               with multiple sets of texture coordinates in the same
+//               geom.  The return value will be NULL if there are no
+//               texture coordinates with the indicated name.
+////////////////////////////////////////////////////////////////////
+INLINE PTA_TexCoordf Geom::
+get_texcoords_array(const TexCoordName *name) const {
+  TexCoordsByName::const_iterator tci = _texcoords_by_name.find(name);
+  if (tci != _texcoords_by_name.end()) {
+    return (*tci).second._texcoords;
+  }
+  return PTA_TexCoordf();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -121,10 +169,52 @@ get_colors_index() const {
 //       Access: Published
 //       Access: Published
 //  Description: Returns the array of indices that, if nonempty, will
 //  Description: Returns the array of indices that, if nonempty, will
 //               be used to traverse the vertices in texcoords_array.
 //               be used to traverse the vertices in texcoords_array.
+//
+//               This method is used in the single-texture case: it
+//               returns the default texture coordinates only.  In the
+//               presence of multitexturing, use the version of
+//               get_texcoords_array() method that takes a
+//               TexCoordName parameter.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE PTA_ushort Geom::
 INLINE PTA_ushort Geom::
 get_texcoords_index() const {
 get_texcoords_index() const {
-  return _tindex;
+  return get_texcoords_index(TexCoordName::get_default());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::get_texcoords_index
+//       Access: Published
+//  Description: Returns the texcoords index within the geom with the
+//               indicated name.  This allows multitexturing support
+//               with multiple sets of texture coordinates in the same
+//               geom.  The return value will be NULL if there are no
+//               texture coordinates with the indicated name, or if
+//               the geom does not use indexed texcoords.
+////////////////////////////////////////////////////////////////////
+INLINE PTA_ushort Geom::
+get_texcoords_index(const TexCoordName *name) const {
+  TexCoordsByName::const_iterator tci = _texcoords_by_name.find(name);
+  if (tci != _texcoords_by_name.end()) {
+    return (*tci).second._tindex;
+  }
+  return PTA_ushort();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::are_texcoords_indexed
+//       Access: Published
+//  Description: Returns true if any texcoords on the Geom (and,
+//               therefore, all of them) are indexed, false if there
+//               are no texcoords on the Geom already or if the
+//               texcoords on the Geom are nonindexed.
+////////////////////////////////////////////////////////////////////
+INLINE bool Geom::
+are_texcoords_indexed() const {
+  if (_texcoords_by_name.empty()) {
+    return false;
+  }
+  TexCoordsByName::const_iterator tci = _texcoords_by_name.begin();
+  return ((*tci).second._tindex != (ushort *)NULL);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -151,34 +241,10 @@ set_num_prims(int num) {
 //  Description: Returns the number of primitives in the Geom.
 //  Description: Returns the number of primitives in the Geom.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE int Geom::
 INLINE int Geom::
-get_num_prims(void) const {
+get_num_prims() const {
   return _numprims;
   return _numprims;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: Geom::get_num_vertices
-//       Access: Published
-//  Description: Returns the number of vertices required by all all
-//               the prims in the Geom.
-////////////////////////////////////////////////////////////////////
-INLINE int Geom::
-get_num_vertices() const {
-  return _num_vertices;
-}
-
-INLINE int PTA_int_arraysum(const PTA_int &lengths) {
-    assert(lengths.size()>0);
-    
-    int *pLen=&lengths[0];
-    int *pArrayEnd=pLen+lengths.size();
-    int nVerts = 0;
-    for (;pLen<pArrayEnd;pLen++) {
-      nVerts += *pLen;
-    }
-    return nVerts;
-}
-
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::set_lengths
 //     Function: Geom::set_lengths
 //       Access: Published
 //       Access: Published
@@ -191,7 +257,7 @@ INLINE int PTA_int_arraysum(const PTA_int &lengths) {
 INLINE void Geom::
 INLINE void Geom::
 set_lengths(const PTA_int &lengths) {
 set_lengths(const PTA_int &lengths) {
   _primlengths = lengths;
   _primlengths = lengths;
-  _num_vertices = PTA_int_arraysum(lengths);
+  _num_vertices = sum_lengths(lengths);
   make_dirty();
   make_dirty();
 }
 }
 
 
@@ -209,6 +275,17 @@ get_lengths() const {
   return _primlengths;
   return _primlengths;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::get_num_vertices
+//       Access: Published
+//  Description: Returns the number of vertices required by all all
+//               the prims in the Geom.
+////////////////////////////////////////////////////////////////////
+INLINE int Geom::
+get_num_vertices() const {
+  return _num_vertices;
+}
+
 
 
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -293,8 +370,17 @@ INLINE Geom::TexCoordIterator Geom::
 make_texcoord_iterator() const {
 make_texcoord_iterator() const {
   check_config();
   check_config();
   TexCoordIterator i;
   TexCoordIterator i;
-  i._array = _texcoords;
-  i._index = _tindex;
+
+  TexCoordsByName::const_iterator tci = 
+    _texcoords_by_name.find(TexCoordName::get_default());
+  if (tci != _texcoords_by_name.end()) {
+    i._array = (*tci).second._texcoords;
+    i._index = (*tci).second._tindex;;
+  } else {
+    i._array = NULL;
+    i._index = NULL;
+  }
+
   return i;
   return i;
 }
 }
 
 
@@ -308,6 +394,16 @@ get_next_texcoord(TexCoordIterator &tciterator) const {
   return _get_texcoord(tciterator);
   return _get_texcoord(tciterator);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::get_next_texcoord
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE const TexCoordf &Geom::
+get_next_multitexcoord(MultiTexCoordIterator &tciterator, int n) const {
+  return _get_texcoord(tciterator._stages[n]);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::make_color_iterator
 //     Function: Geom::make_color_iterator
 //       Access: Public
 //       Access: Public

+ 603 - 370
panda/src/gobj/geom.cxx

@@ -132,6 +132,110 @@ ostream &operator << (ostream &out, GeomAttrType t) {
   return out << "(**invalid**)";
   return out << "(**invalid**)";
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: describe_attr
+//  Description: A handy helper function for output_verbose,
+//               below.
+////////////////////////////////////////////////////////////////////
+template <class VecType>
+static void
+describe_attr(ostream &out, const Geom *geom,
+              GeomBindType bind, const PTA(VecType) &array,
+              bool newline, int indent_level) {
+  PTA_int lengths = geom->get_lengths();
+  int num_prims = geom->get_num_prims();
+  bool components = geom->uses_components();
+
+  int i, j, vi;
+  switch (bind) {
+  case G_PER_VERTEX:
+    indent(out, indent_level)
+      << "Per vertex:";
+    vi = 0;
+    int num_verts;
+    num_verts = geom->get_num_vertices_per_prim();
+    for (i = 0; i < num_prims; i++) {
+      if (components) {
+        num_verts = lengths[i];
+      }
+      out << "\n";
+      indent(out, indent_level) << "[ ";
+      if (num_verts > 0) {
+        out << array[vi++];
+        for (j = 1; j < num_verts; j++) {
+          if (newline) {
+            out << "\n";
+            indent(out, indent_level + 2);
+          } else {
+            out << " ";
+          }
+          out << array[vi++];
+        }
+      }
+      out << " ]";
+    }
+    break;
+
+  case G_PER_COMPONENT:
+    if (!components) {
+      indent(out, indent_level)
+        << "Invalid per-component attribute specified!";
+    } else {
+      indent(out, indent_level)
+        << "Per component:";
+      vi = 0;
+      for (i = 0; i < num_prims; i++) {
+        num_verts = lengths[i] - geom->get_num_more_vertices_than_components();
+        out << "\n";
+        indent(out, indent_level) << "[ ";
+        if (num_verts > 0) {
+          out << array[vi++];
+          for (j = 1; j < num_verts; j++) {
+            if (newline) {
+              out << "\n";
+              indent(out, indent_level + 2);
+            } else {
+              out << " ";
+            }
+            out << array[vi++];
+          }
+          out << " ]";
+        }
+      }
+    }
+    break;
+
+  case G_PER_PRIM:
+    indent(out, indent_level)
+      << "Per prim:";
+    for (i = 0; i < num_prims; i++) {
+      if (newline) {
+        out << "\n";
+        indent(out, indent_level + 2);
+      } else {
+        out << " ";
+      }
+      out << array[i];
+    }
+    break;
+
+  case G_OVERALL:
+    indent(out, indent_level)
+      << "Overall:";
+    if (newline) {
+      out << "\n";
+      indent(out, indent_level + 2);
+    } else {
+      out << " ";
+    }
+    out << array[0];
+
+  case G_OFF:
+    break;
+  }
+  out << "\n";
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::Constructor
 //     Function: Geom::Constructor
 //       Access: Public
 //       Access: Public
@@ -144,19 +248,19 @@ Geom() {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: Geom::Constructor
+//     Function: Geom::Copy Constructor
 //       Access: Public
 //       Access: Public
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 Geom::
 Geom::
-Geom(const Geom& copy) : dDrawable() {
+Geom(const Geom &copy) : dDrawable() {
   _all_dirty_flags = 0;
   _all_dirty_flags = 0;
   *this = copy;
   *this = copy;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::Destructor
 //     Function: Geom::Destructor
-//       Access: Public
+//       Access: Public, Virtual
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 Geom::
 Geom::
@@ -174,12 +278,12 @@ operator = (const Geom &copy) {
   _coords = copy._coords;
   _coords = copy._coords;
   _norms = copy._norms;
   _norms = copy._norms;
   _colors = copy._colors;
   _colors = copy._colors;
-  _texcoords = copy._texcoords;
 
 
   _vindex = copy._vindex;
   _vindex = copy._vindex;
   _nindex = copy._nindex;
   _nindex = copy._nindex;
   _cindex = copy._cindex;
   _cindex = copy._cindex;
-  _tindex = copy._tindex;
+
+  _texcoords_by_name = copy._texcoords_by_name;
 
 
   _numprims = copy._numprims;
   _numprims = copy._numprims;
   _num_vertices = copy._num_vertices;
   _num_vertices = copy._num_vertices;
@@ -197,42 +301,6 @@ operator = (const Geom &copy) {
   make_dirty();
   make_dirty();
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: Geom::calc_tight_bounds
-//       Access: Public
-//  Description: Expands min_point and max_point to include all of the
-//               vertices in the Geom, if any.  found_any is set true
-//               if any points are found.  It is the caller's
-//               responsibility to initialize min_point, max_point,
-//               and found_any before calling this function.
-////////////////////////////////////////////////////////////////////
-void Geom::
-calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point, 
-                  bool &found_any) const {
-  Geom::VertexIterator vi = make_vertex_iterator();
-  int num_prims = get_num_prims();
-    
-  for (int p = 0; p < num_prims; p++) {
-    int length = get_length(p);
-    for (int v = 0; v < length; v++) {
-      Vertexf vertex = get_next_vertex(vi);
-      
-      if (found_any) {
-        min_point.set(min(min_point[0], vertex[0]),
-                      min(min_point[1], vertex[1]),
-                      min(min_point[2], vertex[2]));
-        max_point.set(max(max_point[0], vertex[0]),
-                      max(max_point[1], vertex[1]),
-                      max(max_point[2], vertex[2]));
-      } else {
-        min_point = vertex;
-        max_point = vertex;
-        found_any = true;
-      }
-    }
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::transform_vertices
 //     Function: Geom::transform_vertices
 //       Access: Published
 //       Access: Published
@@ -322,15 +390,76 @@ set_colors(const PTA_Colorf &colors, GeomBindType bind,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::set_texcoords
 //     Function: Geom::set_texcoords
 //       Access: Published
 //       Access: Published
-//  Description:
+//  Description: This single-texturing version of set_texcoords() only
+//               changes the default texcoords name.  Use the version
+//               of set_texcoords() that takes a TexCoordName
+//               parameter to set up different texture coordinates for
+//               different stages of a multitexture pipeline.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Geom::
 void Geom::
 set_texcoords(const PTA_TexCoordf &texcoords, GeomBindType bind,
 set_texcoords(const PTA_TexCoordf &texcoords, GeomBindType bind,
               const PTA_ushort &tindex) {
               const PTA_ushort &tindex) {
-  _texcoords = texcoords;
-  assert(bind == G_PER_VERTEX || bind == G_OFF);
-  _bind[G_TEXCOORD] = bind;
-  _tindex = tindex;
+  nassertv(bind == G_PER_VERTEX || bind == G_OFF);
+
+  if (bind == G_OFF) {
+    remove_texcoords(TexCoordName::get_default());
+  } else {
+    set_texcoords(TexCoordName::get_default(), texcoords, tindex);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::set_texcoords
+//       Access: Published
+//  Description: Sets the texture coordinates for a particular name of
+//               the pipeline.  This implicitly sets the binding of
+//               these texture coordinates to G_PER_VERTEX.  See also
+//               remove_texcoords().
+////////////////////////////////////////////////////////////////////
+void Geom::
+set_texcoords(const TexCoordName *name, const PTA_TexCoordf &texcoords,
+              const PTA_ushort &tindex) {
+  nassertv(name != (TexCoordName *)NULL);
+
+#ifndef NDEBUG
+  // All of the texture coordinates must be either nonindexed, or all
+  // must be indexed.  If there are already (different) texture
+  // coordinates set on the Geom, then ensure this is so.
+  remove_texcoords(name);
+  if (!_texcoords_by_name.empty()) {
+    TexCoordDef &def = (*_texcoords_by_name.begin()).second;
+    bool previous_is_indexed = (def._tindex != (ushort *)NULL);
+    bool this_is_indexed = (tindex != (ushort *)NULL);
+    nassertv(this_is_indexed == previous_is_indexed);
+  }
+#endif
+
+  TexCoordDef &def = _texcoords_by_name[name];
+  def._texcoords = texcoords;
+  def._tindex = tindex;
+
+  if (name == TexCoordName::get_default()) {
+    _bind[G_TEXCOORD] = G_PER_VERTEX;
+  }
+
+  make_dirty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::remove_texcoords
+//       Access: Published
+//  Description: Removes the texture coordinates for a particular name
+//               of the pipeline.  This implicitly sets the binding of
+//               these texture coordinates to G_OFF.  See also
+//               set_texcoords().
+////////////////////////////////////////////////////////////////////
+void Geom::
+remove_texcoords(const TexCoordName *name) {
+  _texcoords_by_name.erase(name);
+
+  if (name == TexCoordName::get_default()) {
+    _bind[G_TEXCOORD] = G_OFF;
+  }
 
 
   make_dirty();
   make_dirty();
 }
 }
@@ -341,8 +470,7 @@ set_texcoords(const PTA_TexCoordf &texcoords, GeomBindType bind,
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Geom::
 void Geom::
-get_coords(PTA_Vertexf &coords,
-           PTA_ushort &vindex) const {
+get_coords(PTA_Vertexf &coords, PTA_ushort &vindex) const {
   coords = _coords;
   coords = _coords;
   vindex = _vindex;
   vindex = _vindex;
 
 
@@ -391,14 +519,27 @@ get_colors(PTA_Colorf &colors, GeomBindType &bind,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::get_texcoords
 //     Function: Geom::get_texcoords
 //       Access: Public
 //       Access: Public
-//  Description:
+//  Description: Returns the texcoords associated with the default
+//               name only.  See has_texcoords(),
+//               get_texcoords_array(), and get_texcoords_index() to
+//               get the texcoords associated with an arbitrary name
+//               of a multitexture pipeline.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Geom::
 void Geom::
 get_texcoords(PTA_TexCoordf &texcoords, GeomBindType &bind,
 get_texcoords(PTA_TexCoordf &texcoords, GeomBindType &bind,
-              PTA_ushort &tindex) const {
-  texcoords = _texcoords;
-  bind = _bind[G_TEXCOORD];
-  tindex = _tindex;
+              PTA_ushort &tindex) const { 
+  TexCoordsByName::const_iterator tci = 
+    _texcoords_by_name.find(TexCoordName::get_default());
+  if (tci != _texcoords_by_name.end()) {
+    const TexCoordDef &def = (*tci).second;
+    texcoords = def._texcoords;
+    bind = G_PER_VERTEX;
+    tindex = def._tindex;
+  } else {
+    texcoords.clear();
+    bind = G_OFF;
+    tindex.clear();
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -469,70 +610,6 @@ get_tris() const {
   return PTA_ushort();
   return PTA_ushort();
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: Geom::draw
-//       Access: Public, Virtual
-//  Description: Actually draws the Geom with the indicated GSG.
-////////////////////////////////////////////////////////////////////
-void Geom::
-draw(GraphicsStateGuardianBase *gsg) {
-  PreparedGraphicsObjects *prepared_objects = gsg->get_prepared_objects();
-  if (is_dirty()) {
-    config(); 
-    release(prepared_objects);
-  }
-
-  if (retained_mode) {
-    GeomContext *gc = prepare_now(gsg->get_prepared_objects(), gsg);
-    draw_immediate(gsg, gc);
-  } else {
-    draw_immediate(gsg, NULL);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Geom::config
-//       Access: Public, Virtual
-//  Description: Configure rendering based on current settings
-////////////////////////////////////////////////////////////////////
-void Geom::
-config() {
-  WritableConfigurable::config();
-
-  // Only per vertex binding makes any sense
-  if (_coords != (Vertexf*)0L && _bind[G_COORD] != G_OFF) {
-    _get_vertex =
-      (_vindex == (ushort*)0L) ? get_vertex_nonindexed : get_vertex_indexed;
-  } else {
-    gobj_cat.error()
-      << "Geom::Config() - no vertex array!" << endl;
-  }
-
-  // Set up normal rendering configuration
-  if (_norms != (Normalf*)0L && _bind[G_NORMAL] != G_OFF) {
-    _get_normal =
-      (_nindex == (ushort*)0L) ? get_normal_nonindexed : get_normal_indexed;
-  } else {
-    _get_normal = get_normal_noop;
-  }
-
-  // Set up texture coordinate rendering configuration
-  if (_texcoords != (TexCoordf*)0L && _bind[G_TEXCOORD] != G_OFF) {
-    _get_texcoord =
-      (_tindex == (ushort*)0L) ? get_texcoord_nonindexed : get_texcoord_indexed;
-  } else {
-    _get_texcoord = get_texcoord_noop;
-  }
-
-  // Set up color rendering configuration
-  if (_colors != (Colorf*)0L && _bind[G_COLOR] != G_OFF) {
-    _get_color =
-      (_cindex == (ushort*)0L) ? get_color_nonindexed : get_color_indexed;
-  } else {
-    _get_color = get_color_noop;
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::write
 //     Function: Geom::write
 //       Access: Public
 //       Access: Public
@@ -565,31 +642,170 @@ output(ostream &out) const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: Geom::prepare_now
+//     Function: Geom::write_verbose
 //       Access: Public
 //       Access: Public
-//  Description: Creates a context for the geom on the particular
-//               GSG, if it does not already exist.  Returns the new
-//               (or old) GeomContext.  This assumes that the
-//               GraphicsStateGuardian is the currently active
-//               rendering context and that it is ready to accept new
-//               geoms.  If this is not necessarily the case, you
-//               should use prepare() instead.
-//
-//               Normally, this is not called directly except by the
-//               GraphicsStateGuardian; a geom does not need to be
-//               explicitly prepared by the user before it may be
-//               rendered.
+//  Description: Writes to the indicated ostream a formatted picture
+//               of the contents of the Geom, in detail--but hopefully
+//               not too much detail.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-GeomContext *Geom::
-prepare_now(PreparedGraphicsObjects *prepared_objects, 
-            GraphicsStateGuardianBase *gsg) {
-  Contexts::const_iterator ci;
-  ci = _contexts.find(prepared_objects);
-  if (ci != _contexts.end()) {
-    return (*ci).second;
-  }
-
-  GeomContext *gc = prepared_objects->prepare_geom_now(this, gsg);
+void Geom::
+write_verbose(ostream &out, int indent_level) const {
+  GeomBindType bind_normals;
+  GeomBindType bind_colors;
+
+  PTA_Vertexf g_coords;
+  PTA_Normalf g_normals;
+  PTA_Colorf g_colors;
+
+  PTA_ushort i_coords;
+  PTA_ushort i_normals;
+  PTA_ushort i_colors;
+
+  get_coords(g_coords, i_coords);
+  get_normals(g_normals, bind_normals, i_normals);
+  get_colors(g_colors, bind_colors, i_colors);
+
+  out << "\n";
+  indent(out, indent_level)
+    << get_type() << " contains "
+    << get_num_prims() << " primitives:\n";
+
+  if ((i_coords == (ushort *)NULL) && (g_coords == (Vertexf *)NULL)) {
+    indent(out, indent_level)
+      << "No coords\n";
+  } else if (i_coords!=(ushort*)0L) {
+    indent(out, indent_level)
+      << "Indexed coords = " << (void *)g_coords << ", length = "
+      << g_coords.size() << ":\n";
+    describe_attr(out, this, G_PER_VERTEX, i_coords, false, indent_level + 2);
+  } else {
+    indent(out, indent_level)
+      << "Nonindexed coords:\n";
+    describe_attr(out, this, G_PER_VERTEX, g_coords, true, indent_level + 2);
+  }
+
+  if (bind_colors == G_OFF) {
+    indent(out, indent_level)
+      << "No colors\n";
+  } else if (i_colors!=(ushort*)0L) {
+    indent(out, indent_level)
+      << "Indexed colors = " << (void *)g_colors << ", length = "
+      << g_colors.size() << "\n";
+    describe_attr(out, this, bind_colors, i_colors, false, indent_level + 2);
+  } else {
+    indent(out, indent_level)
+      << "Nonindexed colors:\n";
+    describe_attr(out, this, bind_colors, g_colors, true, indent_level + 2);
+  }
+
+  if (bind_normals == G_OFF) {
+    indent(out, indent_level)
+      << "No normals\n";
+  } else if (i_normals!=(ushort*)0L) {
+    indent(out, indent_level)
+      << "Indexed normals = " << (void *)g_normals << ", length = "
+      << g_normals.size() << "\n";
+    describe_attr(out, this, bind_normals, i_normals, false, indent_level + 2);
+  } else {
+    indent(out, indent_level)
+      << "Nonindexed normals:\n";
+    describe_attr(out, this, bind_normals, g_normals, true, indent_level + 2);
+  }
+
+  if (_texcoords_by_name.empty()) {
+    indent(out, indent_level)
+      << "No texcoords\n";
+
+  } else {
+    TexCoordsByName::const_iterator tci;
+    for (tci = _texcoords_by_name.begin(); 
+         tci != _texcoords_by_name.end();
+         ++tci) {
+      const TexCoordName *name = (*tci).first;
+      const TexCoordDef &def = (*tci).second;
+      if (def._tindex != (ushort *)NULL) {
+        indent(out, indent_level)
+          << "Indexed texcoords \"" << name->get_name() << "\" = "
+          << def._texcoords << ", length = " << def._texcoords.size() << "\n";
+        describe_attr(out, this, G_PER_VERTEX, def._tindex, false, 
+                      indent_level + 2);
+
+      } else {
+        indent(out, indent_level)
+          << "Nonindexed texcoords \"" << name->get_name() << "\":\n";
+        describe_attr(out, this, G_PER_VERTEX, def._texcoords, true, 
+                      indent_level + 2);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::setup_multitexcoord_iterator
+//       Access: Public
+//  Description: Fills in the values on the indicated
+//               MultiTexCoordIterator to prepare it to walk through
+//               the texture coordinates on this Geom, to issue the
+//               appropriate texture coordinates for the currently
+//               active stages.
+////////////////////////////////////////////////////////////////////
+void Geom::
+setup_multitexcoord_iterator(MultiTexCoordIterator &iterator,
+                             const ActiveTextureStages &active_stages,
+                             const NoTexCoordStages &no_texcoords) const {
+  check_config();
+  iterator._num_stages = 0;
+  int max_stage_index = (int)active_stages.size();
+  int i = 0;
+  for (int stage_index = 0; stage_index < max_stage_index; stage_index++) {
+    TextureStage *stage = active_stages[stage_index];
+    if (no_texcoords.find(stage) == no_texcoords.end()) {
+      // This stage is not one of the stages that doesn't need
+      // texcoords issued for it.
+      const TexCoordName *name = stage->get_texcoord_name();
+      TexCoordsByName::const_iterator tci = _texcoords_by_name.find(name);
+      if (tci != _texcoords_by_name.end()) {
+        // This Geom does have texcoords for this stage.
+        const TexCoordDef &def = (*tci).second;
+        
+        nassertv(i < max_geom_texture_stages);
+        iterator._stages[i]._array = def._texcoords;
+        iterator._stages[i]._index = def._tindex;
+        iterator._stage_index[i] = stage_index;
+        i++;
+      }
+    }
+  }
+
+  iterator._num_stages = i;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::prepare_now
+//       Access: Public
+//  Description: Creates a context for the geom on the particular
+//               GSG, if it does not already exist.  Returns the new
+//               (or old) GeomContext.  This assumes that the
+//               GraphicsStateGuardian is the currently active
+//               rendering context and that it is ready to accept new
+//               geoms.  If this is not necessarily the case, you
+//               should use prepare() instead.
+//
+//               Normally, this is not called directly except by the
+//               GraphicsStateGuardian; a geom does not need to be
+//               explicitly prepared by the user before it may be
+//               rendered.
+////////////////////////////////////////////////////////////////////
+GeomContext *Geom::
+prepare_now(PreparedGraphicsObjects *prepared_objects, 
+            GraphicsStateGuardianBase *gsg) {
+  Contexts::const_iterator ci;
+  ci = _contexts.find(prepared_objects);
+  if (ci != _contexts.end()) {
+    return (*ci).second;
+  }
+
+  GeomContext *gc = prepared_objects->prepare_geom_now(this, gsg);
   if (gc != (GeomContext *)NULL) {
   if (gc != (GeomContext *)NULL) {
     _contexts[prepared_objects] = gc;
     _contexts[prepared_objects] = gc;
 
 
@@ -653,6 +869,118 @@ release_all() {
   return num_freed;
   return num_freed;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::draw
+//       Access: Public, Virtual
+//  Description: Actually draws the Geom with the indicated GSG.
+////////////////////////////////////////////////////////////////////
+void Geom::
+draw(GraphicsStateGuardianBase *gsg) const {
+  PreparedGraphicsObjects *prepared_objects = gsg->get_prepared_objects();
+  if (is_dirty()) {
+    ((Geom *)this)->config(); 
+    ((Geom *)this)->release(prepared_objects);
+  }
+
+  if (retained_mode) {
+    GeomContext *gc = ((Geom *)this)->prepare_now(gsg->get_prepared_objects(), gsg);
+    ((Geom *)this)->draw_immediate(gsg, gc);
+  } else {
+    ((Geom *)this)->draw_immediate(gsg, NULL);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::config
+//       Access: Public, Virtual
+//  Description: Configure rendering based on current settings
+////////////////////////////////////////////////////////////////////
+void Geom::
+config() {
+  WritableConfigurable::config();
+
+  // Only per vertex binding makes any sense
+  if (_coords != (Vertexf*)0L && _bind[G_COORD] != G_OFF) {
+    _get_vertex =
+      (_vindex == (ushort*)0L) ? get_vertex_nonindexed : get_vertex_indexed;
+  } else {
+    gobj_cat.error()
+      << "Geom::Config() - no vertex array!" << endl;
+  }
+
+  // Set up normal rendering configuration
+  if (_norms != (Normalf*)0L && _bind[G_NORMAL] != G_OFF) {
+    _get_normal =
+      (_nindex == (ushort*)0L) ? get_normal_nonindexed : get_normal_indexed;
+  } else {
+    _get_normal = get_normal_noop;
+  }
+
+  // Set up texture coordinate rendering configuration
+  if (_texcoords_by_name.empty()) {
+    _get_texcoord = get_texcoord_noop;
+
+  } else {
+    _get_texcoord = get_texcoord_indexed;
+
+    // If any of the texture coordinates are nonindexed, all of them
+    // must be.
+    TexCoordsByName::const_iterator tci;
+    for (tci = _texcoords_by_name.begin(); 
+         tci != _texcoords_by_name.end();
+         ++tci) {
+      if ((*tci).second._tindex == (ushort *)NULL) {
+        _get_texcoord = get_texcoord_nonindexed;
+        break;
+      }
+    }
+  }
+
+  // Set up color rendering configuration
+  if (_colors != (Colorf*)0L && _bind[G_COLOR] != G_OFF) {
+    _get_color =
+      (_cindex == (ushort*)0L) ? get_color_nonindexed : get_color_indexed;
+  } else {
+    _get_color = get_color_noop;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::calc_tight_bounds
+//       Access: Public
+//  Description: Expands min_point and max_point to include all of the
+//               vertices in the Geom, if any.  found_any is set true
+//               if any points are found.  It is the caller's
+//               responsibility to initialize min_point, max_point,
+//               and found_any before calling this function.
+////////////////////////////////////////////////////////////////////
+void Geom::
+calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point, 
+                  bool &found_any) const {
+  Geom::VertexIterator vi = make_vertex_iterator();
+  int num_prims = get_num_prims();
+    
+  for (int p = 0; p < num_prims; p++) {
+    int length = get_length(p);
+    for (int v = 0; v < length; v++) {
+      Vertexf vertex = get_next_vertex(vi);
+      
+      if (found_any) {
+        min_point.set(min(min_point[0], vertex[0]),
+                      min(min_point[1], vertex[1]),
+                      min(min_point[2], vertex[2]));
+        max_point.set(max(max_point[0], vertex[0]),
+                      max(max_point[1], vertex[1]),
+                      max(max_point[2], vertex[2]));
+      } else {
+        min_point = vertex;
+        max_point = vertex;
+        found_any = true;
+      }
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::init
 //     Function: Geom::init
 //       Access: Protected
 //       Access: Protected
@@ -665,11 +993,10 @@ init() {
   _coords.clear();
   _coords.clear();
   _norms.clear();
   _norms.clear();
   _colors.clear();
   _colors.clear();
-  _texcoords.clear();
   _vindex.clear();
   _vindex.clear();
   _nindex.clear();
   _nindex.clear();
   _cindex.clear();
   _cindex.clear();
-  _tindex.clear();
+  _texcoords_by_name.clear();
   _primlengths.clear();
   _primlengths.clear();
 
 
   for ( i = 0; i < num_GeomAttrTypes; i++ )
   for ( i = 0; i < num_GeomAttrTypes; i++ )
@@ -738,6 +1065,25 @@ clear_prepared(PreparedGraphicsObjects *prepared_objects) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::sum_lengths
+//       Access: Private, Static
+//  Description: Returns the total number of vertices named in the
+//               lengths array.
+////////////////////////////////////////////////////////////////////
+int Geom::
+sum_lengths(const PTA_int &lengths) {
+  int num_vertices = 0;
+
+  for (PTA_int::const_iterator li = lengths.begin();
+       li != lengths.end();
+       ++li) {
+    num_vertices += (*li);
+  }
+
+  return num_vertices;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::write_datagram
 //     Function: Geom::write_datagram
 //       Access: Public
 //       Access: Public
@@ -748,31 +1094,85 @@ void Geom::
 write_datagram(BamWriter *manager, Datagram &me) {
 write_datagram(BamWriter *manager, Datagram &me) {
   int i;
   int i;
 
 
-  //Coordinates
-  WRITE_PTA(manager, me, IPD_Vertexf::write_datagram, _coords)
-  //Normals
-  WRITE_PTA(manager, me, IPD_Normalf::write_datagram, _norms)
-  //Colors
-  WRITE_PTA(manager, me, IPD_Colorf::write_datagram, _colors)
-  //Texture Coordinates
-  WRITE_PTA(manager, me, IPD_TexCoordf::write_datagram, _texcoords)
-
-  //Now write out the indices for each array
-  WRITE_PTA(manager, me, IPD_ushort::write_datagram, _vindex)
-  WRITE_PTA(manager, me, IPD_ushort::write_datagram, _nindex)
-  WRITE_PTA(manager, me, IPD_ushort::write_datagram, _cindex)
-  WRITE_PTA(manager, me, IPD_ushort::write_datagram, _tindex)
+  // Coordinates
+  WRITE_PTA(manager, me, IPD_Vertexf::write_datagram, _coords);
+  // Normals
+  WRITE_PTA(manager, me, IPD_Normalf::write_datagram, _norms);
+  // Colors
+  WRITE_PTA(manager, me, IPD_Colorf::write_datagram, _colors);
+  /*
+  // pre bam 4.11
+  // Texture Coordinates
+  WRITE_PTA(manager, me, IPD_TexCoordf::write_datagram, get_texcoords_array());
+  */
+  // write the multitexture data
+  nassertv(_texcoords_by_name.size() < 256);
+  me.add_uint8(_texcoords_by_name.size());  // write the size of the map
+  // write the TexCoordsByName pointers if any
+  TexCoordsByName::const_iterator tci;
+  for (tci = _texcoords_by_name.begin(); tci != _texcoords_by_name.end(); ++tci) {
+    CPT(TexCoordName) tc = (*tci).first;
+    manager->write_pointer(me, tc);
+    WRITE_PTA(manager, me, IPD_TexCoordf::write_datagram, (*tci).second._texcoords);
+    WRITE_PTA(manager, me, IPD_ushort::write_datagram, (*tci).second._tindex);
+  }
+
+  // Now write out the indices for each array
+  WRITE_PTA(manager, me, IPD_ushort::write_datagram, _vindex);
+  WRITE_PTA(manager, me, IPD_ushort::write_datagram, _nindex);
+  WRITE_PTA(manager, me, IPD_ushort::write_datagram, _cindex);
+  /*
+  // pre bam 4.11
+  WRITE_PTA(manager, me, IPD_ushort::write_datagram, get_texcoords_index());
+  */
+  // the texcoord indices are already written with the texcoords
 
 
   me.add_uint16(_numprims);
   me.add_uint16(_numprims);
-  WRITE_PTA(manager, me, IPD_int::write_datagram, _primlengths)
+  WRITE_PTA(manager, me, IPD_int::write_datagram, _primlengths);
 
 
-  //Write out the bindings for vertices, normals,
-  //colors and texture coordinates
+  // Write out the bindings for vertices, normals, colors and texture
+  // coordinates
   for(i = 0; i < num_GeomAttrTypes; i++) {
   for(i = 0; i < num_GeomAttrTypes; i++) {
     me.add_uint8(_bind[i]);
     me.add_uint8(_bind[i]);
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::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 Geom::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = dDrawable::complete_pointers(p_list, manager);
+
+  // complete the pointers and build the _texcoords_by_name
+  TexCoordDefSet::iterator ci;
+  for (ci = _temp_texcoord_set.begin();
+       ci != _temp_texcoord_set.end();
+       ++ci) {
+    CPT(TexCoordName) tc = DCAST(TexCoordName, p_list[pi++]);
+    TexCoordDef *def = (*ci);
+    _texcoords_by_name[tc] = *def;
+    /*
+    cerr << "TexCoordName from Geom " << (void *)this
+         << " complete pointers " << tc << " " << *tc 
+         << " = " << def->_texcoords << " and " << def->_tindex << "\n";
+    */
+    delete def;
+    if (tc == TexCoordName::get_default()) {
+      _bind[G_TEXCOORD] = G_PER_VERTEX;
+    }
+  }
+  _temp_texcoord_set.clear();
+
+  make_dirty();
+
+  return pi;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::fillin
 //     Function: Geom::fillin
@@ -786,230 +1186,63 @@ void Geom::
 fillin(DatagramIterator& scan, BamReader* manager) {
 fillin(DatagramIterator& scan, BamReader* manager) {
   int i;
   int i;
 
 
-  //Coordinates
-  READ_PTA(manager, scan, IPD_Vertexf::read_datagram, _coords)
-  //Normals
-  READ_PTA(manager, scan, IPD_Normalf::read_datagram, _norms)
-  //Colors
-  READ_PTA(manager, scan, IPD_Colorf::read_datagram, _colors)
-  //Texture Coordinates
-  READ_PTA(manager, scan, IPD_TexCoordf::read_datagram, _texcoords)
-
-  //Now read in the indices for each array
-  READ_PTA(manager, scan, IPD_ushort::read_datagram, _vindex)
-  READ_PTA(manager, scan, IPD_ushort::read_datagram, _nindex)
-  READ_PTA(manager, scan, IPD_ushort::read_datagram, _cindex)
-  READ_PTA(manager, scan, IPD_ushort::read_datagram, _tindex)
+  // Coordinates
+  READ_PTA(manager, scan, IPD_Vertexf::read_datagram, _coords);
+  // Normals
+  READ_PTA(manager, scan, IPD_Normalf::read_datagram, _norms);
+  // Colors
+  READ_PTA(manager, scan, IPD_Colorf::read_datagram, _colors);
+  // Texture Coordinates
+  PTA_TexCoordf texcoords;
+  if (manager->get_file_minor_ver() < 11) {
+    READ_PTA(manager, scan, IPD_TexCoordf::read_datagram, texcoords);
+  } else {
+    // read the multitexture data
+    int num_texcoords_by_names = scan.get_uint8();
+    // read the TexCoordsByName pointers of num_texcoords_by_names
+    TexCoordsByName::const_iterator tci;
+    _temp_texcoord_set.reserve(num_texcoords_by_names);
+    for (int i=0; i<num_texcoords_by_names; ++i) {
+      manager->read_pointer(scan);
+      TexCoordDef *tcd = new TexCoordDef;
+      READ_PTA(manager, scan, IPD_TexCoordf::read_datagram, tcd->_texcoords);
+      READ_PTA(manager, scan, IPD_ushort::read_datagram, tcd->_tindex);
+      _temp_texcoord_set.push_back(tcd);
+    }
+  }
+      
+  // Now read in the indices for each array
+  READ_PTA(manager, scan, IPD_ushort::read_datagram, _vindex);
+  READ_PTA(manager, scan, IPD_ushort::read_datagram, _nindex);
+  READ_PTA(manager, scan, IPD_ushort::read_datagram, _cindex);
+  PTA_ushort tindex;
+  if (manager->get_file_minor_ver() < 11) {
+    READ_PTA(manager, scan, IPD_ushort::read_datagram, tindex);
+  }
 
 
   _numprims = scan.get_uint16();
   _numprims = scan.get_uint16();
 
 
   // is there any point in doing this for uses_components()==false?
   // is there any point in doing this for uses_components()==false?
-  READ_PTA(manager, scan, IPD_int::read_datagram, _primlengths)
+  READ_PTA(manager, scan, IPD_int::read_datagram, _primlengths);
 
 
   if (uses_components()) {
   if (uses_components()) {
-      _num_vertices = PTA_int_arraysum(_primlengths);
+    _num_vertices = sum_lengths(_primlengths);
+
   } else {
   } else {
-      // except for strips & fans with the length arrays, total verts will be simply this
-      _num_vertices = _numprims*get_num_vertices_per_prim();
+    // except for strips & fans with the length arrays, total verts
+    // will be simply this
+    _num_vertices = _numprims * get_num_vertices_per_prim();
   }
   }
 
 
-  //Write out the bindings for vertices, normals,
-  //colors and texture coordinates
+  // Read in the bindings for vertices, normals, colors and texture
+  // coordinates
   for(i = 0; i < num_GeomAttrTypes; i++) {
   for(i = 0; i < num_GeomAttrTypes; i++) {
     _bind[i] = (enum GeomBindType) scan.get_uint8();
     _bind[i] = (enum GeomBindType) scan.get_uint8();
   }
   }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: describe_attr
-//  Description: A handy helper function for output_verbose,
-//               below.
-////////////////////////////////////////////////////////////////////
-template <class VecType>
-static void
-describe_attr(ostream &out, const Geom *geom,
-              GeomBindType bind, const PTA(VecType) &array,
-              bool newline, int indent_level) {
-  PTA_int lengths = geom->get_lengths();
-  int num_prims = geom->get_num_prims();
-  bool components = geom->uses_components();
-
-  int i, j, vi;
-  switch (bind) {
-  case G_PER_VERTEX:
-    indent(out, indent_level)
-      << "Per vertex:";
-    vi = 0;
-    int num_verts;
-    num_verts = geom->get_num_vertices_per_prim();
-    for (i = 0; i < num_prims; i++) {
-      if (components) {
-        num_verts = lengths[i];
-      }
-      out << "\n";
-      indent(out, indent_level) << "[ ";
-      if (num_verts > 0) {
-        out << array[vi++];
-        for (j = 1; j < num_verts; j++) {
-          if (newline) {
-            out << "\n";
-            indent(out, indent_level + 2);
-          } else {
-            out << " ";
-          }
-          out << array[vi++];
-        }
-      }
-      out << " ]";
-    }
-    break;
 
 
-  case G_PER_COMPONENT:
-    if (!components) {
-      indent(out, indent_level)
-        << "Invalid per-component attribute specified!";
-    } else {
-      indent(out, indent_level)
-        << "Per component:";
-      vi = 0;
-      for (i = 0; i < num_prims; i++) {
-        num_verts = lengths[i] - geom->get_num_more_vertices_than_components();
-        out << "\n";
-        indent(out, indent_level) << "[ ";
-        if (num_verts > 0) {
-          out << array[vi++];
-          for (j = 1; j < num_verts; j++) {
-            if (newline) {
-              out << "\n";
-              indent(out, indent_level + 2);
-            } else {
-              out << " ";
-            }
-            out << array[vi++];
-          }
-          out << " ]";
-        }
-      }
+  if (manager->get_file_minor_ver() < 11) {
+    if (_bind[G_TEXCOORD] == G_PER_VERTEX) {
+      set_texcoords(texcoords, G_PER_VERTEX, tindex);
     }
     }
-    break;
-
-  case G_PER_PRIM:
-    indent(out, indent_level)
-      << "Per prim:";
-    for (i = 0; i < num_prims; i++) {
-      if (newline) {
-        out << "\n";
-        indent(out, indent_level + 2);
-      } else {
-        out << " ";
-      }
-      out << array[i];
-    }
-    break;
-
-  case G_OVERALL:
-    indent(out, indent_level)
-      << "Overall:";
-    if (newline) {
-      out << "\n";
-      indent(out, indent_level + 2);
-    } else {
-      out << " ";
-    }
-    out << array[0];
-
-  case G_OFF:
-    break;
-  }
-  out << "\n";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Geom::write_verbose
-//       Access: Public
-//  Description: Writes to the indicated ostream a formatted picture
-//               of the contents of the Geom, in detail--but hopefully
-//               not too much detail.
-////////////////////////////////////////////////////////////////////
-void Geom::
-write_verbose(ostream &out, int indent_level) const {
-  GeomBindType bind_normals;
-  GeomBindType bind_tcoords;
-  GeomBindType bind_colors;
-
-  PTA_Vertexf g_coords;
-  PTA_Normalf g_normals;
-  PTA_TexCoordf g_tcoords;
-  PTA_Colorf g_colors;
-
-  PTA_ushort i_coords;
-  PTA_ushort i_normals;
-  PTA_ushort i_tcoords;
-  PTA_ushort i_colors;
-
-  get_coords(g_coords, i_coords);
-  get_normals(g_normals, bind_normals, i_normals);
-  get_texcoords(g_tcoords, bind_tcoords, i_tcoords);
-  get_colors(g_colors, bind_colors, i_colors);
-
-  out << "\n";
-  indent(out, indent_level)
-    << get_type() << " contains "
-    << get_num_prims() << " primitives:\n";
-
-  if ((i_coords == (ushort *)NULL) && (g_coords == (Vertexf *)NULL)) {
-    indent(out, indent_level)
-      << "No coords\n";
-  } else if (i_coords!=(ushort*)0L) {
-    indent(out, indent_level)
-      << "Indexed coords = " << (void *)g_coords << ", length = "
-      << g_coords.size() << ":\n";
-    describe_attr(out, this, G_PER_VERTEX, i_coords, false, indent_level + 2);
-  } else {
-    indent(out, indent_level)
-      << "Nonindexed coords:\n";
-    describe_attr(out, this, G_PER_VERTEX, g_coords, true, indent_level + 2);
-  }
-
-  if (bind_colors == G_OFF) {
-    indent(out, indent_level)
-      << "No colors\n";
-  } else if (i_colors!=(ushort*)0L) {
-    indent(out, indent_level)
-      << "Indexed colors = " << (void *)g_colors << ", length = "
-      << g_colors.size() << "\n";
-    describe_attr(out, this, bind_colors, i_colors, false, indent_level + 2);
-  } else {
-    indent(out, indent_level)
-      << "Nonindexed colors:\n";
-    describe_attr(out, this, bind_colors, g_colors, true, indent_level + 2);
-  }
-
-  if (bind_tcoords == G_OFF) {
-    indent(out, indent_level)
-      << "No tcoords\n";
-  } else if (i_tcoords!=(ushort*)0L) {
-    indent(out, indent_level)
-      << "Indexed tcoords = " << (void *)g_tcoords << ", length = "
-      << g_tcoords.size() << "\n";
-    describe_attr(out, this, bind_tcoords, i_tcoords, false, indent_level + 2);
-  } else {
-    indent(out, indent_level)
-      << "Nonindexed tcoords:\n";
-    describe_attr(out, this, bind_tcoords, g_tcoords, true, indent_level + 2);
-  }
-
-  if (bind_normals == G_OFF) {
-    indent(out, indent_level)
-      << "No normals\n";
-  } else if (i_normals!=(ushort*)0L) {
-    indent(out, indent_level)
-      << "Indexed normals = " << (void *)g_normals << ", length = "
-      << g_normals.size() << "\n";
-    describe_attr(out, this, bind_normals, i_normals, false, indent_level + 2);
-  } else {
-    indent(out, indent_level)
-      << "Nonindexed normals:\n";
-    describe_attr(out, this, bind_normals, g_normals, true, indent_level + 2);
   }
   }
 }
 }

+ 84 - 51
panda/src/gobj/geom.h

@@ -23,6 +23,7 @@
 #include "drawable.h"
 #include "drawable.h"
 
 
 #include "vector_typedWritable.h"
 #include "vector_typedWritable.h"
+#include "ordered_vector.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
 #include "pointerToArray.h"
 #include "pointerToArray.h"
 #include "typedef.h"
 #include "typedef.h"
@@ -33,12 +34,16 @@
 #include "pta_TexCoordf.h"
 #include "pta_TexCoordf.h"
 #include "pta_ushort.h"
 #include "pta_ushort.h"
 #include "pta_int.h"
 #include "pta_int.h"
-#include "texture.h"
+#include "texCoordName.h"
+#include "textureStage.h"
+#include "pset.h"
 
 
 class Datagram;
 class Datagram;
 class DatagramIterator;
 class DatagramIterator;
 class BamReader;
 class BamReader;
 class BamWriter;
 class BamWriter;
+class GeomContext;
+class PreparedGraphicsObjects;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 // Defines
 // Defines
@@ -67,6 +72,11 @@ static const int num_GeomAttrTypes = 4;
 ostream &operator << (ostream &out, GeomBindType t);
 ostream &operator << (ostream &out, GeomBindType t);
 ostream &operator << (ostream &out, GeomAttrType t);
 ostream &operator << (ostream &out, GeomAttrType t);
 
 
+// This is a totally arbitrary limit and may be increased almost
+// without penalty.  It is just used to control the static size of the
+// array stored in the MultiTexcoordIterator, below.
+static const int max_geom_texture_stages = 32;
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : Geom
 //       Class : Geom
 // Description : Geometry parent class
 // Description : Geometry parent class
@@ -92,6 +102,12 @@ public:
     const TexCoordf *_array;
     const TexCoordf *_array;
     const ushort *_index;
     const ushort *_index;
   };
   };
+  class MultiTexCoordIterator {
+  public:
+    int _num_stages;
+    TexCoordIterator _stages[max_geom_texture_stages];
+    int _stage_index[max_geom_texture_stages];
+  };
   class ColorIterator {
   class ColorIterator {
   public:
   public:
     const Colorf *_array;
     const Colorf *_array;
@@ -112,84 +128,63 @@ public:
 
 
   Geom();
   Geom();
   Geom(const Geom &copy);
   Geom(const Geom &copy);
-  ~Geom();
+  virtual ~Geom();
 
 
   void operator = (const Geom &copy);
   void operator = (const Geom &copy);
   virtual Geom *make_copy() const=0;
   virtual Geom *make_copy() const=0;
 
 
-PUBLISHED:
-  void write(ostream &out, int indent_level = 0) const;
-  virtual void output(ostream &out) const;
-  void write_verbose(ostream &out, int indent_level) const;
-
-public:
-  // From parent dDrawable
-  virtual void draw(GraphicsStateGuardianBase *gsg);
-
-  // From parent Configurable
-  virtual void config();
-
-  // Immediate mode drawing functions - issue graphics commands
-  virtual void draw_immediate(GraphicsStateGuardianBase *gsg, GeomContext *gc) = 0;
-  virtual void print_draw_immediate() const = 0;
-
-  void calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
-                         bool &found_any) const;
 PUBLISHED:
 PUBLISHED:
   void transform_vertices(const LMatrix4f &mat);
   void transform_vertices(const LMatrix4f &mat);
 
 
   void set_coords(const PTA_Vertexf &coords,
   void set_coords(const PTA_Vertexf &coords,
-                   const PTA_ushort &vindex =
-                   PTA_ushort());
-  void set_coords(const PTA_Vertexf &coords,
-                   GeomBindType bind,
-                   const PTA_ushort &vindex =
-                   PTA_ushort());
-  void set_normals(const PTA_Normalf &norms,
-                   GeomBindType bind,
-                   const PTA_ushort &nindex =
-                   PTA_ushort());
-  void set_colors(const PTA_Colorf &colors,
-                  GeomBindType bind,
-                  const PTA_ushort &cindex =
-                  PTA_ushort());
-  void set_texcoords(const PTA_TexCoordf &texcoords,
-                     GeomBindType bind,
-                     const PTA_ushort &tindex =
-                     PTA_ushort());
+                  const PTA_ushort &vindex = PTA_ushort());
+  void set_coords(const PTA_Vertexf &coords, GeomBindType bind,
+                  const PTA_ushort &vindex = PTA_ushort());
+  void set_normals(const PTA_Normalf &norms, GeomBindType bind,
+                   const PTA_ushort &nindex = PTA_ushort());
+  void set_colors(const PTA_Colorf &colors, GeomBindType bind,
+                  const PTA_ushort &cindex = PTA_ushort());
+  void set_texcoords(const PTA_TexCoordf &texcoords, GeomBindType bind,
+                     const PTA_ushort &tindex = PTA_ushort());
+  void set_texcoords(const TexCoordName *name, const PTA_TexCoordf &texcoords,
+                     const PTA_ushort &tindex = PTA_ushort());
+  void remove_texcoords(const TexCoordName *name);
 
 
 public:
 public:
   // These can't be published because of the pass-by-reference
   // These can't be published because of the pass-by-reference
   // primitive types.
   // primitive types.
-  void get_coords(PTA_Vertexf &coords,
-                  GeomBindType &bind,
+  void get_coords(PTA_Vertexf &coords, GeomBindType &bind,
                   PTA_ushort &vindex) const;
                   PTA_ushort &vindex) const;
 
 
-  void get_coords(PTA_Vertexf &coords,
-                  PTA_ushort &vindex) const;
+  void get_coords(PTA_Vertexf &coords, PTA_ushort &vindex) const;
 
 
-  void get_normals(PTA_Normalf &norms,
-                   GeomBindType &bind,
+  void get_normals(PTA_Normalf &norms, GeomBindType &bind,
                    PTA_ushort &nindex) const;
                    PTA_ushort &nindex) const;
   void get_colors(PTA_Colorf &colors,
   void get_colors(PTA_Colorf &colors,
-                  GeomBindType &bind,
-                  PTA_ushort &cindex) const;
-  void get_texcoords(PTA_TexCoordf &texcoords,
-                     GeomBindType &bind,
+                  GeomBindType &bind, PTA_ushort &cindex) const;
+  void get_texcoords(PTA_TexCoordf &texcoords, GeomBindType &bind,
                      PTA_ushort &tindex) const;
                      PTA_ushort &tindex) const;
 
 
+
 PUBLISHED:
 PUBLISHED:
   virtual bool is_dynamic() const;
   virtual bool is_dynamic() const;
 
 
   INLINE GeomBindType get_binding(int attr) const;
   INLINE GeomBindType get_binding(int attr) const;
+  INLINE bool has_any_texcoords() const;
+  INLINE bool has_texcoords(const TexCoordName *name) const;
+
   INLINE PTA_Vertexf get_coords_array() const;
   INLINE PTA_Vertexf get_coords_array() const;
   INLINE PTA_Normalf get_normals_array() const;
   INLINE PTA_Normalf get_normals_array() const;
   INLINE PTA_Colorf get_colors_array() const;
   INLINE PTA_Colorf get_colors_array() const;
   INLINE PTA_TexCoordf get_texcoords_array() const;
   INLINE PTA_TexCoordf get_texcoords_array() const;
+  INLINE PTA_TexCoordf get_texcoords_array(const TexCoordName *name) const;
+
   INLINE PTA_ushort get_coords_index() const;
   INLINE PTA_ushort get_coords_index() const;
   INLINE PTA_ushort get_normals_index() const;
   INLINE PTA_ushort get_normals_index() const;
   INLINE PTA_ushort get_colors_index() const;
   INLINE PTA_ushort get_colors_index() const;
   INLINE PTA_ushort get_texcoords_index() const;
   INLINE PTA_ushort get_texcoords_index() const;
+  INLINE PTA_ushort get_texcoords_index(const TexCoordName *name) const;
+  INLINE bool are_texcoords_indexed() const;
 
 
   void prepare(PreparedGraphicsObjects *prepared_objects);
   void prepare(PreparedGraphicsObjects *prepared_objects);
 
 
@@ -213,7 +208,14 @@ PUBLISHED:
   virtual Geom *explode() const;
   virtual Geom *explode() const;
   virtual PTA_ushort get_tris() const;
   virtual PTA_ushort get_tris() const;
 
 
+  void write(ostream &out, int indent_level = 0) const;
+  virtual void output(ostream &out) const;
+  void write_verbose(ostream &out, int indent_level) const;
+
 public:
 public:
+  typedef pvector< PT(TextureStage) > ActiveTextureStages;
+  typedef pset<TextureStage *> NoTexCoordStages;
+
   INLINE VertexIterator make_vertex_iterator() const;
   INLINE VertexIterator make_vertex_iterator() const;
   INLINE const Vertexf &get_next_vertex(VertexIterator &viterator) const;
   INLINE const Vertexf &get_next_vertex(VertexIterator &viterator) const;
 
 
@@ -222,6 +224,10 @@ public:
 
 
   INLINE TexCoordIterator make_texcoord_iterator() const;
   INLINE TexCoordIterator make_texcoord_iterator() const;
   INLINE const TexCoordf &get_next_texcoord(TexCoordIterator &tciterator) const;
   INLINE const TexCoordf &get_next_texcoord(TexCoordIterator &tciterator) const;
+  void setup_multitexcoord_iterator(MultiTexCoordIterator &iterator,
+                                    const ActiveTextureStages &active_stages,
+                                    const NoTexCoordStages &no_texcoords) const;
+  INLINE const TexCoordf &get_next_multitexcoord(MultiTexCoordIterator &tciterator, int n) const;
 
 
   INLINE ColorIterator make_color_iterator() const;
   INLINE ColorIterator make_color_iterator() const;
   INLINE const Colorf &get_next_color(ColorIterator &citerator) const;
   INLINE const Colorf &get_next_color(ColorIterator &citerator) const;
@@ -231,6 +237,19 @@ public:
   bool release(PreparedGraphicsObjects *prepared_objects);
   bool release(PreparedGraphicsObjects *prepared_objects);
   int release_all();
   int release_all();
 
 
+  // From parent dDrawable
+  virtual void draw(GraphicsStateGuardianBase *gsg) const;
+
+  // From parent Configurable
+  virtual void config();
+
+  // Immediate mode drawing functions - issue graphics commands
+  virtual void draw_immediate(GraphicsStateGuardianBase *gsg, GeomContext *gc) = 0;
+  virtual void print_draw_immediate() const = 0;
+
+  void calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
+                         bool &found_any) const;
+
 protected:
 protected:
   void init();
   void init();
   virtual BoundingVolume *recompute_bound();
   virtual BoundingVolume *recompute_bound();
@@ -240,25 +259,38 @@ protected:
   PTA_Vertexf _coords;
   PTA_Vertexf _coords;
   PTA_Normalf _norms;
   PTA_Normalf _norms;
   PTA_Colorf _colors;
   PTA_Colorf _colors;
-  PTA_TexCoordf _texcoords;
 
 
   PTA_ushort _vindex;
   PTA_ushort _vindex;
   PTA_ushort _nindex;
   PTA_ushort _nindex;
   PTA_ushort _cindex;
   PTA_ushort _cindex;
-  PTA_ushort _tindex;
 
 
   int _numprims,_num_vertices;
   int _numprims,_num_vertices;
   PTA_int _primlengths;
   PTA_int _primlengths;
   enum GeomBindType _bind[num_GeomAttrTypes];
   enum GeomBindType _bind[num_GeomAttrTypes];
 
 
+  class TexCoordDef {
+  public:
+    PTA_TexCoordf _texcoords;
+    PTA_ushort _tindex;
+  };
+
+  typedef pmap<CPT(TexCoordName), TexCoordDef> TexCoordsByName;
+  TexCoordsByName _texcoords_by_name;
+
   // Functions to extract component values, one at a time.
   // Functions to extract component values, one at a time.
   GetNextVertex *_get_vertex;
   GetNextVertex *_get_vertex;
   GetNextNormal *_get_normal;
   GetNextNormal *_get_normal;
-  GetNextTexCoord *_get_texcoord;
   GetNextColor *_get_color;
   GetNextColor *_get_color;
+  GetNextTexCoord *_get_texcoord;
+
+  // temporary storage until complete_pointers fills in _texcoords_by_name's TexCoordName *
+  typedef pvector<TexCoordDef *> TexCoordDefSet;
+  TexCoordDefSet _temp_texcoord_set;
+
 
 
 private:
 private:
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
+  static int sum_lengths(const PTA_int &lengths);
 
 
   // A Geom keeps a list (actually, a map) of all the
   // A Geom keeps a list (actually, a map) of all the
   // PreparedGraphicsObjects tables that it has been prepared into.
   // PreparedGraphicsObjects tables that it has been prepared into.
@@ -276,6 +308,7 @@ private:
 public:
 public:
   //static void register_with_read_factory(void);
   //static void register_with_read_factory(void);
   virtual void write_datagram(BamWriter* manager, Datagram &me);
   virtual void write_datagram(BamWriter* manager, Datagram &me);
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
 
 
   //static TypedWritable *make_Generic(const FactoryParams &params);
   //static TypedWritable *make_Generic(const FactoryParams &params);
 
 

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

@@ -21,6 +21,7 @@
 
 
 #include "pta_float.h"
 #include "pta_float.h"
 #include "geom.h"
 #include "geom.h"
+#include "texture.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : GeomSprite
 //       Class : GeomSprite

+ 2 - 0
panda/src/gobj/gobj_composite2.cxx

@@ -16,3 +16,5 @@
 #include "texture.cxx"
 #include "texture.cxx"
 #include "textureContext.cxx"
 #include "textureContext.cxx"
 #include "texturePool.cxx"
 #include "texturePool.cxx"
+#include "texCoordName.cxx"
+#include "textureStage.cxx"

+ 4 - 4
panda/src/gobj/material.h

@@ -15,17 +15,17 @@
 // [email protected] .
 // [email protected] .
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
+
 #ifndef MATERIAL_H
 #ifndef MATERIAL_H
 #define MATERIAL_H
 #define MATERIAL_H
-//
-////////////////////////////////////////////////////////////////////
-// Includes
-////////////////////////////////////////////////////////////////////
+
 #include "pandabase.h"
 #include "pandabase.h"
 
 
 #include "typedWritableReferenceCount.h"
 #include "typedWritableReferenceCount.h"
 #include "luse.h"
 #include "luse.h"
 
 
+class FactoryParams;
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : Material
 //       Class : Material
 // Description : Defines the way an object appears in the presence of
 // Description : Defines the way an object appears in the presence of

+ 49 - 0
panda/src/gobj/texCoordName.I

@@ -0,0 +1,49 @@
+// Filename: texCoordName.I
+// Created by:  masad (15Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::get_name
+//       Access: Published
+//  Description: Return the name string of this texcoordname
+////////////////////////////////////////////////////////////////////
+INLINE const string &TexCoordName::
+get_name() const {
+  return _name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::get_default
+//       Access: Published, Static
+//  Description: Returns the default TexCoordName that will be used
+//               for all geometry that does not give a particular name
+//               for its UV's
+////////////////////////////////////////////////////////////////////
+INLINE const TexCoordName *TexCoordName::
+get_default() {
+  if (_default_name == (TexCoordName *)NULL) {
+    _default_name = TexCoordName::make("default");
+  }
+  return _default_name;
+}
+
+INLINE ostream &
+operator << (ostream &out, const TexCoordName &tcn) {
+  tcn.output(out);
+  return out;
+}

+ 161 - 0
panda/src/gobj/texCoordName.cxx

@@ -0,0 +1,161 @@
+// Filename: texCoordName.cxx
+// Created by: masad (15Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pandabase.h"
+#include "texCoordName.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "bamReader.h"
+#include "preparedGraphicsObjects.h"
+
+TexCoordName::TexCoordsByName TexCoordName::_texcoords_by_name;
+CPT(TexCoordName) TexCoordName::_default_name;
+
+TypeHandle TexCoordName::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::Constructor
+//       Access: Private
+//  Description: Use make() to make a new TexCoordName instance.
+////////////////////////////////////////////////////////////////////
+TexCoordName::
+TexCoordName(const string &name) {
+  _name = name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::Destructor
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+TexCoordName::
+~TexCoordName() {
+  TexCoordsByName::iterator ni = _texcoords_by_name.find(_name);
+  nassertv(ni != _texcoords_by_name.end());
+  _texcoords_by_name.erase(ni);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::make
+//       Access: Published
+//  Description: The public interface for constructing a TexCoordName
+//               pointer.  This will return a new TexCoordName
+//               associated with the indicated name, if this is the
+//               first time the particular name has been requested; if
+//               the name is already in use, it will return the
+//               existing pointer.
+////////////////////////////////////////////////////////////////////
+const TexCoordName *TexCoordName::
+make(const string &name) {
+  TexCoordsByName::iterator ni = _texcoords_by_name.find(name);
+  if (ni != _texcoords_by_name.end()) {
+    return (*ni).second;
+  }
+
+  TexCoordName *texcoord = new TexCoordName(name);
+  _texcoords_by_name[name] = texcoord;
+  return texcoord;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::output
+//       Access: Published
+//  Description: output the TexCoordName and its member data
+////////////////////////////////////////////////////////////////////
+void TexCoordName::
+output(ostream &out) const {
+  out << get_name();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::register_with_read_factory
+//       Access: Public, Static
+//  Description: Factory method to generate a TexCoordName object
+////////////////////////////////////////////////////////////////////
+void TexCoordName::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::finalize
+//       Access: Public, Virtual
+//  Description: Method to ensure that any necessary clean up tasks
+//               that have to be performed by this object are performed
+////////////////////////////////////////////////////////////////////
+void TexCoordName::
+finalize() {
+  // Unref the pointer that we explicitly reffed in make_from_bam().
+  unref();
+
+  // We should never get back to zero after unreffing our own count,
+  // because we expect to have been stored in a pointer somewhere.  If
+  // we do get to zero, it's a memory leak; the way to avoid this is
+  // to call unref_delete() above instead of unref(), but this is
+  // dangerous to do from within a virtual function.
+  nassertv(get_ref_count() != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type TexCoordName is encountered
+//               in the Bam file.  It should create the TexCoordName
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *TexCoordName::
+make_from_bam(const FactoryParams &params) {
+  // The process of making a TexCoordName is slightly
+  // different than making other Writable objects.
+  // That is because all creation of TexCoordNames should
+  // be done through the make() constructor.
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  // The name is the only thing written to the data stream.
+  string name = scan.get_string();
+
+  // Make a new TexCoordName with that name (or get the previous one
+  // if there is one already).
+  PT(TexCoordName) me = (TexCoordName *)make(name);
+
+  // But now we have a problem, since we have to hold the reference
+  // count and there's no way to return a TypedWritable while still
+  // holding the reference count!  We work around this by explicitly
+  // upping the count, and also setting a finalize() callback to down
+  // it later.
+  me->ref();
+  manager->register_finalize(me);
+  
+  return me.p();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexCoordName::write_datagram
+//       Access: Public
+//  Description: Function to write the important information in
+//               the particular object to a Datagram
+////////////////////////////////////////////////////////////////////
+void TexCoordName::
+write_datagram(BamWriter *manager, Datagram &me) {
+  me.add_string(_name);
+}
+

+ 92 - 0
panda/src/gobj/texCoordName.h

@@ -0,0 +1,92 @@
+// Filename: texCoordName.h
+// Created by:  drose (15Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXCOORDNAME_H
+#define TEXCOORDNAME_H
+
+#include "pandabase.h"
+
+#include "typedWritableReferenceCount.h"
+
+class FactoryParams;
+
+////////////////////////////////////////////////////////////////////
+//       Class : TexCoordName
+// Description : Associates a name with a set of texture coordinates,
+//               for the purpose of storing within a Geom.  This is
+//               used in conjunction with TextureStage to support
+//               multitexturing.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TexCoordName : public TypedWritableReferenceCount {
+private:
+  TexCoordName(const string &name);
+
+PUBLISHED:
+  virtual ~TexCoordName();
+
+  static const TexCoordName *make(const string &name);
+  INLINE const string &get_name() const;
+
+  void output(ostream &out) const;
+
+  INLINE static const TexCoordName *get_default();
+
+private:
+  string _name;
+
+  typedef pmap<string, TexCoordName *> TexCoordsByName;
+  static TexCoordsByName _texcoords_by_name;
+
+  static CPT(TexCoordName) _default_name;
+  
+public:
+  // Datagram stuff
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &me);
+
+  virtual void finalize();
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWritableReferenceCount::init_type();
+    register_type(_type_handle, "TexCoordName",
+                  TypedWritableReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const TexCoordName &tcn);
+
+#include "texCoordName.I"
+
+#endif
+
+
+  

+ 581 - 0
panda/src/gobj/textureStage.I

@@ -0,0 +1,581 @@
+// Filename: textureStage.I
+// Created by:  masad (15Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_name
+//       Access: Published
+//  Description: Returns the name of this texture stage
+////////////////////////////////////////////////////////////////////
+INLINE const string &TextureStage::
+get_name() const {
+  return _name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_name
+//       Access: Published
+//  Description: Changes the name of this texture stage
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_name(const string &name) {
+  _name = name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_sort
+//       Access: Published
+//  Description: Changes the order in which the texture associated
+//               with this stage is rendered relative to the other
+//               texture stages.  When geometry is rendered with
+//               multiple textures, the textures are rendered in order
+//               from the lowest sort number to the highest sort
+//               number.
+//
+//               Also see set_priority(), which is used to select the
+//               most important textures for rendering when some must
+//               be omitted because of hardware limitations.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_sort(int sort) {
+  _sort = sort;
+
+  // Update the global flag to indicate that all TextureAttribs in the
+  // world must now re-sort their lists.
+  _sort_seq++;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_sort
+//       Access: Published
+//  Description: Returns the sort order of this texture stage.
+////////////////////////////////////////////////////////////////////
+INLINE int TextureStage::
+get_sort() const {
+  return _sort;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_priority
+//       Access: Published
+//  Description: Changes the relative importance of the texture
+//               associated with this stage relative to the other
+//               texture stages that are applied simultaneously.
+//
+//               This is unrelated to set_sort(), which controls the
+//               order in which multiple textures are applied.  The
+//               priority number is used to decide which of the
+//               requested textures are to be selected for rendering
+//               when more textures are requested than the hardware
+//               will support.  The highest-priority n textures are
+//               selected for rendering, and then rendered in order by
+//               their sort factor.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_priority(int priority) {
+  _priority = priority;
+
+  // Update the global flag to indicate that all TextureAttribs in the
+  // world must now re-sort their lists.
+  _sort_seq++;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_priority
+//       Access: Published
+//  Description: Returns the priority associated with this stage.
+//
+//               This is specially helpful for cards that do not
+//               support more than n stages of multi-texturing.
+////////////////////////////////////////////////////////////////////
+INLINE int TextureStage::
+get_priority() const {
+  return _priority;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::opertor <
+//       Access: Published
+//  Description: Compare if the sort order is lower
+////////////////////////////////////////////////////////////////////
+INLINE bool TextureStage::
+operator < (const TextureStage &other) const {
+  return (_sort < other._sort);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::opertor =
+//       Access: Published
+//  Description: just copy the members of other to this
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage &TextureStage::
+operator = (const TextureStage &other) {
+  _name = other._name;
+  _sort = other._sort;
+  _priority = other._priority;
+  _texcoord_name = other._texcoord_name;
+  _mode = other._mode;
+  _color = other._color;
+  _combine_rgb_mode = other._combine_rgb_mode;
+  _combine_rgb_source0 = other._combine_rgb_source0;
+  _combine_rgb_operand0 = other._combine_rgb_operand0;
+  _combine_rgb_source1 = other._combine_rgb_source1;
+  _combine_rgb_operand1 = other._combine_rgb_operand1;
+  _combine_rgb_source2 = other._combine_rgb_source2;
+  _combine_rgb_operand2 = other._combine_rgb_operand2;
+  _combine_alpha_mode = other._combine_alpha_mode;
+  _combine_alpha_source0 = other._combine_alpha_source0;
+  _combine_alpha_operand0 = _combine_alpha_operand0;
+  _combine_alpha_source1 = other._combine_alpha_source1;
+  _combine_alpha_operand1 = other._combine_alpha_operand1;
+  _combine_alpha_source2 = other._combine_alpha_source2;
+  _combine_alpha_operand2 = other._combine_alpha_operand2;
+
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_texcoord_name
+//       Access: Published
+//  Description: Indicate which set of UV's this texture stage will
+//               use.  Geometry may have any number of associated UV
+//               sets, each of which must have a unique name.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_texcoord_name(const TexCoordName *name) {
+  _texcoord_name = name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_texcoord_name
+//       Access: Published
+//  Description: Indicate which set of UV's this texture stage will
+//               use.  Geometry may have any number of associated UV
+//               sets, each of which must have a unique name.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_texcoord_name(const string &name) {
+  _texcoord_name = TexCoordName::make(name);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_texcoord_name
+//       Access: Published
+//  Description: Returns the TexCoordName
+////////////////////////////////////////////////////////////////////
+INLINE const TexCoordName *TextureStage::
+get_texcoord_name() const {
+  return _texcoord_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_mode
+//       Access: Published
+//  Description: Set the mode of this texture stage
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_mode(TextureStage::Mode mode) {
+  _mode = mode;
+
+  if (_mode != M_combine) {
+    _num_combine_rgb_operands = 0;
+    _num_combine_alpha_operands = 0;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_mode
+//       Access: Published
+//  Description: Return the mode of this stage
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::Mode TextureStage::
+get_mode() const {
+  return _mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_color
+//       Access: Published
+//  Description: Set the color for this stage
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_color(const Colorf &color) {
+  _color = color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_color
+//       Access: Published
+//  Description: return the color for this stage
+////////////////////////////////////////////////////////////////////
+INLINE Colorf TextureStage::
+get_color() const {
+  return _color;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_combine_rgb
+//       Access: Published
+//  Description: Specifies any of the CombineMode values that
+//               represent a one-parameter operation.  Specifically,
+//               this is CM_replace only.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_combine_rgb(CombineMode mode, 
+                CombineSource source0, CombineOperand operand0) {
+  nassertv(get_expected_num_combine_operands(mode) == 1);
+  nassertv(operand_valid_for_rgb(operand0));
+  _mode = M_combine;
+  _num_combine_rgb_operands = 1;
+  _combine_rgb_mode = mode;
+  _combine_rgb_source0 = source0;
+  _combine_rgb_operand0 = operand0;
+  _combine_rgb_source1 = CS_undefined;
+  _combine_rgb_operand1 = CO_undefined;
+  _combine_rgb_source2 = CS_undefined;
+  _combine_rgb_operand2 = CO_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_combine_rgb
+//       Access: Published
+//  Description: Specifies any of the CombineMode values that
+//               represent a two-parameter operation.  Specifically,
+//               this is everything except for CM_replace and
+//               CM_interpolate.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_combine_rgb(CombineMode mode, 
+                CombineSource source0, CombineOperand operand0,
+                CombineSource source1, CombineOperand operand1) {
+  nassertv(get_expected_num_combine_operands(mode) == 2);
+  nassertv(operand_valid_for_rgb(operand0));
+  nassertv(operand_valid_for_rgb(operand1));
+  _mode = M_combine;
+  _num_combine_rgb_operands = 2;
+  _combine_rgb_mode = mode;
+  _combine_rgb_source0 = source0;
+  _combine_rgb_operand0 = operand0;
+  _combine_rgb_source1 = source1;
+  _combine_rgb_operand1 = operand1;
+  _combine_rgb_source2 = CS_undefined;
+  _combine_rgb_operand2 = CO_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_combine_rgb
+//       Access: Published
+//  Description: Specifies any of the CombineMode values that
+//               represent a one-parameter operation.  Specifically,
+//               this is CM_interpolate only.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_combine_rgb(CombineMode mode, 
+                CombineSource source0, CombineOperand operand0,
+                CombineSource source1, CombineOperand operand1,
+                CombineSource source2, CombineOperand operand2) {
+  nassertv(get_expected_num_combine_operands(mode) == 3);
+  nassertv(operand_valid_for_rgb(operand0));
+  nassertv(operand_valid_for_rgb(operand1));
+  nassertv(operand_valid_for_rgb(operand2));
+  _mode = M_combine;
+  _num_combine_rgb_operands = 3;
+  _combine_rgb_mode = mode;
+  _combine_rgb_source0 = source0;
+  _combine_rgb_operand0 = operand0;
+  _combine_rgb_source1 = source1;
+  _combine_rgb_operand1 = operand1;
+  _combine_rgb_source2 = source2;
+  _combine_rgb_operand2 = operand2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_rgb_mode
+//       Access: Published
+//  Description: Get the combine_rgb_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineMode TextureStage::
+get_combine_rgb_mode() const {
+  return _combine_rgb_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_num_combine_rgb_operands
+//       Access: Published
+//  Description: Returns the number of meaningful operands that may be
+//               retrieved via get_combine_rgb_sourceN() and
+//               get_combine_rgb_operandN().
+////////////////////////////////////////////////////////////////////
+INLINE int TextureStage::
+get_num_combine_rgb_operands() const {
+  return _num_combine_rgb_operands;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_rgb_source0
+//       Access: Published
+//  Description: Get source0 of combine_rgb_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineSource TextureStage::
+get_combine_rgb_source0() const {
+  return _combine_rgb_source0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_rgb_operand0
+//       Access: Published
+//  Description: Get operand0 of combine_rgb_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineOperand TextureStage::
+get_combine_rgb_operand0() const {
+  return _combine_rgb_operand0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_rgb_source1
+//       Access: Published
+//  Description: Get source1 of combine_rgb_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineSource TextureStage::
+get_combine_rgb_source1() const {
+  return _combine_rgb_source1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_rgb_operand1
+//       Access: Published
+//  Description: Get operand1 of combine_rgb_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineOperand TextureStage::
+get_combine_rgb_operand1() const {
+  return _combine_rgb_operand1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_rgb_source2
+//       Access: Published
+//  Description: Get source2 of combine_rgb_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineSource TextureStage::
+get_combine_rgb_source2() const {
+  return _combine_rgb_source2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_rgb_operand2
+//       Access: Published
+//  Description: Get operand2 of combine_rgb_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineOperand TextureStage::
+get_combine_rgb_operand2() const {
+  return _combine_rgb_operand2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_combine_alpha
+//       Access: Published
+//  Description: Specifies any of the CombineMode values that
+//               represent a one-parameter operation.  Specifically,
+//               this is CM_replace only.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_combine_alpha(CombineMode mode, 
+                  CombineSource source0, CombineOperand operand0) {
+  nassertv(get_expected_num_combine_operands(mode) == 1);
+  nassertv(operand_valid_for_alpha(operand0));
+  _mode = M_combine;
+  _num_combine_alpha_operands = 1;
+  _combine_alpha_mode = mode;
+  _combine_alpha_source0 = source0;
+  _combine_alpha_operand0 = operand0;
+  _combine_alpha_source1 = CS_undefined;
+  _combine_alpha_operand1 = CO_undefined;
+  _combine_alpha_source2 = CS_undefined;
+  _combine_alpha_operand2 = CO_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_combine_alpha
+//       Access: Published
+//  Description: Specifies any of the CombineMode values that
+//               represent a two-parameter operation.  Specifically,
+//               this is everything except for CM_replace and
+//               CM_interpolate.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_combine_alpha(CombineMode mode, 
+                  CombineSource source0, CombineOperand operand0,
+                  CombineSource source1, CombineOperand operand1) {
+  nassertv(get_expected_num_combine_operands(mode) == 2);
+  nassertv(operand_valid_for_alpha(operand0));
+  nassertv(operand_valid_for_alpha(operand1));
+  _mode = M_combine;
+  _num_combine_alpha_operands = 2;
+  _combine_alpha_mode = mode;
+  _combine_alpha_source0 = source0;
+  _combine_alpha_operand0 = operand0;
+  _combine_alpha_source1 = source1;
+  _combine_alpha_operand1 = operand1;
+  _combine_alpha_source2 = CS_undefined;
+  _combine_alpha_operand2 = CO_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::set_combine_alpha
+//       Access: Published
+//  Description: Specifies any of the CombineMode values that
+//               represent a one-parameter operation.  Specifically,
+//               this is CM_interpolate only.
+////////////////////////////////////////////////////////////////////
+INLINE void TextureStage::
+set_combine_alpha(CombineMode mode, 
+                  CombineSource source0, CombineOperand operand0,
+                  CombineSource source1, CombineOperand operand1,
+                  CombineSource source2, CombineOperand operand2) {
+  nassertv(get_expected_num_combine_operands(mode) == 3);
+  nassertv(operand_valid_for_alpha(operand0));
+  nassertv(operand_valid_for_alpha(operand1));
+  nassertv(operand_valid_for_alpha(operand2));
+  _mode = M_combine;
+  _num_combine_alpha_operands = 3;
+  _combine_alpha_mode = mode;
+  _combine_alpha_source0 = source0;
+  _combine_alpha_operand0 = operand0;
+  _combine_alpha_source1 = source1;
+  _combine_alpha_operand1 = operand1;
+  _combine_alpha_source2 = source2;
+  _combine_alpha_operand2 = operand2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_alpha
+//       Access: Published
+//  Description: Get combine_alpha_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineMode TextureStage::
+get_combine_alpha_mode() const {
+  return _combine_alpha_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_num_combine_alpha_operands
+//       Access: Published
+//  Description: Returns the number of meaningful operands that may be
+//               retrieved via get_combine_alpha_sourceN() and
+//               get_combine_alpha_operandN().
+////////////////////////////////////////////////////////////////////
+INLINE int TextureStage::
+get_num_combine_alpha_operands() const {
+  return _num_combine_alpha_operands;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_alpha_source0
+//       Access: Published
+//  Description: Get source0 of combine_alpha_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineSource TextureStage::
+get_combine_alpha_source0() const {
+  return _combine_alpha_source0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_alpha_operand0
+//       Access: Published
+//  Description: Get operand0 of combine_alpha_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineOperand TextureStage::
+get_combine_alpha_operand0() const {
+  return _combine_alpha_operand0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_alpha_source1
+//       Access: Published
+//  Description: Get source1 of combine_alpha_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineSource TextureStage::
+get_combine_alpha_source1() const {
+  return _combine_alpha_source1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_alpha_operand1
+//       Access: Published
+//  Description: Get operand1 of combine_alpha_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineOperand TextureStage::
+get_combine_alpha_operand1() const {
+  return _combine_alpha_operand1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_alpha_source2
+//       Access: Published
+//  Description: Get source2 of combine_alpha_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineSource TextureStage::
+get_combine_alpha_source2() const {
+  return _combine_alpha_source2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_combine_alpha_operand2
+//       Access: Published
+//  Description: Get operand2 of combine_alpha_mode
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage::CombineOperand TextureStage::
+get_combine_alpha_operand2() const {
+  return _combine_alpha_operand2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_default
+//       Access: Published, Static
+//  Description: Returns the default TextureStage that will be used
+//               for all texturing that does not name a particular
+//               stage.  This generally handles the normal
+//               single-texture case.
+////////////////////////////////////////////////////////////////////
+INLINE TextureStage *TextureStage::
+get_default() {
+  if (_default_stage == (TextureStage *)NULL) {
+    _default_stage = new TextureStage("default");
+  }
+  return _default_stage;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_sort_seq
+//       Access: Published, Static
+//  Description: Returns a global sequence number that is incremented
+//               any time any TextureStage in the world changes sort
+//               or priority.  This is used by TextureAttrib to
+//               determine when it is necessary to re-sort its
+//               internal array of stages.
+////////////////////////////////////////////////////////////////////
+INLINE UpdateSeq TextureStage::
+get_sort_seq() {
+  return _sort_seq;
+}
+
+INLINE ostream &
+operator << (ostream &out, const TextureStage &ts) {
+  ts.output(out);
+  return out;
+}

+ 451 - 0
panda/src/gobj/textureStage.cxx

@@ -0,0 +1,451 @@
+// Filename: textureStage.cxx
+// Created by:  MAsaduzz (16Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "textureStage.h"
+#include "texCoordName.h"
+
+PT(TextureStage) TextureStage::_default_stage;
+UpdateSeq TextureStage::_sort_seq;
+
+TypeHandle TextureStage::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::Constructor
+//       Access: Published
+//  Description: Initialize the texture stage at construction
+////////////////////////////////////////////////////////////////////
+TextureStage::
+TextureStage(const string &name) {
+  _name = name;
+  _sort = 0;
+  _priority = 0;
+  _texcoord_name = TexCoordName::get_default();
+  _mode = M_modulate;
+  _color.set(0.0f, 0.0f, 0.0f, 1.0f);
+  _combine_rgb_mode = CM_undefined;
+  _num_combine_rgb_operands = 0;
+  _combine_rgb_source0 = CS_undefined;
+  _combine_rgb_operand0 = CO_undefined;
+  _combine_rgb_source1 = CS_undefined;
+  _combine_rgb_operand1 = CO_undefined;
+  _combine_rgb_source2 = CS_undefined;
+  _combine_rgb_operand2 = CO_undefined;
+  _combine_alpha_mode = CM_undefined;
+  _num_combine_alpha_operands = 0;
+  _combine_alpha_source0 = CS_undefined;
+  _combine_alpha_operand0 = CO_undefined;
+  _combine_alpha_source1 = CS_undefined;
+  _combine_alpha_operand1 = CO_undefined;
+  _combine_alpha_source2 = CS_undefined;
+  _combine_alpha_operand2 = CO_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::Constructor
+//       Access: Published
+//  Description: Initialize the texture stage from other
+////////////////////////////////////////////////////////////////////
+TextureStage::
+TextureStage(TextureStage *copy) {
+  _name = copy->_name;
+  _sort = copy->_sort;
+  _priority = copy->_priority;
+  _texcoord_name = copy->_texcoord_name;
+  _mode = copy->_mode;
+  _color = copy->_color;
+  _combine_rgb_mode = copy->_combine_rgb_mode;
+  _combine_rgb_source0 = copy->_combine_rgb_source0;
+  _combine_rgb_operand0 = copy->_combine_rgb_operand0;
+  _combine_rgb_source1 = copy->_combine_rgb_source1;
+  _combine_rgb_operand1 = copy->_combine_rgb_operand1;
+  _combine_rgb_source2 = copy->_combine_rgb_source2;
+  _combine_rgb_operand2 = copy->_combine_rgb_operand2;
+  _combine_alpha_mode = copy->_combine_alpha_mode;
+  _combine_alpha_source0 = copy->_combine_alpha_source0;
+  _combine_alpha_operand0 = _combine_alpha_operand0;
+  _combine_alpha_source1 = copy->_combine_alpha_source1;
+  _combine_alpha_operand1 = copy->_combine_alpha_operand1;
+  _combine_alpha_source2 = copy->_combine_alpha_source2;
+  _combine_alpha_operand2 = copy->_combine_alpha_operand2;
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::Destructor
+//       Access: Published, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+TextureStage::
+~TextureStage() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::Destructor
+//       Access: Published
+//  Description: Writes the details of this stage
+////////////////////////////////////////////////////////////////////
+void TextureStage::
+write(ostream &out) const {
+  out << "TextureStage " << get_name() << ", sort = " << get_sort() << ", priority = " << get_priority() << "\n"
+      << "  texcoords = " << get_texcoord_name()->get_name()
+      << ", mode = " << get_mode() << ", color = " << get_color() << "\n";
+
+  if (get_mode() == M_combine) {
+    out << "  RGB combine mode =  " << get_combine_rgb_mode() << "\n";
+    if (get_num_combine_rgb_operands() >= 1) {
+      out << "    0: " << get_combine_rgb_source0() << ", "
+          << get_combine_rgb_operand0() << "\n";
+    }
+    if (get_num_combine_rgb_operands() >= 2) {
+      out << "    1: " << get_combine_rgb_source1() << ", "
+          << get_combine_rgb_operand1() << "\n";
+    }
+    if (get_num_combine_rgb_operands() >= 3) {
+      out << "    2: " << get_combine_rgb_source2() << ", "
+          << get_combine_rgb_operand2() << "\n";
+    }
+    out << "  alpha combine mode =  " << get_combine_alpha_mode() << "\n";
+    if (get_num_combine_alpha_operands() >= 1) {
+      out << "    0: " << get_combine_alpha_source0() << ", "
+          << get_combine_alpha_operand0() << "\n";
+    }
+    if (get_num_combine_alpha_operands() >= 2) {
+      out << "    1: " << get_combine_alpha_source1() << ", "
+          << get_combine_alpha_operand1() << "\n";
+    }
+    if (get_num_combine_alpha_operands() >= 3) {
+      out << "    2: " << get_combine_alpha_source2() << ", "
+          << get_combine_alpha_operand2() << "\n";
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::Destructor
+//       Access: Published
+//  Description: Just a single line output
+////////////////////////////////////////////////////////////////////
+void TextureStage::
+output(ostream &out) const {
+  out << "TextureStage " << get_name();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::get_expected_num_combine_operands
+//       Access: Private, Static
+//  Description: Returns the number of combine operands expected with
+//               the indicated combine mode (0, 1, 2, or 3).
+////////////////////////////////////////////////////////////////////
+int TextureStage::
+get_expected_num_combine_operands(TextureStage::CombineMode cm) {
+  switch (cm) {
+  case CM_undefined:
+    return 0;
+
+  case CM_replace:
+    return 1;
+
+  case CM_modulate:
+  case CM_add:
+  case CM_add_signed:
+  case CM_subtract:
+  case CM_dot3_rgb:
+  case CM_dot3_rgba:
+    return 2;
+
+  case CM_interpolate:
+    return 3;
+  }
+
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::operand_valid_for_rgb
+//       Access: Private, Static
+//  Description: Returns true if the indicated CombineOperand is valid
+//               for one of the RGB modes, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TextureStage::
+operand_valid_for_rgb(TextureStage::CombineOperand co) {
+  switch (co) {
+  case CO_undefined:
+    return false;
+
+  case CO_src_color:
+  case CO_one_minus_src_color:
+  case CO_src_alpha:
+  case CO_one_minus_src_alpha:
+    return true;
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::operand_valid_for_alpha
+//       Access: Private, Static
+//  Description: Returns true if the indicated CombineOperand is valid
+//               for one of the alpha modes, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TextureStage::
+operand_valid_for_alpha(TextureStage::CombineOperand co) {
+  switch (co) {
+  case CO_undefined:
+  case CO_src_color:
+  case CO_one_minus_src_color:
+    return false;
+
+  case CO_src_alpha:
+  case CO_one_minus_src_alpha:
+    return true;
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::register_with_read_factory
+//       Access: Public, Static
+//  Description: Factory method to generate a TextureStage object
+////////////////////////////////////////////////////////////////////
+void TextureStage::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_TextureStage);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::make_TextureStage
+//       Access: Protected
+//  Description: Factory method to generate a TextureStage object
+////////////////////////////////////////////////////////////////////
+TypedWritable* TextureStage::
+make_TextureStage(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  bool is_default = scan.get_bool();
+  if (is_default) {
+    return get_default();
+  } else {
+    TextureStage *me = new TextureStage("");
+    me->fillin(scan, manager);
+    return me;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::fillin
+//       Access: Protected
+//  Description: Function that reads out of the datagram (or asks
+//               manager to read) all of the data that is needed to
+//               re-create this object and stores it in the appropiate
+//               place
+////////////////////////////////////////////////////////////////////
+void TextureStage::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _name = scan.get_string();
+  _sort = scan.get_int32();
+  _priority = scan.get_int32();
+
+  manager->read_pointer(scan);
+
+  _mode = (TextureStage::Mode) scan.get_uint8();
+  _color.read_datagram(scan);
+  
+  _combine_rgb_mode = (TextureStage::CombineMode) scan.get_uint8();
+  _num_combine_rgb_operands = scan.get_uint8();
+  _combine_rgb_source0 = (TextureStage::CombineSource) scan.get_uint8();
+  _combine_rgb_operand0 = (TextureStage::CombineOperand) scan.get_uint8();
+  _combine_rgb_source1 = (TextureStage::CombineSource) scan.get_uint8();
+  _combine_rgb_operand1 = (TextureStage::CombineOperand) scan.get_uint8();
+  _combine_rgb_source2 = (TextureStage::CombineSource) scan.get_uint8();
+  _combine_rgb_operand2 = (TextureStage::CombineOperand) scan.get_uint8();
+
+  _combine_alpha_mode = (TextureStage::CombineMode) scan.get_uint8();
+  _num_combine_alpha_operands = scan.get_uint8();
+  _combine_alpha_source0 = (TextureStage::CombineSource) scan.get_uint8();
+  _combine_alpha_operand0 = (TextureStage::CombineOperand) scan.get_uint8();
+  _combine_alpha_source1 = (TextureStage::CombineSource) scan.get_uint8();
+  _combine_alpha_operand1 = (TextureStage::CombineOperand) scan.get_uint8();
+  _combine_alpha_source2 = (TextureStage::CombineSource) scan.get_uint8();
+  _combine_alpha_operand2 = (TextureStage::CombineOperand) scan.get_uint8();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::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 TextureStage::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = TypedWritableReferenceCount::complete_pointers(p_list, manager);
+
+  _texcoord_name = DCAST(TexCoordName, p_list[pi++]);
+
+  return pi;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureStage::write_datagram
+//       Access: Public
+//  Description: Function to write the important information in
+//               the particular object to a Datagram
+////////////////////////////////////////////////////////////////////
+void TextureStage::
+write_datagram(BamWriter *manager, Datagram &me) {
+  // These properties are read in again by fillin(), above.
+  if (this == get_default()) {
+    me.add_bool(true);
+  } else {
+    me.add_bool(false);
+    me.add_string(_name);
+    me.add_int32(_sort);
+    me.add_int32(_priority);
+
+    manager->write_pointer(me,_texcoord_name);
+    
+    me.add_uint8(_mode);
+    _color.write_datagram(me);
+    
+    me.add_uint8(_combine_rgb_mode);
+    me.add_uint8(_num_combine_rgb_operands);
+    me.add_uint8(_combine_rgb_source0);
+    me.add_uint8(_combine_rgb_operand0);
+    me.add_uint8(_combine_rgb_source1);
+    me.add_uint8(_combine_rgb_operand1);
+    me.add_uint8(_combine_rgb_source2);
+    me.add_uint8(_combine_rgb_operand2);
+    
+    me.add_uint8(_combine_alpha_mode);
+    me.add_uint8(_num_combine_alpha_operands);
+    me.add_uint8(_combine_alpha_source0);
+    me.add_uint8(_combine_alpha_operand0);
+    me.add_uint8(_combine_alpha_source1);
+    me.add_uint8(_combine_alpha_operand1);
+    me.add_uint8(_combine_alpha_source2);
+    me.add_uint8(_combine_alpha_operand2);
+  }
+}
+
+ostream &
+operator << (ostream &out, TextureStage::Mode mode) {
+  switch (mode) {
+  case TextureStage::M_modulate:
+    return out << "modulate";
+
+  case TextureStage::M_decal:
+    return out << "decal";
+
+  case TextureStage::M_blend:
+    return out << "blend";
+
+  case TextureStage::M_replace:
+    return out << "replace";
+
+  case TextureStage::M_add:
+    return out << "add";
+
+  case TextureStage::M_combine:
+    return out << "combine";
+  }
+
+  return out << "**invalid Mode(" << (int)mode << ")**";
+}
+
+ostream &
+operator << (ostream &out, TextureStage::CombineMode cm) {
+  switch (cm) {
+  case TextureStage::CM_undefined:
+    return out << "undefined";
+
+  case TextureStage::CM_replace:
+    return out << "replace";
+
+  case TextureStage::CM_modulate:
+    return out << "modulate";
+
+  case TextureStage::CM_add:
+    return out << "add";
+
+  case TextureStage::CM_add_signed:
+    return out << "add_signed";
+
+  case TextureStage::CM_interpolate:
+    return out << "interpolate";
+
+  case TextureStage::CM_subtract:
+    return out << "subtract";
+
+  case TextureStage::CM_dot3_rgb:
+    return out << "dot3_rgb";
+
+  case TextureStage::CM_dot3_rgba:
+    return out << "dot3_rgba";
+  }
+
+  return out << "**invalid CombineMode(" << (int)cm << ")**";
+}
+
+ostream &
+operator << (ostream &out, TextureStage::CombineSource cs) {
+  switch (cs) {
+  case TextureStage::CS_undefined:
+    return out << "undefined";
+
+  case TextureStage::CS_texture:
+    return out << "texture";
+
+  case TextureStage::CS_constant:
+    return out << "constant";
+
+  case TextureStage::CS_primary_color:
+    return out << "primary_color";
+
+  case TextureStage::CS_previous:
+    return out << "previous";
+  }
+
+  return out << "**invalid CombineSource(" << (int)cs << ")**";
+}
+
+ostream &
+operator << (ostream &out, TextureStage::CombineOperand co) {
+  switch (co) {
+  case TextureStage::CO_undefined:
+    return out << "undefined";
+
+  case TextureStage::CO_src_color:
+    return out << "src_color";
+
+  case TextureStage::CO_one_minus_src_color:
+    return out << "one_minus_src_color";
+
+  case TextureStage::CO_src_alpha:
+    return out << "src_alpha";
+
+  case TextureStage::CO_one_minus_src_alpha:
+    return out << "one_minus_src_alpha";
+  }
+
+  return out << "**invalid CombineOperand(" << (int)co << ")**";
+}

+ 227 - 0
panda/src/gobj/textureStage.h

@@ -0,0 +1,227 @@
+// Filename: textureStage.h
+// Created by:  drose (14Jul04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTURESTAGE_H
+#define TEXTURESTAGE_H
+
+#include "pandabase.h"
+
+#include "texCoordName.h"
+#include "pointerTo.h"
+#include "typedWritableReferenceCount.h"
+#include "updateSeq.h"
+#include "luse.h"
+
+class FactoryParams;
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextureStage
+// Description : Defines the properties of a named stage of the
+//               multitexture pipeline.  The TextureAttrib will
+//               associated a number of these stages with Texture
+//               objects, and the GSG will render geometry by sorting
+//               all of the currently active TextureStages in order
+//               and then issuing the appropriate rendering calls to
+//               activate them.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TextureStage : public TypedWritableReferenceCount {
+PUBLISHED:
+  TextureStage(const string &name);
+  TextureStage(TextureStage *copy);
+  virtual ~TextureStage();
+
+  enum Mode {
+    M_modulate,
+    M_decal,
+    M_blend,
+    M_replace,
+    M_add,
+    M_combine,
+  };
+
+  enum CombineMode {
+    CM_undefined,
+    CM_replace,
+    CM_modulate,
+    CM_add,
+    CM_add_signed,
+    CM_interpolate,
+    CM_subtract,
+
+    // The following are valid only for combine_rgb, not
+    // combine_alpha.
+    CM_dot3_rgb,
+    CM_dot3_rgba,
+  };
+
+  enum CombineSource {
+    CS_undefined,
+    CS_texture,
+    CS_constant,
+    CS_primary_color,
+    CS_previous,
+  };
+
+  enum CombineOperand {
+    CO_undefined,
+    CO_src_color,
+    CO_one_minus_src_color,
+    CO_src_alpha,
+    CO_one_minus_src_alpha,
+  };
+
+  INLINE void set_name(const string &name);
+  INLINE const string &get_name() const;
+
+  INLINE void set_sort(int sort);
+  INLINE int get_sort() const;
+
+  INLINE void set_priority(int priority);
+  INLINE int get_priority() const;
+
+  INLINE bool operator < (const TextureStage &other) const;
+  INLINE TextureStage &operator = (const TextureStage &copy);
+
+  INLINE void set_texcoord_name(const TexCoordName *name);
+  INLINE void set_texcoord_name(const string &texcoord_name);
+  INLINE const TexCoordName *get_texcoord_name() const;
+
+  INLINE void set_mode(Mode mode);
+  INLINE Mode get_mode() const;
+
+  INLINE void set_color(const Colorf &color);
+  INLINE Colorf get_color() const;
+
+  INLINE void set_combine_rgb(CombineMode mode, 
+                              CombineSource source0, CombineOperand operand0);
+  INLINE void set_combine_rgb(CombineMode mode, 
+                              CombineSource source0, CombineOperand operand0,
+                              CombineSource source1, CombineOperand operand1);
+  INLINE void set_combine_rgb(CombineMode mode, 
+                              CombineSource source0, CombineOperand operand0,
+                              CombineSource source1, CombineOperand operand1,
+                              CombineSource source2, CombineOperand operand2);
+  INLINE CombineMode get_combine_rgb_mode() const;
+  INLINE int get_num_combine_rgb_operands() const;
+  INLINE CombineSource get_combine_rgb_source0() const;
+  INLINE CombineOperand get_combine_rgb_operand0() const;
+  INLINE CombineSource get_combine_rgb_source1() const;
+  INLINE CombineOperand get_combine_rgb_operand1() const;
+  INLINE CombineSource get_combine_rgb_source2() const;
+  INLINE CombineOperand get_combine_rgb_operand2() const;
+
+  INLINE void set_combine_alpha(CombineMode mode, 
+                                CombineSource source0, CombineOperand operand0);
+  INLINE void set_combine_alpha(CombineMode mode, 
+                                CombineSource source0, CombineOperand operand0,
+                                CombineSource source1, CombineOperand operand1);
+  INLINE void set_combine_alpha(CombineMode mode, 
+                                CombineSource source0, CombineOperand operand0,
+                                CombineSource source1, CombineOperand operand1,
+                                CombineSource source2, CombineOperand operand2);
+  INLINE CombineMode get_combine_alpha_mode() const;
+  INLINE int get_num_combine_alpha_operands() const;
+  INLINE CombineSource get_combine_alpha_source0() const;
+  INLINE CombineOperand get_combine_alpha_operand0() const;
+  INLINE CombineSource get_combine_alpha_source1() const;
+  INLINE CombineOperand get_combine_alpha_operand1() const;
+  INLINE CombineSource get_combine_alpha_source2() const;
+  INLINE CombineOperand get_combine_alpha_operand2() const;
+
+  void write(ostream &out) const;
+  void output(ostream &out) const;
+
+  INLINE static TextureStage *get_default();
+  INLINE static UpdateSeq get_sort_seq();
+
+private:
+  static int get_expected_num_combine_operands(CombineMode cm);
+  static bool operand_valid_for_rgb(CombineOperand co);
+  static bool operand_valid_for_alpha(CombineOperand co);
+
+  string _name;
+  int _sort;
+  int _priority;
+  CPT(TexCoordName) _texcoord_name;
+  Mode _mode;
+  Colorf _color;
+
+  CombineMode _combine_rgb_mode;
+  int _num_combine_rgb_operands;
+  CombineSource _combine_rgb_source0;
+  CombineOperand _combine_rgb_operand0;
+  CombineSource _combine_rgb_source1;
+  CombineOperand _combine_rgb_operand1;
+  CombineSource _combine_rgb_source2;
+  CombineOperand _combine_rgb_operand2;
+
+  CombineMode _combine_alpha_mode;
+  int _num_combine_alpha_operands;
+  CombineSource _combine_alpha_source0;
+  CombineOperand _combine_alpha_operand0;
+  CombineSource _combine_alpha_source1;
+  CombineOperand _combine_alpha_operand1;
+  CombineSource _combine_alpha_source2;
+  CombineOperand _combine_alpha_operand2;
+
+  static PT(TextureStage) _default_stage;
+  static UpdateSeq _sort_seq;
+  
+public:
+  // Datagram stuff
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &me);
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
+
+  static TypedWritable *make_TextureStage(const FactoryParams &params);
+
+protected:
+  void fillin(DatagramIterator& scan, BamReader* manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWritableReferenceCount::init_type();
+    register_type(_type_handle, "TextureStage",
+                  TypedWritableReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const TextureStage &ts);
+
+EXPCL_PANDA ostream &operator << (ostream &out, TextureStage::Mode mode);
+EXPCL_PANDA ostream &operator << (ostream &out, TextureStage::CombineMode cm);
+EXPCL_PANDA ostream &operator << (ostream &out, TextureStage::CombineSource cs);
+EXPCL_PANDA ostream &operator << (ostream &out, TextureStage::CombineOperand co);
+
+
+#include "textureStage.I"
+
+#endif
+
+
+  

+ 10 - 0
panda/src/gsgmisc/geomIssuer.I

@@ -56,6 +56,16 @@ issue_texcoord(GeomBindType bind, Geom::TexCoordIterator &i) {
   _texcoord_command[bind](_geom, i, _gsg);
   _texcoord_command[bind](_geom, i, _gsg);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomIssuer::issue_texcoord
+//       Access: Public
+//  Description: Handles the case of multitexture coordinates.
+////////////////////////////////////////////////////////////////////
+INLINE void GeomIssuer::
+issue_texcoord(GeomBindType bind, Geom::MultiTexCoordIterator &i) {
+  _multitexcoord_command[bind](_geom, i, _gsg);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomIssuer::issue_color
 //     Function: GeomIssuer::issue_color
 //       Access: Public
 //       Access: Public

+ 61 - 0
panda/src/gsgmisc/geomIssuer.cxx

@@ -35,6 +35,11 @@ issue_texcoord_noop(const Geom *, Geom::TexCoordIterator &,
                   GraphicsStateGuardianBase *) {
                   GraphicsStateGuardianBase *) {
 }
 }
 
 
+static void
+issue_multitexcoord_noop(const Geom *, Geom::MultiTexCoordIterator &,
+                         GraphicsStateGuardianBase *) {
+}
+
 static void
 static void
 issue_color_noop(const Geom *, Geom::ColorIterator &, 
 issue_color_noop(const Geom *, Geom::ColorIterator &, 
                  GraphicsStateGuardianBase *) {
                  GraphicsStateGuardianBase *) {
@@ -57,6 +62,7 @@ GeomIssuer() {
     _vertex_command[i] = issue_vertex_noop;
     _vertex_command[i] = issue_vertex_noop;
     _normal_command[i] = issue_normal_noop;
     _normal_command[i] = issue_normal_noop;
     _texcoord_command[i] = issue_texcoord_noop;
     _texcoord_command[i] = issue_texcoord_noop;
+    _multitexcoord_command[i] = issue_multitexcoord_noop;
     _color_command[i] = issue_color_noop;
     _color_command[i] = issue_color_noop;
   }
   }
   _geom = NULL;
   _geom = NULL;
@@ -74,6 +80,10 @@ GeomIssuer() {
 //               places to either issue the component or do nothing,
 //               places to either issue the component or do nothing,
 //               according to the requirements of the geom and of the
 //               according to the requirements of the geom and of the
 //               current state of the gsg.
 //               current state of the gsg.
+//
+//               This constructor is deprecated; it only supports
+//               single-stage texturing.  Use the next constructor for
+//               multitexturing support.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 GeomIssuer::
 GeomIssuer::
 GeomIssuer(const Geom *geom,
 GeomIssuer(const Geom *geom,
@@ -107,3 +117,54 @@ GeomIssuer(const Geom *geom,
     _color_command[geom->get_binding(G_COLOR)] = color;
     _color_command[geom->get_binding(G_COLOR)] = color;
   }
   }
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomIssuer::Constructor
+//       Access: Public
+//  Description: This constructor supports switchably issuing
+//               multitexture calls.  If the MultiTexCoordIterator
+//               indicates that multitexturing is in use (e.g. texture
+//               coordinates are to be issued for any stage other than
+//               stage 0), then multi_texcoord() will be called for
+//               each issue_texcoord() call.  Otherwise,
+//               single_texcoord() will be called.
+////////////////////////////////////////////////////////////////////
+GeomIssuer::
+GeomIssuer(const Geom *geom,
+           GraphicsStateGuardianBase *gsg,
+           IssueVertex *vertex,
+           IssueNormal *normal,
+           IssueColor *color,
+           IssueMultiTexCoord *single_texcoord,
+           IssueMultiTexCoord *multi_texcoord,
+           const Geom::MultiTexCoordIterator &ti) {
+  memcpy(this, &noop_issuer, sizeof(GeomIssuer));
+  _geom = geom;
+  _gsg = gsg;
+
+  // Issue vertices by default (we might not want to if we're doing
+  // performance analysis)
+  if (vertex != NULL) {
+    _vertex_command[geom->get_binding(G_COORD)] = vertex;
+  }
+
+  // Issue normals only if we have them and the gsg says we should.
+  if (normal != NULL && gsg->wants_normals()) {
+    _normal_command[geom->get_binding(G_NORMAL)] = normal;
+  }
+
+  // And ditto for colors.
+  if (color != NULL && gsg->wants_colors()) {
+    _color_command[geom->get_binding(G_COLOR)] = color;
+  }
+
+  // Issue texcoords if we have them and the gsg wants them.
+  if ((single_texcoord != NULL && multi_texcoord != NULL) && 
+      ti._num_stages > 0 && gsg->wants_texcoords()) {
+    if (ti._num_stages > 1 || ti._stage_index[0] != 0) {
+      _multitexcoord_command[G_PER_VERTEX] = multi_texcoord;
+    } else {
+      _multitexcoord_command[G_PER_VERTEX] = single_texcoord;
+    }
+  }
+}

+ 15 - 1
panda/src/gsgmisc/geomIssuer.h

@@ -52,8 +52,9 @@ public:
   // backend, and increment the iterator.
   // backend, and increment the iterator.
   typedef void IssueVertex(const Geom *, Geom::VertexIterator &, GraphicsStateGuardianBase *gsg);
   typedef void IssueVertex(const Geom *, Geom::VertexIterator &, GraphicsStateGuardianBase *gsg);
   typedef void IssueNormal(const Geom *, Geom::NormalIterator &, GraphicsStateGuardianBase *gsg);
   typedef void IssueNormal(const Geom *, Geom::NormalIterator &, GraphicsStateGuardianBase *gsg);
-  typedef void IssueTexCoord(const Geom *, Geom::TexCoordIterator &, GraphicsStateGuardianBase *gsg);
   typedef void IssueColor(const Geom *, Geom::ColorIterator &, GraphicsStateGuardianBase *gsg);
   typedef void IssueColor(const Geom *, Geom::ColorIterator &, GraphicsStateGuardianBase *gsg);
+  typedef void IssueTexCoord(const Geom *, Geom::TexCoordIterator &, GraphicsStateGuardianBase *gsg);
+  typedef void IssueMultiTexCoord(const Geom *, Geom::MultiTexCoordIterator &, GraphicsStateGuardianBase *gsg);
 
 
   GeomIssuer();
   GeomIssuer();
   GeomIssuer(const Geom *geom,
   GeomIssuer(const Geom *geom,
@@ -62,6 +63,16 @@ public:
              IssueNormal *normal,
              IssueNormal *normal,
              IssueTexCoord *texcoord,
              IssueTexCoord *texcoord,
              IssueColor *color);
              IssueColor *color);
+  GeomIssuer(const Geom *geom,
+             GraphicsStateGuardianBase *gsg,
+             IssueVertex *vertex,
+             IssueNormal *normal,
+             IssueColor *color,
+             IssueMultiTexCoord *single_texcoord,
+             IssueMultiTexCoord *multi_texcoord,
+             const Geom::MultiTexCoordIterator &ti);
+             
+
 
 
   INLINE void issue_vertex(GeomBindType bind,
   INLINE void issue_vertex(GeomBindType bind,
                            Geom::VertexIterator &i);
                            Geom::VertexIterator &i);
@@ -69,6 +80,8 @@ public:
                            Geom::NormalIterator &i);
                            Geom::NormalIterator &i);
   INLINE void issue_texcoord(GeomBindType bind,
   INLINE void issue_texcoord(GeomBindType bind,
                              Geom::TexCoordIterator &i);
                              Geom::TexCoordIterator &i);
+  INLINE void issue_texcoord(GeomBindType bind,
+                             Geom::MultiTexCoordIterator &i);
   INLINE void issue_color(GeomBindType bind,
   INLINE void issue_color(GeomBindType bind,
                           Geom::ColorIterator &i);
                           Geom::ColorIterator &i);
 
 
@@ -78,6 +91,7 @@ protected:
   IssueVertex *_vertex_command[num_GeomBindTypes];
   IssueVertex *_vertex_command[num_GeomBindTypes];
   IssueNormal *_normal_command[num_GeomBindTypes];
   IssueNormal *_normal_command[num_GeomBindTypes];
   IssueTexCoord *_texcoord_command[num_GeomBindTypes];
   IssueTexCoord *_texcoord_command[num_GeomBindTypes];
+  IssueMultiTexCoord *_multitexcoord_command[num_GeomBindTypes];
   IssueColor *_color_command[num_GeomBindTypes];
   IssueColor *_color_command[num_GeomBindTypes];
 };
 };
 
 

+ 2 - 0
panda/src/mesadisplay/mesagsg.h

@@ -29,10 +29,12 @@
 #ifdef MESA_MGL
 #ifdef MESA_MGL
   #define GLP(name) mgl##name
   #define GLP(name) mgl##name
   #define GLUP(name) mglu##name
   #define GLUP(name) mglu##name
+  #define GLPREFIX_QUOTED "mgl"
   #define USE_MGL_NAMESPACE 1
   #define USE_MGL_NAMESPACE 1
 #else
 #else
   #define GLP(name) gl##name
   #define GLP(name) gl##name
   #define GLUP(name) glu##name
   #define GLUP(name) glu##name
+  #define GLPREFIX_QUOTED "gl"
 #endif
 #endif
 #define CLP(name) Mesa##name
 #define CLP(name) Mesa##name
 #define CLASSPREFIX_QUOTED "Mesa"
 #define CLASSPREFIX_QUOTED "Mesa"

+ 33 - 0
panda/src/mesadisplay/osMesaGraphicsStateGuardian.cxx

@@ -60,3 +60,36 @@ OSMesaGraphicsStateGuardian::
     _context = (OSMesaContext)NULL;
     _context = (OSMesaContext)NULL;
   }
   }
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: OSMesaGraphicsStateGuardian::get_extension_func
+//       Access: Public, Virtual
+//  Description: Returns the pointer to the GL extension function with
+//               the indicated name.  It is the responsibility of the
+//               caller to ensure that the required extension is
+//               defined in the OpenGL runtime prior to calling this;
+//               it is an error to call this for a function that is
+//               not defined.
+////////////////////////////////////////////////////////////////////
+void *OSMesaGraphicsStateGuardian::
+get_extension_func(const char *, const char *name) {
+#if (OSMESA_MAJOR_VERSION == 4 && OSMESA_MINOR_VERSION >= 1) || OSMESA_MAJOR_VERSION > 4
+  // If we've got at least OSMesa version 4.1, then we can use
+  // OSMesaGetProcAddress.
+
+  // We ignore the prefix and always use "gl", since that's what Mesa
+  // does (even if we compile with name mangling enabled to rename the
+  // Mesa functions to "mgl", they're still stored as "gl" in the
+  // OSMesaGetProcAddress() lookup table.
+  string fullname = string("gl") + string(name);
+  return OSMesaGetProcAddress(fullname.c_str());
+
+#else
+  // Otherwise, too bad.  No extension functions for you.  We could
+  // try to write code that would dig around in the system interface
+  // (using dlopen(), for instance) to find the extension functions,
+  // but why should we have to do that?  Just go get the latest Mesa,
+  // for goodness sakes!
+  return NULL;
+#endif
+}

+ 3 - 0
panda/src/mesadisplay/osMesaGraphicsStateGuardian.h

@@ -35,6 +35,9 @@ public:
 
 
   OSMesaContext _context;
   OSMesaContext _context;
 
 
+protected:
+  virtual void *get_extension_func(const char *prefix, const char *name);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 1 - 1
panda/src/particlesystem/spriteParticleRenderer.cxx

@@ -125,7 +125,7 @@ set_from_node(const NodePath &node_path) {
 
 
   // Now examine the UV's of the first Geom within the GeomNode.
   // Now examine the UV's of the first Geom within the GeomNode.
   nassertv(gnode->get_num_geoms() > 0);
   nassertv(gnode->get_num_geoms() > 0);
-  Geom *geom = gnode->get_geom(0);
+  const Geom *geom = gnode->get_geom(0);
 
 
   PTA_TexCoordf texcoords;
   PTA_TexCoordf texcoords;
   GeomBindType bind;
   GeomBindType bind;

+ 6 - 1
panda/src/pgraph/Sources.pp

@@ -88,10 +88,12 @@
     spotlight.I spotlight.h \
     spotlight.I spotlight.h \
     switchNode.I switchNode.h \
     switchNode.I switchNode.h \
     texMatrixAttrib.I texMatrixAttrib.h \
     texMatrixAttrib.I texMatrixAttrib.h \
+    texProjectorEffect.I texProjectorEffect.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureAttrib.I textureAttrib.h \
     textureAttrib.I textureAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     textureCollection.I textureCollection.h \
     textureCollection.I textureCollection.h \
+    textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
     transformState.I transformState.h \
     transparencyAttrib.I transparencyAttrib.h \
     transparencyAttrib.I transparencyAttrib.h \
     workingNodePath.I workingNodePath.h
     workingNodePath.I workingNodePath.h
@@ -152,7 +154,6 @@
     modelNode.cxx \
     modelNode.cxx \
     modelPool.cxx \
     modelPool.cxx \
     modelRoot.cxx \
     modelRoot.cxx \
-    nodePath.cxx \
     nodePathCollection.cxx \
     nodePathCollection.cxx \
     nodePathComponent.cxx \
     nodePathComponent.cxx \
     nodePathLerps.cxx \
     nodePathLerps.cxx \
@@ -177,10 +178,12 @@
     spotlight.cxx \
     spotlight.cxx \
     switchNode.cxx \
     switchNode.cxx \
     texMatrixAttrib.cxx \
     texMatrixAttrib.cxx \
+    texProjectorEffect.cxx \
     textureApplyAttrib.cxx \
     textureApplyAttrib.cxx \
     textureAttrib.cxx \
     textureAttrib.cxx \
     texGenAttrib.cxx \
     texGenAttrib.cxx \
     textureCollection.cxx \
     textureCollection.cxx \
+    textureStageCollection.cxx \
     transformState.cxx \
     transformState.cxx \
     transparencyAttrib.cxx \
     transparencyAttrib.cxx \
     workingNodePath.cxx
     workingNodePath.cxx
@@ -263,10 +266,12 @@
     spotlight.I spotlight.h \
     spotlight.I spotlight.h \
     switchNode.I switchNode.h \
     switchNode.I switchNode.h \
     texMatrixAttrib.I texMatrixAttrib.h \
     texMatrixAttrib.I texMatrixAttrib.h \
+    texProjectorEffect.I texProjectorEffect.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureAttrib.I textureAttrib.h \
     textureAttrib.I textureAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     texGenAttrib.I texGenAttrib.h \
     textureCollection.I textureCollection.h \
     textureCollection.I textureCollection.h \
+    textureStageCollection.I textureStageCollection.h \
     transformState.I transformState.h \
     transformState.I transformState.h \
     transparencyAttrib.I transparencyAttrib.h \
     transparencyAttrib.I transparencyAttrib.h \
     workingNodePath.I workingNodePath.h
     workingNodePath.I workingNodePath.h

+ 40 - 10
panda/src/pgraph/billboardEffect.cxx

@@ -17,6 +17,8 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "billboardEffect.h"
 #include "billboardEffect.h"
+#include "cullTraverser.h"
+#include "cullTraverserData.h"
 #include "nodePath.h"
 #include "nodePath.h"
 #include "look_at.h"
 #include "look_at.h"
 #include "bamReader.h"
 #include "bamReader.h"
@@ -95,15 +97,43 @@ output(ostream &out) const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: BillboardEffect::do_billboard
-//       Access: Public
-//  Description: Computes the appropriate transform to apply to the
-//               billboarded geometry, given its current net
-//               transform, and the camera's inverse net transform.
+//     Function: BillboardEffect::has_cull_callback
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this effect during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool BillboardEffect::
+has_cull_callback() const {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-CPT(TransformState) BillboardEffect::
-do_billboard(const TransformState *net_transform,
-             const TransformState *camera_transform) const {
+//     Function: BillboardEffect::cull_callback
+//       Access: Public, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               At the time this function is called, the current
+//               node's transform and state have not yet been applied
+//               to the net_transform and net_state.  This callback
+//               may modify the node_transform and node_state to apply
+//               an effective change to the render state at this
+//               level.
+////////////////////////////////////////////////////////////////////
+void BillboardEffect::
+cull_callback(CullTraverser *trav, CullTraverserData &data,
+              CPT(TransformState) &node_transform,
+              CPT(RenderState) &) const {
+  CPT(TransformState) net_transform = data._net_transform->compose(node_transform);
+  const TransformState *camera_transform = trav->get_camera_transform();
+
   // Determine the relative transform to our camera (or other look_at
   // Determine the relative transform to our camera (or other look_at
   // coordinate space).
   // coordinate space).
   if (!_look_at.is_empty()) {
   if (!_look_at.is_empty()) {
@@ -112,7 +142,7 @@ do_billboard(const TransformState *net_transform,
 
 
   if (net_transform->is_singular()) {
   if (net_transform->is_singular()) {
     // If we're under a singular transform, never mind.
     // If we're under a singular transform, never mind.
-    return TransformState::make_identity();
+    return;
   }
   }
 
 
   CPT(TransformState) rel_transform =
   CPT(TransformState) rel_transform =
@@ -154,7 +184,7 @@ do_billboard(const TransformState *net_transform,
     rotate.set_row(3, translate);
     rotate.set_row(3, translate);
   }
   }
 
 
-  return TransformState::make_mat(rotate);
+  node_transform = node_transform->compose(TransformState::make_mat(rotate));
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 4 - 2
panda/src/pgraph/billboardEffect.h

@@ -58,8 +58,10 @@ public:
   virtual bool safe_to_transform() const;
   virtual bool safe_to_transform() const;
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
 
 
-  CPT(TransformState) do_billboard(const TransformState *net_transform,
-                                   const TransformState *camera_transform) const;
+  virtual bool has_cull_callback() const;
+  virtual void cull_callback(CullTraverser *trav, CullTraverserData &data,
+                             CPT(TransformState) &node_transform,
+                             CPT(RenderState) &node_state) const;
 
 
 protected:
 protected:
   virtual int compare_to_impl(const RenderEffect *other) const;
   virtual int compare_to_impl(const RenderEffect *other) const;

+ 2 - 0
panda/src/pgraph/colorBlendAttrib.h

@@ -23,6 +23,8 @@
 
 
 #include "renderAttrib.h"
 #include "renderAttrib.h"
 
 
+class FactoryParams;
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : ColorBlendAttrib
 //       Class : ColorBlendAttrib
 // Description : This specifies how colors are blended into the frame
 // Description : This specifies how colors are blended into the frame

+ 2 - 0
panda/src/pgraph/colorWriteAttrib.h

@@ -23,6 +23,8 @@
 
 
 #include "renderAttrib.h"
 #include "renderAttrib.h"
 
 
+class FactoryParams;
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : ColorWriteAttrib
 //       Class : ColorWriteAttrib
 // Description : Enables or disables writing to the color buffer.
 // Description : Enables or disables writing to the color buffer.

+ 41 - 10
panda/src/pgraph/compassEffect.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "compassEffect.h"
 #include "compassEffect.h"
+#include "cullTraverserData.h"
 #include "config_pgraph.h"
 #include "config_pgraph.h"
 #include "nodePath.h"
 #include "nodePath.h"
 #include "bamReader.h"
 #include "bamReader.h"
@@ -103,20 +104,47 @@ output(ostream &out) const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: CompassEffect::do_compass
-//       Access: Public
-//  Description: Computes the appropriate transform to rotate the node
-//               according to the reference node, or to the root
-//               transform if there is no reference node.
+//     Function: CompassEffect::has_cull_callback
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this effect during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool CompassEffect::
+has_cull_callback() const {
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-CPT(TransformState) CompassEffect::
-do_compass(const TransformState *net_transform,
-           const TransformState *node_transform) const {
+//     Function: CompassEffect::cull_callback
+//       Access: Public, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               At the time this function is called, the current
+//               node's transform and state have not yet been applied
+//               to the net_transform and net_state.  This callback
+//               may modify the node_transform and node_state to apply
+//               an effective change to the render state at this
+//               level.
+////////////////////////////////////////////////////////////////////
+void CompassEffect::
+cull_callback(CullTraverser *, CullTraverserData &data,
+              CPT(TransformState) &node_transform,
+              CPT(RenderState) &) const {
   if (_properties == 0) {
   if (_properties == 0) {
     // Nothing to do.
     // Nothing to do.
-    return TransformState::make_identity();
+    return;
   }
   }
 
 
+  CPT(TransformState) net_transform = data._net_transform->compose(node_transform);
+
   // Compute the reference transform: our transform, as applied to the
   // Compute the reference transform: our transform, as applied to the
   // reference node.
   // reference node.
   CPT(TransformState) ref_transform;
   CPT(TransformState) ref_transform;
@@ -194,7 +222,10 @@ do_compass(const TransformState *net_transform,
 
 
   // Now compute the transform that will convert net_transform to
   // Now compute the transform that will convert net_transform to
   // want_transform.  This is inv(net_transform) * want_transform.
   // want_transform.  This is inv(net_transform) * want_transform.
-  return net_transform->invert_compose(want_transform);
+  CPT(TransformState) compass_transform =
+    net_transform->invert_compose(want_transform);
+
+  node_transform = node_transform->compose(compass_transform);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 4 - 2
panda/src/pgraph/compassEffect.h

@@ -83,8 +83,10 @@ public:
   virtual bool safe_to_transform() const;
   virtual bool safe_to_transform() const;
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
 
 
-  CPT(TransformState) do_compass(const TransformState *net_transform,
-                                 const TransformState *node_transform) const;
+  virtual bool has_cull_callback() const;
+  virtual void cull_callback(CullTraverser *trav, CullTraverserData &data,
+                             CPT(TransformState) &node_transform,
+                             CPT(RenderState) &node_state) const;
 
 
 protected:
 protected:
   virtual int compare_to_impl(const RenderEffect *other) const;
   virtual int compare_to_impl(const RenderEffect *other) const;

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

@@ -78,6 +78,7 @@
 #include "spotlight.h"
 #include "spotlight.h"
 #include "switchNode.h"
 #include "switchNode.h"
 #include "texMatrixAttrib.h"
 #include "texMatrixAttrib.h"
+#include "texProjectorEffect.h"
 #include "textureApplyAttrib.h"
 #include "textureApplyAttrib.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
 #include "texGenAttrib.h"
 #include "texGenAttrib.h"
@@ -230,6 +231,7 @@ init_libpgraph() {
   Spotlight::init_type();
   Spotlight::init_type();
   SwitchNode::init_type();
   SwitchNode::init_type();
   TexMatrixAttrib::init_type();
   TexMatrixAttrib::init_type();
+  TexProjectorEffect::init_type();
   TextureApplyAttrib::init_type();
   TextureApplyAttrib::init_type();
   TextureAttrib::init_type();
   TextureAttrib::init_type();
   TexGenAttrib::init_type();
   TexGenAttrib::init_type();
@@ -282,6 +284,7 @@ init_libpgraph() {
   Spotlight::register_with_read_factory();
   Spotlight::register_with_read_factory();
   SwitchNode::register_with_read_factory();
   SwitchNode::register_with_read_factory();
   TexMatrixAttrib::register_with_read_factory();
   TexMatrixAttrib::register_with_read_factory();
+  TexProjectorEffect::register_with_read_factory();
   TextureApplyAttrib::register_with_read_factory();
   TextureApplyAttrib::register_with_read_factory();
   TextureAttrib::register_with_read_factory();
   TextureAttrib::register_with_read_factory();
   TexGenAttrib::register_with_read_factory();
   TexGenAttrib::register_with_read_factory();

+ 2 - 0
panda/src/pgraph/cullFaceAttrib.h

@@ -23,6 +23,8 @@
 
 
 #include "renderAttrib.h"
 #include "renderAttrib.h"
 
 
+class FactoryParams;
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : CullFaceAttrib
 //       Class : CullFaceAttrib
 // Description : Indicates which faces should be culled based on their
 // Description : Indicates which faces should be culled based on their

Some files were not shown because too many files changed in this diff