diff --git a/panda/src/pgraph/qpgeomTransformer.I b/panda/src/pgraph/qpgeomTransformer.I new file mode 100644 index 0000000000..feb21c69ed --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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); +} diff --git a/panda/src/pgraph/qpgeomTransformer.cxx b/panda/src/pgraph/qpgeomTransformer.cxx new file mode 100644 index 0000000000..aa1aa0b915 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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; +} diff --git a/panda/src/pgraph/qpgeomTransformer.h b/panda/src/pgraph/qpgeomTransformer.h new file mode 100644 index 0000000000..af38550c74 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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 Vertices; + Vertices _vertices; + + class SourceNormals { + public: + INLINE bool operator < (const SourceNormals &other) const; + + LMatrix4f _mat; + PTA_Normalf _norms; + }; + typedef pmap Normals; + Normals _normals; + + class SourceTexCoords { + public: + INLINE bool operator < (const SourceTexCoords &other) const; + + LMatrix4f _mat; + PTA_TexCoordf _texcoords; + }; + typedef pmap 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 FColors; + FColors _fcolors; + + class SourceColors { + public: + INLINE bool operator < (const SourceColors &other) const; + + LVecBase4f _scale; + PTA_Colorf _colors; + }; + typedef pmap TColors; + TColors _tcolors; +}; + +#include "qpgeomTransformer.I" + +#endif + diff --git a/panda/src/pgraph/qpsceneGraphReducer.I b/panda/src/pgraph/qpsceneGraphReducer.I new file mode 100644 index 0000000000..3e36cbbbd5 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// 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 ©) : + _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 ©) { + _transform = copy._transform; + _color = copy._color; + _color_scale = copy._color_scale; + _tex_matrix = copy._tex_matrix; + _other = copy._other; +} diff --git a/panda/src/pgraph/qpsceneGraphReducer.cxx b/panda/src/pgraph/qpsceneGraphReducer.cxx new file mode 100644 index 0000000000..aa5cd8f842 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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 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); + } +} diff --git a/panda/src/pgraph/qpsceneGraphReducer.h b/panda/src/pgraph/qpsceneGraphReducer.h new file mode 100644 index 0000000000..7111e962e6 --- /dev/null +++ b/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 +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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 ©); + INLINE void operator = (const AccumulatedAttribs ©); + + 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