Bladeren bron

support flat-shading properly in new geom

David Rose 21 jaren geleden
bovenliggende
commit
d72e2da5c5
69 gewijzigde bestanden met toevoegingen van 2056 en 716 verwijderingen
  1. 7 7
      panda/src/display/graphicsStateGuardian.cxx
  2. 3 3
      panda/src/display/graphicsStateGuardian.h
  3. 3 1
      panda/src/dxgsg8/dxGeomMunger8.I
  4. 24 0
      panda/src/dxgsg8/dxGeomMunger8.cxx
  5. 4 1
      panda/src/dxgsg8/dxGeomMunger8.h
  6. 0 13
      panda/src/dxgsg8/dxGraphicsStateGuardian8.I
  7. 94 33
      panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx
  8. 8 2
      panda/src/dxgsg8/dxGraphicsStateGuardian8.h
  9. 70 0
      panda/src/egg/eggAttributes.I
  10. 4 0
      panda/src/egg/eggAttributes.h
  11. 176 171
      panda/src/egg/eggCompositePrimitive.cxx
  12. 4 3
      panda/src/egg/eggCompositePrimitive.h
  13. 109 14
      panda/src/egg/eggGroupNode.cxx
  14. 7 4
      panda/src/egg/eggGroupNode.h
  15. 41 0
      panda/src/egg/eggPrimitive.I
  16. 265 161
      panda/src/egg/eggPrimitive.cxx
  17. 21 4
      panda/src/egg/eggPrimitive.h
  18. 46 0
      panda/src/egg/eggTriangleStrip.cxx
  19. 3 0
      panda/src/egg/eggTriangleStrip.h
  20. 0 9
      panda/src/egg2pg/eggBinner.cxx
  21. 26 0
      panda/src/egg2pg/eggLoader.I
  22. 33 24
      panda/src/egg2pg/eggLoader.cxx
  23. 12 2
      panda/src/egg2pg/eggLoader.h
  24. 3 3
      panda/src/egg2pg/eggRenderState.I
  25. 3 16
      panda/src/egg2pg/eggRenderState.cxx
  26. 1 1
      panda/src/egg2pg/eggRenderState.h
  27. 3 1
      panda/src/glstuff/glGeomMunger_src.I
  28. 2 1
      panda/src/glstuff/glGeomMunger_src.h
  29. 4 4
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  30. 1 1
      panda/src/glstuff/glGraphicsStateGuardian_src.h
  31. 0 5
      panda/src/gobj/geom.cxx
  32. 4 2
      panda/src/gobj/qpgeom.I
  33. 160 5
      panda/src/gobj/qpgeom.cxx
  34. 29 3
      panda/src/gobj/qpgeom.h
  35. 14 14
      panda/src/gobj/qpgeomMunger.I
  36. 33 23
      panda/src/gobj/qpgeomMunger.cxx
  37. 12 1
      panda/src/gobj/qpgeomMunger.h
  38. 103 0
      panda/src/gobj/qpgeomPrimitive.I
  39. 66 18
      panda/src/gobj/qpgeomPrimitive.cxx
  40. 28 5
      panda/src/gobj/qpgeomPrimitive.h
  41. 60 1
      panda/src/gobj/qpgeomTriangles.cxx
  42. 6 1
      panda/src/gobj/qpgeomTriangles.h
  43. 28 4
      panda/src/gobj/qpgeomTrifans.cxx
  44. 5 2
      panda/src/gobj/qpgeomTrifans.h
  45. 130 31
      panda/src/gobj/qpgeomTristrips.cxx
  46. 5 2
      panda/src/gobj/qpgeomTristrips.h
  47. 170 49
      panda/src/gobj/qpgeomVertexCacheManager.I
  48. 67 21
      panda/src/gobj/qpgeomVertexCacheManager.cxx
  49. 60 10
      panda/src/gobj/qpgeomVertexCacheManager.h
  50. 3 3
      panda/src/gobj/qpgeomVertexData.cxx
  51. 1 1
      panda/src/gobj/qpgeomVertexData.h
  52. 3 3
      panda/src/gsgbase/graphicsStateGuardianBase.h
  53. 2 2
      panda/src/pgraph/cullResult.cxx
  54. 5 13
      panda/src/pgraph/cullableObject.I
  55. 22 0
      panda/src/pgraph/cullableObject.cxx
  56. 2 1
      panda/src/pgraph/cullableObject.h
  57. 1 1
      panda/src/pgraph/drawCullHandler.cxx
  58. 18 0
      panda/src/pgraph/renderState.I
  59. 21 0
      panda/src/pgraph/renderState.cxx
  60. 4 0
      panda/src/pgraph/renderState.h
  61. 1 0
      panda/src/pstatclient/pStatProperties.cxx
  62. 1 1
      pandatool/src/egg-qtess/eggQtess.cxx
  63. 3 3
      pandatool/src/eggbase/eggMultiBase.cxx
  64. 3 3
      pandatool/src/eggbase/eggWriter.cxx
  65. 1 1
      pandatool/src/eggprogs/eggCrop.cxx
  66. 2 2
      pandatool/src/eggprogs/eggToC.cxx
  67. 4 4
      pandatool/src/eggprogs/eggTrans.cxx
  68. 1 1
      pandatool/src/lwoegg/lwoToEggConverter.cxx
  69. 1 1
      pandatool/src/xfileegg/xFileMaker.cxx

+ 7 - 7
panda/src/display/graphicsStateGuardian.cxx

