ソースを参照

better treatment of colors, etc in new geom loader

David Rose 21 年 前
コミット
8b2854f82a

+ 242 - 1
panda/src/egg/eggCompositePrimitive.cxx

@@ -49,6 +49,246 @@ triangulate_in_place() {
   return save_me;
 }
 
+////////////////////////////////////////////////////////////////////
+//     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.
+//
+//               After this call, either the primitive will have
+//               normals or its vertices will, but not both.  Ditto
+//               for colors.
+//
+//               This may create redundant vertices in the vertex
+//               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);
+  }
+
+  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;
+      }
+    }
+    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;
+      }
+    }
+  }
+
+  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();
+      }
+      if (!color_different) {
+        component->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;
+      }
+
+      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 (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);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggCompositePrimitive::post_apply_last_attribute
+//       Access: Published, Virtual
+//  Description: Intended as a followup to apply_last_attribute(),
+//               this also sets an attribute on the first vertices of
+//               the primitive, if they don't already have an
+//               attribute set, just so they end up with *something*.
+////////////////////////////////////////////////////////////////////
+void EggCompositePrimitive::
+post_apply_last_attribute() {
+  if (!empty()) {
+    for (int i = 0; i < (int)size(); i++) {
+      EggVertex *vertex = get_vertex(i);
+      EggAttributes *component = get_component(max(i - 2, 0));
+
+      if (component->has_normal() && !vertex->has_normal()) {
+        vertex->set_normal(component->get_normal());
+      } else if (has_normal() && !vertex->has_normal()) {
+        vertex->set_normal(get_normal());
+      }
+
+      if (component->has_color() && !vertex->has_color()) {
+        vertex->set_color(component->get_color());
+      } else if (has_color() && !vertex->has_color()) {
+        vertex->set_color(get_color());
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggCompositePrimitive::cleanup
+//       Access: Published, Virtual
+//  Description: Cleans up modeling errors in whatever context this
+//               makes sense.  For instance, for a polygon, this calls
+//               remove_doubled_verts(true).  For a point, it calls
+//               remove_nonunique_verts().  Returns true if the
+//               primitive is valid, or false if it is degenerate.
+////////////////////////////////////////////////////////////////////
+bool EggCompositePrimitive::
+cleanup() {
+  return size() >= 3;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggCompositePrimitive::prepare_add_vertex
 //       Access: Protected, Virtual
@@ -113,7 +353,8 @@ write_body(ostream &out, int indent_level) const {
 
   for (int i = 0; i < get_num_components(); i++) {
     const EggAttributes *attrib = get_component(i);
-    if (attrib->compare_to(*this) != 0) {
+    if (attrib->compare_to(*this) != 0 &&
+        (attrib->has_color() || attrib->has_normal())) {
       indent(out, indent_level)
         << "<Component> " << i << " {\n";
       attrib->write(out, indent_level + 2);

+ 5 - 0
panda/src/egg/eggCompositePrimitive.h

@@ -45,6 +45,11 @@ 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 bool cleanup();
+
 protected:
   virtual void prepare_add_vertex(EggVertex *vertex, int i, int n);
   virtual void prepare_remove_vertex(EggVertex *vertex, int i, int n);

+ 145 - 10
panda/src/egg/eggGroupNode.cxx

@@ -703,10 +703,11 @@ strip_normals() {
 //               the total number of new triangles produced, less
 //               degenerate polygons removed.
 //
-//               If flags contains T_convex, both concave and convex
-//               polygons will be subdivided into triangles;
-//               otherwise, only concave polygons will be subdivided,
-//               and convex polygons will be largely unchanged.
+//               If flags contains T_polygon and T_convex, both
+//               concave and convex polygons will be subdivided into
+//               triangles; with only T_polygon, only concave polygons
+//               will be subdivided, and convex polygons will be
+//               largely unchanged.
 ////////////////////////////////////////////////////////////////////
 int EggGroupNode::
 triangulate_polygons(int flags) {
@@ -721,15 +722,19 @@ triangulate_polygons(int flags) {
     EggNode *child = (*ci);
 
     if (child->is_of_type(EggPolygon::get_class_type())) {
-      EggPolygon *poly = DCAST(EggPolygon, child);
-      poly->triangulate_in_place((flags & T_convex) != 0);
+      if ((flags & T_polygon) != 0) {
+        EggPolygon *poly = DCAST(EggPolygon, child);
+        poly->triangulate_in_place((flags & T_convex) != 0);
+      }
 
     } else if (child->is_of_type(EggCompositePrimitive::get_class_type())) {
-      EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, child);
-      comp->triangulate_in_place();
+      if ((flags & T_composite) != 0) {
+        EggCompositePrimitive *comp = DCAST(EggCompositePrimitive, child);
+        comp->triangulate_in_place();
+      }
 
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {
-      if (flags & T_recurse) {
+      if ((flags & T_recurse) != 0) {
         num_produced += DCAST(EggGroupNode, child)->triangulate_polygons(flags);
       }
     }
@@ -750,7 +755,7 @@ mesh_triangles(int flags) {
   EggMesher mesher;
   mesher.mesh(this);
 
-  if (flags & T_recurse) {
+  if ((flags & T_recurse) != 0) {
     EggGroupNode::iterator ci;
     for (ci = begin(); ci != end(); ++ci) {
       if ((*ci)->is_of_type(EggGroupNode::get_class_type())) {
@@ -845,6 +850,136 @@ remove_invalid_primitives() {
   return num_removed;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggGroupNode::rebuild_vertex_pool
+//       Access: Published
+//  Description: Copies vertices used by the primitives at this group
+//               node (and below, if recurse is true) into the
+//               indicated vertex pool, and updates the primitives to
+//               reference this pool.  It is up to the caller to
+//               parent the new vertex pool somewhere appropriate in
+//               the egg hierarchy.
+////////////////////////////////////////////////////////////////////
+void EggGroupNode::
+rebuild_vertex_pool(EggVertexPool *vertex_pool, 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())) {
+      typedef pvector< PT(EggVertex) > Vertices;
+      Vertices vertices;
+
+      EggPrimitive *prim = DCAST(EggPrimitive, child);
+      EggPrimitive::const_iterator pi;
+      for (pi = prim->begin(); pi != prim->end(); ++pi) {
+        vertices.push_back(*pi);
+      }
+
+      prim->clear();
+
+      Vertices::const_iterator vi;
+      for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
+        EggVertex *vertex = (*vi);
+        prim->add_vertex(vertex_pool->create_unique_vertex(*vertex));
+      }
+
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      if (recurse) {
+        DCAST(EggGroupNode, child)->rebuild_vertex_pool(vertex_pool, recurse);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggGroupNode::unify_attributes
+//       Access: Published
+//  Description: Applies per-vertex normal and color to all vertices,
+//               if they are in fact per-vertex (and different for
+//               each vertex), or moves them to the primitive if they
+//               are all the same.
+//
+//               After this call, either the primitive will have
+//               normals or its vertices will, but not both.  Ditto
+//               for colors.
+//
+//               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) {
+  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();
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      if (recurse) {
+        DCAST(EggGroupNode, child)->unify_attributes(recurse);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggGroupNode::apply_last_attribute
+//       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.
+//
+//               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_last_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_last_attribute();
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      if (recurse) {
+        DCAST(EggGroupNode, child)->apply_last_attribute(recurse);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggGroupNode::post_apply_last_attribute
+//       Access: Published
+//  Description: Intended as a followup to apply_last_attribute(),
+//               this also sets an attribute on the first vertices of
+//               the primitive, if they don't already have an
+//               attribute set, just so they end up with *something*.
+////////////////////////////////////////////////////////////////////
+void EggGroupNode::
+post_apply_last_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();
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      if (recurse) {
+        DCAST(EggGroupNode, child)->post_apply_last_attribute(recurse);
+      }
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggGroupNode::has_primitives
 //       Access: Published, Virtual

+ 9 - 3
panda/src/egg/eggGroupNode.h

@@ -34,6 +34,7 @@ class EggTextureCollection;
 class EggMaterialCollection;
 class EggPolygon;
 class EggVertex;
+class EggVertexPool;
 class DSearchPath;
 
 ////////////////////////////////////////////////////////////////////
@@ -130,9 +131,10 @@ PUBLISHED:
   void strip_normals();
 
   enum TriangulateFlags {
-    T_convex    = 0x001,
-    T_composite = 0x002,
-    T_recurse   = 0x004
+    T_polygon   = 0x001,
+    T_convex    = 0x002,
+    T_composite = 0x004,
+    T_recurse   = 0x008
   };
 
   int triangulate_polygons(int flags);
@@ -140,6 +142,10 @@ PUBLISHED:
 
   int remove_unused_vertices();
   int remove_invalid_primitives();
+  void rebuild_vertex_pool(EggVertexPool *vertex_pool, bool recurse);
+  void unify_attributes(bool recurse);
+  void apply_last_attribute(bool recurse);
+  void post_apply_last_attribute(bool recurse);
   virtual bool has_primitives() const;
   virtual bool joint_has_primitives() const;
   virtual bool has_normals() const;

+ 14 - 0
panda/src/egg/eggMesher.cxx

@@ -374,6 +374,13 @@ get_prim(EggMesherStrip &strip) {
     // Now color1 is the color we want to assign to the whole
     // primitive.
     egg_prim->set_color(color1);
+    if (egg_prim->is_of_type(EggCompositePrimitive::get_class_type())) {
+      EggCompositePrimitive *egg_comp = DCAST(EggCompositePrimitive, egg_prim);
+      int num_components = egg_comp->get_num_components();
+      for (int i = 0; i < num_components; i++) {
+        egg_comp->get_component(i)->clear_color();
+      }
+    }
     int num_verts = egg_prim->size();
     for (int i = 0; i < num_verts; i++) {
       egg_prim->get_vertex(i)->clear_color();
@@ -446,6 +453,13 @@ get_prim(EggMesherStrip &strip) {
     // Now color1 is the color we want to assign to the whole
     // primitive.
     egg_prim->set_color(color1);
+    if (egg_prim->is_of_type(EggCompositePrimitive::get_class_type())) {
+      EggCompositePrimitive *egg_comp = DCAST(EggCompositePrimitive, egg_prim);
+      int num_components = egg_comp->get_num_components();
+      for (int i = 0; i < num_components; i++) {
+        egg_comp->get_component(i)->clear_color();
+      }
+    }
     int num_verts = egg_prim->size();
     for (int i = 0; i < num_verts; i++) {
       egg_prim->get_vertex(i)->clear_color();

+ 0 - 3
panda/src/egg/eggMesherStrip.cxx

@@ -89,9 +89,6 @@ make_prim(const EggVertexPool *vertex_pool) {
 
   switch (_type) {
   case PT_quad:
-    dest_type = egg_show_quads ? PT_poly : PT_tristrip;
-    break;
-
   case PT_tristrip:
   case PT_quadstrip:
     dest_type = PT_tristrip;

+ 2 - 2
panda/src/egg/eggPrimitive.I

@@ -370,7 +370,7 @@ replace(iterator position, EggVertex *x) {
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::clear
 //       Access: Published
-//  Description:
+//  Description: Removes all of the vertices from the primitive.
 ////////////////////////////////////////////////////////////////////
 INLINE void EggPrimitive::
 clear() {
@@ -422,5 +422,5 @@ get_vertex(int index) const {
 ////////////////////////////////////////////////////////////////////
 INLINE EggVertexPool *EggPrimitive::
 get_pool() const {
-  return empty() ? (EggVertexPool *)0L : _vertices.front()->get_pool();
+  return empty() ? (EggVertexPool *)NULL : _vertices.front()->get_pool();
 }

+ 226 - 0
panda/src/egg/eggPrimitive.cxx

@@ -220,6 +220,9 @@ copy_attributes(const EggPrimitive &other) {
 //       Access: Published
 //  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.
 ////////////////////////////////////////////////////////////////////
 bool EggPrimitive::
 has_vertex_normal() const {
@@ -237,6 +240,9 @@ has_vertex_normal() const {
 //       Access: Published
 //  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.
 ////////////////////////////////////////////////////////////////////
 bool EggPrimitive::
 has_vertex_color() const {
@@ -249,6 +255,226 @@ 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.
+//
+//               After this call, either the primitive will have
+//               normals or its vertices will, but not both.  Ditto
+//               for colors.
+//
+//               This may create redundant vertices in the vertex
+//               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;
+      }
+    }
+  }
+
+  if (!overall.has_normal()) {
+    normal_different = true;
+  }
+  if (!overall.has_color()) {
+    color_different = true;
+  }
+
+  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();
+      }
+      if (!color_different) {
+        vertex->clear_color();
+      }
+    }
+  }
+
+  // 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);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggPrimitive::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
+//               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 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;
+      }
+    }
+
+    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::post_apply_last_attribute
+//       Access: Published, Virtual
+//  Description: Intended as a followup to apply_last_attribute(),
+//               this also sets an attribute on the first vertices of
+//               the primitive, if they don't already have an
+//               attribute set, just so they end up with *something*.
+////////////////////////////////////////////////////////////////////
+void EggPrimitive::
+post_apply_last_attribute() {
+  if (!empty()) {
+    for (int i = 0; i < (int)size(); i++) {
+      EggVertex *vertex = get_vertex(i);
+
+      if (has_normal() && !vertex->has_normal()) {
+        vertex->set_normal(get_normal());
+      }
+      if (has_color() && !vertex->has_color()) {
+        vertex->set_color(get_color());
+      }
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggPrimitive::reverse_vertex_ordering
 //       Access: Published, Virtual

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

@@ -101,7 +101,11 @@ PUBLISHED:
 
   bool has_vertex_normal() const;
   bool has_vertex_color() const;
+  bool is_flat_shaded() const;
 
+  virtual void unify_attributes();
+  virtual void apply_last_attribute();
+  virtual void post_apply_last_attribute();
   virtual void reverse_vertex_ordering();
   virtual bool cleanup();
 

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

@@ -28,20 +28,6 @@
 TypeHandle EggTriangleStrip::_type_handle;
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: EggTriangleStrip::cleanup
-//       Access: Published, Virtual
-//  Description: Cleans up modeling errors in whatever context this
-//               makes sense.  For instance, for a polygon, this calls
-//               remove_doubled_verts(true).  For a point, it calls
-//               remove_nonunique_verts().  Returns true if the
-//               primitive is valid, or false if it is degenerate.
-////////////////////////////////////////////////////////////////////
-bool EggTriangleStrip::
-cleanup() {
-  return size() >= 3;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: EggTriangleStrip::write
 //       Access: Published, Virtual

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

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

+ 6 - 5
panda/src/egg/eggVertexPool.cxx

@@ -31,7 +31,7 @@ TypeHandle EggVertexPool::_type_handle;
 ////////////////////////////////////////////////////////////////////
 EggVertexPool::
 EggVertexPool(const string &name) : EggNode(name) {
-  _highest_index = 0;
+  _highest_index = -1;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -171,7 +171,8 @@ get_forward_vertex(int index) {
 //     Function: EggVertexPool::get_highest_index
 //       Access: Public
 //  Description: Returns the highest index number used by any vertex
-//               in the pool (except forward references).
+//               in the pool (except forward references).  Returns -1
+//               if the pool is empty.
 ////////////////////////////////////////////////////////////////////
 int EggVertexPool::
 get_highest_index() const {
@@ -434,7 +435,7 @@ remove_vertex(EggVertex *vertex) {
   if (_highest_index == vertex->_index) {
     // Find the new highest vertex index.
     if (_index_vertices.empty()) {
-      _highest_index = 0;
+      _highest_index = -1;
     } else {
       IndexVertices::reverse_iterator ivi = _index_vertices.rbegin();
       while (ivi != _index_vertices.rend() &&
@@ -444,7 +445,7 @@ remove_vertex(EggVertex *vertex) {
       if (ivi != _index_vertices.rend()) {
         _highest_index = (*ivi).first;
       } else {
-        _highest_index = 0;
+        _highest_index = -1;
       }
     }
   }
@@ -507,7 +508,7 @@ remove_unused_vertices() {
   // All done.  Lose the old lists.
   _unique_vertices.swap(new_unique_vertices);
   _index_vertices.swap(new_index_vertices);
-  _highest_index = _index_vertices.size() - 1;
+  _highest_index = (int)_index_vertices.size() - 1;
 
   nassertr(_index_vertices.size() == _unique_vertices.size(), num_removed);
 

+ 16 - 7
panda/src/egg2pg/eggBinner.cxx

@@ -86,16 +86,25 @@ sorts_less(int bin_number, const EggNode *a, const EggNode *b) {
       DCAST_INTO_R(pa, a, false);
       DCAST_INTO_R(pb, b, false);
 
-      // Different vertex pools have to be binned separately.
-      if (pa->get_pool() != pb->get_pool()) {
-        return pa->get_pool() < pb->get_pool();
-      }
-      
-      // Otherwise, different render states are binned separately.
+      // Different render states are binned separately.
       const EggRenderState *rsa, *rsb;
       DCAST_INTO_R(rsa, a->get_user_data(EggRenderState::get_class_type()), false);
       DCAST_INTO_R(rsb, b->get_user_data(EggRenderState::get_class_type()), false);
-      return (*rsa) < (*rsb);
+      int compare = rsa->compare_to(*rsb);
+      if (compare != 0) {
+        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;
     }
 
   case BN_lod:

+ 40 - 16
panda/src/egg2pg/eggLoader.cxx

@@ -158,6 +158,7 @@ build_graph() {
   load_textures();
 
   // Then bin up the polysets and LOD nodes.
+  _data->remove_invalid_primitives();
   EggBinner binner(*this);
   binner.make_bins(_data);
 
@@ -1412,8 +1413,8 @@ make_polyset(EggBin *egg_bin, PandaNode *parent) {
   }
 
   // We know that all of the primitives in the bin have the same
-  // vertex pool and same render state, so we can get that information
-  // from the first primitive.
+  // render state, so we can get that information from the first
+  // primitive.
   EggGroup::const_iterator ci = egg_bin->begin();
   nassertr(ci != egg_bin->end(), NULL);
   CPT(EggPrimitive) first_prim = DCAST(EggPrimitive, (*ci));
@@ -1438,30 +1439,41 @@ make_polyset(EggBin *egg_bin, PandaNode *parent) {
     return NULL;
   }
 
-  // Convert the primitives' vertex pool to a GeomVertexData.
-  if (first_prim->get_pool() == (EggVertexPool *)NULL) {
-    // Whoops, must be a degenerate primitive.
-    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();
   }
-  PT(qpGeomVertexData) vertex_data = 
-    make_vertex_data(first_prim->get_pool(), first_prim->get_vertex_to_node());
-  nassertr(vertex_data != (qpGeomVertexData *)NULL, NULL);
-
-  // And now create a Geom to hold the primitives.
-  PT(qpGeom) geom = new qpGeom;
-  geom->set_vertex_data(vertex_data);
 
   if (egg_mesh) {
     // If we're using the mesher, mesh now.
     egg_bin->mesh_triangles(0);
-    egg_bin->write(cerr, 0);
 
   } else {
     // If we're not using the mesher, at least triangulate any
     // higher-order polygons we might have.
-    egg_bin->triangulate_polygons(EggGroupNode::T_convex);
+    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();
   }
 
+  //  vertex_pool->write(cerr, 0);
+  //  egg_bin->write(cerr, 0);
+
   // Now create a handful of GeomPrimitives corresponding to the
   // various types of primitives we have.
   Primitives primitives;
@@ -1472,6 +1484,16 @@ make_polyset(EggBin *egg_bin, PandaNode *parent) {
   }
 
   if (!primitives.empty()) {
+    // Now convert the vertex pool to a GeomVertexData.
+    nassertr(vertex_pool != (EggVertexPool *)NULL, NULL);
+    PT(qpGeomVertexData) vertex_data = 
+      make_vertex_data(vertex_pool, first_prim->get_vertex_to_node());
+    nassertr(vertex_data != (qpGeomVertexData *)NULL, NULL);
+
+    // And create a Geom to hold the primitives.
+    PT(qpGeom) geom = new qpGeom;
+    geom->set_vertex_data(vertex_data);
+
     // Add each new primitive to the Geom.
     Primitives::const_iterator pi;
     for (pi = primitives.begin(); pi != primitives.end(); ++pi) {
@@ -1479,7 +1501,9 @@ make_polyset(EggBin *egg_bin, PandaNode *parent) {
       geom->add_primitive(primitive);
     }
 
-    geom->write(cerr);
+    //    vertex_data->write(cerr);
+    //    geom->write(cerr);
+    //    render_state->_state->write(cerr, 0);
     
     // Now, is our parent node a GeomNode, or just an ordinary
     // PandaNode?  If it's a GeomNode, we can add the new Geom directly

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

@@ -198,7 +198,7 @@ private:
   class VertexPoolTransform {
   public:
     INLINE bool operator < (const VertexPoolTransform &other) const;
-    EggVertexPool *_vertex_pool;
+    PT(EggVertexPool) _vertex_pool;
     LMatrix4d _transform;
   };
   typedef pmap<VertexPoolTransform, PT(qpGeomVertexData) > VertexPoolData;

+ 12 - 6
panda/src/egg2pg/eggRenderState.I

@@ -26,6 +26,7 @@ EggRenderState::
 EggRenderState(EggLoader &loader) :
   _state(RenderState::make_empty()),
   _hidden(false),
+  _indexed(false),
   _loader(loader)
 {
 }
@@ -42,17 +43,22 @@ add_attrib(const RenderAttrib *attrib) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: EggRenderState::operator <
+//     Function: EggRenderState::int compare_to
 //       Access: Public
 //  Description: Provides a unique ordering for different
 //               EggRenderState objects, so that primitives of similar
 //               state can be grouped together by the EggBinner.
 ////////////////////////////////////////////////////////////////////
-INLINE bool EggRenderState::
-operator < (const EggRenderState &other) const {
+INLINE int EggRenderState::
+compare_to(const EggRenderState &other) const {
   if (_state != other._state) {
-    return _state < other._state;
+    return _state < other._state ? -1 : 1;
   }
-
-  return (int)_hidden < (int)other._hidden;
+  if (_hidden != other._hidden) {
+    return (int)_hidden - (int)other._hidden;
+  }
+  if (_indexed != other._indexed) {
+    return (int)_indexed - (int)other._indexed;
+  }
+  return 0;
 }

+ 25 - 0
panda/src/egg2pg/eggRenderState.cxx

@@ -27,6 +27,7 @@
 #include "eggSurface.h"
 #include "cullBinAttrib.h"
 #include "cullFaceAttrib.h"
+#include "shadeModelAttrib.h"
 #include "transparencyAttrib.h"
 #include "depthWriteAttrib.h"
 #include "depthTestAttrib.h"
@@ -339,6 +340,30 @@ 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;
+  }
+
+  if (use_qpgeom) {
+    if (flat_shaded) {
+      add_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_flat));
+    } else {
+      add_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_smooth));
+    }
+  }
+
   if (has_bin) {
     add_attrib(CullBinAttrib::make(bin, draw_order));
 

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

@@ -49,7 +49,7 @@ public:
 
   void fill_state(EggPrimitive *egg_prim);
 
-  INLINE bool operator < (const EggRenderState &other) const;
+  INLINE int compare_to(const EggRenderState &other) const;
 
 private:
   CPT(RenderAttrib) get_material_attrib(const EggMaterial *egg_mat,
@@ -63,6 +63,7 @@ private:
 public:
   CPT(RenderState) _state;
   bool _hidden;
+  bool _indexed;
 
   typedef EggLoader::BakeInUVs BakeInUVs;
   typedef EggLoader::TextureDef TextureDef;

+ 2 - 0
panda/src/framework/windowFramework.cxx

@@ -48,6 +48,7 @@
 #include "depthWriteAttrib.h"
 #include "cullFaceAttrib.h"
 #include "rescaleNormalAttrib.h"
+#include "shadeModelAttrib.h"
 #include "pgTop.h"
 #include "geomNode.h"
 #include "geomTristrip.h"
@@ -238,6 +239,7 @@ get_render() {
     _render = NodePath("render");
 
     _render.node()->set_attrib(RescaleNormalAttrib::make_default());
+    _render.node()->set_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_smooth));
 
     // This is maybe here temporarily, and maybe not.
     _render.set_two_sided(0);

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

@@ -500,3 +500,29 @@ issue_scene_graph_color() {
     _scene_graph_color_stale = false;
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::issue_flat_shading
+//       Access: Public
+//  Description: Checks whether the indicated geom requires flat
+//               shading or smooth shading, and sets the appropriate
+//               mode.
+////////////////////////////////////////////////////////////////////
+INLINE void CLP(GraphicsStateGuardian)::
+issue_flat_shading(Geom *geom) {
+  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
+      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
+    // With per-vertex colors or normals, we need smooth shading.
+    if (_flat_shade_model) {
+      modify_state(get_smooth_state());
+    }
+  } else if ((geom->get_binding(G_COLOR) == G_PER_COMPONENT && wants_colors()) ||
+             (geom->get_binding(G_NORMAL) == G_PER_COMPONENT && wants_normals())) {
+    // With per-component colors or normals, we need flat shading.
+    if (!_flat_shade_model) {
+      modify_state(get_flat_state());
+    }
+  } else {
+    // Otherwise, it's a uniform-colored primitive; we can take either one.
+  }
+}

+ 78 - 87
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -47,6 +47,7 @@
 #include "rescaleNormalAttrib.h"
 #include "fogAttrib.h"
 #include "depthOffsetAttrib.h"
+#include "shadeModelAttrib.h"
 #include "fog.h"
 #include "clockObject.h"
 #include "string_utils.h"
@@ -600,6 +601,7 @@ reset() {
   _fog_enabled = false;
   _alpha_test_enabled = false;
   _polygon_offset_enabled = false;
+  _flat_shade_model = false;
   _decal_level = 0;
 
   // Dither is on by default in GL; let's turn it off
@@ -1023,14 +1025,7 @@ draw_line(GeomLine *geom, GeomContext *gc) {
                     issue_texcoord_multi_gl,
                     ti);
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
-      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
+  issue_flat_shading(geom);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1107,14 +1102,7 @@ draw_linestrip(GeomLinestrip *geom, GeomContext *gc) {
                     issue_texcoord_multi_gl,
                     ti);
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
-      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
+  issue_flat_shading(geom);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1489,14 +1477,7 @@ draw_polygon(GeomPolygon *geom, GeomContext *gc) {
                     issue_texcoord_multi_gl,
                     ti);
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
-      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
+  issue_flat_shading(geom);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1577,14 +1558,7 @@ draw_tri(GeomTri *geom, GeomContext *gc) {
                     issue_texcoord_multi_gl,
                     ti);
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
-      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
+  issue_flat_shading(geom);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1663,14 +1637,7 @@ draw_quad(GeomQuad *geom, GeomContext *gc) {
                     issue_texcoord_multi_gl,
                     ti);
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
-      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
+  issue_flat_shading(geom);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1753,14 +1720,7 @@ draw_tristrip(GeomTristrip *geom, GeomContext *gc) {
                     issue_texcoord_multi_gl,
                     ti);
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
-      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
+  issue_flat_shading(geom);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1861,14 +1821,7 @@ draw_trifan(GeomTrifan *geom, GeomContext *gc) {
                     issue_texcoord_multi_gl,
                     ti);
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if ((geom->get_binding(G_COLOR) == G_PER_VERTEX && wants_colors()) ||
-      (geom->get_binding(G_NORMAL) == G_PER_VERTEX && wants_normals())) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
+  issue_flat_shading(geom);
 
   // Draw overall
   issuer.issue_color(G_OVERALL, ci);
@@ -1967,9 +1920,9 @@ draw_sphere(GeomSphere *geom, GeomContext *gc) {
                     ti);
 
   if (wants_normals()) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
+    if (_flat_shade_model) {
+      modify_state(get_smooth_state());
+    }
   }
 
   // Draw overall
@@ -2048,7 +2001,6 @@ begin_draw_primitives(const qpGeomVertexData *vertex_data) {
     return false;
   }
 
-  bool has_normals = false;
   if (wants_normals() && 
       _vertex_data->get_array_info(InternalName::get_normal(),
                                    array_data, num_components, numeric_type, 
@@ -2056,12 +2008,10 @@ begin_draw_primitives(const qpGeomVertexData *vertex_data) {
     GLP(NormalPointer)(get_numeric_type(numeric_type), stride, 
                        array_data + start);
     GLP(EnableClientState)(GL_NORMAL_ARRAY);
-    has_normals = true;
   } else {
     GLP(DisableClientState)(GL_NORMAL_ARRAY);
   }
 
-  bool has_colors = false;
   if (wants_colors()) {
     if (_vertex_data->get_array_info(InternalName::get_color(),
                                      array_data, num_components, numeric_type, 
@@ -2075,7 +2025,6 @@ begin_draw_primitives(const qpGeomVertexData *vertex_data) {
                           stride, array_data + start);
       }
       GLP(EnableClientState)(GL_COLOR_ARRAY);
-      has_colors = true;
 
     } else {
       // We wanted colors, but the geom didn't have any; just issue
@@ -2098,14 +2047,6 @@ begin_draw_primitives(const qpGeomVertexData *vertex_data) {
     GLP(DisableClientState)(GL_TEXTURE_COORD_ARRAY);
   }
 
-  // If we have per-vertex colors or normals, we need smooth shading.
-  // Otherwise we want flat shading for performance reasons.
-  if (has_colors || has_normals) {
-    GLP(ShadeModel)(GL_SMOOTH);
-  } else {
-    GLP(ShadeModel)(GL_FLAT);
-  }
-
   issue_scene_graph_color();
 
   return true;
@@ -2641,6 +2582,42 @@ issue_tex_matrix(const TexMatrixAttrib *attrib) {
   _needs_tex_mat = true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::issue_tex_gen
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void CLP(GraphicsStateGuardian)::
+issue_tex_gen(const TexGenAttrib *attrib) {
+  // We don't apply the texture coordinate generation commands right
+  // away, since we might yet get a TextureAttrib that changes the set
+  // of TextureStages we have active.  Instead, we simply set a flag
+  // that indicates we need to re-issue the TexGenAttrib after all of
+  // the other attribs are done being issued.
+  _current_tex_gen = attrib;
+  _needs_tex_gen = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::issue_shade_model
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void CLP(GraphicsStateGuardian)::
+issue_shade_model(const ShadeModelAttrib *attrib) {
+  switch (attrib->get_mode()) {
+  case ShadeModelAttrib::M_smooth:
+    GLP(ShadeModel)(GL_SMOOTH);
+    _flat_shade_model = false;
+    break;
+
+  case ShadeModelAttrib::M_flat:
+    GLP(ShadeModel)(GL_FLAT);
+    _flat_shade_model = true;
+    break;
+  }
+}
+
 
 
 
@@ -2684,22 +2661,6 @@ issue_cg_shader_bind(const CgShaderAttrib *attrib) {
 }
 #endif
 
-////////////////////////////////////////////////////////////////////
-//     Function: CLP(GraphicsStateGuardian)::issue_tex_gen
-//       Access: Public, Virtual
-//  Description:
-////////////////////////////////////////////////////////////////////
-void CLP(GraphicsStateGuardian)::
-issue_tex_gen(const TexGenAttrib *attrib) {
-  // We don't apply the texture coordinate generation commands right
-  // away, since we might yet get a TextureAttrib that changes the set
-  // of TextureStages we have active.  Instead, we simply set a flag
-  // that indicates we need to re-issue the TexGenAttrib after all of
-  // the other attribs are done being issued.
-  _current_tex_gen = attrib;
-  _needs_tex_gen = true;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::issue_material
 //       Access: Public, Virtual
@@ -5082,6 +5043,36 @@ get_untextured_state() {
   return state;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::get_smooth_state
+//       Access: Protected, Static
+//  Description: Returns a RenderState object that represents
+//               smooth, per-vertex shading.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) CLP(GraphicsStateGuardian)::
+get_smooth_state() {
+  static CPT(RenderState) state;
+  if (state == (RenderState *)NULL) {
+    state = RenderState::make(ShadeModelAttrib::make(ShadeModelAttrib::M_smooth));
+  }
+  return state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CLP(GraphicsStateGuardian)::get_flat_state
+//       Access: Protected, Static
+//  Description: Returns a RenderState object that represents
+//               flat, per-primitive shading.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) CLP(GraphicsStateGuardian)::
+get_flat_state() {
+  static CPT(RenderState) state;
+  if (state == (RenderState *)NULL) {
+    state = RenderState::make(ShadeModelAttrib::make(ShadeModelAttrib::M_flat));
+  }
+  return state;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CLP(GraphicsStateGuardian)::do_auto_rescale_normal
 //       Access: Protected

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

@@ -127,9 +127,9 @@ 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);
 #ifdef HAVE_CGGL
   virtual void issue_cg_shader_bind(const CgShaderAttrib *attrib);
-  //  virtual void issue_stencil(const StencilAttrib *attrib);
 #endif
 
   virtual void bind_light(PointLight *light_obj, const NodePath &light, 
@@ -207,6 +207,7 @@ protected:
   INLINE GLenum get_clip_plane_id(int index) const;
 
   INLINE void issue_scene_graph_color();
+  INLINE void issue_flat_shading(Geom *geom);
 
   void set_draw_buffer(const RenderBuffer &rb);
   void set_read_buffer(const RenderBuffer &rb);
@@ -236,6 +237,8 @@ protected:
   static GLenum get_blend_func(ColorBlendAttrib::Operand operand);
 
   static CPT(RenderState) get_untextured_state();
+  static CPT(RenderState) get_smooth_state();
+  static CPT(RenderState) get_flat_state();
 
   void do_auto_rescale_normal();
   void do_issue_texture();
@@ -269,6 +272,7 @@ protected:
   bool _fog_enabled;
   bool _alpha_test_enabled;
   bool _polygon_offset_enabled;
+  bool _flat_shade_model;
   int _decal_level;
 
   bool _dithering_enabled;

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

@@ -64,6 +64,7 @@ output(ostream &out) const {
   switch (get_mode()) {
   case M_flat:
     out << "flat";
+    break;
 
   case M_smooth:
     out << "smooth";