Browse Source

pgraph flatten

David Rose 24 years ago
parent
commit
f6d8d7bd2a

+ 67 - 0
panda/src/pgraph/qpgeomTransformer.I

@@ -0,0 +1,67 @@
+// Filename: qpgeomTransformer.I
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::SourceVertices::Ordering Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomTransformer::SourceVertices::
+operator < (const qpGeomTransformer::SourceVertices &other) const {
+  if (_coords != other._coords) {
+    return _coords < other._coords;
+  }
+  return (_mat.compare_to(other._mat) < 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::SourceNormals::Ordering Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomTransformer::SourceNormals::
+operator < (const qpGeomTransformer::SourceNormals &other) const {
+  if (_norms != other._norms) {
+    return _norms < other._norms;
+  }
+  return (_mat.compare_to(other._mat) < 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::SourceTexCoords::Ordering Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomTransformer::SourceTexCoords::
+operator < (const qpGeomTransformer::SourceTexCoords &other) const {
+  if (_texcoords != other._texcoords) {
+    return _texcoords < other._texcoords;
+  }
+  return (_mat.compare_to(other._mat) < 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::SourceColors::Ordering Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomTransformer::SourceColors::
+operator < (const qpGeomTransformer::SourceColors &other) const {
+  return (_scale.compare_to(other._scale) < 0);
+}

+ 377 - 0
panda/src/pgraph/qpgeomTransformer.cxx

@@ -0,0 +1,377 @@
+// Filename: qpgeomTransformer.cxx
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qpgeomTransformer.h"
+#include "qpgeomNode.h"
+#include "renderState.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpGeomTransformer::
+qpGeomTransformer() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::Destructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpGeomTransformer::
+~qpGeomTransformer() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::transform_vertices
+//       Access: Public
+//  Description: Transforms the vertices and the normals in the
+//               indicated Geom by the indicated matrix.  Returns true
+//               if the Geom was changed, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+transform_vertices(Geom *geom, const LMatrix4f &mat) {
+  bool transformed = false;
+
+  nassertr(geom != (Geom *)NULL, false);
+
+  PTA_Vertexf coords;
+  PTA_ushort index;
+
+  geom->get_coords(coords, index);
+
+  if (!coords.empty()) {
+    // Look up the Geom's coords in our table--have we already
+    // transformed this array?
+    SourceVertices sv;
+    sv._mat = mat;
+    sv._coords = coords;
+
+    PTA_Vertexf &new_coords = _vertices[sv];
+
+    if (new_coords.is_null()) {
+      // We have not transformed the array yet.  Do so now.
+      new_coords.reserve(coords.size());
+      PTA_Vertexf::const_iterator vi;
+      for (vi = coords.begin(); vi != coords.end(); ++vi) {
+        new_coords.push_back((*vi) * mat);
+      }
+      nassertr(new_coords.size() == coords.size(), false);
+    }
+
+    geom->set_coords(new_coords, index);
+    transformed = true;
+  }
+
+  // Now do the same thing for normals.
+  PTA_Normalf norms;
+  GeomBindType bind;
+
+  geom->get_normals(norms, bind, index);
+
+  if (bind != G_OFF) {
+    SourceNormals sn;
+    sn._mat = mat;
+    sn._norms = norms;
+
+    PTA_Normalf &new_norms = _normals[sn];
+
+    if (new_norms.is_null()) {
+      // We have not transformed the array yet.  Do so now.
+      new_norms.reserve(norms.size());
+      PTA_Normalf::const_iterator ni;
+      for (ni = norms.begin(); ni != norms.end(); ++ni) {
+        Normalf new_norm = (*ni) * mat;
+        new_norm.normalize();
+        new_norms.push_back(new_norm);
+      }
+      nassertr(new_norms.size() == norms.size(), false);
+    }
+
+    geom->set_normals(new_norms, bind, index);
+    transformed = true;
+  }
+
+  return transformed;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::transform_vertices
+//       Access: Public
+//  Description: Transforms the vertices and the normals in all of the
+//               Geoms within the indicated qpGeomNode by the indicated
+//               matrix.  Does not destructively change Geoms;
+//               instead, a copy will be made of each Geom to be
+//               changed, in case multiple qpGeomNodes reference the
+//               same Geom. Returns true if the qpGeomNode was changed,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+transform_vertices(qpGeomNode *node, const LMatrix4f &mat) {
+  bool any_changed = false;
+
+  qpGeomNode::CDWriter cdata(node->_cycler);
+  qpGeomNode::Geoms::iterator gi;
+  for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
+    qpGeomNode::GeomEntry &entry = (*gi);
+    PT(Geom) new_geom = entry._geom->make_copy();
+    if (transform_vertices(new_geom, mat)) {
+      entry._geom = new_geom;
+      any_changed = true;
+    }
+  }
+
+  return any_changed;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::transform_texcoords
+//       Access: Public
+//  Description: Transforms the texture coordinates in the indicated
+//               Geom by the indicated matrix.  Returns true if the
+//               Geom was changed, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+transform_texcoords(Geom *geom, const LMatrix4f &mat) {
+  bool transformed = false;
+
+  nassertr(geom != (Geom *)NULL, false);
+
+  PTA_TexCoordf texcoords;
+  GeomBindType bind;
+  PTA_ushort index;
+
+  geom->get_texcoords(texcoords, bind, index);
+
+  if (bind != G_OFF) {
+    // Look up the Geom's texcoords in our table--have we already
+    // transformed this array?
+    SourceTexCoords stc;
+    stc._mat = mat;
+    stc._texcoords = texcoords;
+
+    PTA_TexCoordf &new_texcoords = _texcoords[stc];
+
+    if (new_texcoords.is_null()) {
+      // We have not transformed the array yet.  Do so now.
+      new_texcoords.reserve(texcoords.size());
+      PTA_TexCoordf::const_iterator tci;
+      for (tci = texcoords.begin(); tci != texcoords.end(); ++tci) {
+        const TexCoordf &tc = (*tci);
+        LVecBase4f v4(tc[0], tc[1], 0.0f, 1.0f);
+        v4 = v4 * mat;
+        new_texcoords.push_back(TexCoordf(v4[0] / v4[3], v4[1] / v4[3]));
+      }
+      nassertr(new_texcoords.size() == texcoords.size(), false);
+    }
+
+    geom->set_texcoords(new_texcoords, bind, index);
+    transformed = true;
+  }
+
+  return transformed;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::transform_texcoords
+//       Access: Public
+//  Description: Transforms the texture coordinates in all of the
+//               Geoms within the indicated qpGeomNode by the indicated
+//               matrix.  Does not destructively change Geoms;
+//               instead, a copy will be made of each Geom to be
+//               changed, in case multiple qpGeomNodes reference the
+//               same Geom. Returns true if the qpGeomNode was changed,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+transform_texcoords(qpGeomNode *node, const LMatrix4f &mat) {
+  bool any_changed = false;
+
+  qpGeomNode::CDWriter cdata(node->_cycler);
+  qpGeomNode::Geoms::iterator gi;
+  for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
+    qpGeomNode::GeomEntry &entry = (*gi);
+    PT(Geom) new_geom = entry._geom->make_copy();
+    if (transform_texcoords(new_geom, mat)) {
+      entry._geom = new_geom;
+      any_changed = true;
+    }
+  }
+
+  return any_changed;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::set_color
+//       Access: Public
+//  Description: Overrides the color indicated within the Geom with
+//               the given replacement color.  Returns true if the
+//               Geom was changed, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+set_color(Geom *geom, const Colorf &color) {
+  // In this case, we always replace whatever color array was there
+  // with a new color array containing just this color.
+
+  // We do want to share this one-element array between Geoms, though.
+  PTA_Colorf &new_colors = _fcolors[color];
+
+  if (new_colors.is_null()) {
+    // We haven't seen this color before; define a new color array.
+    new_colors.push_back(color);
+  }
+
+  geom->set_colors(new_colors, G_OVERALL);
+  return true;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::transform_texcoords
+//       Access: Public
+//  Description: Overrides the color indicated within the qpGeomNode
+//               with the given replacement color.  Returns true if
+//               any Geom in the qpGeomNode was changed, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+set_color(qpGeomNode *node, const Colorf &color) {
+  bool any_changed = false;
+
+  qpGeomNode::CDWriter cdata(node->_cycler);
+  qpGeomNode::Geoms::iterator gi;
+  for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
+    qpGeomNode::GeomEntry &entry = (*gi);
+    PT(Geom) new_geom = entry._geom->make_copy();
+    if (set_color(new_geom, color)) {
+      entry._geom = new_geom;
+      any_changed = true;
+    }
+  }
+
+  return any_changed;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::transform_colors
+//       Access: Public
+//  Description: Transforms the colors in the indicated Geom by the
+//               indicated scale.  Returns true if the Geom was
+//               changed, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+transform_colors(Geom *geom, const LVecBase4f &scale) {
+  bool transformed = false;
+
+  nassertr(geom != (Geom *)NULL, false);
+
+  PTA_Colorf colors;
+  GeomBindType bind;
+  PTA_ushort index;
+
+  geom->get_colors(colors, bind, index);
+
+  if (bind != G_OFF) {
+    // Look up the Geom's colors in our table--have we already
+    // transformed this array?
+    SourceColors sc;
+    sc._scale = scale;
+
+    PTA_Colorf &new_colors = _tcolors[sc];
+
+    if (new_colors.is_null()) {
+      // We have not transformed the array yet.  Do so now.
+      new_colors.reserve(colors.size());
+      PTA_Colorf::const_iterator ci;
+      for (ci = colors.begin(); ci != colors.end(); ++ci) {
+        const Colorf &c = (*ci);
+        Colorf transformed(c[0] * scale[0], 
+                           c[1] * scale[1], 
+                           c[2] * scale[2],
+                           c[3] * scale[3]);
+        new_colors.push_back(transformed);
+      }
+      nassertr(new_colors.size() == colors.size(), false);
+    }
+
+    geom->set_colors(new_colors, bind, index);
+    transformed = true;
+  }
+
+  return transformed;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::transform_colors
+//       Access: Public
+//  Description: Transforms the colors in all of the Geoms within the
+//               indicated qpGeomNode by the indicated scale.  Does
+//               not destructively change Geoms; instead, a copy will
+//               be made of each Geom to be changed, in case multiple
+//               qpGeomNodes reference the same Geom. Returns true if
+//               the qpGeomNode was changed, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+transform_colors(qpGeomNode *node, const LVecBase4f &scale) {
+  bool any_changed = false;
+
+  qpGeomNode::CDWriter cdata(node->_cycler);
+  qpGeomNode::Geoms::iterator gi;
+  for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
+    qpGeomNode::GeomEntry &entry = (*gi);
+    PT(Geom) new_geom = entry._geom->make_copy();
+    if (transform_colors(new_geom, scale)) {
+      entry._geom = new_geom;
+      any_changed = true;
+    }
+  }
+
+  return any_changed;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomTransformer::apply_state
+//       Access: Public
+//  Description: Applies the indicated render state to all the of
+//               Geoms.  Returns true if the GeomNode was changed,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpGeomTransformer::
+apply_state(qpGeomNode *node, const RenderState *state) {
+  bool any_changed = false;
+
+  qpGeomNode::CDWriter cdata(node->_cycler);
+  qpGeomNode::Geoms::iterator gi;
+  for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) {
+    qpGeomNode::GeomEntry &entry = (*gi);
+    CPT(RenderState) new_state = state->compose(entry._state);
+    if (entry._state != new_state) {
+      entry._state = new_state;
+      any_changed = true;
+    }
+  }
+
+  return any_changed;
+}

+ 116 - 0
panda/src/pgraph/qpgeomTransformer.h

@@ -0,0 +1,116 @@
+// Filename: qpgeomTransformer.h
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpGEOMTRANSFORMER_H
+#define qpGEOMTRANSFORMER_H
+
+#include "pandabase.h"
+
+#include "geom.h"
+#include "luse.h"
+
+class qpGeomNode;
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpGeomTransformer
+// Description : An object specifically designed to transform the
+//               vertices of a Geom without disturbing indexing or
+//               affecting any other Geoms that may share the same
+//               vertex arrays, and without needlessly wasting memory
+//               when different Geoms sharing the same vertex arrays
+//               are transformed by the same amount.
+//
+//               If you create a single qpGeomTransformer and use it to
+//               transform a number of different Geoms by various
+//               transformations, then those Geoms which happen to
+//               share the same arrays and are transformed by the same
+//               amounts will still share the same arrays as each
+//               other (but different from the original arrays).
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpGeomTransformer {
+public:
+  qpGeomTransformer();
+  ~qpGeomTransformer();
+
+  bool transform_vertices(Geom *geom, const LMatrix4f &mat);
+  bool transform_vertices(qpGeomNode *node, const LMatrix4f &mat);
+
+  bool transform_texcoords(Geom *geom, const LMatrix4f &mat);
+  bool transform_texcoords(qpGeomNode *node, const LMatrix4f &mat);
+
+  bool set_color(Geom *geom, const Colorf &color);
+  bool set_color(qpGeomNode *node, const Colorf &color);
+
+  bool transform_colors(Geom *geom, const LVecBase4f &scale);
+  bool transform_colors(qpGeomNode *node, const LVecBase4f &scale);
+
+  bool apply_state(qpGeomNode *node, const RenderState *state);
+
+private:
+  class SourceVertices {
+  public:
+    INLINE bool operator < (const SourceVertices &other) const;
+
+    LMatrix4f _mat;
+    PTA_Vertexf _coords;
+  };
+  typedef pmap<SourceVertices, PTA_Vertexf> Vertices;
+  Vertices _vertices;
+
+  class SourceNormals {
+  public:
+    INLINE bool operator < (const SourceNormals &other) const;
+
+    LMatrix4f _mat;
+    PTA_Normalf _norms;
+  };
+  typedef pmap<SourceNormals, PTA_Normalf> Normals;
+  Normals _normals;
+
+  class SourceTexCoords {
+  public:
+    INLINE bool operator < (const SourceTexCoords &other) const;
+
+    LMatrix4f _mat;
+    PTA_TexCoordf _texcoords;
+  };
+  typedef pmap<SourceTexCoords, PTA_TexCoordf> TexCoords;
+  TexCoords _texcoords;
+
+  // We have two concepts of colors: the "fixed" colors, which are
+  // slapped in complete replacement of the original colors (e.g. via
+  // a ColorAttrib), and the "transformed" colors, which are modified
+  // from the original colors (e.g. via a ColorScaleAttrib).
+  typedef pmap<Colorf, PTA_Colorf> FColors;
+  FColors _fcolors;
+
+  class SourceColors {
+  public:
+    INLINE bool operator < (const SourceColors &other) const;
+
+    LVecBase4f _scale;
+    PTA_Colorf _colors;
+  };
+  typedef pmap<SourceColors, PTA_Colorf> TColors;
+  TColors _tcolors;
+};
+
+#include "qpgeomTransformer.I"
+
+#endif
+

+ 56 - 0
panda/src/pgraph/qpsceneGraphReducer.I

@@ -0,0 +1,56 @@
+// Filename: qpsceneGraphReducer.I
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::AccumulatedAttribs::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpSceneGraphReducer::AccumulatedAttribs::
+AccumulatedAttribs() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::AccumulatedAttribs::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpSceneGraphReducer::AccumulatedAttribs::
+AccumulatedAttribs(const qpSceneGraphReducer::AccumulatedAttribs &copy) :
+  _transform(copy._transform),
+  _color(copy._color),
+  _color_scale(copy._color_scale),
+  _tex_matrix(copy._tex_matrix),
+  _other(copy._other)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::AccumulatedAttribs::Copy Assignment
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpSceneGraphReducer::AccumulatedAttribs::
+operator = (const qpSceneGraphReducer::AccumulatedAttribs &copy) {
+  _transform = copy._transform;
+  _color = copy._color;
+  _color_scale = copy._color_scale;
+  _tex_matrix = copy._tex_matrix;
+  _other = copy._other;
+}

+ 787 - 0
panda/src/pgraph/qpsceneGraphReducer.cxx

@@ -0,0 +1,787 @@
+// Filename: qpqpSceneGraphReducer.cxx
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qpsceneGraphReducer.h"
+#include "config_pgraph.h"
+#include "colorAttrib.h"
+#include "texMatrixAttrib.h"
+#include "colorScaleAttrib.h"
+
+#include "qpgeomNode.h"
+#include "geom.h"
+#include "indent.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::AccumulatedAttribs::write
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpSceneGraphReducer::AccumulatedAttribs::
+write(ostream &out, int attrib_types, int indent_level) const {
+  if ((attrib_types & TT_transform) != 0) {
+    _transform->write(out, indent_level);
+  }
+  if ((attrib_types & TT_color) != 0) {
+    if (_color == (const RenderAttrib *)NULL) {
+      indent(out, indent_level) << "no color\n";
+    } else {
+      _color->write(out, indent_level);
+    }
+  }
+  if ((attrib_types & TT_color_scale) != 0) {
+    if (_color_scale == (const RenderAttrib *)NULL) {
+      indent(out, indent_level) << "no color scale\n";
+    } else {
+      _color_scale->write(out, indent_level);
+    }
+  }
+  if ((attrib_types & TT_tex_matrix) != 0) {
+    if (_tex_matrix == (const RenderAttrib *)NULL) {
+      indent(out, indent_level) << "no tex matrix\n";
+    } else {
+      _tex_matrix->write(out, indent_level);
+    }
+  }
+  if ((attrib_types & TT_other) != 0) {
+    _other->write(out, indent_level);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::AccumulatedAttribs::collect
+//       Access: Public
+//  Description: Collects the state and transform from the indicated
+//               node and adds it to the accumulator, removing it from
+//               the node.
+////////////////////////////////////////////////////////////////////
+void qpSceneGraphReducer::AccumulatedAttribs::
+collect(PandaNode *node, int attrib_types) {
+  if ((attrib_types & TT_transform) != 0) {
+    // Collect the node's transform.
+    nassertv(_transform != (TransformState *)NULL);
+    _transform = _transform->compose(node->get_transform());
+    node->set_transform(TransformState::make_identity());
+  }
+
+  if ((attrib_types & TT_color) != 0) {
+    const RenderAttrib *node_attrib = 
+      node->get_attrib(ColorAttrib::get_class_type());
+    if (node_attrib != (const RenderAttrib *)NULL) {
+      // The node has a color attribute; apply it.
+      if (_color == (const RenderAttrib *)NULL) {
+        _color = node_attrib;
+      } else {
+        _color = _color->compose(node_attrib);
+      }
+      node->clear_attrib(ColorAttrib::get_class_type());
+    }
+  }
+
+  if ((attrib_types & TT_color_scale) != 0) {
+    const RenderAttrib *node_attrib = 
+      node->get_attrib(ColorScaleAttrib::get_class_type());
+    if (node_attrib != (const RenderAttrib *)NULL) {
+      // The node has a color scale attribute; apply it.
+      if (_color_scale == (const RenderAttrib *)NULL) {
+        _color_scale = node_attrib;
+      } else {
+        _color_scale = _color_scale->compose(node_attrib);
+      }
+      node->clear_attrib(ColorScaleAttrib::get_class_type());
+    }
+  }
+
+  if ((attrib_types & TT_tex_matrix) != 0) {
+    const RenderAttrib *node_attrib = 
+      node->get_attrib(TexMatrixAttrib::get_class_type());
+    if (node_attrib != (const RenderAttrib *)NULL) {
+      // The node has a tex matrix attribute; apply it.
+      if (_tex_matrix == (const RenderAttrib *)NULL) {
+        _tex_matrix = node_attrib;
+      } else {
+        _tex_matrix = _tex_matrix->compose(node_attrib);
+      }
+      node->clear_attrib(TexMatrixAttrib::get_class_type());
+    }
+  }
+
+  if ((attrib_types & TT_transform) != 0) {
+    // Collect everything else.
+    nassertv(_other != (RenderState *)NULL);
+    _other = _other->compose(node->get_state());
+    node->set_state(RenderState::make_empty());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::AccumulatedAttribs::apply_to_node
+//       Access: Public
+//  Description: Stores the indicated attributes in the node's
+//               transform and state information; does not attempt to
+//               apply the properties to the vertices.  Clears the
+//               attributes from the accumulator for future
+//               traversals.
+////////////////////////////////////////////////////////////////////
+void qpSceneGraphReducer::AccumulatedAttribs::
+apply_to_node(PandaNode *node, int attrib_types) {
+  if ((attrib_types & TT_transform) != 0) {
+    node->set_transform(_transform->compose(node->get_transform()));
+    _transform = TransformState::make_identity();
+  }
+
+  if ((attrib_types & TT_color) != 0) {
+    if (_color != (RenderAttrib *)NULL) {
+      const RenderAttrib *node_attrib =
+        node->get_attrib(ColorAttrib::get_class_type());
+      if (node_attrib != (RenderAttrib *)NULL) {
+        node->set_attrib(_color->compose(node_attrib));
+      } else {
+        node->set_attrib(_color);
+      }
+      _color = (RenderAttrib *)NULL;
+    }
+  }
+
+  if ((attrib_types & TT_color_scale) != 0) {
+    if (_color_scale != (RenderAttrib *)NULL) {
+      const RenderAttrib *node_attrib =
+        node->get_attrib(ColorScaleAttrib::get_class_type());
+      if (node_attrib != (RenderAttrib *)NULL) {
+        node->set_attrib(_color_scale->compose(node_attrib));
+      } else {
+        node->set_attrib(_color_scale);
+      }
+      _color_scale = (RenderAttrib *)NULL;
+    }
+  }
+
+  if ((attrib_types & TT_tex_matrix) != 0) {
+    if (_tex_matrix != (RenderAttrib *)NULL) {
+      const RenderAttrib *node_attrib =
+        node->get_attrib(TexMatrixAttrib::get_class_type());
+      if (node_attrib != (RenderAttrib *)NULL) {
+        node->set_attrib(_tex_matrix->compose(node_attrib));
+      } else {
+        node->set_attrib(_tex_matrix);
+      }
+      _tex_matrix = (RenderAttrib *)NULL;
+    }
+  }
+
+  if ((attrib_types & TT_other) != 0) {
+    node->set_state(_other->compose(node->get_state()));
+    _other = RenderState::make_empty();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::AccumulatedAttribs::apply_to_vertices
+//       Access: Public
+//  Description: Applies the indicated attributes to the node so that
+//               they do not need to be stored as separate attributes
+//               any more.
+////////////////////////////////////////////////////////////////////
+void qpSceneGraphReducer::AccumulatedAttribs::
+apply_to_vertices(PandaNode *node, int attrib_types, 
+                  qpGeomTransformer &transformer) {
+  if (node->is_geom_node()) {
+    if (pgraph_cat.is_debug()) {
+      pgraph_cat.debug()
+        << "Transforming geometry.\n";
+    }
+
+    // We treat GeomNodes as a special case, since we can apply more
+    // than just a transform matrix and so we can share vertex arrays
+    // across different GeomNodes.
+    qpGeomNode *gnode = DCAST(qpGeomNode, node);
+    if ((attrib_types & TT_transform) != 0) {
+      if (!_transform->is_identity()) {
+        transformer.transform_vertices(gnode, _transform->get_mat());
+      }
+    }
+    if ((attrib_types & TT_color) != 0) {
+      if (_color != (const RenderAttrib *)NULL) {
+        const ColorAttrib *ca = DCAST(ColorAttrib, _color);
+        if (ca->get_color_type() == ColorAttrib::T_flat) {
+          transformer.set_color(gnode, ca->get_color());
+        }
+      }
+    }
+    if ((attrib_types & TT_color_scale) != 0) {
+      if (_color_scale != (const RenderAttrib *)NULL) {
+        const ColorScaleAttrib *csa = DCAST(ColorScaleAttrib, _color_scale);
+        if (csa->get_scale() != LVecBase4f(1.0f, 1.0f, 1.0f, 1.0f)) {
+          transformer.transform_colors(gnode, csa->get_scale());
+        }
+      }
+    }
+    if ((attrib_types & TT_tex_matrix) != 0) {
+      if (_tex_matrix != (const RenderAttrib *)NULL) {
+        const TexMatrixAttrib *tma = DCAST(TexMatrixAttrib, _tex_matrix);
+        if (tma->get_mat() != LMatrix4f::ident_mat()) {
+          transformer.transform_texcoords(gnode, tma->get_mat());
+        }
+      }
+    }
+    if ((attrib_types & TT_other) != 0) {
+      if (!_other->is_empty()) {
+        transformer.apply_state(gnode, _other);
+      }
+    }
+
+  } else {
+    // This handles any kind of node other than a GeomNode.
+    if ((attrib_types & TT_transform) != 0) {
+      node->xform(_transform->get_mat());
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpSceneGraphReducer::
+qpSceneGraphReducer() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpSceneGraphReducer::
+~qpSceneGraphReducer() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::apply_attribs
+//       Access: Public
+//  Description: Walks the scene graph, accumulating attribs of
+//               the indicated types, applying them to the vertices,
+//               and removing them from the scene graph.  This has a
+//               performance optimization benefit in itself, but is
+//               especially useful to pave the way for a call to
+//               flatten() and greatly improve the effectiveness of
+//               the flattening operation.
+//
+//               Multiply instanced geometry is duplicated before the
+//               attribs are applied.
+//
+//               Of course, this operation does make certain dynamic
+//               operations impossible.
+////////////////////////////////////////////////////////////////////
+void qpSceneGraphReducer::
+apply_attribs(PandaNode *node, int attrib_types) {
+  AccumulatedAttribs trans;
+
+  trans._transform = TransformState::make_identity();
+  trans._color = (ColorAttrib *)NULL;
+  trans._color_scale = (ColorScaleAttrib *)NULL;
+  trans._tex_matrix = (TexMatrixAttrib *)NULL;
+  trans._other = RenderState::make_empty();
+
+  r_apply_attribs(node, attrib_types, trans);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::flatten
+//       Access: Public
+//  Description: Simplifies the graph by removing unnecessary nodes
+//               and nodes.
+//
+//               In general, a node (and its parent node) is a
+//               candidate for removal if the node has no siblings and
+//               the node and node have no special properties.  The
+//               definition of what, precisely, is a 'special
+//               property' may be extended by subclassing from this
+//               type and redefining consider_node() appropriately.
+//
+//               If combine_siblings is true, sibling nodes may also
+//               be collapsed into a single node.  This will further
+//               reduce scene graph complexity, sometimes
+//               substantially, at the cost of reduced spatial
+//               separation.
+//
+//               Returns the number of nodes removed from the graph.
+////////////////////////////////////////////////////////////////////
+int qpSceneGraphReducer::
+flatten(PandaNode *root, bool combine_siblings) {
+  int num_total_nodes = 0;
+  int num_pass_nodes;
+
+  do {
+    num_pass_nodes = 0;
+
+    // Get a copy of the children list, so we don't have to worry
+    // about self-modifications.
+    PandaNode::ChildrenCopy cr = root->get_children_copy();
+
+    // Now visit each of the children in turn.
+    int num_children = cr.get_num_children();
+    for (int i = 0; i < num_children; i++) {
+      PandaNode *child_node = cr.get_child(i);
+      num_pass_nodes += r_flatten(root, child_node, combine_siblings);
+    }
+
+    if (combine_siblings && root->get_num_children() >= 2) {
+      num_pass_nodes += flatten_siblings(root);
+    }
+
+    num_total_nodes += num_pass_nodes;
+
+    // If combine_siblings is true, we should repeat the above until
+    // we don't get any more benefit from flattening, because each
+    // pass could convert cousins into siblings, which may get
+    // flattened next pass.  If combine_siblings is not true, the
+    // first pass will be fully effective, and there's no point in
+    // trying again.
+  } while (combine_siblings && num_pass_nodes != 0);
+
+  return num_total_nodes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::r_apply_attribs
+//       Access: Protected
+//  Description: The recursive implementation of apply_attribs().
+////////////////////////////////////////////////////////////////////
+void qpSceneGraphReducer::
+r_apply_attribs(PandaNode *node, int attrib_types,
+                qpSceneGraphReducer::AccumulatedAttribs trans) {
+  if (pgraph_cat.is_debug()) {
+    pgraph_cat.debug()
+      << "r_apply_attribs(" << *node << "), node's attribs are:\n";
+    node->get_transform()->write(pgraph_cat.debug(false), 2);
+    node->get_state()->write(pgraph_cat.debug(false), 2);
+    node->get_effects()->write(pgraph_cat.debug(false), 2);
+  }
+
+  trans.collect(node, attrib_types);
+
+  if (pgraph_cat.is_debug()) {
+    pgraph_cat.debug()
+      << "Got attribs from " << *node << "\n"
+      << "Accumulated attribs are:\n";
+    trans.write(pgraph_cat.debug(false), attrib_types, 2);
+  }
+
+  // Check to see if we can't propagate any of these attribs past
+  // this node for some reason.
+  int apply_types = 0;
+
+  const RenderEffects *effects = node->get_effects();
+  if (!effects->safe_to_transform()) {
+    if (pgraph_cat.is_debug()) {
+      pgraph_cat.debug()
+        << "Node " << *node
+        << " contains a non-transformable effect; leaving transform here.\n";
+    }
+    apply_types |= TT_transform;
+  }
+  if (!node->safe_to_transform()) {
+    if (pgraph_cat.is_debug()) {
+      pgraph_cat.debug()
+        << "Cannot safely transform nodes of type " << node->get_type()
+        << "; leaving a transform here but carrying on otherwise.\n";
+    }
+    apply_types |= TT_transform;
+  }
+
+  // Directly store whatever attributes we must,
+  trans.apply_to_node(node, attrib_types & apply_types);
+
+  // And apply the rest to the vertices.
+  trans.apply_to_vertices(node, attrib_types, _transformer);
+
+  // Do we need to copy any children to flatten instances?
+  bool resist_copy = false;
+  int num_children = node->get_num_children();
+  int i;
+  for (i = 0; i < num_children; i++) {
+    PandaNode *child_node = node->get_child(i);
+
+    if (child_node->get_num_parents() > 1) {
+      if (!child_node->safe_to_flatten()) {
+        if (pgraph_cat.is_debug()) {
+          pgraph_cat.debug()
+            << "Cannot duplicate nodes of type " << child_node->get_type()
+            << ".\n";
+          resist_copy = true;
+        }
+
+      } else {
+        PT(PandaNode) new_node = child_node->make_copy();
+        if (new_node->get_type() != child_node->get_type()) {
+          pgraph_cat.error()
+            << "Don't know how to copy nodes of type "
+            << child_node->get_type() << "\n";
+          resist_copy = true;
+
+        } else {
+          if (pgraph_cat.is_debug()) {
+            pgraph_cat.debug()
+              << "Duplicated " << *child_node << "\n";
+          }
+          
+          new_node->copy_children(child_node);
+          node->replace_child(child_node, new_node);
+          child_node = new_node;
+        }
+      }
+    }
+  }
+
+  if (resist_copy) {
+    // If any of our children should have been copied but weren't, we
+    // need to drop the state here before continuing.
+    trans.apply_to_node(node, attrib_types);
+  }
+
+  // Now it's safe to traverse through all of our children.
+  nassertv(num_children == node->get_num_children());
+  for (i = 0; i < num_children; i++) {
+    PandaNode *child_node = node->get_child(i);
+    r_apply_attribs(child_node, attrib_types, trans);
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::r_flatten
+//       Access: Protected
+//  Description: The recursive implementation of flatten().
+////////////////////////////////////////////////////////////////////
+int qpSceneGraphReducer::
+r_flatten(PandaNode *grandparent_node, PandaNode *parent_node,
+          bool combine_siblings) {
+  int num_nodes = 0;
+
+  // First, recurse on each of the children.
+  {
+    PandaNode::ChildrenCopy cr = parent_node->get_children_copy();
+    int num_children = cr.get_num_children();
+    for (int i = 0; i < num_children; i++) {
+      PandaNode *child_node = cr.get_child(i);
+      num_nodes += r_flatten(parent_node, child_node, combine_siblings);
+    }
+  }
+
+  // Now that the above loop has removed some children, the child list
+  // saved above is no longer accurate, so hereafter we must ask the
+  // node for its real child list.
+  if (combine_siblings && parent_node->get_num_children() >= 2) {
+    num_nodes += flatten_siblings(parent_node);
+  }
+
+  if (parent_node->get_num_children() == 1) {
+    // If we now have exactly one child, consider flattening the node
+    // out.
+    PT(PandaNode) child_node = parent_node->get_child(0);
+    int child_sort = parent_node->get_child_sort(0);
+
+    if (consider_child(grandparent_node, parent_node, child_node)) {
+      // Ok, do it.
+      parent_node->remove_child(0);
+
+      if (do_flatten_child(grandparent_node, parent_node, child_node)) {
+        // Done!
+        num_nodes++;
+      } else {
+        // Chicken out.
+        parent_node->add_child(child_node, child_sort);
+      }
+    }
+  }
+
+  return num_nodes;
+}
+
+class SortByState {
+public:
+  INLINE bool
+  operator () (const PandaNode *node1, const PandaNode *node2) const;
+};
+
+INLINE bool SortByState::
+operator () (const PandaNode *node1, const PandaNode *node2) const {
+  if (node1->get_transform() != node2->get_transform()) {
+    return node1->get_transform() < node2->get_transform();
+  }
+  if (node1->get_state() != node2->get_state()) {
+    return node1->get_state() < node2->get_state();
+  }
+  return node1->get_effects() < node2->get_effects();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::flatten_siblings
+//       Access: Protected
+//  Description: Attempts to collapse together any pairs of siblings
+//               of the indicated node that share the same properties.
+////////////////////////////////////////////////////////////////////
+int qpSceneGraphReducer::
+flatten_siblings(PandaNode *parent_node) {
+  int num_nodes = 0;
+
+  // First, collect the children into groups of nodes with common
+  // properties.
+  typedef plist< PT(PandaNode) > NodeList;
+  typedef pmap<PandaNode *, NodeList, SortByState> Collected;
+  Collected collected;
+
+  {
+    // Protect this within a local scope, so the Children member will
+    // destruct and free the read pointer before we try to write to
+    // these nodes.
+    PandaNode::Children cr = parent_node->get_children();
+    int num_children = cr.get_num_children();
+    for (int i = 0; i < num_children; i++) {
+      PandaNode *child_node = cr.get_child(i);
+      if (child_node->safe_to_combine()) {
+        collected[child_node].push_back(child_node);
+      }
+    }
+  }
+
+  // Now visit each of those groups and try to collapse them together.
+  // A O(n^2) operation, but presumably the number of nodes in each
+  // group is small.  And if each node in the group can collapse with
+  // any other node, it becomes a O(n) operation.
+  Collected::iterator ci;
+  for (ci = collected.begin(); ci != collected.end(); ++ci) {
+    const RenderEffects *effects = (*ci).first->get_effects();
+    if (effects->safe_to_combine()) {
+      NodeList &nodes = (*ci).second;
+
+      NodeList::iterator ai1;
+      ai1 = nodes.begin();
+      while (ai1 != nodes.end()) {
+        NodeList::iterator ai1_hold = ai1;
+        PandaNode *child1 = (*ai1);
+        ++ai1;
+        NodeList::iterator ai2 = ai1;
+        while (ai2 != nodes.end()) {
+          NodeList::iterator ai2_hold = ai2;
+          PandaNode *child2 = (*ai2);
+          ++ai2;
+
+          if (consider_siblings(parent_node, child1, child2)) {
+            PT(PandaNode) new_node = 
+              do_flatten_siblings(parent_node, child1, child2);
+            if (new_node != (PandaNode *)NULL) {
+              // We successfully collapsed a node.
+              nodes.erase(ai2_hold);
+              nodes.erase(ai1_hold);
+              nodes.push_back(new_node);
+              ai1 = nodes.begin();
+              ai2 = nodes.end();
+              num_nodes++;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return num_nodes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::consider_child
+//       Access: Protected, Virtual
+//  Description: Decides whether or not the indicated child node is a
+//               suitable candidate for removal.  Returns true if the
+//               node may be removed, false if it should be kept.  This
+//               function may be extended in a user class to protect
+//               special kinds of nodes from deletion.
+////////////////////////////////////////////////////////////////////
+bool qpSceneGraphReducer::
+consider_child(PandaNode *grandparent_node, PandaNode *parent_node, 
+               PandaNode *child_node) {
+  if (!parent_node->safe_to_combine() || !child_node->safe_to_combine()) {
+    // One or both nodes cannot be safely combined with another node;
+    // do nothing.
+    return false;
+  }
+
+  if (parent_node->get_transform() != child_node->get_transform() ||
+      parent_node->get_state() != child_node->get_state() ||
+      parent_node->get_effects() != child_node->get_effects()) {
+    // The two nodes have a different state; too bad.
+    return false;
+  }
+
+  if (!parent_node->get_effects()->safe_to_combine()) {
+    // The effects don't want to be combined.
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::consider_siblings
+//       Access: Protected, Virtual
+//  Description: Decides whether or not the indicated sibling nodes
+//               should be collapsed into a single node or not.
+//               Returns true if the nodes may be collapsed, false if
+//               they should be kept distinct.
+////////////////////////////////////////////////////////////////////
+bool qpSceneGraphReducer::
+consider_siblings(PandaNode *parent_node, PandaNode *child1,
+                  PandaNode *child2) {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::do_flatten_child
+//       Access: Protected, Virtual
+//  Description: Collapses together the indicated parent node and
+//               child node and leaves the result attached to the
+//               grandparent.  The return value is true if the node is
+//               successfully collapsed, false if we chickened out.
+//
+//               This function may be extended in a user class to
+//               handle special kinds of nodes.
+////////////////////////////////////////////////////////////////////
+bool qpSceneGraphReducer::
+do_flatten_child(PandaNode *grandparent_node, PandaNode *parent_node, 
+                 PandaNode *child_node) {
+  if (pgraph_cat.is_debug()) {
+    pgraph_cat.debug()
+      << "Collapsing " << *parent_node << " and " << *child_node << "\n";
+  }
+
+  PT(PandaNode) new_parent = collapse_nodes(parent_node, child_node, false);
+  if (new_parent == (PandaNode *)NULL) {
+    if (pgraph_cat.is_debug()) {
+      pgraph_cat.debug()
+        << "Decided not to collapse " << *parent_node 
+        << " and " << *child_node << "\n";
+    }
+    return false;
+  }
+
+  choose_name(new_parent, parent_node, child_node);
+
+  if (new_parent != child_node) {
+    new_parent->steal_children(child_node);
+  }
+
+  if (new_parent != parent_node) {
+    grandparent_node->replace_child(parent_node, new_parent);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::do_flatten_siblings
+//       Access: Protected, Virtual
+//  Description: Performs the work of collapsing two sibling nodes
+//               together into a single node, leaving the resulting
+//               node attached to the parent.
+//
+//               Returns a pointer to a PandaNode the reflects the
+//               combined node (which may be either of the source nodes,
+//               or a new node altogether) if the siblings are
+//               successfully collapsed, or NULL if we chickened out.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpSceneGraphReducer::
+do_flatten_siblings(PandaNode *parent_node, PandaNode *child1,
+                    PandaNode *child2) {
+  if (pgraph_cat.is_debug()) {
+    pgraph_cat.debug()
+      << "Collapsing " << *child1 << " and " << *child2 << "\n";
+  }
+
+  PT(PandaNode) new_child = collapse_nodes(child1, child2, true);
+  if (new_child == (PandaNode *)NULL) {
+    if (pgraph_cat.is_debug()) {
+      pgraph_cat.debug()
+        << "Decided not to collapse " << *child1 << " and " << *child2 << "\n";
+    }
+    return NULL;
+  }
+
+  choose_name(new_child, child1, child2);
+
+  if (new_child == child1) {
+    new_child->steal_children(child2);
+    parent_node->remove_child(child2);
+
+  } else if (new_child == child2) {
+    new_child->steal_children(child1);
+    parent_node->remove_child(child1);
+
+  } else {
+    new_child->steal_children(child1);
+    new_child->steal_children(child2);
+    parent_node->remove_child(child2);
+    parent_node->replace_child(child1, new_child);
+  }
+
+  return new_child;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::collapse_nodes
+//       Access: Protected, Virtual
+//  Description: Collapses the two nodes into a single node, if
+//               possible.  The 'siblings' flag is true if the two
+//               nodes are siblings nodes; otherwise, node1 is a
+//               parent of node2.  The return value is the resulting
+//               node, which may be either one of the source nodes, or
+//               a new node altogether, or it may be NULL to indicate
+//               that the collapse operation could not take place.
+//
+//               This function may be extended in a user class to
+//               handle combining special kinds of nodes.
+////////////////////////////////////////////////////////////////////
+PT(PandaNode) qpSceneGraphReducer::
+collapse_nodes(PandaNode *node1, PandaNode *node2, bool siblings) {
+  return node2->combine_with(node1);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSceneGraphReducer::choose_name
+//       Access: Protected, Virtual
+//  Description: Chooses a suitable name for the collapsed node, based
+//               on the names of the two sources nodes.
+////////////////////////////////////////////////////////////////////
+void qpSceneGraphReducer::
+choose_name(PandaNode *preserve, PandaNode *source1, PandaNode *source2) {
+  string name;
+  bool got_name = false;
+
+  name = source1->get_name();
+  got_name = !name.empty() || source1->preserve_name();
+
+  if (source2->preserve_name() || !got_name) {
+    name = source2->get_name();
+    got_name = !name.empty() || source2->preserve_name();
+  }
+
+  if (got_name) {
+    preserve->set_name(name);
+  }
+}

+ 109 - 0
panda/src/pgraph/qpsceneGraphReducer.h

@@ -0,0 +1,109 @@
+// Filename: qpsceneGraphReducer.h
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpSCENEGRAPHREDUCER_H
+#define qpSCENEGRAPHREDUCER_H
+
+#include "pandabase.h"
+#include "transformState.h"
+#include "renderAttrib.h"
+#include "renderState.h"
+#include "qpgeomTransformer.h"
+
+#include "typedObject.h"
+#include "pointerTo.h"
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpSceneGraphReducer
+// Description : An interface for simplifying ("flattening") scene
+//               graphs by eliminating unneeded nodes and collapsing
+//               out unneeded state changes and transforms.
+//
+//               This class is designed so that it may be inherited
+//               from and specialized, if needed, to fine-tune the
+//               flattening behavior, but normally the default
+//               behavior is sufficient.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpSceneGraphReducer {
+PUBLISHED:
+  qpSceneGraphReducer();
+  virtual ~qpSceneGraphReducer();
+
+  enum AttribTypes {
+    TT_transform       = 0x001,
+    TT_color           = 0x002,
+    TT_color_scale     = 0x004,
+    TT_tex_matrix      = 0x008,
+    TT_other           = 0x010,
+  };
+
+  void apply_attribs(PandaNode *node, int attrib_types = ~0);
+
+  int flatten(PandaNode *root, bool combine_siblings);
+
+protected:
+  class AccumulatedAttribs {
+  public:
+    INLINE AccumulatedAttribs();
+    INLINE AccumulatedAttribs(const AccumulatedAttribs &copy);
+    INLINE void operator = (const AccumulatedAttribs &copy);
+
+    void write(ostream &out, int attrib_types, int indent_level) const;
+
+    void collect(PandaNode *node, int attrib_types);
+    void apply_to_node(PandaNode *node, int attrib_types);
+    void apply_to_vertices(PandaNode *node, int attrib_types,
+                           qpGeomTransformer &transfomer);
+
+    CPT(TransformState) _transform;
+    CPT(RenderAttrib) _color;
+    CPT(RenderAttrib) _color_scale;
+    CPT(RenderAttrib) _tex_matrix;
+    CPT(RenderState) _other;
+  };
+
+  void r_apply_attribs(PandaNode *node, int attrib_types,
+                       AccumulatedAttribs trans);
+
+  int r_flatten(PandaNode *grandparent_node, PandaNode *parent_node,
+                bool combine_siblings);
+  int flatten_siblings(PandaNode *parent_node);
+
+  virtual bool consider_child(PandaNode *grandparent_node,
+                              PandaNode *parent_node, PandaNode *child_node);
+  virtual bool consider_siblings(PandaNode *parent_node, PandaNode *child1,
+                                 PandaNode *child2);
+
+  virtual bool do_flatten_child(PandaNode *grandparent_node, 
+                                PandaNode *parent_node, PandaNode *child_node);
+
+  virtual PandaNode *do_flatten_siblings(PandaNode *parent_node, 
+                                         PandaNode *child1, PandaNode *child2);
+
+  virtual PT(PandaNode) collapse_nodes(PandaNode *node1, PandaNode *node2, 
+                                       bool siblings);
+  virtual void choose_name(PandaNode *preserve, PandaNode *source1, 
+                           PandaNode *source2);
+
+private:
+  qpGeomTransformer _transformer;
+};
+
+#include "qpsceneGraphReducer.I"
+
+#endif