@@ -274,12 +274,12 @@ release_geom(GeomContext *) {
 //               appropriate to this GSG for the indicated state.
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomMunger) GraphicsStateGuardian::
-get_geom_munger(const RenderState *) {
+get_geom_munger(const RenderState *state) {
   // The default implementation returns a munger that does nothing,
   // but presumably, every kind of GSG needs some special munging
   // action, so real GSG's will override this to return something more
   // useful.
-  return qpGeomMunger::register_munger(new qpGeomMunger);
+  return qpGeomMunger::register_munger(new qpGeomMunger(this, state));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -632,7 +632,7 @@ begin_draw_primitives(const qpGeomVertexData *data) {
 //  Description: Draws a series of disconnected triangles.
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
-draw_triangles(qpGeomTriangles *) {
+draw_triangles(const qpGeomTriangles *) {
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -641,8 +641,8 @@ draw_triangles(qpGeomTriangles *) {
 //  Description: Draws a series of triangle strips.
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
-draw_tristrips(qpGeomTristrips *primitive) {
-  PT(qpGeomPrimitive) new_prim = primitive->decompose();
+draw_tristrips(const qpGeomTristrips *primitive) {
+  CPT(qpGeomPrimitive) new_prim = primitive->decompose();
   if (!new_prim->is_of_type(qpGeomTristrips::get_class_type())) {
     new_prim->draw(this);
   }
@@ -654,8 +654,8 @@ draw_tristrips(qpGeomTristrips *primitive) {
 //  Description: Draws a series of triangle fans.
 ////////////////////////////////////////////////////////////////////
 void GraphicsStateGuardian::
-draw_trifans(qpGeomTrifans *primitive) {
-  PT(qpGeomPrimitive) new_prim = primitive->decompose();
+draw_trifans(const qpGeomTrifans *primitive) {
+  CPT(qpGeomPrimitive) new_prim = primitive->decompose();
   if (!new_prim->is_of_type(qpGeomTrifans::get_class_type())) {
     new_prim->draw(this);
   }

+ 3 - 3
panda/src/display/graphicsStateGuardian.h

@@ -144,9 +144,9 @@ public:
   virtual void finish_decal();
 
   virtual bool begin_draw_primitives(const qpGeomVertexData *vertex_data);
-  virtual void draw_triangles(qpGeomTriangles *primitive);
-  virtual void draw_tristrips(qpGeomTristrips *primitive);
-  virtual void draw_trifans(qpGeomTrifans *primitive);
+  virtual void draw_triangles(const qpGeomTriangles *primitive);
+  virtual void draw_tristrips(const qpGeomTristrips *primitive);
+  virtual void draw_trifans(const qpGeomTrifans *primitive);
   virtual void end_draw_primitives();
 
   virtual bool framebuffer_bind_to_texture(GraphicsOutput *win, Texture *tex);

+ 3 - 1
panda/src/dxgsg8/dxGeomMunger8.I

@@ -23,5 +23,7 @@
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE DXGeomMunger8::
-DXGeomMunger8() {
+DXGeomMunger8(GraphicsStateGuardian *gsg, const RenderState *state) :
+  qpGeomMunger(gsg, state)
+{
 }

+ 24 - 0
panda/src/dxgsg8/dxGeomMunger8.cxx

@@ -72,3 +72,27 @@ munge_format_impl(const qpGeomVertexFormat *orig) {
   PT(qpGeomVertexFormat) new_format = new qpGeomVertexFormat(new_array_format);
   return qpGeomVertexFormat::register_format(new_format);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: DXGeomMunger8::munge_geom_impl
+//       Access: Protected, Virtual
+//  Description: Converts a Geom and/or its data as necessary.
+////////////////////////////////////////////////////////////////////
+void DXGeomMunger8::
+munge_geom_impl(CPT(qpGeom) &geom, CPT(qpGeomVertexData) &data) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DXGeomMunger8::compare_to_impl
+//       Access: Protected, Virtual
+//  Description: Called to compare two GeomMungers who are known to be
+//               of the same type, for an apples-to-apples comparison.
+//               This will never be called on two pointers of a
+//               different type.
+////////////////////////////////////////////////////////////////////
+int DXGeomMunger8::
+compare_to_impl(const qpGeomMunger *other) const {
+  //  const DXGeomMunger8 *om = DCAST(DXGeomMunger8, other);
+
+  return 0;
+}

+ 4 - 1
panda/src/dxgsg8/dxGeomMunger8.h

@@ -21,6 +21,7 @@
 
 #include "pandabase.h"
 #include "qpgeomMunger.h"
+#include "graphicsStateGuardian.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : DXGeomMunger8
@@ -32,10 +33,12 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDADX DXGeomMunger8 : public qpGeomMunger {
 public:
-  INLINE DXGeomMunger8();
+  INLINE DXGeomMunger8(GraphicsStateGuardian *gsg, const RenderState *state);
 
 protected:
   virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig);
+  virtual void munge_geom_impl(CPT(qpGeom) &geom, CPT(qpGeomVertexData) &data);
+  virtual int compare_to_impl(const qpGeomMunger *other) const;
 
 public:
   static TypeHandle get_class_type() {

+ 0 - 13
panda/src/dxgsg8/dxGraphicsStateGuardian8.I

@@ -188,19 +188,6 @@ set_color_writemask(UINT color_writemask) {
   }
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: DXGraphicsStateGuardian8::enable_blend
-//       Access:
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void DXGraphicsStateGuardian8::
-enable_gouraud_shading(bool val) {
-  if (_bGouraudShadingOn != val) {
-    _bGouraudShadingOn = val;
-    _pD3DDevice->SetRenderState(D3DRS_SHADEMODE, (val ? D3DSHADE_GOURAUD : D3DSHADE_FLAT));
-  }
-}
-
 INLINE void DXGraphicsStateGuardian8::
 enable_primitive_clipping(bool val) {
   if (_clipping_enabled != val) {

+ 94 - 33
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -34,6 +34,7 @@
 #include "textureAttrib.h"
 #include "texGenAttrib.h"
 #include "lightAttrib.h"
+#include "shadeModelAttrib.h"
 #include "cullFaceAttrib.h"
 #include "transparencyAttrib.h"
 #include "alphaTestAttrib.h"
@@ -323,8 +324,8 @@ dx_init(void) {
     _color_writemask = 0xFFFFFFFF;
     _CurFVFType = 0x0;  // guards SetVertexShader fmt
 
-    _bGouraudShadingOn = false;
-    _pD3DDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
+    _bGouraudShadingOn = true;
+    _pD3DDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
 
 //   this specifies if lighting model uses material color or vertex color
 //   (not related to gouraud/flat shading)
@@ -520,21 +521,8 @@ dx_init(void) {
     }
 #endif
 
-#ifndef NDEBUG
-    if(dx_force_backface_culling!=0) {
-      if((dx_force_backface_culling > 0) &&
-         (dx_force_backface_culling < D3DCULL_FORCE_DWORD)) {
-             _pD3DDevice->SetRenderState(D3DRS_CULLMODE, dx_force_backface_culling);
-      } else {
-          dx_force_backface_culling=0;
-          if(dxgsg8_cat.is_debug())
-              dxgsg8_cat.debug() << "error, invalid value for dx-force-backface-culling\n";
-      }
-    }
-    _pD3DDevice->SetRenderState(D3DRS_CULLMODE, dx_force_backface_culling);
-#else
+    _cull_face_mode = CullFaceAttrib::M_cull_none;
     _pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
-#endif
 
     _alpha_func = D3DCMP_ALWAYS;
     _alpha_func_refval = 1.0f;
@@ -730,6 +718,22 @@ prepare_lens() {
   return SUCCEEDED(hr);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DXGraphicsStateGuardian8::enable_gouraud_shading
+//       Access:
+//  Description:
+////////////////////////////////////////////////////////////////////
+void DXGraphicsStateGuardian8::
+enable_gouraud_shading(bool val) {
+  if (_bGouraudShadingOn != val) {
+    if (val) {
+      modify_state(get_smooth_state());
+    } else {
+      modify_state(get_flat_state());
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DXGraphicsStateGuardian8::get_blend_func
 //       Access: Protected, Static
@@ -1053,9 +1057,6 @@ draw_prim_setup(const Geom *geom) {
     vertex_size += sizeof(float) * 2;
   }
   
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  
   // Note on fogging:
   // the fogging expression should really be || (_fog_enabled && (_doFogType==PerVertexFog))
   // instead of just || (_fog_enabled), since GOURAUD shading should not be required for PerPixel
@@ -1063,10 +1064,17 @@ draw_prim_setup(const Geom *geom) {
   // but dont force the shading mode to gouraud internally, so you end up with flat-shaded fog colors
   // (note, TNT does the right thing tho).  So I guess we must do gouraud shading for all fog rendering for now
   // note that if _doFogType==None, _fog_enabled will always be false
-  
-  bool need_gouraud_shading = ((_perVertex & (PER_COLOR | (wants_normals() ? PER_NORMAL : 0))) || _fog_enabled);
-  
-  enable_gouraud_shading(need_gouraud_shading);
+
+  if ((_perVertex & (PER_COLOR | (wants_normals() ? PER_NORMAL : 0))) || _fog_enabled) {
+    // We definitely need gouraud shading.
+    enable_gouraud_shading(true);
+  } else if ((_perComp & (PER_COLOR | (wants_normals() ? PER_NORMAL : 0)))) {
+    // We definitely need flat shading.
+    enable_gouraud_shading(false);
+  } else {
+    // Don't care about gouraud shading.
+  }
+
   set_vertex_format(newFVFflags);
   
   return vertex_size;
@@ -1517,6 +1525,12 @@ draw_sprite(GeomSprite *geom, GeomContext *gc) {
   
   
   // Note: for DX8, try to use the PointSprite primitive instead of doing all the stuff below
+
+  // see note on fog and gouraud-shading in draw_prim_setup
+  bool bUseGouraudShadedColor=_fog_enabled;
+  if (_fog_enabled) {
+    enable_gouraud_shading(true);
+  }
   
 #ifdef GSG_VERBOSE
   dxgsg8_cat.debug() << "draw_sprite()" << endl;
@@ -1727,9 +1741,6 @@ draw_sprite(GeomSprite *geom, GeomContext *gc) {
     CurColor = _curD3Dcolor;
   }
   
-  // see note on fog and gouraud-shading in draw_prim_setup
-  bool bUseGouraudShadedColor=_fog_enabled;
-  enable_gouraud_shading(_fog_enabled);
   set_vertex_format(FVFType);
   
 #ifdef _DEBUG
@@ -2665,13 +2676,13 @@ begin_draw_primitives(const qpGeomVertexData *vertex_data) {
 //  Description: Draws a series of disconnected triangles.
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian8::
-draw_triangles(qpGeomTriangles *primitive) {
+draw_triangles(const qpGeomTriangles *primitive) {
   HRESULT hr = _pD3DDevice->DrawIndexedPrimitiveUP
     (D3DPT_TRIANGLELIST, 
      primitive->get_min_vertex(),
      primitive->get_max_vertex() - primitive->get_min_vertex() + 1,
      primitive->get_num_primitives(), 
-     primitive->get_vertices(),
+     primitive->get_flat_first_vertices(),
      D3DFMT_INDEX16,
      _vertex_data->get_array_data(0), 
      _vertex_data->get_format()->get_array(0)->get_stride());
@@ -2903,8 +2914,8 @@ release_texture(TextureContext *tc) {
 //               appropriate to this GSG for the indicated state.
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomMunger) DXGraphicsStateGuardian8::
-get_geom_munger(const RenderState *) {
-  PT(DXGeomMunger8) munger = new DXGeomMunger8;
+get_geom_munger(const RenderState *state) {
+  PT(DXGeomMunger8) munger = new DXGeomMunger8(this, state);
   return qpGeomMunger::register_munger(munger);
 }
 
@@ -3304,6 +3315,26 @@ issue_tex_gen(const TexGenAttrib *attrib) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DXGraphicsStateGuardian8::issue_shade_model
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void DXGraphicsStateGuardian8::
+issue_shade_model(const ShadeModelAttrib *attrib) {
+  switch (attrib->get_mode()) {
+  case ShadeModelAttrib::M_smooth:
+    _pD3DDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
+    _bGouraudShadingOn = true;
+    break;
+
+  case ShadeModelAttrib::M_flat:
+    _pD3DDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
+    _bGouraudShadingOn = false;
+    break;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DXGraphicsStateGuardian8::issue_texture
 //       Access: Public, Virtual
@@ -3464,9 +3495,9 @@ issue_depth_write(const DepthWriteAttrib *attrib) {
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian8::
 issue_cull_face(const CullFaceAttrib *attrib) {
-  CullFaceAttrib::Mode mode = attrib->get_effective_mode();
+  _cull_face_mode = attrib->get_effective_mode();
 
-  switch (mode) {
+  switch (_cull_face_mode) {
   case CullFaceAttrib::M_cull_none:
     _pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
     break;
@@ -3478,7 +3509,7 @@ issue_cull_face(const CullFaceAttrib *attrib) {
     break;
   default:
     dxgsg8_cat.error()
-      << "invalid cull face mode " << (int)mode << endl;
+      << "invalid cull face mode " << (int)_cull_face_mode << endl;
     break;
   }
 }
@@ -3881,6 +3912,36 @@ set_read_buffer(const RenderBuffer &rb) {
     return;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: DXGraphicsStateGuardian8::get_smooth_state
+//       Access: Protected, Static
+//  Description: Returns a RenderState object that represents
+//               smooth, per-vertex shading.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) DXGraphicsStateGuardian8::
+get_smooth_state() {
+  static CPT(RenderState) state;
+  if (state == (RenderState *)NULL) {
+    state = RenderState::make(ShadeModelAttrib::make(ShadeModelAttrib::M_smooth));
+  }
+  return state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DXGraphicsStateGuardian8::get_flat_state
+//       Access: Protected, Static
+//  Description: Returns a RenderState object that represents
+//               flat, per-primitive shading.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) DXGraphicsStateGuardian8::
+get_flat_state() {
+  static CPT(RenderState) state;
+  if (state == (RenderState *)NULL) {
+    state = RenderState::make(ShadeModelAttrib::make(ShadeModelAttrib::M_flat));
+  }
+  return state;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: DXGraphicsStateGuardian8::do_auto_rescale_normal
 //       Access: Protected

+ 8 - 2
panda/src/dxgsg8/dxGraphicsStateGuardian8.h

@@ -32,6 +32,7 @@
 #include "displayRegion.h"
 #include "material.h"
 #include "depthTestAttrib.h"
+#include "cullFaceAttrib.h"
 #include "renderModeAttrib.h"
 #include "textureApplyAttrib.h"
 #include "fog.h"
@@ -90,7 +91,7 @@ public:
   virtual void draw_sphere(GeomSphere *geom, GeomContext *gc);
 
   virtual bool begin_draw_primitives(const qpGeomVertexData *vertex_data);
-  virtual void draw_triangles(qpGeomTriangles *primitive);
+  virtual void draw_triangles(const qpGeomTriangles *primitive);
   virtual void end_draw_primitives();
 
   virtual TextureContext *prepare_texture(Texture *tex);
@@ -122,6 +123,7 @@ public:
   virtual void issue_fog(const FogAttrib *attrib);
   virtual void issue_depth_offset(const DepthOffsetAttrib *attrib);
   virtual void issue_tex_gen(const TexGenAttrib *attrib);
+  virtual void issue_shade_model(const ShadeModelAttrib *attrib);
 
   virtual void bind_light(PointLight *light, int light_id);
   virtual void bind_light(DirectionalLight *light, int light_id);
@@ -163,6 +165,9 @@ protected:
 
   INLINE void add_to_FVFBuf(void *data,  size_t bytes) ;
 
+  static CPT(RenderState) get_smooth_state();
+  static CPT(RenderState) get_flat_state();
+
   void do_auto_rescale_normal();
 
   bool                  _bDXisReady;
@@ -194,7 +199,7 @@ protected:
   INLINE void enable_fog(bool val);
   INLINE void enable_zwritemask(bool val);
   INLINE void set_color_writemask(UINT color_writemask);
-  INLINE void enable_gouraud_shading(bool val);
+  void enable_gouraud_shading(bool val);
   INLINE void set_vertex_format(DWORD NewFvfType);
 
   INLINE D3DTEXTUREADDRESS get_texture_wrap_mode(Texture::WrapMode wm) const;
@@ -291,6 +296,7 @@ protected:
   bool _depth_write_enabled;
   bool _alpha_test_enabled;
   DWORD _clip_plane_bits;
+  CullFaceAttrib::Mode _cull_face_mode;
 
   RenderModeAttrib::Mode _current_fill_mode;  //poinr/wireframe/solid
 

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

@@ -59,6 +59,41 @@ clear_normal() {
   _flags &= ~F_has_normal;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggAttributes::matches_normal
+//       Access: Published
+//  Description: Returns true if this normal matches that of the other
+//               EggAttributes object, include the morph list.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggAttributes::
+matches_normal(const EggAttributes &other) const {
+  if (((_flags ^ other._flags) & F_has_normal) != 0) {
+    return false;
+  }
+  if (!has_normal()) {
+    return true;
+  }
+  return (get_normal() == other.get_normal() && 
+          _dnormals.compare_to(other._dnormals) == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggAttributes::copy_normal
+//       Access: Published
+//  Description: Sets this normal to be the same as the other's,
+//               include morphs.  If the other has no normal, this
+//               clears the normal.
+////////////////////////////////////////////////////////////////////
+INLINE void EggAttributes::
+copy_normal(const EggAttributes &other) {
+  if (!other.has_normal()) {
+    clear_normal();
+  } else {
+    set_normal(other.get_normal());
+    _dnormals = other._dnormals;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggAttributes::has_color
 //       Access: Published
@@ -105,6 +140,41 @@ clear_color() {
   _flags &= ~F_has_color;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggAttributes::matches_color
+//       Access: Published
+//  Description: Returns true if this color matches that of the other
+//               EggAttributes object, include the morph list.
+////////////////////////////////////////////////////////////////////
+INLINE bool EggAttributes::
+matches_color(const EggAttributes &other) const {
+  if (((_flags ^ other._flags) & F_has_color) != 0) {
+    return false;
+  }
+  if (!has_color()) {
+    return true;
+  }
+  return (get_color() == other.get_color() && 
+          _drgbas.compare_to(other._drgbas) == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggAttributes::copy_color
+//       Access: Published
+//  Description: Sets this color to be the same as the other's,
+//               include morphs.  If the other has no color, this
+//               clears the color.
+////////////////////////////////////////////////////////////////////
+INLINE void EggAttributes::
+copy_color(const EggAttributes &other) {
+  if (!other.has_color()) {
+    clear_color();
+  } else {
+    set_color(other.get_color());
+    _drgbas = other._drgbas;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggAttributes::sorts_less_than
 //       Access: Published

+ 4 - 0
panda/src/egg/eggAttributes.h

@@ -49,11 +49,15 @@ PUBLISHED:
   INLINE const Normald &get_normal() const;
   INLINE void set_normal(const Normald &normal);
   INLINE void clear_normal();
+  INLINE bool matches_normal(const EggAttributes &other) const;
+  INLINE void copy_normal(const EggAttributes &other);
 
   INLINE bool has_color() const;
   INLINE Colorf get_color() const;
   INLINE void set_color(const Colorf &Color);
   INLINE void clear_color();
+  INLINE bool matches_color(const EggAttributes &other) const;
+  INLINE void copy_color(const EggAttributes &other);
 
   void write(ostream &out, int indent_level) const;
   INLINE bool sorts_less_than(const EggAttributes &other) const;

+ 176 - 171
panda/src/egg/eggCompositePrimitive.cxx

@@ -22,6 +22,72 @@
 TypeHandle EggCompositePrimitive::_type_handle;
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggCompositePrimitive::get_shading
+//       Access: Published, Virtual
+//  Description: Returns the shading properties apparent on this
+//               particular primitive.  This returns S_per_vertex if
+//               the vertices have colors or normals (and they are not
+//               all the same values), or for a simple primitive,
+//               S_overall otherwise.  A composite primitive may also
+//               return S_per_face if the individual component
+//               primitives have colors or normals that are not all
+//               the same values.
+//
+//               To get the most accurate results, you should call
+//               clear_shading() on all connected primitives (or on
+//               all primitives in the egg file), followed by
+//               determine_shading() on each primitive.  You may find
+//               it easiest to call these methods on the EggData root
+//               node (they are defined on EggGroupNode).
+////////////////////////////////////////////////////////////////////
+EggPrimitive::Shading EggCompositePrimitive::
+get_shading() const {
+  Shading basic_shading = EggPrimitive::get_shading();
+  if (basic_shading == S_per_vertex) {
+    return basic_shading;
+  }
+  if (_components.empty()) {
+    return S_overall;
+  }
+
+  // Check if the components all have the same normal.
+  {
+    const EggAttributes *first_component = get_component(0);
+    if (!first_component->has_normal()) {
+      first_component = this;
+    }
+    for (int i = 1; i < get_num_components(); i++) {
+      const EggAttributes *component = get_component(i);
+      if (!component->has_normal()) {
+        component = this;
+      }
+      if (!component->matches_normal(*first_component)) {
+        return S_per_face;
+      }
+    }
+  }
+
+  // Check if the components all have the same color.
+  {
+    const EggAttributes *first_component = get_component(0);
+    if (!first_component->has_color()) {
+      first_component = this;
+    }
+    for (int i = 1; i < get_num_components(); i++) {
+      const EggAttributes *component = get_component(i);
+      if (!component->has_color()) {
+        component = this;
+      }
+      if (!component->matches_color(*first_component)) {
+        return S_per_face;
+      }
+    }
+  }
+
+  return S_overall;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggCompositePrimitive::triangulate_in_place
 //       Access: Published
@@ -52,10 +118,19 @@ triangulate_in_place() {
 ////////////////////////////////////////////////////////////////////
 //     Function: EggCompositePrimitive::unify_attributes
 //       Access: Published, Virtual
-//  Description: Applies per-vertex normal and color to all vertices,
-//               if they are in fact per-vertex (and actually
-//               different for each vertex), or moves them to the
-//               primitive if they are all the same.
+//  Description: If the shading property is S_per_vertex, ensures that
+//               all vertices have a normal and a color, and the
+//               overall primitive does not.
+//
+//               If the shading property is S_per_face, and this is a
+//               composite primitive, ensures that all components have
+//               a normal and a color, and the vertices and overall
+//               primitive do not.  (If this is a simple primitive,
+//               S_per_face works the same as S_overall, below).
+//
+//               If the shading property is S_overall, ensures that no
+//               vertices or components have a normal or a color, and
+//               the overall primitive does (if any exists at all).
 //
 //               After this call, either the primitive will have
 //               normals or its vertices will, but not both.  Ditto
@@ -65,188 +140,114 @@ triangulate_in_place() {
 //               pool.
 ////////////////////////////////////////////////////////////////////
 void EggCompositePrimitive::
-unify_attributes() {
-  // A composite primitive wants to do the same sort of thing with
-  // components that the fundamental primitive does with vertices.
-  // But first, we want to make sure that the primitive overall
-  // attributes have been correctly moved from the vertices, so call
-  // up to the base class.
-  EggPrimitive::unify_attributes();
-
-  // Now the rest of this body is more or less what an EggPrimitive
-  // does for vertices, modified to work on components.
-
-  Components components;
-
-  // First, go through the components and apply the primitive overall
-  // attributes to any component that omits them.
-  bool all_have_normal = true;
-  bool all_have_color = true;
-  Components::iterator ci;
-  for (ci = _components.begin(); ci != _components.end(); ++ci) {
-    EggAttributes *orig_component = (*ci);
-    EggAttributes *component = new EggAttributes(*orig_component);
-
-    if (!component->has_normal()) {
-      if (has_normal()) {
-        component->set_normal(get_normal());
-        component->_dnormals = _dnormals;
-      } else {
-        all_have_normal = false;
-      }
-    }
-    if (!component->has_color()) {
-      if (has_color()) {
-        component->set_color(get_color());
-        component->_drgbas = _drgbas;
-      } else {
-        all_have_color = false;
-      }
-    }
-
-    components.push_back(component);
+unify_attributes(EggPrimitive::Shading shading) {
+  if (shading == S_unknown) {
+    shading = get_shading();
   }
 
-  clear_normal();
-  clear_color();
-
-  // Now see if any ended up different.
-  EggAttributes overall;
-  bool normal_different = false;
-  bool color_different = false;
-
-  Components::iterator vi;
-  for (vi = components.begin(); vi != components.end(); ++vi) {
-    EggAttributes *component = (*vi);
-    if (!all_have_normal) {
-      component->clear_normal();
-    } else if (!normal_different) {
-      if (!overall.has_normal()) {
-        overall.set_normal(component->get_normal());
-        overall._dnormals = component->_dnormals;
-      } else if (overall.get_normal() != component->get_normal() ||
-                 overall._dnormals.compare_to(component->_dnormals) != 0) {
-        normal_different = true;
+  switch (shading) {
+  case S_per_vertex:
+    // Propagate everything to the vertices.
+    {
+      iterator pi;
+      for (pi = begin(); pi != end(); ++pi) {
+        EggVertex *orig_vertex = (*pi);
+        PT(EggVertex) vertex = new EggVertex(*orig_vertex);
+        if (!vertex->has_normal() && has_normal()) {
+          vertex->copy_normal(*this);
+        }
+        if (!vertex->has_color() && has_color()) {
+          vertex->copy_color(*this);
+        }
+
+        vertex = vertex->get_pool()->create_unique_vertex(*vertex);
+        replace(pi, vertex);
       }
-    }
-    if (!all_have_color) {
-      component->clear_color();
-    } else if (!color_different) {
-      if (!overall.has_color()) {
-        overall.set_color(component->get_color());
-        overall._drgbas = component->_drgbas;
-      } else if (overall.get_color() != component->get_color() ||
-                 overall._drgbas.compare_to(component->_drgbas) != 0) {
-        color_different = true;
+      Components::iterator ci;
+      for (ci = _components.begin(); ci != _components.end(); ++ci) {
+        (*ci)->clear_normal();
+        (*ci)->clear_color();
       }
+      clear_normal();
+      clear_color();
     }
-  }
-
-  if (!color_different || !normal_different) {
-    // Ok, it's flat-shaded after all.  Go back through and remove
-    // this stuff from the components.
-    if (!normal_different) {
-      set_normal(overall.get_normal());
-      _dnormals = overall._dnormals;
-    }
-    if (!color_different) {
-      set_color(overall.get_color());
-      _drgbas = overall._drgbas;
-    }
-    for (vi = components.begin(); vi != components.end(); ++vi) {
-      EggAttributes *component = (*vi);
-      if (!normal_different) {
-        component->clear_normal();
+    break;
+
+  case S_per_face:
+    // Propagate everything to the components.
+    {
+      Components::iterator ci;
+      for (ci = _components.begin(); ci != _components.end(); ++ci) {
+        EggAttributes *component = (*ci);
+        if (!component->has_normal() && has_normal()) {
+          component->copy_normal(*this);
+        }
+        if (!component->has_color() && has_color()) {
+          component->copy_color(*this);
+        }
       }
-      if (!color_different) {
-        component->clear_color();
+      iterator pi;
+      for (pi = begin(); pi != end(); ++pi) {
+        EggVertex *orig_vertex = (*pi);
+        if (orig_vertex->has_normal() || orig_vertex->has_color()) {
+          PT(EggVertex) vertex = new EggVertex(*orig_vertex);
+          vertex->clear_normal();
+          vertex->clear_color();
+          
+          vertex = vertex->get_pool()->create_unique_vertex(*vertex);
+          replace(pi, vertex);
+        }
       }
+      clear_normal();
+      clear_color();
     }
-  }
-
-  // Finally, assign the new components.
-  for (size_t i = 0; i < components.size(); i++) {
-    EggAttributes *component = components[i];
-    set_component(i, component);
-    delete component;
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: EggCompositePrimitive::apply_last_attribute
-//       Access: Published, Virtual
-//  Description: Sets the last vertex of the triangle (or each
-//               component) to the primitive normal and/or color, if
-//               they exist.  This reflects the Panda convention of
-//               storing flat-shaded properties on the last vertex,
-//               although it is not usually a convention in Egg.
-//
-//               This may introduce redundant vertices to the vertex
-//               pool.
-////////////////////////////////////////////////////////////////////
-void EggCompositePrimitive::
-apply_last_attribute() {
-  // The first component gets applied to the third vertex, and so on
-  // from there.
-  for (int i = 0; i < get_num_components(); i++) {
-    EggAttributes *component = get_component(i);
-
-    // The significant_change flag is set if we have changed the
-    // vertex in some important way, that will invalidate it for other
-    // primitives that might share it.  We don't consider *adding* a
-    // normal where there wasn't one before to be significant, but we
-    // do consider it significant to change a vertex's normal to
-    // something different.  Similarly for color.
-    bool significant_change = false;
-
-    EggVertex *orig_vertex = get_vertex(i + 2);
-    nassertv(orig_vertex != (EggVertex *)NULL);
-    PT(EggVertex) new_vertex = new EggVertex(*orig_vertex);
-
-    if (component->has_normal() || has_normal()) {
-      if (component->has_normal()) {
-        new_vertex->set_normal(component->get_normal());
-        new_vertex->_dnormals = component->_dnormals;
-      } else {
-        new_vertex->set_normal(get_normal());
-        new_vertex->_dnormals = _dnormals;
+    break;
+
+  case S_overall:
+    // Remove everything from the vertices and components.
+    {
+      iterator pi;
+      for (pi = begin(); pi != end(); ++pi) {
+        EggVertex *vertex = (*pi);
+        if (vertex->has_normal()) {
+          if (!has_normal()) {
+            copy_normal(*vertex);
+          }
+          vertex->clear_normal();
+        }
+        if (vertex->has_color()) {
+          if (!has_color()) {
+            copy_color(*vertex);
+          }
+          vertex->clear_color();
+        }
       }
-
-      if (orig_vertex->has_normal() &&
-          (orig_vertex->get_normal() != new_vertex->get_normal() ||
-           orig_vertex->_dnormals.compare_to(new_vertex->_dnormals) != 0)) {
-        significant_change = true;
+      Components::iterator ci;
+      for (ci = _components.begin(); ci != _components.end(); ++ci) {
+        EggAttributes *component = (*ci);
+        if (component->has_normal()) {
+          if (!has_normal()) {
+            copy_normal(*component);
+          }
+          component->clear_normal();
+        }
+        if (component->has_color()) {
+          if (!has_color()) {
+            copy_color(*component);
+          }
+          component->clear_color();
+        }
       }
     }
+    break;
 
-    if (component->has_color() || has_color()) {
-      if (component->has_color()) {
-        new_vertex->set_color(component->get_color());
-        new_vertex->_drgbas = component->_drgbas;
-      } else {
-        new_vertex->set_color(get_color());
-        new_vertex->_drgbas = _drgbas;
-      }
-
-      if (orig_vertex->has_color() &&
-          (orig_vertex->get_color() != new_vertex->get_color() ||
-           orig_vertex->_drgbas.compare_to(new_vertex->_drgbas) != 0)) {
-        significant_change = true;
-      }
-    }
-
-    if (significant_change) {
-      new_vertex = get_pool()->create_unique_vertex(*new_vertex);
-      set_vertex(i + 2, new_vertex);
-    } else {
-      // Just copy the new attributes back into the pool.
-      ((EggAttributes *)orig_vertex)->operator = (*new_vertex);
-    }
+  case S_unknown:
+    break;
   }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: EggCompositePrimitive::post_apply_last_attribute
+//     Function: EggCompositePrimitive::post_apply_flat_attribute
 //       Access: Published, Virtual
 //  Description: Intended as a followup to apply_last_attribute(),
 //               this also sets an attribute on the first vertices of
@@ -254,12 +255,16 @@ apply_last_attribute() {
 //               attribute set, just so they end up with *something*.
 ////////////////////////////////////////////////////////////////////
 void EggCompositePrimitive::
-post_apply_last_attribute() {
+post_apply_flat_attribute() {
   if (!empty()) {
     for (int i = 0; i < (int)size(); i++) {
       EggVertex *vertex = get_vertex(i);
       EggAttributes *component = get_component(max(i - 2, 0));
 
+      // Use set_normal() instead of copy_normal(), to avoid getting
+      // the morphs--we don't want them here, since we're just putting
+      // a bogus value on the normal anyway.
+
       if (component->has_normal() && !vertex->has_normal()) {
         vertex->set_normal(component->get_normal());
       } else if (has_normal() && !vertex->has_normal()) {

+ 4 - 3
panda/src/egg/eggCompositePrimitive.h

@@ -37,6 +37,8 @@ PUBLISHED:
   INLINE EggCompositePrimitive &operator = (const EggCompositePrimitive &copy);
   INLINE ~EggCompositePrimitive();
 
+  virtual Shading get_shading() const;
+
   INLINE int get_num_components() const;
   INLINE const EggAttributes *get_component(int i) const;
   INLINE EggAttributes *get_component(int i);
@@ -45,9 +47,8 @@ PUBLISHED:
   INLINE bool triangulate_into(EggGroupNode *container) const;
   PT(EggCompositePrimitive) triangulate_in_place();
 
-  virtual void unify_attributes();
-  virtual void apply_last_attribute();
-  virtual void post_apply_last_attribute();
+  virtual void unify_attributes(Shading shading);
+  virtual void post_apply_flat_attribute();
   virtual bool cleanup();
 
 protected:

+ 109 - 14
panda/src/egg/eggGroupNode.cxx

@@ -784,7 +784,7 @@ mesh_triangles(int flags) {
 //               appear anywhere in the hierarchy.
 ////////////////////////////////////////////////////////////////////
 int EggGroupNode::
-remove_unused_vertices() {
+remove_unused_vertices(bool recurse) {
   int num_removed = 0;
 
   Children::iterator ci, cnext;
@@ -805,7 +805,9 @@ remove_unused_vertices() {
       }
 
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {
-      num_removed += DCAST(EggGroupNode, child)->remove_unused_vertices();
+      if (recurse) {
+        num_removed += DCAST(EggGroupNode, child)->remove_unused_vertices(recurse);
+      }
     }
 
     ci = cnext;
@@ -823,7 +825,7 @@ remove_unused_vertices() {
 //               primitives removed.
 ////////////////////////////////////////////////////////////////////
 int EggGroupNode::
-remove_invalid_primitives() {
+remove_invalid_primitives(bool recurse) {
   int num_removed = 0;
 
   Children::iterator ci, cnext;
@@ -841,7 +843,9 @@ remove_invalid_primitives() {
       }
 
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {
-      num_removed += DCAST(EggGroupNode, child)->remove_invalid_primitives();
+      if (recurse) {
+        num_removed += DCAST(EggGroupNode, child)->remove_invalid_primitives(recurse);
+      }
     }
 
     ci = cnext;
@@ -850,6 +854,56 @@ remove_invalid_primitives() {
   return num_removed;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggGroupNode::clear_connected_shading
+//       Access: Published
+//  Description: Resets the connected_shading information on all
+//               primitives at this node and below, so that it may be
+//               accurately rederived by the next call to
+//               get_connected_shading().
+//
+//               It may be a good idea to call
+//               remove_unused_vertices() at the same time, to
+//               establish the correct connectivity.
+////////////////////////////////////////////////////////////////////
+void EggGroupNode::
+clear_connected_shading() {
+  Children::iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    EggNode *child = *ci;
+
+    if (child->is_of_type(EggPrimitive::get_class_type())) {
+      EggPrimitive *prim = DCAST(EggPrimitive, child);
+      prim->clear_connected_shading();
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      DCAST(EggGroupNode, child)->clear_connected_shading();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggGroupNode::get_connected_shading
+//       Access: Published
+//  Description: Queries the connected_shading information on all
+//               primitives at this node and below, to ensure that it
+//               has been completely filled in before we start mucking
+//               around with vertices.
+////////////////////////////////////////////////////////////////////
+void EggGroupNode::
+get_connected_shading() {
+  Children::iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    EggNode *child = *ci;
+
+    if (child->is_of_type(EggPrimitive::get_class_type())) {
+      EggPrimitive *prim = DCAST(EggPrimitive, child);
+      prim->get_connected_shading();
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      DCAST(EggGroupNode, child)->get_connected_shading();
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggGroupNode::rebuild_vertex_pool
 //       Access: Published
@@ -904,22 +958,31 @@ rebuild_vertex_pool(EggVertexPool *vertex_pool, bool recurse) {
 //               normals or its vertices will, but not both.  Ditto
 //               for colors.
 //
+//               If use_connected_shading is true, each polygon is
+//               considered in conjunction with all connected
+//               polygons; otherwise, each polygon is considered
+//               individually.
+//
 //               This may create redundant vertices in the vertex
 //               pool, so it may be a good idea to follow this up with
 //               remove_unused_vertices().
 ////////////////////////////////////////////////////////////////////
 void EggGroupNode::
-unify_attributes(bool recurse) {
+unify_attributes(bool use_connected_shading, bool recurse) {
   Children::iterator ci;
   for (ci = _children.begin(); ci != _children.end(); ++ci) {
     EggNode *child = *ci;
 
     if (child->is_of_type(EggPrimitive::get_class_type())) {
       EggPrimitive *prim = DCAST(EggPrimitive, child);
-      prim->unify_attributes();
+      if (use_connected_shading) {
+        prim->unify_attributes(prim->get_connected_shading());
+      } else {
+        prim->unify_attributes(prim->get_shading());
+      }
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {
       if (recurse) {
-        DCAST(EggGroupNode, child)->unify_attributes(recurse);
+        DCAST(EggGroupNode, child)->unify_attributes(use_connected_shading, recurse);
       }
     }
   }
@@ -930,9 +993,10 @@ unify_attributes(bool recurse) {
 //       Access: Published
 //  Description: Sets the last vertex of the triangle (or each
 //               component) to the primitive normal and/or color, if
-//               they exist.  This reflects the Panda convention of
-//               storing flat-shaded properties on the last vertex,
-//               although it is not usually a convention in Egg.
+//               the primitive is flat-shaded.  This reflects the
+//               OpenGL convention of storing flat-shaded properties on
+//               the last vertex, although it is not usually a
+//               convention in Egg.
 //
 //               This may create redundant vertices in the vertex
 //               pool, so it may be a good idea to follow this up with
@@ -956,7 +1020,38 @@ apply_last_attribute(bool recurse) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: EggGroupNode::post_apply_last_attribute
+//     Function: EggGroupNode::apply_first_attribute
+//       Access: Published
+//  Description: Sets the first vertex of the triangle (or each
+//               component) to the primitive normal and/or color, if
+//               the primitive is flat-shaded.  This reflects the
+//               DirectX convention of storing flat-shaded properties on
+//               the first vertex, although it is not usually a
+//               convention in Egg.
+//
+//               This may create redundant vertices in the vertex
+//               pool, so it may be a good idea to follow this up with
+//               remove_unused_vertices().
+////////////////////////////////////////////////////////////////////
+void EggGroupNode::
+apply_first_attribute(bool recurse) {
+  Children::iterator ci;
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    EggNode *child = *ci;
+
+    if (child->is_of_type(EggPrimitive::get_class_type())) {
+      EggPrimitive *prim = DCAST(EggPrimitive, child);
+      prim->apply_first_attribute();
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      if (recurse) {
+        DCAST(EggGroupNode, child)->apply_first_attribute(recurse);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggGroupNode::post_apply_flat_attribute
 //       Access: Published
 //  Description: Intended as a followup to apply_last_attribute(),
 //               this also sets an attribute on the first vertices of
@@ -964,17 +1059,17 @@ apply_last_attribute(bool recurse) {
 //               attribute set, just so they end up with *something*.
 ////////////////////////////////////////////////////////////////////
 void EggGroupNode::
-post_apply_last_attribute(bool recurse) {
+post_apply_flat_attribute(bool recurse) {
   Children::iterator ci;
   for (ci = _children.begin(); ci != _children.end(); ++ci) {
     EggNode *child = *ci;
 
     if (child->is_of_type(EggPrimitive::get_class_type())) {
       EggPrimitive *prim = DCAST(EggPrimitive, child);
-      prim->post_apply_last_attribute();
+      prim->post_apply_flat_attribute();
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {
       if (recurse) {
-        DCAST(EggGroupNode, child)->post_apply_last_attribute(recurse);
+        DCAST(EggGroupNode, child)->post_apply_flat_attribute(recurse);
       }
     }
   }

+ 7 - 4
panda/src/egg/eggGroupNode.h

@@ -140,12 +140,15 @@ PUBLISHED:
   int triangulate_polygons(int flags);
   void mesh_triangles(int flags);
 
-  int remove_unused_vertices();
-  int remove_invalid_primitives();
+  int remove_unused_vertices(bool recurse);
+  int remove_invalid_primitives(bool recurse);
+  void clear_connected_shading();
+  void get_connected_shading();
   void rebuild_vertex_pool(EggVertexPool *vertex_pool, bool recurse);
-  void unify_attributes(bool recurse);
+  void unify_attributes(bool use_connected_shading, bool recurse);
   void apply_last_attribute(bool recurse);
-  void post_apply_last_attribute(bool recurse);
+  void apply_first_attribute(bool recurse);
+  void post_apply_flat_attribute(bool recurse);
   virtual bool has_primitives() const;
   virtual bool joint_has_primitives() const;
   virtual bool has_normals() const;

+ 41 - 0
panda/src/egg/eggPrimitive.I

@@ -25,6 +25,7 @@
 INLINE EggPrimitive::
 EggPrimitive(const string &name): EggNode(name) {
   _bface = false;
+  _connected_shading = S_unknown;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -41,6 +42,7 @@ EggPrimitive(const EggPrimitive &copy) :
   _bface(copy._bface)
 {
   copy_vertices(copy);
+  _connected_shading = S_unknown;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -56,6 +58,7 @@ operator = (const EggPrimitive &copy) {
   _textures = copy._textures;
   _material = copy._material;
   _bface = copy._bface;
+  _connected_shading = S_unknown;
   return *this;
 }
 
@@ -69,6 +72,44 @@ INLINE EggPrimitive::
   clear();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::clear_connected_shading
+//       Access: Published
+//  Description: Resets the connected_shading member in this
+//               primitive, so that get_connected_shading() will
+//               recompute a new value.
+////////////////////////////////////////////////////////////////////
+INLINE void EggPrimitive::
+clear_connected_shading() {
+  _connected_shading = S_unknown;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::get_connected_shading
+//       Access: Published
+//  Description: Determines what sort of shading properties this
+//               primitive's connected neighbors have.
+//
+//               To get the most accurate results, you should first
+//               call clear_connected_shading() on all connected
+//               primitives (or on all primitives in the egg file).
+//               It might also be a good idea to call
+//               remove_unused_vertices() to ensure proper
+//               connectivity.
+//
+//               You may find it easiest to call these other methods
+//               on the EggData root node (they are defined on
+//               EggGroupNode).
+////////////////////////////////////////////////////////////////////
+INLINE EggPrimitive::Shading EggPrimitive::
+get_connected_shading() {
+  if (_connected_shading == S_unknown) {
+    set_connected_shading(S_unknown, this);
+  }
+
+  return _connected_shading;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::set_texture
 //       Access: Published

+ 265 - 161
panda/src/egg/eggPrimitive.cxx

@@ -190,6 +190,69 @@ determine_bin() {
   return result;
 }
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::get_shading
+//       Access: Published, Virtual
+//  Description: Returns the shading properties apparent on this
+//               particular primitive.  This returns S_per_vertex if
+//               the vertices have colors or normals (and they are not
+//               all the same values), or for a simple primitive,
+//               S_overall otherwise.  A composite primitive may also
+//               return S_per_face if the individual component
+//               primitives have colors or normals that are not all
+//               the same values.
+//
+//               To get the most accurate results, you should call
+//               clear_shading() on all connected primitives (or on
+//               all primitives in the egg file), followed by
+//               determine_shading() on each primitive.  You may find
+//               it easiest to call these methods on the EggData root
+//               node (they are defined on EggGroupNode).
+////////////////////////////////////////////////////////////////////
+EggPrimitive::Shading EggPrimitive::
+get_shading() const {
+  if (empty()) {
+    return S_overall;
+  }
+
+  if (has_vertex_normal()) {
+    // Check if the vertices all have the same normal.
+    const EggAttributes *first_vertex = get_vertex(0);
+    if (!first_vertex->has_normal()) {
+      first_vertex = this;
+    }
+    for (int i = 1; i < get_num_vertices(); i++) {
+      const EggAttributes *vertex = get_vertex(i);
+      if (!vertex->has_normal()) {
+        vertex = this;
+      }
+      if (!vertex->matches_normal(*first_vertex)) {
+        return S_per_vertex;
+      }
+    }
+  }
+
+  if (has_vertex_color()) {
+    // Check if the vertices all have the same color.
+    const EggAttributes *first_vertex = get_vertex(0);
+    if (!first_vertex->has_color()) {
+      first_vertex = this;
+    }
+    for (int i = 1; i < get_num_vertices(); i++) {
+      const EggAttributes *vertex = get_vertex(i);
+      if (!vertex->has_color()) {
+        vertex = this;
+      }
+      if (!vertex->matches_color(*first_vertex)) {
+        return S_per_vertex;
+      }
+    }
+  }
+
+  return S_overall;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::copy_attributes
 //       Access: Published
@@ -221,8 +284,10 @@ copy_attributes(const EggPrimitive &other) {
 //  Description: Returns true if any vertex on the primitive has a
 //               specific normal set, false otherwise.
 //
-//               For the most accurate measure of this, call
-//               unify_attributes() first.
+//               If you call unify_attributes() first, this will also
+//               return false even if all the vertices were set to the
+//               same value (since unify_attributes() removes
+//               redundant vertex properties).
 ////////////////////////////////////////////////////////////////////
 bool EggPrimitive::
 has_vertex_normal() const {
@@ -241,8 +306,10 @@ has_vertex_normal() const {
 //  Description: Returns true if any vertex on the primitive has a
 //               specific color set, false otherwise.
 //
-//               For the most accurate measure of this, call
-//               unify_attributes() first.
+//               If you call unify_attributes() first, this will also
+//               return false even if all the vertices were set to the
+//               same value (since unify_attributes() removes
+//               redundant vertex properties).
 ////////////////////////////////////////////////////////////////////
 bool EggPrimitive::
 has_vertex_color() const {
@@ -255,29 +322,22 @@ has_vertex_color() const {
   return false;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: EggPrimitive::is_flat_shaded
-//       Access: Published
-//  Description: Returns true if the primitive is flat shaded, meaning
-//               it has no per-vertex normal and no per-vertex color
-//               (but in the case of a composite primitive, it may
-//               still have per-component normal and color).
-//
-//               For the most accurate measure of this, call
-//               unify_attributes() first.
-////////////////////////////////////////////////////////////////////
-bool EggPrimitive::
-is_flat_shaded() const {
-  return !has_vertex_normal() && !has_vertex_color();
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::unify_attributes
 //       Access: Published, Virtual
-//  Description: Applies per-vertex normal and color to all vertices,
-//               if they are in fact per-vertex (and actually
-//               different for each vertex), or moves them to the
-//               primitive if they are all the same.
+//  Description: If the shading property is S_per_vertex, ensures that
+//               all vertices have a normal and a color, and the
+//               overall primitive does not.
+//
+//               If the shading property is S_per_face, and this is a
+//               composite primitive, ensures that all components have
+//               a normal and a color, and the vertices and overall
+//               primitive do not.  (If this is a simple primitive,
+//               S_per_face works the same as S_overall, below).
+//
+//               If the shading property is S_overall, ensures that no
+//               vertices or components have a normal or a color, and
+//               the overall primitive does (if any exists at all).
 //
 //               After this call, either the primitive will have
 //               normals or its vertices will, but not both.  Ditto
@@ -287,109 +347,59 @@ is_flat_shaded() const {
 //               pool.
 ////////////////////////////////////////////////////////////////////
 void EggPrimitive::
-unify_attributes() {
-  typedef pvector< PT(EggVertex) > Vertices;
-  Vertices vertices;
-
-  // First, go through the vertices and apply the primitive overall
-  // attributes to any vertex that omits them.
-  bool all_have_normal = true;
-  bool all_have_color = true;
-  iterator pi;
-  for (pi = begin(); pi != end(); ++pi) {
-    EggVertex *orig_vertex = (*pi);
-    PT(EggVertex) vertex = new EggVertex(*orig_vertex);
-
-    if (!vertex->has_normal()) {
-      if (has_normal()) {
-        vertex->set_normal(get_normal());
-        vertex->_dnormals = _dnormals;
-      } else {
-        all_have_normal = false;
-      }
-    }
-    if (!vertex->has_color()) {
-      if (has_color()) {
-        vertex->set_color(get_color());
-        vertex->_drgbas = _drgbas;
-      } else {
-        all_have_color = false;
-      }
-    }
-
-    vertices.push_back(vertex);
-  }
-
-  clear_normal();
-  clear_color();
-
-  // Now see if any ended up different.
-  EggAttributes overall;
-  bool normal_different = false;
-  bool color_different = false;
-
-  Vertices::iterator vi;
-  for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
-    EggVertex *vertex = (*vi);
-    if (!all_have_normal) {
-      vertex->clear_normal();
-    } else if (!normal_different) {
-      if (!overall.has_normal()) {
-        overall.set_normal(vertex->get_normal());
-        overall._dnormals = vertex->_dnormals;
-      } else if (overall.get_normal() != vertex->get_normal() ||
-                 overall._dnormals.compare_to(vertex->_dnormals) != 0) {
-        normal_different = true;
-      }
-    }
-    if (!all_have_color) {
-      vertex->clear_color();
-    } else if (!color_different) {
-      if (!overall.has_color()) {
-        overall.set_color(vertex->get_color());
-        overall._drgbas = vertex->_drgbas;
-      } else if (overall.get_color() != vertex->get_color() ||
-                 overall._drgbas.compare_to(vertex->_drgbas) != 0) {
-        color_different = true;
-      }
-    }
+unify_attributes(EggPrimitive::Shading shading) {
+  if (shading == S_unknown) {
+    shading = get_shading();
   }
 
-  if (!overall.has_normal()) {
-    normal_different = true;
-  }
-  if (!overall.has_color()) {
-    color_different = true;
-  }
+  switch (shading) {
+  case S_per_vertex:
+    // Propagate everything to the vertices.
+    {
+      iterator pi;
+      for (pi = begin(); pi != end(); ++pi) {
+        EggVertex *orig_vertex = (*pi);
+        PT(EggVertex) vertex = new EggVertex(*orig_vertex);
+        if (!vertex->has_normal() && has_normal()) {
+          vertex->copy_normal(*this);
+        }
+        if (!vertex->has_color() && has_color()) {
+          vertex->copy_color(*this);
+        }
 
-  if (!color_different || !normal_different) {
-    // Ok, it's flat-shaded after all.  Go back through and remove
-    // this stuff from the vertices.
-    if (!normal_different) {
-      set_normal(overall.get_normal());
-      _dnormals = overall._dnormals;
-    }
-    if (!color_different) {
-      set_color(overall.get_color());
-      _drgbas = overall._drgbas;
-    }
-    for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
-      EggVertex *vertex = (*vi);
-      if (!normal_different) {
-        vertex->clear_normal();
+        vertex = vertex->get_pool()->create_unique_vertex(*vertex);
+        replace(pi, vertex);
       }
-      if (!color_different) {
-        vertex->clear_color();
+      clear_normal();
+      clear_color();
+    }
+    break;
+
+  case S_per_face:
+  case S_overall:
+    // Remove everything from the vertices.
+    {
+      iterator pi;
+      for (pi = begin(); pi != end(); ++pi) {
+        EggVertex *vertex = (*pi);
+        if (vertex->has_normal()) {
+          if (!has_normal()) {
+            copy_normal(*vertex);
+          }
+          vertex->clear_normal();
+        }
+        if (vertex->has_color()) {
+          if (!has_color()) {
+            copy_color(*vertex);
+          }
+          vertex->clear_color();
+        }
       }
     }
-  }
+    break;
 
-  // Finally, move the new vertices to the vertex pool.
-  EggVertexPool *vertex_pool = get_pool();
-  for (size_t i = 0; i < vertices.size(); i++) {
-    EggVertex *vertex = vertices[i];
-    vertex = vertex_pool->create_unique_vertex(*vertex);
-    set_vertex(i, vertex);
+  case S_unknown:
+    break;
   }
 }
 
@@ -399,7 +409,7 @@ unify_attributes() {
 //  Description: Sets the last vertex of the triangle (or each
 //               component) to the primitive normal and/or color, if
 //               the primitive is flat-shaded.  This reflects the
-//               Panda convention of storing flat-shaded properties on
+//               OpenGL convention of storing flat-shaded properties on
 //               the last vertex, although it is not usually a
 //               convention in Egg.
 //
@@ -409,50 +419,32 @@ unify_attributes() {
 void EggPrimitive::
 apply_last_attribute() {
   if (!empty()) {
-    // The significant_change flag is set if we have changed the
-    // vertex in some important way, that will invalidate it for other
-    // primitives that might share it.  We don't consider *adding* a
-    // normal where there wasn't one before to be significant, but we
-    // do consider it significant to change a vertex's normal to
-    // something different.  Similarly for color.
-    bool significant_change = false;
-
-    EggVertex *orig_vertex = get_vertex(size() - 1);
-    PT(EggVertex) new_vertex = new EggVertex(*orig_vertex);
-
-    if (has_normal()) {
-      new_vertex->set_normal(get_normal());
-      new_vertex->_dnormals = _dnormals;
-
-      if (orig_vertex->has_normal() &&
-          (orig_vertex->get_normal() != new_vertex->get_normal() ||
-           orig_vertex->_dnormals.compare_to(new_vertex->_dnormals) != 0)) {
-        significant_change = true;
-      }
-    }
-    if (has_color()) {
-      new_vertex->set_color(get_color());
-      new_vertex->_drgbas = _drgbas;
-
-      if (orig_vertex->has_color() &&
-          (orig_vertex->get_color() != new_vertex->get_color() ||
-           orig_vertex->_drgbas.compare_to(new_vertex->_drgbas) != 0)) {
-        significant_change = true;
-      }
-    }
+    do_apply_flat_attribute(size() - 1, this);
+  }
+}
 
-    if (significant_change) {
-      new_vertex = get_pool()->create_unique_vertex(*new_vertex);
-      set_vertex(size() - 1, new_vertex);
-    } else {
-      // Just copy the new attributes back into the pool.
-      ((EggAttributes *)orig_vertex)->operator = (*new_vertex);
-    }
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::apply_first_attribute
+//       Access: Published, Virtual
+//  Description: Sets the first vertex of the triangle (or each
+//               component) to the primitive normal and/or color, if
+//               the primitive is flat-shaded.  This reflects the
+//               DirectX convention of storing flat-shaded properties on
+//               the first vertex, although it is not usually a
+//               convention in Egg.
+//
+//               This may introduce redundant vertices to the vertex
+//               pool.
+////////////////////////////////////////////////////////////////////
+void EggPrimitive::
+apply_first_attribute() {
+  if (!empty()) {
+    do_apply_flat_attribute(0, this);
   }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: EggPrimitive::post_apply_last_attribute
+//     Function: EggPrimitive::post_apply_flat_attribute
 //       Access: Published, Virtual
 //  Description: Intended as a followup to apply_last_attribute(),
 //               this also sets an attribute on the first vertices of
@@ -460,11 +452,15 @@ apply_last_attribute() {
 //               attribute set, just so they end up with *something*.
 ////////////////////////////////////////////////////////////////////
 void EggPrimitive::
-post_apply_last_attribute() {
+post_apply_flat_attribute() {
   if (!empty()) {
     for (int i = 0; i < (int)size(); i++) {
       EggVertex *vertex = get_vertex(i);
 
+      // Use set_normal() instead of copy_normal(), to avoid getting
+      // the morphs--we don't want them here, since we're just putting
+      // a bogus value on the normal anyway.
+
       if (has_normal() && !vertex->has_normal()) {
         vertex->set_normal(get_normal());
       }
@@ -1042,3 +1038,111 @@ r_apply_texmats(EggTextureCollection &textures) {
 
   _textures.swap(new_textures);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::apply_attribute_to_vertex
+//       Access: Protected
+//  Description: This is used to implement apply_first_attribute() and
+//               apply_last_attribute().  It copies the indicated
+//               attributes to the specified vertex.
+////////////////////////////////////////////////////////////////////
+void EggPrimitive::
+do_apply_flat_attribute(int vertex_index, EggAttributes *attrib) {
+  // The significant_change flag is set if we have changed the
+  // vertex in some important way, that will invalidate it for other
+  // primitives that might share it.  We don't consider *adding* a
+  // normal where there wasn't one before to be significant, but we
+  // do consider it significant to change a vertex's normal to
+  // something different.  Similarly for color.
+  bool significant_change = false;
+
+  EggVertex *orig_vertex = get_vertex(vertex_index);
+  PT(EggVertex) new_vertex = new EggVertex(*orig_vertex);
+
+  if (attrib->has_normal()) {
+    new_vertex->copy_normal(*attrib);
+    
+    if (orig_vertex->has_normal() && 
+        !orig_vertex->matches_normal(*new_vertex)) {
+      significant_change = true;
+    }
+  } else if (has_normal()) {
+    new_vertex->copy_normal(*this);
+    
+    if (orig_vertex->has_normal() && 
+        !orig_vertex->matches_normal(*new_vertex)) {
+      significant_change = true;
+    }
+  }
+
+  if (attrib->has_color()) {
+    new_vertex->copy_color(*attrib);
+    
+    if (orig_vertex->has_color() && 
+        !orig_vertex->matches_color(*new_vertex)) {
+      significant_change = true;
+    }
+  } else if (has_color()) {
+    new_vertex->copy_color(*this);
+    
+    if (orig_vertex->has_color() && 
+        !orig_vertex->matches_color(*new_vertex)) {
+      significant_change = true;
+    }
+  }
+
+  if (significant_change) {
+    new_vertex = get_pool()->create_unique_vertex(*new_vertex);
+    set_vertex(vertex_index, new_vertex);
+  } else {
+    // Just copy the new attributes back into the pool.
+    ((EggAttributes *)orig_vertex)->operator = (*new_vertex);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::set_connected_shading
+//       Access: Private
+//  Description: Recursively updates the connected_shading member in
+//               all connected primitives.
+////////////////////////////////////////////////////////////////////
+void EggPrimitive::
+set_connected_shading(EggPrimitive::Shading shading, 
+                      const EggAttributes *neighbor) {
+  bool propagate = false;
+
+  if (_connected_shading == S_unknown) {
+    // We haven't visited this node before; propagate now.
+    _connected_shading = get_shading();
+    propagate = true;
+  }
+
+  if (shading > _connected_shading) {
+    // More specific information just came in.  Save it, and propagate
+    // it to all connected primitives.
+    _connected_shading = shading;
+    propagate = true;
+
+  } else if (shading == S_overall && _connected_shading == S_overall) {
+    // If both neighbors are overall shaded, check if the two
+    // neighbors have different properties.  If they do, elevate to
+    // per_face.
+    if (!matches_normal(*neighbor) || !matches_color(*neighbor)) {
+      _connected_shading = S_per_face;
+      propagate = true;
+    }
+  }
+
+  if (propagate) {
+    Vertices::const_iterator vi;
+    for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) {
+      EggVertex *vertex = (*vi);
+      EggVertex::PrimitiveRef::const_iterator pi;
+      for (pi = vertex->pref_begin();
+           pi != vertex->pref_end();
+           ++pi) {
+        (*pi)->set_connected_shading(_connected_shading, this);
+      }
+    }
+  }
+}

+ 21 - 4
panda/src/egg/eggPrimitive.h

@@ -66,6 +66,15 @@ private:
   // Here begins the actual public interface to EggPrimitive.
 
 PUBLISHED:
+  enum Shading {
+    // The order here is important.  The later choices are more
+    // specific than the earlier ones.
+    S_unknown,
+    S_overall,
+    S_per_face,
+    S_per_vertex
+  };
+
   INLINE EggPrimitive(const string &name = "");
   INLINE EggPrimitive(const EggPrimitive &copy);
   INLINE EggPrimitive &operator = (const EggPrimitive &copy);
@@ -78,6 +87,10 @@ PUBLISHED:
   virtual EggRenderMode *determine_draw_order();
   virtual EggRenderMode *determine_bin();
 
+  virtual Shading get_shading() const;
+  INLINE void clear_connected_shading();
+  INLINE Shading get_connected_shading();
+
   INLINE void set_texture(EggTexture *texture);
   INLINE bool has_texture() const;
   INLINE bool has_texture(EggTexture *texture) const;
@@ -101,11 +114,11 @@ PUBLISHED:
 
   bool has_vertex_normal() const;
   bool has_vertex_color() const;
-  bool is_flat_shaded() const;
 
-  virtual void unify_attributes();
+  virtual void unify_attributes(Shading shading);
   virtual void apply_last_attribute();
-  virtual void post_apply_last_attribute();
+  virtual void apply_first_attribute();
+  virtual void post_apply_flat_attribute();
   virtual void reverse_vertex_ordering();
   virtual bool cleanup();
 
@@ -180,7 +193,6 @@ protected:
   virtual void prepare_add_vertex(EggVertex *vertex, int i, int n);
   virtual void prepare_remove_vertex(EggVertex *vertex, int i, int n);
 
-
 protected:
   void write_body(ostream &out, int indent_level) const;
 
@@ -190,12 +202,17 @@ protected:
   virtual void r_flatten_transforms();
   virtual void r_apply_texmats(EggTextureCollection &textures);
 
+  void do_apply_flat_attribute(int vertex_index, EggAttributes *attrib);
+
+private:
+  void set_connected_shading(Shading shading, const EggAttributes *neighbor);
 
 private:
   typedef vector_PT_EggTexture Textures;
   Textures _textures;
   PT_EggMaterial _material;
   bool _bface;
+  Shading _connected_shading;
 
 public:
 

+ 46 - 0
panda/src/egg/eggTriangleStrip.cxx

@@ -28,6 +28,52 @@
 TypeHandle EggTriangleStrip::_type_handle;
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggTriangleStrip::apply_last_attribute
+//       Access: Published, Virtual
+//  Description: Sets the last vertex of the triangle (or each
+//               component) to the primitive normal and/or color, if
+//               the primitive is flat-shaded.  This reflects the
+//               OpenGL convention of storing flat-shaded properties on
+//               the last vertex, although it is not usually a
+//               convention in Egg.
+//
+//               This may introduce redundant vertices to the vertex
+//               pool.
+////////////////////////////////////////////////////////////////////
+void EggTriangleStrip::
+apply_last_attribute() {
+  // The first component gets applied to the third vertex, and so on
+  // from there.
+  for (int i = 0; i < get_num_components(); i++) {
+    EggAttributes *component = get_component(i);
+    do_apply_flat_attribute(i + 2, component);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggTriangleStrip::apply_first_attribute
+//       Access: Published, Virtual
+//  Description: Sets the first vertex of the triangle (or each
+//               component) to the primitive normal and/or color, if
+//               the primitive is flat-shaded.  This reflects the
+//               DirectX convention of storing flat-shaded properties
+//               on the first vertex, although it is not usually a
+//               convention in Egg.
+//
+//               This may introduce redundant vertices to the vertex
+//               pool.
+////////////////////////////////////////////////////////////////////
+void EggTriangleStrip::
+apply_first_attribute() {
+  // The first component gets applied to the first vertex, and so on
+  // from there.
+  for (int i = 0; i < get_num_components(); i++) {
+    EggAttributes *component = get_component(i);
+    do_apply_flat_attribute(i, component);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTriangleStrip::write
 //       Access: Published, Virtual

+ 3 - 0
panda/src/egg/eggTriangleStrip.h

@@ -35,6 +35,9 @@ PUBLISHED:
   INLINE EggTriangleStrip(const EggTriangleStrip &copy);
   INLINE EggTriangleStrip &operator = (const EggTriangleStrip &copy);
 
+  virtual void apply_last_attribute();
+  virtual void apply_first_attribute();
+
   virtual void write(ostream &out, int indent_level) const;
 
 protected:

+ 0 - 9
panda/src/egg2pg/eggBinner.cxx

@@ -95,15 +95,6 @@ sorts_less(int bin_number, const EggNode *a, const EggNode *b) {
         return (compare < 0);
       }
 
-      // If the render state indicates indexed, meaning we keep the
-      // existing vertex pools, then different pools get sorted
-      // separately.
-      if (rsa->_indexed) {
-        if (pa->get_pool() != pb->get_pool()) {
-          return pa->get_pool() < pb->get_pool();
-        }
-      }
-
       return false;
     }
 

+ 26 - 0
panda/src/egg2pg/eggLoader.I

@@ -17,6 +17,32 @@
 ////////////////////////////////////////////////////////////////////
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::PrimitiveUnifier::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE EggLoader::PrimitiveUnifier::
+PrimitiveUnifier(const qpGeomPrimitive *prim) :
+  _type(prim->get_type()),
+  _shade_model(prim->get_shade_model())
+{
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::PrimitiveUnifier::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool EggLoader::PrimitiveUnifier::
+operator < (const PrimitiveUnifier &other) const {
+  if (_type != other._type) {
+    return _type < other._type;
+  }
+  return _shade_model < other._shade_model;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggLoader::VertexPoolTransform::operator <
 //       Access: Public

+ 33 - 24
panda/src/egg2pg/eggLoader.cxx

@@ -157,8 +157,13 @@ build_graph() {
   // First, load up all of the textures.
   load_textures();
 
+  // Clean up the vertices.
+  _data->clear_connected_shading();
+  _data->remove_unused_vertices(true);
+  _data->unify_attributes(true, true);
+
   // Then bin up the polysets and LOD nodes.
-  _data->remove_invalid_primitives();
+  _data->remove_invalid_primitives(true);
   EggBinner binner(*this);
   binner.make_bins(_data);
 
@@ -1439,19 +1444,11 @@ make_polyset(EggBin *egg_bin, PandaNode *parent) {
     return NULL;
   }
 
-  // In the normal case--not indexed--we can generate an optimal
-  // vertex pool for the polygons in the bin (which translates
-  // directly to an optimal GeomVertexData structure).  However, if
-  // the indexed flag is set on these polygons, we don't do this,
-  // since the indexed flag means to use the supplied vertex pool
-  // exactly as it is.
-  PT(EggVertexPool) vertex_pool;
-  if (!render_state->_indexed) {
-    vertex_pool = new EggVertexPool("bin");
-    egg_bin->rebuild_vertex_pool(vertex_pool, false);
-  } else {
-    vertex_pool = first_prim->get_pool();
-  }
+  // Generate an optimal vertex pool for the polygons within just the
+  // bin (which translates directly to an optimal GeomVertexData
+  // structure).
+  PT(EggVertexPool) vertex_pool = new EggVertexPool("bin");
+  egg_bin->rebuild_vertex_pool(vertex_pool, false);
 
   if (egg_mesh) {
     // If we're using the mesher, mesh now.
@@ -1463,13 +1460,11 @@ make_polyset(EggBin *egg_bin, PandaNode *parent) {
     egg_bin->triangulate_polygons(EggGroupNode::T_polygon | EggGroupNode::T_convex);
   }
 
-  if (!render_state->_indexed) {
-    // Now that we've meshed, apply the per-prim attributes onto the
-    // vertices, so we can copy them to the GeomVertexData.
-    egg_bin->apply_last_attribute(false);
-    egg_bin->post_apply_last_attribute(false);
-    vertex_pool->remove_unused_vertices();
-  }
+  // Now that we've meshed, apply the per-prim attributes onto the
+  // vertices, so we can copy them to the GeomVertexData.
+  egg_bin->apply_first_attribute(false);
+  egg_bin->post_apply_flat_attribute(false);
+  vertex_pool->remove_unused_vertices();
 
   //  vertex_pool->write(cerr, 0);
   //  egg_bin->write(cerr, 0);
@@ -1480,7 +1475,7 @@ make_polyset(EggBin *egg_bin, PandaNode *parent) {
   for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
     EggPrimitive *egg_prim;
     DCAST_INTO_R(egg_prim, (*ci), NULL);
-    make_primitive(egg_prim, primitives);
+    make_primitive(render_state, egg_prim, primitives);
   }
 
   if (!primitives.empty()) {
@@ -1977,7 +1972,8 @@ make_vertex_data(EggVertexPool *vertex_pool, const LMatrix4d &transform) {
 //               indicated EggPrimitive, and adds it to the set.
 ////////////////////////////////////////////////////////////////////
 void EggLoader::
-make_primitive(EggPrimitive *egg_prim, EggLoader::Primitives &primitives) {
+make_primitive(const EggRenderState *render_state, EggPrimitive *egg_prim, 
+               EggLoader::Primitives &primitives) {
   PT(qpGeomPrimitive) primitive;
   if (egg_prim->is_of_type(EggPolygon::get_class_type())) {
     if (egg_prim->size() == 3) {
@@ -1989,13 +1985,26 @@ make_primitive(EggPrimitive *egg_prim, EggLoader::Primitives &primitives) {
 
   if (primitive == (qpGeomPrimitive *)NULL) {
     // Don't know how to make this kind of primitive.
+    egg2pg_cat.warning()
+      << "Ignoring " << egg_prim->get_class_type() << "\n";
     return;
   }
 
+  if (render_state->_flat_shaded) {
+    primitive->set_shade_model(qpGeomPrimitive::SM_flat_first_vertex);
+
+  } else if (egg_prim->get_shading() == EggPrimitive::S_overall) {
+    primitive->set_shade_model(qpGeomPrimitive::SM_uniform);
+
+  } else {
+    primitive->set_shade_model(qpGeomPrimitive::SM_smooth);
+  }
+
   // Insert the primitive into the set, but if we already have a
   // primitive of that type, reset the pointer to that one instead.
+  PrimitiveUnifier pu(primitive);
   pair<Primitives::iterator, bool> result =
-    primitives.insert(Primitives::value_type(primitive->get_type(), primitive));
+    primitives.insert(Primitives::value_type(pu, primitive));
   primitive = (*result.first).second;
 
   // Now add the vertices.

+ 12 - 2
panda/src/egg2pg/eggLoader.h

@@ -57,6 +57,7 @@ class CollisionPlane;
 class CollisionPolygon;
 class PortalNode;
 class PolylightNode;
+class EggRenderState;
 
 ///////////////////////////////////////////////////////////////////
 //       Class : EggLoader
@@ -99,7 +100,15 @@ private:
   typedef pmap<CPT(InternalName), const EggTexture *> BakeInUVs;
 
   // This is used by make_primitive().
-  typedef pmap<TypeHandle, PT(qpGeomPrimitive) > Primitives;
+  class PrimitiveUnifier {
+  public:
+    INLINE PrimitiveUnifier(const qpGeomPrimitive *prim);
+    INLINE bool operator < (const PrimitiveUnifier &other) const;
+
+    TypeHandle _type;
+    qpGeomPrimitive::ShadeModel _shade_model;
+  };
+  typedef pmap<PrimitiveUnifier, PT(qpGeomPrimitive) > Primitives;
   
   void make_nurbs_curve(EggNurbsCurve *egg_curve, PandaNode *parent,
                         const LMatrix4d &mat);
@@ -130,7 +139,8 @@ private:
                           bool &any_hidden);
   PT(qpGeomVertexData) make_vertex_data(EggVertexPool *vertex_pool, 
                                         const LMatrix4d &transform);
-  void make_primitive(EggPrimitive *egg_prim, Primitives &primitives);
+  void make_primitive(const EggRenderState *render_state, 
+                      EggPrimitive *egg_prim, Primitives &primitives);
 
   void set_portal_polygon(EggGroup *egg_group, PortalNode *pnode);
   PT(EggPolygon) find_first_polygon(EggGroup *egg_group);

+ 3 - 3
panda/src/egg2pg/eggRenderState.I

@@ -26,7 +26,7 @@ EggRenderState::
 EggRenderState(EggLoader &loader) :
   _state(RenderState::make_empty()),
   _hidden(false),
-  _indexed(false),
+  _flat_shaded(false),
   _loader(loader)
 {
 }
@@ -57,8 +57,8 @@ compare_to(const EggRenderState &other) const {
   if (_hidden != other._hidden) {
     return (int)_hidden - (int)other._hidden;
   }
-  if (_indexed != other._indexed) {
-    return (int)_indexed - (int)other._indexed;
+  if (_flat_shaded != other._flat_shaded) {
+    return (int)_flat_shaded - (int)other._flat_shaded;
   }
   return 0;
 }

+ 3 - 16
panda/src/egg2pg/eggRenderState.cxx

@@ -340,24 +340,11 @@ fill_state(EggPrimitive *egg_prim) {
     break;
   }
 
-  _indexed = egg_prim->determine_indexed();
-
-  bool flat_shaded;
-
-  if (!_indexed) {
-    // If the primitive is not indexed, we're allowed to call
-    // unify_attributes(), which will modify the vertex pool.
-    egg_prim->unify_attributes();
-    flat_shaded = egg_prim->is_flat_shaded();
-
-  } else {
-    // If the primitive *is* indexed, we're not allowed to modify the
-    // vertex pool, and so we won't have flat shading anyway.
-    flat_shaded = false;
-  }
+  _flat_shaded = 
+    (egg_prim->get_connected_shading() == EggPrimitive::S_per_face);
 
   if (use_qpgeom) {
-    if (flat_shaded) {
+    if (_flat_shaded) {
       add_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_flat));
     } else {
       add_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_smooth));

+ 1 - 1
panda/src/egg2pg/eggRenderState.h

@@ -63,7 +63,7 @@ private:
 public:
   CPT(RenderState) _state;
   bool _hidden;
-  bool _indexed;
+  bool _flat_shaded;
 
   typedef EggLoader::BakeInUVs BakeInUVs;
   typedef EggLoader::TextureDef TextureDef;

+ 3 - 1
panda/src/glstuff/glGeomMunger_src.I

@@ -23,5 +23,7 @@
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE CLP(GeomMunger)::
-CLP(GeomMunger)() {
+CLP(GeomMunger)(GraphicsStateGuardian *gsg, const RenderState *state) :
+  qpGeomMunger(gsg, state)
+{
 }

+ 2 - 1
panda/src/glstuff/glGeomMunger_src.h

@@ -18,6 +18,7 @@
 
 #include "pandabase.h"
 #include "qpgeomMunger.h"
+#include "graphicsStateGuardian.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : GLGeomMunger
@@ -27,7 +28,7 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_GL CLP(GeomMunger) : public qpGeomMunger {
 public:
-  INLINE CLP(GeomMunger)();
+  INLINE CLP(GeomMunger)(GraphicsStateGuardian *gsg, const RenderState *state);
 
 protected:
   virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig);

+ 4 - 4
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -2058,14 +2058,14 @@ begin_draw_primitives(const qpGeomVertexData *vertex_data) {
 //  Description: Draws a series of disconnected triangles.
 ////////////////////////////////////////////////////////////////////
 void CLP(GraphicsStateGuardian)::
-draw_triangles(qpGeomTriangles *primitive) {
+draw_triangles(const qpGeomTriangles *primitive) {
   setup_antialias_polygon();
 
   _glDrawRangeElements(GL_TRIANGLES, 
                        primitive->get_min_vertex(),
                        primitive->get_max_vertex(),
                        primitive->get_num_vertices(),
-                       GL_UNSIGNED_SHORT, primitive->get_vertices());
+                       GL_UNSIGNED_SHORT, primitive->get_flat_last_vertices());
 
   report_my_gl_errors();
 }
@@ -2283,8 +2283,8 @@ static int binary_log_cap(const int x) {
 //               appropriate to this GSG for the indicated state.
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomMunger) CLP(GraphicsStateGuardian)::
-get_geom_munger(const RenderState *) {
-  PT(CLP(GeomMunger)) munger = new CLP(GeomMunger);
+get_geom_munger(const RenderState *state) {
+  PT(CLP(GeomMunger)) munger = new CLP(GeomMunger)(this, state);
   return qpGeomMunger::register_munger(munger);
 }
 

+ 1 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -90,7 +90,7 @@ public:
   virtual void draw_sphere(GeomSphere *geom, GeomContext *gc);
 
   virtual bool begin_draw_primitives(const qpGeomVertexData *vertex_data);
-  virtual void draw_triangles(qpGeomTriangles *primitive);
+  virtual void draw_triangles(const qpGeomTriangles *primitive);
   virtual void end_draw_primitives();
 
   INLINE bool draw_display_list(GeomContext *gc);

+ 0 - 5
panda/src/gobj/geom.cxx

@@ -1221,11 +1221,6 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
     CPT(InternalName) tc = DCAST(InternalName, p_list[pi++]);
     TexCoordDef *def = (*ci);
     _texcoords_by_name[tc] = *def;
-    /*
-    cerr << "InternalName from Geom " << (void *)this
-         << " complete pointers " << tc << " " << *tc 
-         << " = " << def->_texcoords << " and " << def->_tindex << "\n";
-    */
     delete def;
     if (tc == InternalName::get_texcoord()) {
       _bind[G_TEXCOORD] = G_PER_VERTEX;

+ 4 - 2
panda/src/gobj/qpgeom.I

@@ -68,6 +68,7 @@ get_primitive(int i) const {
 ////////////////////////////////////////////////////////////////////
 INLINE qpGeomPrimitive *qpGeom::
 modify_primitive(int i) {
+  clear_cache();
   CDWriter cdata(_cycler);
   nassertr(i >= 0 && i < (int)cdata->_primitives.size(), NULL);
   return cdata->_primitives[i];
@@ -80,10 +81,11 @@ modify_primitive(int i) {
 //               the Geom with the new object.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpGeom::
-set_primitive(int i, qpGeomPrimitive *primitive) {
+set_primitive(int i, const qpGeomPrimitive *primitive) {
+  clear_cache();
   CDWriter cdata(_cycler);
   nassertv(i >= 0 && i < (int)cdata->_primitives.size());
-  cdata->_primitives[i] = primitive;
+  cdata->_primitives[i] = (qpGeomPrimitive *)primitive;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 160 - 5
panda/src/gobj/qpgeom.cxx

@@ -17,6 +17,8 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "qpgeom.h"
+#include "qpgeomVertexCacheManager.h"
+#include "pStatTimer.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 
@@ -58,6 +60,7 @@ operator = (const qpGeom &copy) {
   TypedWritableReferenceCount::operator = (copy);
   BoundedObject::operator = (copy);
   */
+  clear_cache();
   Geom::operator = (copy);
   _cycler = copy._cycler;
   mark_bound_stale();
@@ -70,6 +73,25 @@ operator = (const qpGeom &copy) {
 ////////////////////////////////////////////////////////////////////
 qpGeom::
 ~qpGeom() {
+  // When we destruct, we should ensure that all of our cached
+  // entries, across all pipeline stages, are properly removed from
+  // the cache manager.
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+
+  int num_stages = _cycler.get_num_stages();
+  for (int i = 0; i < num_stages; i++) {
+    if (_cycler.is_stage_unique(i)) {
+      CData *cdata = _cycler.write_stage(i);
+      for (MungedCache::iterator ci = cdata->_munged_cache.begin();
+           ci != cdata->_munged_cache.end();
+           ++ci) {
+        cache_mgr->remove_geom(this, (*ci).first);
+      }
+      cdata->_munged_cache.clear();
+      _cycler.release_write_stage(i, cdata);
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -94,7 +116,7 @@ modify_vertex_data() {
   // Perform copy-on-write: if the reference count on the vertex data
   // is greater than 1, assume some other Geom has the same pointer,
   // so make a copy of it first.
-
+  clear_cache();
   CDWriter cdata(_cycler);
   if (cdata->_data->get_ref_count() > 1) {
     cdata->_data = new qpGeomVertexData(*cdata->_data);
@@ -110,9 +132,10 @@ modify_vertex_data() {
 //               a completely new table.
 ////////////////////////////////////////////////////////////////////
 void qpGeom::
-set_vertex_data(PT(qpGeomVertexData) data) {
+set_vertex_data(const qpGeomVertexData *data) {
+  clear_cache();
   CDWriter cdata(_cycler);
-  cdata->_data = data;
+  cdata->_data = (qpGeomVertexData *)data;
   mark_bound_stale();
 }
 
@@ -125,9 +148,10 @@ set_vertex_data(PT(qpGeomVertexData) data) {
 //               of the indicated type.
 ////////////////////////////////////////////////////////////////////
 void qpGeom::
-add_primitive(qpGeomPrimitive *primitive) {
+add_primitive(const qpGeomPrimitive *primitive) {
+  clear_cache();
   CDWriter cdata(_cycler);
-  cdata->_primitives.push_back(primitive);
+  cdata->_primitives.push_back((qpGeomPrimitive *)primitive);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -137,6 +161,7 @@ add_primitive(qpGeomPrimitive *primitive) {
 ////////////////////////////////////////////////////////////////////
 void qpGeom::
 remove_primitive(int i) {
+  clear_cache();
   CDWriter cdata(_cycler);
   nassertv(i >= 0 && i < (int)cdata->_primitives.size());
   cdata->_primitives.erase(cdata->_primitives.begin() + i);
@@ -152,10 +177,97 @@ remove_primitive(int i) {
 ////////////////////////////////////////////////////////////////////
 void qpGeom::
 clear_primitives() {
+  clear_cache();
   CDWriter cdata(_cycler);
   cdata->_primitives.clear();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeom::get_num_bytes
+//       Access: Published
+//  Description: Returns the number of bytes consumed by the geom and
+//               its primitives (but not including its vertex table).
+////////////////////////////////////////////////////////////////////
+int qpGeom::
+get_num_bytes() const {
+  CDReader cdata(_cycler);
+
+  int num_bytes = sizeof(qpGeom);
+  Primitives::const_iterator pi;
+  for (pi = cdata->_primitives.begin(); 
+       pi != cdata->_primitives.end();
+       ++pi) {
+    num_bytes += (*pi)->get_num_bytes();
+  }
+
+  return num_bytes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeom::munge_geom
+//       Access: Published
+//  Description: Applies the indicated munger to the geom and its
+//               data, and returns a (possibly different) geom and
+//               data, according to the munger's whim.  
+//
+//               The assumption is that for a particular geom and a
+//               particular munger, the result will always be the
+//               same; so this result may be cached.
+////////////////////////////////////////////////////////////////////
+void qpGeom::
+munge_geom(const qpGeomMunger *munger,
+           CPT(qpGeom) &result, CPT(qpGeomVertexData) &data) const {
+  // Look up the format in our cache--maybe we've recently applied the
+  // indicated munger.
+  {
+    // Use read() and release_read() instead of CDReader, because the
+    // call to record_geom() might recursively call back into this
+    // object, and require a write.
+    const CData *cdata = _cycler.read();
+    MungedCache::const_iterator ci = cdata->_munged_cache.find(munger);
+    if (ci != cdata->_munged_cache.end()) {
+      _cycler.release_read(cdata);
+      // Record a cache hit, so this element will stay in the cache a
+      // while longer.
+      qpGeomVertexCacheManager *cache_mgr = 
+        qpGeomVertexCacheManager::get_global_ptr();
+      cache_mgr->record_geom(this, munger, 
+                             (*ci).second._geom->get_num_bytes() +
+                             (*ci).second._data->get_num_bytes());
+
+      result = (*ci).second._geom;
+      data = (*ci).second._data;
+      return;
+    }
+    _cycler.release_read(cdata);
+  }
+
+  // Ok, invoke the munger.
+  PStatTimer timer(qpGeomMunger::_munge_pcollector);
+
+  result = this;
+  data = munger->munge_data(get_vertex_data());
+  ((qpGeomMunger *)munger)->munge_geom_impl(result, data);
+
+  if (result.p() != this) {
+    // Record the new result in the cache.
+    {
+      CDWriter cdata(((qpGeom *)this)->_cycler);
+      MungeResult &mr = cdata->_munged_cache[munger];
+      mr._geom = result;
+      mr._data = data;
+    }
+    
+    // And tell the cache manager about the new entry.  (It might
+    // immediately request a delete from the cache of the thing we just
+    // added.)
+    qpGeomVertexCacheManager *cache_mgr = 
+      qpGeomVertexCacheManager::get_global_ptr();
+    cache_mgr->record_geom(this, munger, 
+                           result->get_num_bytes() + data->get_num_bytes());
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeom::output
 //       Access: Published
@@ -200,6 +312,28 @@ write(ostream &out, int indent_level) const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeom::clear_cache
+//       Access: Published
+//  Description: Removes all of the previously-cached results of
+//               munge_geom().
+////////////////////////////////////////////////////////////////////
+void qpGeom::
+clear_cache() {
+  // Probably we shouldn't do anything at all here unless we are
+  // running in pipeline stage 0.
+  qpGeomVertexCacheManager *cache_mgr = 
+    qpGeomVertexCacheManager::get_global_ptr();
+
+  CData *cdata = CDWriter(_cycler);
+  for (MungedCache::iterator ci = cdata->_munged_cache.begin();
+       ci != cdata->_munged_cache.end();
+       ++ci) {
+    cache_mgr->remove_geom(this, (*ci).first);
+  }
+  cdata->_munged_cache.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeom::draw
 //       Access: Public
@@ -291,6 +425,27 @@ recompute_bound() {
   return bound;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeom::remove_cache_entry
+//       Access: Private
+//  Description: Removes a particular entry from the local cache; it
+//               has already been removed from the cache manager.
+//               This is only called from GeomVertexCacheManager.
+////////////////////////////////////////////////////////////////////
+void qpGeom::
+remove_cache_entry(const qpGeomMunger *modifier) const {
+  // We have to operate on stage 0 of the pipeline, since that's where
+  // the cache really counts.  Because of the multistage pipeline, we
+  // might not actually have a cache entry there (it might have been
+  // added to stage 1 instead).  No big deal if we don't.
+  CData *cdata = ((qpGeom *)this)->_cycler.write_stage(0);
+  MungedCache::iterator ci = cdata->_munged_cache.find(modifier);
+  if (ci != cdata->_munged_cache.end()) {
+    cdata->_munged_cache.erase(ci);
+  }
+  ((qpGeom *)this)->_cycler.release_write_stage(0, cdata);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeom::register_with_read_factory
 //       Access: Public, Static

+ 29 - 3
panda/src/gobj/qpgeom.h

@@ -28,6 +28,7 @@
 #include "pipelineCycler.h"
 #include "qpgeomVertexData.h"
 #include "qpgeomPrimitive.h"
+#include "qpgeomMunger.h"
 #include "pointerTo.h"
 #include "geom.h"
 
@@ -62,19 +63,26 @@ PUBLISHED:
 
   INLINE CPT(qpGeomVertexData) get_vertex_data() const;
   PT(qpGeomVertexData) modify_vertex_data();
-  void set_vertex_data(PT(qpGeomVertexData) data);
+  void set_vertex_data(const qpGeomVertexData *data);
 
   INLINE int get_num_primitives() const;
   INLINE const qpGeomPrimitive *get_primitive(int i) const;
   INLINE qpGeomPrimitive *modify_primitive(int i);
-  INLINE void set_primitive(int i, qpGeomPrimitive *primitive);
-  void add_primitive(qpGeomPrimitive *primitive);
+  INLINE void set_primitive(int i, const qpGeomPrimitive *primitive);
+  void add_primitive(const qpGeomPrimitive *primitive);
   void remove_primitive(int i);
   void clear_primitives();
 
+  int get_num_bytes() const;
+
+  void munge_geom(const qpGeomMunger *munger,
+                  CPT(qpGeom) &result, CPT(qpGeomVertexData) &data) const;
+
   void output(ostream &out) const;
   void write(ostream &out, int indent_level = 0) const;
 
+  void clear_cache();
+
 public:
   void draw(GraphicsStateGuardianBase *gsg, 
             const qpGeomVertexData *vertex_data) const;
@@ -82,9 +90,24 @@ public:
 protected:
   virtual BoundingVolume *recompute_bound();
 
+private:
+  void remove_cache_entry(const qpGeomMunger *modifier) const;
+
 private:
   typedef pvector<PT(qpGeomPrimitive) > Primitives;
 
+  // We have to use reference-counting pointers here instead of having
+  // explicit cleanup in the GeomVertexFormat destructor, because the
+  // cache needs to be stored in the CycleData, which makes accurate
+  // cleanup more difficult.  We use the GeomVertexCacheManager class
+  // to avoid cache bloat.
+  class MungeResult {
+  public:
+    CPT(qpGeom) _geom;
+    CPT(qpGeomVertexData) _data;
+  };
+  typedef pmap<CPT(qpGeomMunger), MungeResult> MungedCache;
+
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
   public:
@@ -97,6 +120,7 @@ private:
 
     PT(qpGeomVertexData) _data;
     Primitives _primitives;
+    MungedCache _munged_cache;
   };
 
   PipelineCycler<CData> _cycler;
@@ -131,6 +155,8 @@ public:
 
 private:
   static TypeHandle _type_handle;
+
+  friend class qpGeomVertexCacheManager;
 };
 
 INLINE ostream &operator << (ostream &out, const qpGeom &obj);

+ 14 - 14
panda/src/gobj/qpgeomMunger.I

@@ -71,20 +71,6 @@ compare_to(const qpGeomMunger &other) const {
   return compare_to_impl(&other);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomMunger::munge_data
-//       Access: Public
-//  Description: Given a source GeomVertexData, converts it if
-//               necessary to the appropriate data for rendering.
-////////////////////////////////////////////////////////////////////
-INLINE CPT(qpGeomVertexData) qpGeomMunger::
-munge_data(const qpGeomVertexData *data) const {
-  // We cast away the const pointer, because do_munge_data() needs to
-  // update caches and stuff, but we trust it not to change any
-  // user-definable parameters.
-  return ((qpGeomMunger *)this)->do_munge_data(data);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::munge_format
 //       Access: Public
@@ -99,6 +85,20 @@ munge_format(const qpGeomVertexFormat *format) const {
   return ((qpGeomMunger *)this)->do_munge_format(format);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomMunger::munge_data
+//       Access: Public
+//  Description: Given a source GeomVertexData, converts it if
+//               necessary to the appropriate data for rendering.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(qpGeomVertexData) qpGeomMunger::
+munge_data(const qpGeomVertexData *data) const {
+  // We cast away the const pointer, because do_munge_data() needs to
+  // update caches and stuff, but we trust it not to change any
+  // user-definable parameters.
+  return ((qpGeomMunger *)this)->do_munge_data(data);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::get_registry
 //       Access: Private

+ 33 - 23
panda/src/gobj/qpgeomMunger.cxx

@@ -32,7 +32,7 @@ PStatCollector qpGeomMunger::_munge_pcollector("Cull:Munge");
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 qpGeomMunger::
-qpGeomMunger() :
+qpGeomMunger(const GraphicsStateGuardianBase *, const RenderState *) :
   _is_registered(false)
 {
 }
@@ -92,28 +92,6 @@ remove_data(const qpGeomVertexData *data) {
   nassertv(_is_registered);
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomMunger::do_munge_data
-//       Access: Protected
-//  Description: The protected implementation of munge_data().  This
-//               exists just to cast away the const pointer.
-////////////////////////////////////////////////////////////////////
-CPT(qpGeomVertexData) qpGeomMunger::
-do_munge_data(const qpGeomVertexData *data) {
-  nassertr(_is_registered, NULL);
-  PStatTimer timer(_munge_pcollector);
-
-  CPT(qpGeomVertexFormat) orig_format = data->get_format();
-  CPT(qpGeomVertexFormat) new_format = munge_format(orig_format);
-
-  if (new_format == orig_format) {
-    // Trivial case.
-    return data;
-  }
-
-  return data->convert_to(new_format);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::do_munge_format
 //       Access: Protected
@@ -161,6 +139,28 @@ do_munge_format(const qpGeomVertexFormat *format) {
   return derived_format;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomMunger::do_munge_data
+//       Access: Protected
+//  Description: The protected implementation of munge_data().  This
+//               exists just to cast away the const pointer.
+////////////////////////////////////////////////////////////////////
+CPT(qpGeomVertexData) qpGeomMunger::
+do_munge_data(const qpGeomVertexData *data) {
+  nassertr(_is_registered, NULL);
+  PStatTimer timer(_munge_pcollector);
+
+  CPT(qpGeomVertexFormat) orig_format = data->get_format();
+  CPT(qpGeomVertexFormat) new_format = munge_format(orig_format);
+
+  if (new_format == orig_format) {
+    // Trivial case.
+    return data;
+  }
+
+  return data->convert_to(new_format);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::munge_format_impl
 //       Access: Protected, Virtual
@@ -172,6 +172,16 @@ munge_format_impl(const qpGeomVertexFormat *orig) {
   return orig;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomMunger::munge_geom_impl
+//       Access: Protected, Virtual
+//  Description: Converts a Geom and/or its data as necessary.
+////////////////////////////////////////////////////////////////////
+void qpGeomMunger::
+munge_geom_impl(CPT(qpGeom) &, CPT(qpGeomVertexData) &) {
+  // The default implementation does nothing.
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::compare_to_impl
 //       Access: Protected, Virtual

+ 12 - 1
panda/src/gobj/qpgeomMunger.h

@@ -22,11 +22,17 @@
 #include "pandabase.h"
 #include "typedReferenceCount.h"
 #include "qpgeomVertexFormat.h"
+#include "qpgeomVertexData.h"
 #include "indirectCompareTo.h"
 #include "pStatCollector.h"
+#include "pointerTo.h"
 #include "pmap.h"
 #include "pset.h"
 
+class GraphicsStateGuardianBase;
+class RenderState;
+class qpGeom;
+
 ////////////////////////////////////////////////////////////////////
 //       Class : qpGeomMunger
 // Description : Objects of this class are used to convert vertex data
@@ -52,7 +58,7 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA qpGeomMunger : public TypedReferenceCount {
 public:
-  qpGeomMunger();
+  qpGeomMunger(const GraphicsStateGuardianBase *gsg, const RenderState *state);
   virtual ~qpGeomMunger();
 
   INLINE bool is_registered() const;
@@ -64,6 +70,8 @@ public:
   INLINE CPT(qpGeomVertexData) munge_data(const qpGeomVertexData *data) const;
   void remove_data(const qpGeomVertexData *data);
 
+  // Also see Geom::munge_geom() for the primary interface.
+
 public:
   INLINE int compare_to(const qpGeomMunger &other) const;
 
@@ -72,6 +80,7 @@ protected:
   CPT(qpGeomVertexData) do_munge_data(const qpGeomVertexData *data);
 
   virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig);
+  virtual void munge_geom_impl(CPT(qpGeom) &geom, CPT(qpGeomVertexData) &data);
   virtual int compare_to_impl(const qpGeomMunger *other) const;
 
 private:
@@ -116,6 +125,8 @@ public:
 
 private:
   static TypeHandle _type_handle;
+
+  friend class qpGeom;
 };
 
 #include "qpgeomMunger.I"

+ 103 - 0
panda/src/gobj/qpgeomPrimitive.I

@@ -17,6 +17,37 @@
 ////////////////////////////////////////////////////////////////////
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::get_shade_model
+//       Access: Published
+//  Description: Returns the ShadeModel hint for this primitive.
+//               This is intended as a hint to the renderer to tell it
+//               how the per-vertex colors and normals are applied.
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomPrimitive::ShadeModel qpGeomPrimitive::
+get_shade_model() const {
+  CDReader cdata(_cycler);
+  return cdata->_shade_model;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::set_shade_model
+//       Access: Published
+//  Description: Changes the ShadeModel hint for this primitive.
+//               This is different from the ShadeModelAttrib that
+//               might also be applied from the scene graph.  This
+//               does not affect the shade model that is in effect
+//               when rendering, but rather serves as a hint to the
+//               renderer to tell it how the per-vertex colors and
+//               normals on this primitive are applied.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomPrimitive::
+set_shade_model(qpGeomPrimitive::ShadeModel shade_model) {
+  CDWriter cdata(_cycler);
+  cdata->_shade_model = shade_model;
+  cdata->_rotated_vertices.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::get_num_vertices
 //       Access: Published
@@ -55,6 +86,64 @@ get_vertices() const {
   return cdata->_vertices;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::get_flat_first_vertices
+//       Access: Published
+//  Description: Returns a const pointer to the vertex index array,
+//               suitable for traversing for a renderer that expects
+//               the key vertex of each triangle in a flat-shaded
+//               model to be the triangle's first vertex (as DirectX
+//               does).
+//
+//               The result of this is dependent on the value
+//               specified to set_shade_model().  If the shade model
+//               indicates flat_last_vertex, this will return the
+//               vertices rotated to bring them into first_vertex
+//               compliance; otherwise, it will be the unmodified
+//               result from get_vertices().
+////////////////////////////////////////////////////////////////////
+INLINE CPTA_ushort qpGeomPrimitive::
+get_flat_first_vertices() const {
+  CDReader cdata(_cycler);
+  if (cdata->_shade_model == SM_flat_last_vertex) {
+    if (cdata->_rotated_vertices == (const ushort *)NULL) {
+      ((qpGeomPrimitive *)this)->do_rotate();
+    }
+    return cdata->_rotated_vertices;
+  } else {
+    return cdata->_vertices;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::get_flat_last_vertices
+//       Access: Published
+//  Description: Returns a const pointer to the vertex index array,
+//               suitable for traversing for a renderer that expects
+//               the key vertex of each triangle in a flat-shaded
+//               model to be the triangle's last vertex (as OpenGL
+//               does).
+//
+//               The result of this is dependent on the value
+//               specified to set_shade_model().  If the shade model
+//               indicates flat_first_vertex, this will return the
+//               vertices rotated to bring them into last_vertex
+//               compliance; otherwise, it will be the unmodified
+//               result from get_vertices().
+////////////////////////////////////////////////////////////////////
+INLINE CPTA_ushort qpGeomPrimitive::
+get_flat_last_vertices() const {
+  CDReader cdata(_cycler);
+  if (cdata->_shade_model == SM_flat_first_vertex) {
+    if (cdata->_rotated_vertices == (const ushort *)NULL) {
+      ((qpGeomPrimitive *)this)->do_rotate();
+    }
+    return cdata->_rotated_vertices;
+  } else {
+    return cdata->_vertices;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::get_vertices
 //       Access: Published
@@ -103,6 +192,17 @@ get_max_vertex() const {
   return cdata->_max_vertex;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::get_primitive_end
+//       Access: Published
+//  Description: Returns the element within the _vertices list at which
+//               the ith primitive ends.  
+////////////////////////////////////////////////////////////////////
+INLINE int qpGeomPrimitive::
+get_primitive_end(int i) const {
+  return get_primitive_start(i + 1);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::CData::Constructor
 //       Access: Public
@@ -110,6 +210,7 @@ get_max_vertex() const {
 ////////////////////////////////////////////////////////////////////
 INLINE qpGeomPrimitive::CData::
 CData() :
+  _shade_model(SM_smooth),
   _got_minmax(true),
   _min_vertex(0),
   _max_vertex(0)
@@ -123,7 +224,9 @@ CData() :
 ////////////////////////////////////////////////////////////////////
 INLINE qpGeomPrimitive::CData::
 CData(const qpGeomPrimitive::CData &copy) :
+  _shade_model(copy._shade_model),
   _vertices(copy._vertices),
+  _rotated_vertices(copy._rotated_vertices),
   _ends(copy._ends),
   _got_minmax(copy._got_minmax),
   _min_vertex(copy._min_vertex),

+ 66 - 18
panda/src/gobj/qpgeomPrimitive.cxx

@@ -28,6 +28,8 @@
 
 TypeHandle qpGeomPrimitive::_type_handle;
 
+PStatCollector qpGeomPrimitive::_rotate_pcollector("Cull:Rotate");
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::Constructor
 //       Access: Published
@@ -67,7 +69,7 @@ qpGeomPrimitive::
     if (_cycler.is_stage_unique(i)) {
       CData *cdata = _cycler.write_stage(i);
       if (cdata->_decomposed != (qpGeomPrimitive *)NULL) {
-        cache_mgr->remove_decompose(this);
+        cache_mgr->remove_primitive(this);
         cdata->_decomposed = NULL;
       }
       _cycler.release_write_stage(i, cdata);
@@ -93,6 +95,7 @@ add_vertex(int vertex) {
   clear_cache();
   CDWriter cdata(_cycler);
   cdata->_vertices.push_back(short_vertex);
+  cdata->_rotated_vertices.clear();
 
   if (cdata->_got_minmax) {
     cdata->_min_vertex = min(cdata->_min_vertex, short_vertex);
@@ -118,6 +121,7 @@ add_consecutive_vertices(int start, int num_vertices) {
   for (unsigned short v = short_start; v <= short_end; ++v) {
     cdata->_vertices.push_back(v);
   }
+  cdata->_rotated_vertices.clear();
 
   if (cdata->_got_minmax) {
     cdata->_min_vertex = min(cdata->_min_vertex, short_start);
@@ -171,6 +175,7 @@ clear_vertices() {
   clear_cache();
   CDWriter cdata(_cycler);
   cdata->_vertices.clear();
+  cdata->_rotated_vertices.clear();
   cdata->_ends.clear();
 }
 
@@ -186,6 +191,7 @@ PTA_ushort qpGeomPrimitive::
 modify_vertices() {
   clear_cache();
   CDWriter cdata(_cycler);
+  cdata->_rotated_vertices.clear();
   return cdata->_vertices;
 }
 
@@ -201,6 +207,7 @@ set_vertices(PTA_ushort vertices) {
   clear_cache();
   CDWriter cdata(_cycler);
   cdata->_vertices = vertices;
+  cdata->_rotated_vertices.clear();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -244,14 +251,14 @@ set_ends(PTA_int ends) {
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::get_num_bytes
 //       Access: Published
-//  Description: Records the number of bytes consumed by the primitive
+//  Description: Returns the number of bytes consumed by the primitive
 //               and its index table(s).
 ////////////////////////////////////////////////////////////////////
 int qpGeomPrimitive::
 get_num_bytes() const {
   CDReader cdata(_cycler);
   return cdata->_vertices.size() * sizeof(short) +
-    cdata->_ends.size() * sizeof(int);
+    cdata->_ends.size() * sizeof(int) + sizeof(qpGeomPrimitive);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -309,7 +316,7 @@ get_num_primitives() const {
 //     Function: qpGeomPrimitive::get_primitive_start
 //       Access: Published
 //  Description: Returns the element within the _vertices list at which
-//               the ith primitive ends.  
+//               the ith primitive starts.  
 //
 //               If i is one more than the highest valid primitive
 //               vertex, the return value will be one more than the
@@ -382,12 +389,12 @@ get_primitive_num_vertices(int i) const {
 //               primitive without having to write handlers for each
 //               possible kind of primitive type.
 ////////////////////////////////////////////////////////////////////
-PT(qpGeomPrimitive) qpGeomPrimitive::
-decompose() {
-  PT(qpGeomPrimitive) result;
+CPT(qpGeomPrimitive) qpGeomPrimitive::
+decompose() const {
+  CPT(qpGeomPrimitive) result;
   {
     // Use read() and release_read() instead of CDReader, because the
-    // call to record_decompose() might recursively call back into
+    // call to record_primitive() might recursively call back into
     // this object, and require a write.
     const CData *cdata = _cycler.read();
     if (cdata->_decomposed != (qpGeomPrimitive *)NULL) {
@@ -397,7 +404,7 @@ decompose() {
       // while longer.
       qpGeomVertexCacheManager *cache_mgr = 
         qpGeomVertexCacheManager::get_global_ptr();
-      cache_mgr->record_decompose(this, result->get_num_bytes());
+      cache_mgr->record_primitive(this, result->get_num_bytes());
 
       return result;
     }
@@ -405,8 +412,8 @@ decompose() {
   }
 
   result = decompose_impl();
-  if (result.p() == this) {
-    // decomposing this primitive has no effect.
+  if (result.p() == this || result.p() == NULL) {
+    // decomposing this primitive has no effect or cannot be done.
     return this;
   }
 
@@ -416,13 +423,13 @@ decompose() {
   }
 
   // Record the result for the future.
-  CDWriter cdata(_cycler);
+  CDWriter cdata(((qpGeomPrimitive *)this)->_cycler);
   cdata->_decomposed = result;
 
   // And add *this* object to the cache manager.
   qpGeomVertexCacheManager *cache_mgr = 
     qpGeomVertexCacheManager::get_global_ptr();
-  cache_mgr->record_decompose(this, result->get_num_bytes());
+  cache_mgr->record_primitive(this, result->get_num_bytes());
 
   return result;
 }
@@ -464,7 +471,7 @@ write(ostream &out, int indent_level) const {
 //     Function: qpGeomPrimitive::clear_cache
 //       Access: Published
 //  Description: Removes all of the previously-cached results of
-//               convert_to().
+//               decompose().
 ////////////////////////////////////////////////////////////////////
 void qpGeomPrimitive::
 clear_cache() {
@@ -475,7 +482,7 @@ clear_cache() {
 
   CData *cdata = CDWriter(_cycler);
   if (cdata->_decomposed != (qpGeomPrimitive *)NULL) {
-    cache_mgr->remove_decompose(this);
+    cache_mgr->remove_primitive(this);
     cdata->_decomposed = NULL;
   }
 }
@@ -538,7 +545,7 @@ calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::decompose_impl
-//       Access: Protected
+//       Access: Protected, Virtual
 //  Description: Decomposes a complex primitive type into a simpler
 //               primitive type, for instance triangle strips to
 //               triangles, and returns a pointer to the new primitive
@@ -550,11 +557,51 @@ calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
 //               primitive without having to write handlers for each
 //               possible kind of primitive type.
 ////////////////////////////////////////////////////////////////////
-PT(qpGeomPrimitive) qpGeomPrimitive::
-decompose_impl() {
+CPT(qpGeomPrimitive) qpGeomPrimitive::
+decompose_impl() const {
   return this;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::rotate_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of do_rotate().
+////////////////////////////////////////////////////////////////////
+CPTA_ushort qpGeomPrimitive::
+rotate_impl() const {
+  // The default implementation doesn't even try to do anything.
+  nassertr(false, get_vertices());
+  return get_vertices();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomPrimitive::do_rotate
+//       Access: Private
+//  Description: Fills _rotated_vertices by rotating the individual
+//               primitives to bring each flat-colored vertex to the
+//               opposite position.  If the current shade_model
+//               indicates flat_vertex_last, this should bring the
+//               last vertex to the first position; if it indicates
+//               flat_vertex_first, this should bring the first vertex
+//               to the last position.  This is used internally to
+//               implement get_flat_first_vertices() and
+//               get_flat_last_vertices().
+////////////////////////////////////////////////////////////////////
+void qpGeomPrimitive::
+do_rotate() {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "Rotating " << get_type() << ": " << (void *)this << "\n";
+  }
+
+  PStatTimer timer(_rotate_pcollector);
+  CDReader cdata(_cycler);
+  CPTA_ushort rotated_vertices = rotate_impl();
+
+  CDWriter cdataw(_cycler, cdata);
+  cdataw->_rotated_vertices = rotated_vertices;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomPrimitive::remove_cache_entry
 //       Access: Private
@@ -570,6 +617,7 @@ remove_cache_entry() const {
   // added to stage 1 instead).  No big deal if we don't.
   CData *cdata = ((qpGeomPrimitive *)this)->_cycler.write_stage(0);
   cdata->_decomposed = NULL;
+  ((qpGeomPrimitive *)this)->_cycler.release_write_stage(0, cdata);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 28 - 5
panda/src/gobj/qpgeomPrimitive.h

@@ -25,6 +25,7 @@
 #include "pointerTo.h"
 #include "pta_ushort.h"
 #include "pta_int.h"
+#include "pStatCollector.h"
 #include "cycleData.h"
 #include "cycleDataReader.h"
 #include "cycleDataWriter.h"
@@ -64,6 +65,18 @@ PUBLISHED:
   qpGeomPrimitive(const qpGeomPrimitive &copy);
   virtual ~qpGeomPrimitive();
 
+  virtual PT(qpGeomPrimitive) make_copy() const=0;
+
+  enum ShadeModel {
+    SM_smooth,
+    SM_uniform,
+    SM_flat_first_vertex,
+    SM_flat_last_vertex,
+  };
+
+  INLINE ShadeModel get_shade_model() const;
+  INLINE void set_shade_model(ShadeModel shade_model);
+
   INLINE int get_num_vertices() const;
   INLINE int get_vertex(int i) const;
   void add_vertex(int vertex);
@@ -72,6 +85,8 @@ PUBLISHED:
   void clear_vertices();
 
   INLINE CPTA_ushort get_vertices() const;
+  INLINE CPTA_ushort get_flat_first_vertices() const;
+  INLINE CPTA_ushort get_flat_last_vertices() const;
   PTA_ushort modify_vertices();
   void set_vertices(PTA_ushort vertices);
 
@@ -88,9 +103,10 @@ PUBLISHED:
   virtual int get_min_num_vertices_per_primitive() const;
   int get_num_primitives() const;
   int get_primitive_start(int i) const;
+  INLINE int get_primitive_end(int i) const;
   int get_primitive_num_vertices(int i) const;
 
-  PT(qpGeomPrimitive) decompose();
+  CPT(qpGeomPrimitive) decompose() const;
 
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;
@@ -98,18 +114,23 @@ PUBLISHED:
   void clear_cache();
 
 public:
-  virtual void draw(GraphicsStateGuardianBase *gsg)=0;
+  virtual void draw(GraphicsStateGuardianBase *gsg) const=0;
 
   virtual void calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point,
                                  bool &found_any, 
                                  const qpGeomVertexData *vertex_data) const;
 
 protected:
-  virtual PT(qpGeomPrimitive) decompose_impl();
+  virtual CPT(qpGeomPrimitive) decompose_impl() const;
+  virtual CPTA_ushort rotate_impl() const;
 
-private:
+private: 
+  void do_rotate();
   void remove_cache_entry() const;
 
+protected:
+  static PStatCollector _rotate_pcollector;
+
 private:
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
@@ -121,14 +142,16 @@ private:
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
 
+    ShadeModel _shade_model;
     PTA_ushort _vertices;
+    CPTA_ushort _rotated_vertices;
     PTA_int _ends;
 
     bool _got_minmax;
     unsigned short _min_vertex;
     unsigned short _max_vertex;
 
-    PT(qpGeomPrimitive) _decomposed;
+    CPT(qpGeomPrimitive) _decomposed;
   };
 
   PipelineCycler<CData> _cycler;

+ 60 - 1
panda/src/gobj/qpgeomTriangles.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "qpgeomTriangles.h"
+#include "pStatTimer.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 
@@ -51,6 +52,16 @@ qpGeomTriangles::
 ~qpGeomTriangles() {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTriangles::make_copy
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PT(qpGeomPrimitive) qpGeomTriangles::
+make_copy() const {
+  return new qpGeomTriangles(*this);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTriangles::get_num_vertices_per_primitive
 //       Access: Published, Virtual
@@ -74,10 +85,58 @@ get_num_vertices_per_primitive() const {
 //               primitive.
 ////////////////////////////////////////////////////////////////////
 void qpGeomTriangles::
-draw(GraphicsStateGuardianBase *gsg) {
+draw(GraphicsStateGuardianBase *gsg) const {
   gsg->draw_triangles(this);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTriangles::rotate_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of do_rotate().
+////////////////////////////////////////////////////////////////////
+CPTA_ushort qpGeomTriangles::
+rotate_impl() const {
+  // To rotate triangles, we just move one vertex from the front to
+  // the back, or vice-versa; but we have to know what direction we're
+  // going.
+  CPTA_ushort vertices = get_vertices();
+  ShadeModel shade_model = get_shade_model();
+
+  PTA_ushort new_vertices;
+  new_vertices.reserve(vertices.size());
+
+  switch (shade_model) {
+  case SM_flat_first_vertex:
+    // Move the first vertex to the end.
+    {
+      for (int begin = 0; begin < (int)vertices.size(); begin += 3) {
+        new_vertices.push_back(vertices[begin + 1]);
+        new_vertices.push_back(vertices[begin + 2]);
+        new_vertices.push_back(vertices[begin]);
+      }
+    }
+    break;
+    
+  case SM_flat_last_vertex:
+    // Move the last vertex to the front.
+    {
+      for (int begin = 0; begin < (int)vertices.size(); begin += 3) {
+        new_vertices.push_back(vertices[begin + 2]);
+        new_vertices.push_back(vertices[begin]);
+        new_vertices.push_back(vertices[begin + 1]);
+      }
+    }
+    break;
+      
+  default:
+    // This shouldn't get called with any other shade model.
+    nassertr(false, vertices);
+  }
+  
+  nassertr(new_vertices.size() == vertices.size(), vertices);
+  return new_vertices;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTriangles::register_with_read_factory
 //       Access: Public, Static

+ 6 - 1
panda/src/gobj/qpgeomTriangles.h

@@ -34,10 +34,15 @@ PUBLISHED:
   qpGeomTriangles(const qpGeomTriangles &copy);
   virtual ~qpGeomTriangles();
 
+  virtual PT(qpGeomPrimitive) make_copy() const;
+
   virtual int get_num_vertices_per_primitive() const;
 
 public:
-  virtual void draw(GraphicsStateGuardianBase *gsg);
+  virtual void draw(GraphicsStateGuardianBase *gsg) const;
+
+protected:
+  virtual CPTA_ushort rotate_impl() const;
 
 public:
   static void register_with_read_factory();

+ 28 - 4
panda/src/gobj/qpgeomTrifans.cxx

@@ -52,6 +52,16 @@ qpGeomTrifans::
 ~qpGeomTrifans() {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTrifans::make_copy
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PT(qpGeomPrimitive) qpGeomTrifans::
+make_copy() const {
+  return new qpGeomTrifans(*this);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTrifans::draw
 //       Access: Public, Virtual
@@ -59,13 +69,13 @@ qpGeomTrifans::
 //               primitive.
 ////////////////////////////////////////////////////////////////////
 void qpGeomTrifans::
-draw(GraphicsStateGuardianBase *gsg) {
+draw(GraphicsStateGuardianBase *gsg) const {
   gsg->draw_trifans(this);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTrifans::decompose_impl
-//       Access: Published, Virtual
+//       Access: Protected, Virtual
 //  Description: Decomposes a complex primitive type into a simpler
 //               primitive type, for instance triangle strips to
 //               triangles, and returns a pointer to the new primitive
@@ -77,9 +87,10 @@ draw(GraphicsStateGuardianBase *gsg) {
 //               primitive without having to write handlers for each
 //               possible kind of primitive type.
 ////////////////////////////////////////////////////////////////////
-PT(qpGeomPrimitive) qpGeomTrifans::
-decompose_impl() {
+CPT(qpGeomPrimitive) qpGeomTrifans::
+decompose_impl() const {
   PT(qpGeomTriangles) triangles = new qpGeomTriangles;
+  triangles->set_shade_model(get_shade_model());
   CPTA_ushort vertices = get_vertices();
   CPTA_int ends = get_ends();
 
@@ -109,6 +120,19 @@ decompose_impl() {
   return triangles.p();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTrifans::rotate_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of do_rotate().
+////////////////////////////////////////////////////////////////////
+CPTA_ushort qpGeomTrifans::
+rotate_impl() const {
+  // Actually, we can't rotate fans without chaging the winding order.
+  // It's an error to define a flat shade model for a GeomTrifan.
+  nassertr(false, get_vertices());
+  return get_vertices();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTrifans::register_with_read_factory
 //       Access: Public, Static

+ 5 - 2
panda/src/gobj/qpgeomTrifans.h

@@ -34,11 +34,14 @@ PUBLISHED:
   qpGeomTrifans(const qpGeomTrifans &copy);
   virtual ~qpGeomTrifans();
 
+  virtual PT(qpGeomPrimitive) make_copy() const;
+
 public:
-  virtual void draw(GraphicsStateGuardianBase *gsg);
+  virtual void draw(GraphicsStateGuardianBase *gsg) const;
 
 protected:
-  virtual PT(qpGeomPrimitive) decompose_impl();
+  virtual CPT(qpGeomPrimitive) decompose_impl() const;
+  virtual CPTA_ushort rotate_impl() const;
 
 public:
   static void register_with_read_factory();

+ 130 - 31
panda/src/gobj/qpgeomTristrips.cxx

@@ -18,6 +18,7 @@
 
 #include "qpgeomTristrips.h"
 #include "qpgeomTriangles.h"
+#include "pStatTimer.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 
@@ -52,6 +53,16 @@ qpGeomTristrips::
 ~qpGeomTristrips() {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTristrips::make_copy
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PT(qpGeomPrimitive) qpGeomTristrips::
+make_copy() const {
+  return new qpGeomTristrips(*this);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTristrips::draw
 //       Access: Public, Virtual
@@ -59,13 +70,13 @@ qpGeomTristrips::
 //               primitive.
 ////////////////////////////////////////////////////////////////////
 void qpGeomTristrips::
-draw(GraphicsStateGuardianBase *gsg) {
+draw(GraphicsStateGuardianBase *gsg) const {
   gsg->draw_tristrips(this);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTristrips::decompose_impl
-//       Access: Published, Virtual
+//       Access: Protected, Virtual
 //  Description: Decomposes a complex primitive type into a simpler
 //               primitive type, for instance triangle strips to
 //               triangles, and returns a pointer to the new primitive
@@ -77,49 +88,137 @@ draw(GraphicsStateGuardianBase *gsg) {
 //               primitive without having to write handlers for each
 //               possible kind of primitive type.
 ////////////////////////////////////////////////////////////////////
-PT(qpGeomPrimitive) qpGeomTristrips::
-decompose_impl() {
+CPT(qpGeomPrimitive) qpGeomTristrips::
+decompose_impl() const {
   PT(qpGeomTriangles) triangles = new qpGeomTriangles;
+  triangles->set_shade_model(get_shade_model());
   CPTA_ushort vertices = get_vertices();
   CPTA_int ends = get_ends();
 
-  int vi = 0;
-  int li = 0;
-  while (li < (int)ends.size()) {
-    int end = ends[li];
-    nassertr(vi + 2 <= end, triangles.p());
-    nassertr(vi < (int)vertices.size(), this);
-    int v0 = vertices[vi];
-    ++vi;
-    nassertr(vi < (int)vertices.size(), this);
-    int v1 = vertices[vi];
-    ++vi;
-    bool reversed = false;
-    while (vi < end) {
-      if (reversed) {
-        triangles->add_vertex(v1);
-        triangles->add_vertex(v0);
-        reversed = false;
-      } else {
+  // We need a slightly different algorithm for SM_flat_first_vertex
+  // than for SM_flat_last_vertex, to preserve the key vertex in the
+  // right place.  The remaining shade models can either either
+  // algorithm.
+  if (get_shade_model() == SM_flat_first_vertex) {
+    // Preserve the first vertex of each component triangle as the
+    // first vertex of each generated triangle.
+    int vi = 0;
+    int li = 0;
+    while (li < (int)ends.size()) {
+      int end = ends[li];
+      nassertr(vi + 2 <= end, triangles.p());
+      nassertr(vi < (int)vertices.size(), this);
+      int v0 = vertices[vi];
+      ++vi;
+      nassertr(vi < (int)vertices.size(), this);
+      int v1 = vertices[vi];
+      ++vi;
+      bool reversed = false;
+      while (vi < end) {
         triangles->add_vertex(v0);
-        triangles->add_vertex(v1);
-        reversed = true;
+        if (reversed) {
+          triangles->add_vertex(vertices[vi]);
+          triangles->add_vertex(v1);
+          reversed = false;
+        } else {
+          triangles->add_vertex(v1);
+          triangles->add_vertex(vertices[vi]);
+          reversed = true;
+        }
+        v0 = v1;
+        nassertr(vi < (int)vertices.size(), this);
+        v1 = vertices[vi];
+        triangles->close_primitive();
+        ++vi;
       }
-      triangles->add_vertex(vertices[vi]);
-      v0 = v1;
+      ++li;
+    }
+    nassertr(vi == (int)vertices.size(), triangles.p());
+
+  } else {
+    // Preserve the last vertex of each component triangle as the
+    // last vertex of each generated triangle.
+    int vi = 0;
+    int li = 0;
+    while (li < (int)ends.size()) {
+      int end = ends[li];
+      nassertr(vi + 2 <= end, triangles.p());
       nassertr(vi < (int)vertices.size(), this);
-      v1 = vertices[vi];
-      triangles->close_primitive();
+      int v0 = vertices[vi];
       ++vi;
+      nassertr(vi < (int)vertices.size(), this);
+      int v1 = vertices[vi];
+      ++vi;
+      bool reversed = false;
+      while (vi < end) {
+        if (reversed) {
+          triangles->add_vertex(v1);
+          triangles->add_vertex(v0);
+          reversed = false;
+        } else {
+          triangles->add_vertex(v0);
+          triangles->add_vertex(v1);
+          reversed = true;
+        }
+        triangles->add_vertex(vertices[vi]);
+        v0 = v1;
+        nassertr(vi < (int)vertices.size(), this);
+        v1 = vertices[vi];
+        triangles->close_primitive();
+        ++vi;
+      }
+      ++li;
     }
-    ++li;
+    nassertr(vi == (int)vertices.size(), triangles.p());
   }
 
-  nassertr(vi == (int)vertices.size(), triangles.p());
-
   return triangles.p();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTristrips::rotate_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of do_rotate().
+////////////////////////////////////////////////////////////////////
+CPTA_ushort qpGeomTristrips::
+rotate_impl() const {
+  // To rotate a triangle strip with an even number of vertices, we
+  // just reverse the vertices.  But if we have an odd number of
+  // vertices, that doesn't work--in fact, nothing works (without also
+  // changing the winding order), so we don't allow an odd number of
+  // vertices in a flat-shaded tristrip.
+  CPTA_ushort vertices = get_vertices();
+  CPTA_int ends = get_ends();
+  PTA_ushort new_vertices;
+  new_vertices.reserve(vertices.size());
+
+  bool any_odd = false;
+
+  int begin = 0;
+  CPTA_int::const_iterator ei;
+  for (ei = ends.begin(); ei != ends.end(); ++ei) {
+    int end = (*ei);
+    int num_vertices = end - begin;
+
+    if ((num_vertices & 1) == 0) {
+      for (int vi = end - 1; vi >= begin; --vi) {
+        new_vertices.push_back(vertices[vi]);
+      }
+    } else {
+      any_odd = true;
+    }
+
+    begin = end;
+  }
+  nassertr(new_vertices.size() == vertices.size(), vertices);
+
+  // If this assertion is triggered, there was a triangle strip with
+  // an odd number of vertices and either SM_flat_first_vertex or
+  // SM_flat_last_vertex specified--which is not allowed.
+  nassertr(!any_odd, new_vertices);
+  return new_vertices;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomTristrips::register_with_read_factory
 //       Access: Public, Static

+ 5 - 2
panda/src/gobj/qpgeomTristrips.h

@@ -34,11 +34,14 @@ PUBLISHED:
   qpGeomTristrips(const qpGeomTristrips &copy);
   virtual ~qpGeomTristrips();
 
+  virtual PT(qpGeomPrimitive) make_copy() const;
+
 public:
-  virtual void draw(GraphicsStateGuardianBase *gsg);
+  virtual void draw(GraphicsStateGuardianBase *gsg) const;
 
 protected:
-  virtual PT(qpGeomPrimitive) decompose_impl();
+  virtual CPT(qpGeomPrimitive) decompose_impl() const;
+  virtual CPTA_ushort rotate_impl() const;
 
 public:
   static void register_with_read_factory();

+ 170 - 49
panda/src/gobj/qpgeomVertexCacheManager.I

@@ -69,18 +69,12 @@ get_total_size() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void qpGeomVertexCacheManager::
 record_munger(const qpGeomMunger *munger) {
-  Entry entry;
-  entry._munger = munger;
-  entry._primitive = NULL;
-  entry._source = NULL;
-  entry._format = NULL;
-  entry._result_size = 100;  // Make up a nominal number.
-  
-  record_entry(entry);
+  // Make up a nominal result_size for a munger.
+  record_entry(Entry(munger, 100));
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomVertexCacheManager::record_decompose
+//     Function: qpGeomVertexCacheManager::record_primitive
 //       Access: Private
 //  Description: Records a new entry in the cache, or marks a cache
 //               hit for a previous entry in the cache.  This should
@@ -88,34 +82,23 @@ record_munger(const qpGeomMunger *munger) {
 //
 //               The cache manager will not hold a reference on the
 //               GeomPrimitive pointer; if it destructs, it should
-//               call remove_decompose() to remove itself from the cache.
+//               call remove_primitive() to remove itself from the cache.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpGeomVertexCacheManager::
-record_decompose(const qpGeomPrimitive *primitive, int result_size) {
-  Entry entry;
-  entry._primitive = primitive;
-  entry._source = NULL;
-  entry._format = NULL;
-  entry._result_size = result_size;
-  
-  record_entry(entry);
+record_primitive(const qpGeomPrimitive *primitive, int result_size) {
+  record_entry(Entry(primitive, result_size));
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomVertexCacheManager::remove_decompose
+//     Function: qpGeomVertexCacheManager::remove_primitive
 //       Access: Private
 //  Description: Removes an entry from the cache, if it is there.
 //               Quietly ignores it if it is not.  This should only be
 //               called by GeomPrimitive.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpGeomVertexCacheManager::
-remove_decompose(const qpGeomPrimitive *primitive) {
-  Entry entry;
-  entry._primitive = primitive;
-  entry._source = NULL;
-  entry._format = NULL;
-
-  remove_entry(entry);
+remove_primitive(const qpGeomPrimitive *primitive) {
+  remove_entry(Entry(primitive, 0));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -130,15 +113,9 @@ remove_decompose(const qpGeomPrimitive *primitive) {
 //               call remove_data() to remove itself from the cache.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpGeomVertexCacheManager::
-record_data(const qpGeomVertexData *source, const qpGeomVertexFormat *format,
+record_data(const qpGeomVertexData *source, const qpGeomVertexFormat *modifier,
             int result_size) {
-  Entry entry;
-  entry._primitive = NULL;
-  entry._source = source;
-  entry._format = format;
-  entry._result_size = result_size;
-  
-  record_entry(entry);
+  record_entry(Entry(source, modifier, result_size));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -150,13 +127,139 @@ record_data(const qpGeomVertexData *source, const qpGeomVertexFormat *format,
 ////////////////////////////////////////////////////////////////////
 INLINE void qpGeomVertexCacheManager::
 remove_data(const qpGeomVertexData *source,
-            const qpGeomVertexFormat *format) {
-  Entry entry;
-  entry._primitive = NULL;
-  entry._source = source;
-  entry._format = format;
+            const qpGeomVertexFormat *modifier) {
+  remove_entry(Entry(source, modifier, 0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::record_geom
+//       Access: Private
+//  Description: Records a new entry in the cache, or marks a cache
+//               hit for a previous entry in the cache.  This should
+//               only be called by Geom.
+//
+//               The cache manager will not hold a reference on the
+//               Geom pointer; if it destructs, it should
+//               call remove_geom() to remove itself from the cache.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+record_geom(const qpGeom *source, const qpGeomMunger *modifier,
+            int result_size) {
+  record_entry(Entry(source, modifier, result_size));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::remove_geom
+//       Access: Private
+//  Description: Removes an entry from the cache, if it is there.
+//               Quietly ignores it if it is not.  This should only be
+//               called by Geom.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::
+remove_geom(const qpGeom *source, const qpGeomMunger *modifier) {
+  remove_entry(Entry(source, modifier, 0));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexCacheManager::Entry::
+Entry(const qpGeomMunger *munger, int result_size) :
+  _cache_type(CT_munger),
+  _result_size(result_size)
+{
+  _u._munger = munger;
+  _u._munger->ref();
+}
 
-  remove_entry(entry);
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexCacheManager::Entry::
+Entry(const qpGeomPrimitive *primitive, int result_size) :
+  _cache_type(CT_primitive),
+  _result_size(result_size)
+{
+  _u._primitive = primitive;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexCacheManager::Entry::
+Entry(const qpGeomVertexData *source, const qpGeomVertexFormat *modifier,
+      int result_size) :
+  _cache_type(CT_data),
+  _result_size(result_size)
+{
+  _u._data._source = source;
+  _u._data._modifier = modifier;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexCacheManager::Entry::
+Entry(const qpGeom *source, const qpGeomMunger *modifier, int result_size) :
+  _cache_type(CT_geom),
+  _result_size(result_size)
+{
+  _u._geom._source = source;
+  _u._geom._modifier = modifier;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexCacheManager::Entry::
+Entry(const qpGeomVertexCacheManager::Entry &copy) :
+  _cache_type(copy._cache_type),
+  _result_size(copy._result_size)
+{
+  _u = copy._u;
+  if (_cache_type == CT_munger) {
+    _u._munger->ref();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexCacheManager::Entry::
+operator = (const qpGeomVertexCacheManager::Entry &copy) {
+  if (_cache_type == CT_munger) {
+    unref_delete(_u._munger);
+  }
+  _cache_type = copy._cache_type;
+  _result_size = copy._result_size;
+  _u = copy._u;
+  if (_cache_type == CT_munger) {
+    _u._munger->ref();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexCacheManager::Entry::
+~Entry() {
+  if (_cache_type == CT_munger) {
+    unref_delete(_u._munger);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -166,14 +269,32 @@ remove_data(const qpGeomVertexData *source,
 ////////////////////////////////////////////////////////////////////
 INLINE bool qpGeomVertexCacheManager::Entry::
 operator < (const qpGeomVertexCacheManager::Entry &other) const {
-  if (_munger != other._munger) {
-    return _munger < other._munger;
-  }
-  if (_primitive != other._primitive) {
-    return _primitive < other._primitive;
-  }
-  if (_source != other._source) {
-    return _source < other._source;
+  switch (_cache_type) {
+  case CT_munger:
+    return _u._munger < other._u._munger;
+
+  case CT_primitive:
+    return _u._primitive < other._u._primitive;
+
+  case CT_data:
+    if (_u._data._source != other._u._data._source) {
+      return _u._data._source < other._u._data._source;
+    }
+    return _u._data._modifier < other._u._data._modifier;
+
+  case CT_geom:
+    if (_u._geom._source != other._u._geom._source) {
+      return _u._geom._source < other._u._geom._source;
+    }
+    return _u._geom._modifier < other._u._geom._modifier;
   }
-  return _format < other._format;
+
+  return 0;
+}
+
+INLINE ostream &
+operator << (ostream &out, const qpGeomVertexCacheManager::Entry &entry) {
+  entry.output(out);
+  return out;
 }
+

+ 67 - 21
panda/src/gobj/qpgeomVertexCacheManager.cxx

@@ -62,13 +62,6 @@ get_global_ptr() {
 ////////////////////////////////////////////////////////////////////
 void qpGeomVertexCacheManager::
 record_entry(const qpGeomVertexCacheManager::Entry &entry) {
-  if (gobj_cat.is_debug()) {
-    gobj_cat.debug()
-      << "record_entry(" << entry._munger << ", " 
-      << entry._primitive << ", " << entry._source << ", " 
-      << entry._format << ", " << entry._result_size << ")\n";
-  }
-
   MutexHolder holder(_lock);
   EntriesIndex::iterator ii = _entries_index.find(&entry);
   if (ii != _entries_index.end()) {
@@ -77,6 +70,17 @@ record_entry(const qpGeomVertexCacheManager::Entry &entry) {
     _total_size -= (*ei)._result_size;
     _entries_index.erase(ii);
     _entries.erase(ei);
+
+    if (gobj_cat.is_spam()) {
+      gobj_cat.spam()
+        << "refreshing cache entry: " << entry << "\n";
+    }
+  } else {
+    if (gobj_cat.is_debug()) {
+      gobj_cat.debug()
+        << "recording cache entry: " << entry << ", total_size = "
+        << _total_size + entry._result_size << "\n";
+    }
   }
 
   // Add the new entry at the end of the list, so it will be the last
@@ -97,30 +101,41 @@ record_entry(const qpGeomVertexCacheManager::Entry &entry) {
   // is set to 0.
   int max_size = get_max_size();
   while (_total_size > max_size) {
-    if (gobj_cat.is_debug()) {
-      gobj_cat.debug()
-        << "total_size = " << _total_size << ", max_size = "
-        << max_size << ", flushing\n";
-    }
     nassertv(!_entries.empty());
     ei = _entries.begin();
     entry_pointer = &(*ei);
 
+    if (gobj_cat.is_debug()) {
+      gobj_cat.debug()
+        << "cache total_size = " << _total_size << ", max_size = "
+        << max_size << ", removing " << *entry_pointer << "\n";
+    }
+
     ii = _entries_index.find(entry_pointer);
     nassertv(ii != _entries_index.end());
 
-    if (entry_pointer->_source != (qpGeomVertexData *)NULL) {
-      entry_pointer->_source->remove_cache_entry(entry_pointer->_format);
+    switch (entry_pointer->_cache_type) {
+    case CT_data:
+      entry_pointer->_u._data._source->remove_cache_entry
+        (entry_pointer->_u._data._modifier);
+      break;
+
+    case CT_primitive:
+      entry_pointer->_u._primitive->remove_cache_entry();
+      break;
+
+    case CT_geom:
+      entry_pointer->_u._geom._source->remove_cache_entry
+        (entry_pointer->_u._geom._modifier);
+      break;
+
+    default:
+      break;
     }
     _total_size -= entry_pointer->_result_size;
     _entries_index.erase(ii);
     _entries.erase(ei);
   }
-  if (gobj_cat.is_debug()) {
-    gobj_cat.debug()
-      << "total_size = " << _total_size << ", max_size = "
-      << max_size << ", done\n";
-  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -133,8 +148,7 @@ void qpGeomVertexCacheManager::
 remove_entry(const qpGeomVertexCacheManager::Entry &entry) {
   if (gobj_cat.is_debug()) {
     gobj_cat.debug()
-      << "remove_entry(" << entry._munger << ", "
-      << entry._source << ", " << entry._format << ")\n";
+      << "remove_entry(" << entry << ")\n";
   }
   MutexHolder holder(_lock);
 
@@ -146,3 +160,35 @@ remove_entry(const qpGeomVertexCacheManager::Entry &entry) {
     _entries.erase(ei);
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexCacheManager::Entry::output
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexCacheManager::Entry::
+output(ostream &out) const {
+  out << "[ ";
+  switch (_cache_type) {
+  case CT_munger:
+    out << "munger " << (void *)_u._munger << ":" 
+        << _u._munger->get_ref_count();
+    break;
+
+  case CT_primitive:
+    out << "primitive " << (void *)_u._primitive;
+    break;
+
+  case CT_data:
+    out << "data " << (void *)_u._data._source << ", " 
+        << (void *)_u._data._modifier;
+    break;
+
+  case CT_geom:
+    out << "geom " << (void *)_u._geom._source << ", " 
+        << (void *)_u._geom._modifier;
+    break;
+  }
+
+  out << ", result_size = " << _result_size << " ]";  
+}

+ 60 - 10
panda/src/gobj/qpgeomVertexCacheManager.h

@@ -28,6 +28,7 @@
 #include "plist.h"
 #include "pmap.h"
 
+class qpGeom;
 class qpGeomPrimitive;
 class qpGeomVertexData;
 class qpGeomVertexFormat;
@@ -44,6 +45,12 @@ class qpGeomVertexFormat;
 //               GeomVertexData source objects.  This allows the cache
 //               data to propagate through the multiprocess pipeline.
 //
+//               This structure actually caches any of a number of
+//               different types of pointers, and mixes them all up in
+//               the same LRU cache list.  Some of them (such as
+//               GeomMunger) are reference-counted here in the cache;
+//               most are not.
+//
 //               This is part of the experimental Geom rewrite.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA qpGeomVertexCacheManager {
@@ -51,6 +58,9 @@ protected:
   qpGeomVertexCacheManager();
   ~qpGeomVertexCacheManager();
 
+public:
+  class Entry;
+
 PUBLISHED:
   INLINE void set_max_size(int max_size) const;
   INLINE int get_max_size() const;
@@ -61,16 +71,18 @@ PUBLISHED:
 
 private:
   INLINE void record_munger(const qpGeomMunger *munger);
-  INLINE void record_decompose(const qpGeomPrimitive *primitive,
+  INLINE void record_primitive(const qpGeomPrimitive *primitive,
                                int result_size);
-  INLINE void remove_decompose(const qpGeomPrimitive *primitive);
+  INLINE void remove_primitive(const qpGeomPrimitive *primitive);
   INLINE void record_data(const qpGeomVertexData *source,
-                          const qpGeomVertexFormat *format,
+                          const qpGeomVertexFormat *modifier,
                           int result_size);
   INLINE void remove_data(const qpGeomVertexData *source,
-                          const qpGeomVertexFormat *format);
-
-  class Entry;
+                          const qpGeomVertexFormat *modifier);
+  INLINE void record_geom(const qpGeom *source,
+                          const qpGeomMunger *modifier,
+                          int result_size);
+  INLINE void remove_geom(const qpGeom *geom, const qpGeomMunger *modifier);
 
   void record_entry(const Entry &entry);
   void remove_entry(const Entry &entry);
@@ -81,17 +93,52 @@ private:
 
   int _total_size;
 
+  enum CacheType {
+    CT_munger,
+    CT_primitive,
+    CT_data,
+    CT_geom,
+  };
+
+public:
+  // This class is public only so we can declare the global ostream
+  // output operator.  It doesn't need to be visible outside this
+  // class.  It contains a single cache entry, which might actually be
+  // any of a handful of different pointer types.  The enumerated type
+  // declared above, and the union declared below, serve to implement
+  // this C-style polymorphism.
   class Entry {
   public:
+    INLINE Entry(const qpGeomMunger *munger, int result_size);
+    INLINE Entry(const qpGeomPrimitive *primitive, int result_size);
+    INLINE Entry(const qpGeomVertexData *source,
+                 const qpGeomVertexFormat *modifier, int result_size);
+    INLINE Entry(const qpGeom *source, const qpGeomMunger *modifier, 
+                 int result_size);
+    INLINE Entry(const Entry &copy);
+    INLINE void operator = (const Entry &copy);
+    INLINE ~Entry();
     INLINE bool operator < (const Entry &other) const;
 
-    CPT(qpGeomMunger) _munger;
-    const qpGeomPrimitive *_primitive;
-    const qpGeomVertexData *_source;
-    const qpGeomVertexFormat *_format;
+    void output(ostream &out) const;
+
+    CacheType _cache_type;
     int _result_size;
+    union {
+      const qpGeomMunger *_munger;
+      const qpGeomPrimitive *_primitive;
+      struct {
+        const qpGeomVertexData *_source;
+        const qpGeomVertexFormat *_modifier;
+      } _data;
+      struct {
+        const qpGeom *_source;
+        const qpGeomMunger *_modifier;
+      } _geom;
+    } _u;
   };
 
+private:
   // This list keeps the cache entries in least-recently-used order:
   // the items at the head of the list are ready to be flushed.
   typedef plist<Entry> Entries;
@@ -106,8 +153,11 @@ private:
   friend class qpGeomMunger;
   friend class qpGeomPrimitive;
   friend class qpGeomVertexData;
+  friend class qpGeom;
 };
 
+INLINE ostream &operator << (ostream &out, const qpGeomVertexCacheManager::Entry &entry);
+
 #include "qpgeomVertexCacheManager.I"
 
 #endif

+ 3 - 3
panda/src/gobj/qpgeomVertexData.cxx

@@ -247,7 +247,7 @@ int qpGeomVertexData::
 get_num_bytes() const {
   CDReader cdata(_cycler);
   
-  int num_bytes = 0;
+  int num_bytes = sizeof(qpGeomVertexData);
 
   Arrays::const_iterator ai;
   for (ai = cdata->_arrays.begin(); ai != cdata->_arrays.end(); ++ai) {
@@ -738,13 +738,13 @@ unpack_argb(float data[4], unsigned int packed_argb) {
 //               This is only called from GeomVertexCacheManager.
 ////////////////////////////////////////////////////////////////////
 void qpGeomVertexData::
-remove_cache_entry(const qpGeomVertexFormat *format) const {
+remove_cache_entry(const qpGeomVertexFormat *modifier) const {
   // We have to operate on stage 0 of the pipeline, since that's where
   // the cache really counts.  Because of the multistage pipeline, we
   // might not actually have a cache entry there (it might have been
   // added to stage 1 instead).  No big deal if we don't.
   CData *cdata = ((qpGeomVertexData *)this)->_cycler.write_stage(0);
-  ConvertedCache::iterator ci = cdata->_converted_cache.find(format);
+  ConvertedCache::iterator ci = cdata->_converted_cache.find(modifier);
   if (ci != cdata->_converted_cache.end()) {
     cdata->_converted_cache.erase(ci);
   }

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

@@ -105,7 +105,7 @@ public:
   static void unpack_argb(float data[4], unsigned int packed_argb);
 
 private:
-  void remove_cache_entry(const qpGeomVertexFormat *format) const;
+  void remove_cache_entry(const qpGeomVertexFormat *modifier) const;
 
 private:
   CPT(qpGeomVertexFormat) _format;

+ 3 - 3
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -174,9 +174,9 @@ public:
   virtual void draw_sphere(GeomSphere *geom, GeomContext *gc)=0;
 
   virtual bool begin_draw_primitives(const qpGeomVertexData *vertex_data)=0;
-  virtual void draw_triangles(qpGeomTriangles *primitive)=0;
-  virtual void draw_tristrips(qpGeomTristrips *primitive)=0;
-  virtual void draw_trifans(qpGeomTrifans *primitive)=0;
+  virtual void draw_triangles(const qpGeomTriangles *primitive)=0;
+  virtual void draw_tristrips(const qpGeomTristrips *primitive)=0;
+  virtual void draw_trifans(const qpGeomTrifans *primitive)=0;
   virtual void end_draw_primitives()=0;
 
   virtual void framebuffer_copy_to_texture

+ 2 - 2
panda/src/pgraph/cullResult.cxx

@@ -128,7 +128,7 @@ add_object(CullableObject *object) {
               get_dual_transparent_state_decals() : 
               get_dual_transparent_state();
             transparent_part->_state = state->compose(transparent_state);
-            transparent_part->munge_vertices(_gsg);
+            transparent_part->munge_geom(_gsg);
             CullBin *bin = get_bin(transparent_part->_state->get_bin_index());
             nassertv(bin != (CullBin *)NULL);
             bin->add_object(transparent_part);
@@ -155,7 +155,7 @@ add_object(CullableObject *object) {
 
   // Munge vertices as needed for the GSG's requirements, and the
   // object's current state.
-  object->munge_vertices(_gsg);
+  object->munge_geom(_gsg);
   
   CullBin *bin = get_bin(object->_state->get_bin_index());
   nassertv(bin != (CullBin *)NULL);

+ 5 - 13
panda/src/pgraph/cullableObject.I

@@ -104,23 +104,15 @@ has_decals() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: CullableObject::munge_vertices
+//     Function: CullableObject::munge_geom
 //       Access: Public
 //  Description: Gets a GeomMunger from the GSG to transform the
-//               vertices to meet the GSG's vertex requirements for
-//               the current state.
+//               geom and/or its vertices to meet the GSG's vertex
+//               requirements for the current state.
 ////////////////////////////////////////////////////////////////////
 INLINE void CullableObject::
-munge_vertices(GraphicsStateGuardianBase *gsg) {
-  // Temporary test until the experimental Geom rewrite becomes the
-  // actual Geom implementation.
-  if (_geom == (Geom *)NULL || _geom->is_exact_type(qpGeom::get_class_type())) {
-    CPT(qpGeomMunger) munger = gsg->get_geom_munger(_state);
-    _munged_data = munger->munge_data(((const qpGeom *)_geom.p())->get_vertex_data());
-    if (_next != (CullableObject *)NULL) {
-      _next->munge_vertices(gsg);
-    }
-  }
+munge_geom(GraphicsStateGuardianBase *gsg) {
+  munge_geom(gsg->get_geom_munger(_state));
 }
 
 ////////////////////////////////////////////////////////////////////

+ 22 - 0
panda/src/pgraph/cullableObject.cxx

@@ -23,6 +23,28 @@ CullableObject *CullableObject::_deleted_chain = (CullableObject *)NULL;
 int CullableObject::_num_ever_allocated = 0;
 TypeHandle CullableObject::_type_handle;
 
+////////////////////////////////////////////////////////////////////
+//     Function: CullableObject::munge_geom
+//       Access: Public
+//  Description: Uses the indicated GeomMunger to transform the geom
+//               and/or its vertices.
+////////////////////////////////////////////////////////////////////
+void CullableObject::
+munge_geom(const qpGeomMunger *munger) {
+  if (_geom != (Geom *)NULL) {
+    // Temporary test and dcast until the experimental Geom rewrite
+    // becomes the actual Geom rewrite.
+    if (_geom->is_exact_type(qpGeom::get_class_type())) {
+      CPT(qpGeom) qpgeom = DCAST(qpGeom, _geom);
+      qpgeom->munge_geom(munger, qpgeom, _munged_data);
+      _geom = qpgeom;
+    }
+  }
+  if (_next != (CullableObject *)NULL) {
+    _next->munge_geom(munger);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CullableObject::Destructor
 //       Access: Public

+ 2 - 1
panda/src/pgraph/cullableObject.h

@@ -54,7 +54,8 @@ public:
 
   INLINE bool has_decals() const;
 
-  INLINE void munge_vertices(GraphicsStateGuardianBase *gsg);
+  INLINE void munge_geom(GraphicsStateGuardianBase *gsg);
+  void munge_geom(const qpGeomMunger *munger);
   INLINE void draw(GraphicsStateGuardianBase *gsg);
 
 public:

+ 1 - 1
panda/src/pgraph/drawCullHandler.cxx

@@ -35,7 +35,7 @@ void DrawCullHandler::
 record_object(CullableObject *object) {
   // Munge vertices as needed for the GSG's requirements, and the
   // object's current state.
-  object->munge_vertices(_gsg);
+  object->munge_geom(_gsg);
 
   // And draw the object, then dispense with it.
   draw(object, _gsg);

+ 18 - 0
panda/src/pgraph/renderState.I

@@ -154,6 +154,24 @@ get_bin_index() const {
   return _bin_index;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: RenderState::is_flat_shaded
+//       Access: Published
+//  Description: Returns true if this RenderState specifies a flat
+//               shading model (that is, per-vertex color applies to
+//               entire triangles), or false if it specifies a smooth
+//               shading model or does not specify.
+////////////////////////////////////////////////////////////////////
+INLINE bool RenderState::
+is_flat_shaded() const {
+  if ((_flags & F_checked_flat_shaded) == 0) {
+    // We pretend this function is const, even though it transparently
+    // modifies the internal flat_shaded cache.
+    ((RenderState *)this)->determine_flat_shaded();
+  }
+  return ((_flags & F_flat_shaded) != 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::set_destructing
 //       Access: Private

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

@@ -22,6 +22,7 @@
 #include "cullBinManager.h"
 #include "fogAttrib.h"
 #include "transparencyAttrib.h"
+#include "shadeModelAttrib.h"
 #include "pStatTimer.h"
 #include "config_pgraph.h"
 #include "bamReader.h"
@@ -1511,6 +1512,26 @@ determine_transparency() {
   _flags |= F_checked_transparency;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: RenderState::determine_flat_shaded
+//       Access: Private
+//  Description: This is the private implementation of
+//               is_flat_shaded().
+////////////////////////////////////////////////////////////////////
+void RenderState::
+determine_flat_shaded() {
+  const RenderAttrib *attrib = 
+    get_attrib(ShadeModelAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const ShadeModelAttrib *sma = DCAST(ShadeModelAttrib, attrib);
+    if (sma->get_mode() == ShadeModelAttrib::M_flat) {
+      _flags |= F_flat_shaded;
+    }
+  }
+
+  _flags |= F_checked_flat_shaded;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::register_with_read_factory
 //       Access: Public, Static

+ 4 - 0
panda/src/pgraph/renderState.h

@@ -114,6 +114,7 @@ PUBLISHED:
   INLINE const CullBinAttrib *get_bin() const;
   INLINE const TransparencyAttrib *get_transparency() const;
   INLINE int get_bin_index() const;
+  INLINE bool is_flat_shaded() const;
 
 public:
   CPT(RenderState) issue_delta_modify(const RenderState *other, 
@@ -150,6 +151,7 @@ private:
   void determine_fog();
   void determine_bin();
   void determine_transparency();
+  void determine_flat_shaded();
 
   INLINE void set_destructing();
   INLINE bool is_destructing() const;
@@ -229,6 +231,8 @@ private:
     F_checked_fog          = 0x0002,
     F_checked_bin          = 0x0004,
     F_checked_transparency = 0x0008,
+    F_checked_flat_shaded  = 0x0010,
+    F_flat_shaded          = 0x0020,
     F_is_destructing       = 0x8000,
   };
   unsigned short _flags;

+ 1 - 0
panda/src/pstatclient/pStatProperties.cxx

@@ -126,6 +126,7 @@ static TimeCollectorProperties time_properties[] = {
   { 1, "Cull:Show fps",                    { 0.5, 0.8, 1.0 } },
   { 1, "Cull:Bins",                        { 0.3, 0.6, 0.3 } },
   { 1, "Cull:Munge",                       { 0.3, 0.3, 0.9 } },
+  { 1, "Cull:Rotate",                      { 0.9, 0.8, 0.5 } },
   { 1, "Draw",                             { 1.0, 0.0, 0.0 },  1.0 / 30.0 },
   { 1, "Draw:Make current",                { 0.4, 0.2, 0.6 } },
   { 1, "Draw:Copy texture",                { 0.2, 0.6, 0.4 } },

+ 1 - 1
pandatool/src/egg-qtess/eggQtess.cxx

@@ -215,7 +215,7 @@ run() {
     // vertices.
     _surfaces.clear();
 
-    _data->remove_unused_vertices();
+    _data->remove_unused_vertices(true);
     write_egg_file();
   }
 }

+ 3 - 3
pandatool/src/eggbase/eggMultiBase.cxx

@@ -84,7 +84,7 @@ post_process_egg_files() {
     nout << "Stripping normals.\n";
     for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
       (*ei)->strip_normals();
-      (*ei)->remove_unused_vertices();
+      (*ei)->remove_unused_vertices(true);
     }
     break;
 
@@ -92,7 +92,7 @@ post_process_egg_files() {
     nout << "Recomputing polygon normals.\n";
     for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
       (*ei)->recompute_polygon_normals();
-      (*ei)->remove_unused_vertices();
+      (*ei)->remove_unused_vertices(true);
     }
     break;
 
@@ -100,7 +100,7 @@ post_process_egg_files() {
     nout << "Recomputing vertex normals.\n";
     for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
       (*ei)->recompute_vertex_normals(_normals_threshold);
-      (*ei)->remove_unused_vertices();
+      (*ei)->remove_unused_vertices(true);
     }
     break;
 

+ 3 - 3
pandatool/src/eggbase/eggWriter.cxx

@@ -142,19 +142,19 @@ post_process_egg_file() {
   case NM_strip:
     nout << "Stripping normals.\n";
     _data->strip_normals();
-    _data->remove_unused_vertices();
+    _data->remove_unused_vertices(true);
     break;
 
   case NM_polygon:
     nout << "Recomputing polygon normals.\n";
     _data->recompute_polygon_normals();
-    _data->remove_unused_vertices();
+    _data->remove_unused_vertices(true);
     break;
 
   case NM_vertex:
     nout << "Recomputing vertex normals.\n";
     _data->recompute_vertex_normals(_normals_threshold);
-    _data->remove_unused_vertices();
+    _data->remove_unused_vertices(true);
     break;
 
   case NM_preserve:

+ 1 - 1
pandatool/src/eggprogs/eggCrop.cxx

@@ -76,7 +76,7 @@ run() {
   int num_removed = strip_prims(_data);
   nout << "Removed " << num_removed << " primitives.\n";
 
-  _data->remove_unused_vertices();
+  _data->remove_unused_vertices(true);
   write_egg_file();
 }
 

+ 2 - 2
pandatool/src/eggprogs/eggToC.cxx

@@ -95,7 +95,7 @@ EggToC() :
 void EggToC::
 run() {
   nout << "Removing invalid primitives.\n";
-  int num_removed = _data->remove_invalid_primitives();
+  int num_removed = _data->remove_invalid_primitives(true);
   nout << "  (" << num_removed << " removed.)\n";
 
   if (_triangulate_polygons) {
@@ -106,7 +106,7 @@ run() {
 
   _data->apply_texmats();
   _data->flatten_transforms();
-  _data->remove_unused_vertices();
+  _data->remove_unused_vertices(true);
 
   // Collect all the polygons together into polysets.
   EggPolysetMaker pmaker;

+ 4 - 4
pandatool/src/eggprogs/eggTrans.cxx

@@ -89,9 +89,9 @@ void EggTrans::
 run() {
   if (_remove_invalid_primitives) {
     nout << "Removing invalid primitives.\n";
-    int num_removed = _data->remove_invalid_primitives();
+    int num_removed = _data->remove_invalid_primitives(true);
     nout << "  (" << num_removed << " removed.)\n";
-    _data->remove_unused_vertices();
+    _data->remove_unused_vertices(true);
   }
 
   if (_triangulate_polygons) {
@@ -108,7 +108,7 @@ run() {
   if (_apply_texmats) {
     nout << "Applying texture matrices.\n";
     _data->apply_texmats();
-    _data->remove_unused_vertices();
+    _data->remove_unused_vertices(true);
   }
 
   if (_collapse_equivalent_textures) {
@@ -120,7 +120,7 @@ run() {
   if (_flatten_transforms) {
     nout << "Flattening transforms.\n";
     _data->flatten_transforms();
-    _data->remove_unused_vertices();
+    _data->remove_unused_vertices(true);
   }
 
   if (_standardize_names) {

+ 1 - 1
pandatool/src/lwoegg/lwoToEggConverter.cxx

@@ -164,7 +164,7 @@ convert_lwo(const LwoHeader *lwo_header) {
   make_egg();
   connect_egg();
 
-  _egg_data->remove_unused_vertices();
+  _egg_data->remove_unused_vertices(true);
   cleanup();
 
   return !had_error();

+ 1 - 1
pandatool/src/xfileegg/xFileMaker.cxx

@@ -198,7 +198,7 @@ add_bin(EggBin *egg_bin, XFileNode *x_parent) {
 bool XFileMaker::
 add_polyset(EggBin *egg_bin, XFileNode *x_parent) {
   // Make sure that all our polygons are reasonable.
-  egg_bin->remove_invalid_primitives();
+  egg_bin->remove_invalid_primitives(true);
 
   XFileMesh *mesh = get_mesh(x_parent);