From 4eb9de4b8e601783a752fdc9348c3c224ebdb323 Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 21 Apr 2005 18:55:55 +0000 Subject: [PATCH] unify() --- panda/src/gobj/qpgeom.cxx | 86 ++++++++++++++++++++------ panda/src/gobj/qpgeom.h | 2 + panda/src/gobj/qpgeomPrimitive.cxx | 60 +++++++++++++++++- panda/src/gobj/qpgeomPrimitive.h | 1 + panda/src/pgraph/geomNode.cxx | 69 +++++++++++++++++++++ panda/src/pgraph/geomNode.h | 2 + panda/src/pgraph/geomTransformer.cxx | 2 +- panda/src/pgraph/sceneGraphReducer.I | 11 ++++ panda/src/pgraph/sceneGraphReducer.cxx | 19 ++++++ panda/src/pgraph/sceneGraphReducer.h | 2 + panda/src/testbed/pview.cxx | 10 ++- 11 files changed, 243 insertions(+), 21 deletions(-) diff --git a/panda/src/gobj/qpgeom.cxx b/panda/src/gobj/qpgeom.cxx index 2ee7d16bd5..a2bdb20e62 100644 --- a/panda/src/gobj/qpgeom.cxx +++ b/panda/src/gobj/qpgeom.cxx @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////// #include "qpgeom.h" +#include "qpgeomPoints.h" #include "qpgeomVertexReader.h" #include "qpgeomVertexRewriter.h" #include "pStatTimer.h" @@ -232,8 +233,14 @@ make_nonindexed(bool composite_only) { PT(qpGeomPrimitive) primitive = (*pi)->make_copy(); new_prims.push_back(primitive); + // GeomPoints are considered "composite" for the purposes of + // making nonindexed, since there's no particular advantage to + // having indexed points (as opposed to, say, indexed triangles or + // indexed lines). if (primitive->is_indexed() && - (primitive->is_composite() || !composite_only)) { + (primitive->is_composite() || + primitive->is_exact_type(qpGeomPoints::get_class_type()) || + !composite_only)) { primitive->make_nonindexed(new_data, orig_data); ++num_changed; } else { @@ -281,18 +288,16 @@ set_primitive(int i, const qpGeomPrimitive *primitive) { nassertv(cdata->_primitive_type == PT_none || cdata->_primitive_type == primitive->get_primitive_type()); - // They also should have the same fundamental shade model, but - // SM_uniform is compatible with anything. - nassertv(cdata->_shade_model == SM_uniform || - primitive->get_shade_model() == SM_uniform || - cdata->_shade_model == primitive->get_shade_model()); + // They also should have the a compatible shade model. + CPT(qpGeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model); + nassertv_always(compat != (qpGeomPrimitive *)NULL); - cdata->_primitives[i] = (qpGeomPrimitive *)primitive; - PrimitiveType new_primitive_type = primitive->get_primitive_type(); + cdata->_primitives[i] = (qpGeomPrimitive *)compat.p(); + PrimitiveType new_primitive_type = compat->get_primitive_type(); if (new_primitive_type != cdata->_primitive_type) { cdata->_primitive_type = new_primitive_type; } - ShadeModel new_shade_model = primitive->get_shade_model(); + ShadeModel new_shade_model = compat->get_shade_model(); if (new_shade_model != cdata->_shade_model && new_shade_model != SM_uniform) { cdata->_shade_model = new_shade_model; @@ -323,18 +328,16 @@ add_primitive(const qpGeomPrimitive *primitive) { nassertv(cdata->_primitive_type == PT_none || cdata->_primitive_type == primitive->get_primitive_type()); - // They also should have the same fundamental shade model, but - // SM_uniform is compatible with anything. - nassertv(cdata->_shade_model == SM_uniform || - primitive->get_shade_model() == SM_uniform || - cdata->_shade_model == primitive->get_shade_model()); + // They also should have the a compatible shade model. + CPT(qpGeomPrimitive) compat = primitive->match_shade_model(cdata->_shade_model); + nassertv_always(compat != (qpGeomPrimitive *)NULL); - cdata->_primitives.push_back((qpGeomPrimitive *)primitive); - PrimitiveType new_primitive_type = primitive->get_primitive_type(); + cdata->_primitives.push_back((qpGeomPrimitive *)compat.p()); + PrimitiveType new_primitive_type = compat->get_primitive_type(); if (new_primitive_type != cdata->_primitive_type) { cdata->_primitive_type = new_primitive_type; } - ShadeModel new_shade_model = primitive->get_shade_model(); + ShadeModel new_shade_model = compat->get_shade_model(); if (new_shade_model != cdata->_shade_model && new_shade_model != SM_uniform) { cdata->_shade_model = new_shade_model; @@ -515,6 +518,51 @@ unify_in_place() { reset_geom_rendering(cdata); } + +//////////////////////////////////////////////////////////////////// +// Function: qpGeom::copy_primitives_from +// Access: Published +// Description: Copies the primitives from the indicated Geom into +// this one. This does require that both Geoms contain +// the same fundamental type primitives, both have a +// compatible shade model, and both use the same +// GeomVertexData. +// +// Returns true if the copy is successful, or false +// otherwise (because the Geoms were mismatched). +//////////////////////////////////////////////////////////////////// +bool qpGeom:: +copy_primitives_from(const qpGeom *other) { + if (get_primitive_type() != PT_none && + other->get_primitive_type() != get_primitive_type()) { + return false; + } + if (get_vertex_data() != other->get_vertex_data()) { + return false; + } + + ShadeModel this_shade_model = get_shade_model(); + ShadeModel other_shade_model = other->get_shade_model(); + if (this_shade_model != SM_uniform && other_shade_model != SM_uniform && + this_shade_model != other_shade_model) { + if ((this_shade_model == SM_flat_first_vertex && other_shade_model == SM_flat_last_vertex) || + (this_shade_model == SM_flat_last_vertex && other_shade_model == SM_flat_first_vertex)) { + // This is acceptable; we can rotate the primitives to match. + + } else { + // Otherwise, we have incompatible shade models. + return false; + } + } + + int num_primitives = other->get_num_primitives(); + for (int i = 0; i < num_primitives; i++) { + add_primitive(other->get_primitive(i)); + } + + return true; +} + //////////////////////////////////////////////////////////////////// // Function: qpGeom::get_num_bytes // Access: Published @@ -682,11 +730,13 @@ output(ostream &out) const { CDReader cdata(_cycler); // Get a list of the primitive types contained within this object. + int num_faces = 0; pset types; Primitives::const_iterator pi; for (pi = cdata->_primitives.begin(); pi != cdata->_primitives.end(); ++pi) { + num_faces += (*pi)->get_num_faces(); types.insert((*pi)->get_type()); } @@ -695,7 +745,7 @@ output(ostream &out) const { for (ti = types.begin(); ti != types.end(); ++ti) { out << " " << (*ti); } - out << " ], " << cdata->_data->get_num_rows() << " vertices"; + out << " ], " << num_faces << " faces"; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/qpgeom.h b/panda/src/gobj/qpgeom.h index adbf866154..088b330e20 100644 --- a/panda/src/gobj/qpgeom.h +++ b/panda/src/gobj/qpgeom.h @@ -96,6 +96,8 @@ PUBLISHED: void rotate_in_place(); void unify_in_place(); + bool copy_primitives_from(const qpGeom *other); + int get_num_bytes() const; INLINE UpdateSeq get_modified() const; diff --git a/panda/src/gobj/qpgeomPrimitive.cxx b/panda/src/gobj/qpgeomPrimitive.cxx index 0fa8c37f9e..6bb37e696b 100644 --- a/panda/src/gobj/qpgeomPrimitive.cxx +++ b/panda/src/gobj/qpgeomPrimitive.cxx @@ -675,7 +675,9 @@ decompose() const { // Function: qpGeomPrimitive::rotate // Access: Published // Description: Returns a new primitive with the shade_model reversed -// (if it is flat shaded). +// (if it is flat shaded), if possible. If the +// primitive type cannot be rotated, returns the +// original primitive, unrotated. // // If the current shade_model indicates // flat_vertex_last, this should bring the last vertex @@ -694,14 +696,70 @@ rotate() const { CPT(qpGeomVertexArrayData) rotated_vertices = rotate_impl(); if (rotated_vertices == (qpGeomVertexArrayData *)NULL) { + // This primitive type can't be rotated. return this; } PT(qpGeomPrimitive) new_prim = make_copy(); new_prim->set_vertices(rotated_vertices); + + switch (get_shade_model()) { + case SM_flat_first_vertex: + new_prim->set_shade_model(SM_flat_last_vertex); + break; + + case SM_flat_last_vertex: + new_prim->set_shade_model(SM_flat_first_vertex); + break; + + default: + break; + } + return new_prim; } +//////////////////////////////////////////////////////////////////// +// Function: qpGeomPrimitive::match_shade_model +// Access: Published +// Description: Returns a new primitive that is compatible with the +// indicated shade model, if possible, or NULL if this +// is not possible. +// +// In most cases, this will return either NULL or the +// original primitive. In the case of a +// SM_flat_first_vertex vs. a SM_flat_last_vertex (or +// vice-versa), however, it will return a rotated +// primitive. +//////////////////////////////////////////////////////////////////// +CPT(qpGeomPrimitive) qpGeomPrimitive:: +match_shade_model(qpGeomPrimitive::ShadeModel shade_model) const { + ShadeModel this_shade_model = get_shade_model(); + if (this_shade_model == shade_model) { + // Trivially compatible. + return this; + } + + if (this_shade_model == SM_uniform || shade_model == SM_uniform) { + // SM_uniform is compatible with anything. + return this; + } + + if ((this_shade_model == SM_flat_first_vertex && shade_model == SM_flat_last_vertex) || + (this_shade_model == SM_flat_last_vertex && shade_model == SM_flat_first_vertex)) { + // Needs to be rotated. + CPT(qpGeomPrimitive) rotated = rotate(); + if (rotated == this) { + // Oops, can't be rotated, sorry. + return NULL; + } + return rotated; + } + + // Not compatible, sorry. + return NULL; +} + //////////////////////////////////////////////////////////////////// // Function: qpGeomPrimitive::get_num_bytes // Access: Published diff --git a/panda/src/gobj/qpgeomPrimitive.h b/panda/src/gobj/qpgeomPrimitive.h index 222cadff3c..c99fc5ba71 100644 --- a/panda/src/gobj/qpgeomPrimitive.h +++ b/panda/src/gobj/qpgeomPrimitive.h @@ -122,6 +122,7 @@ PUBLISHED: CPT(qpGeomPrimitive) decompose() const; CPT(qpGeomPrimitive) rotate() const; + CPT(qpGeomPrimitive) match_shade_model(ShadeModel shade_model) const; int get_num_bytes() const; INLINE int get_data_size_bytes() const; diff --git a/panda/src/pgraph/geomNode.cxx b/panda/src/pgraph/geomNode.cxx index ba7ec052ae..729ec35fff 100644 --- a/panda/src/pgraph/geomNode.cxx +++ b/panda/src/pgraph/geomNode.cxx @@ -449,6 +449,75 @@ add_geoms_from(const GeomNode *other) { } } +//////////////////////////////////////////////////////////////////// +// Function: GeomNode::unify +// Access: Published +// Description: Attempts to unify all of the Geoms contained within +// this node into a single Geom, or at least as few +// Geoms as possible. In turn, the individual +// GeomPrimitives contained within each resulting Geom +// are also unified. The goal is to reduce the number +// of GeomPrimitives within the node as far as possible. +// This may result in composite primitives, such as +// triangle strips and triangle fans, being decomposed +// into triangles. See also Geom::unify(). +// +// In order for this to be successful, the primitives +// must reference the same GeomVertexData, have the same +// fundamental primitive type, and have compatible shade +// models. +//////////////////////////////////////////////////////////////////// +void GeomNode:: +unify() { + CDWriter cdata(_cycler); + + Geoms new_geoms; + + // Try to unify each Geom with each preceding Geom. This is an n^2 + // operation, but usually there are only a handful of Geoms to + // consider, so that's not a big deal. + Geoms::iterator gi; + for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) { + const GeomEntry &entry = (*gi); + + bool unified = false; + if (entry._geom->is_of_type(qpGeom::get_class_type())) { + Geoms::iterator gj; + for (gj = new_geoms.begin(); gj != new_geoms.end() && !unified; ++gj) { + GeomEntry &new_entry = (*gj); + if (new_entry._geom->is_of_type(qpGeom::get_class_type())) { + if (entry._state == new_entry._state) { + // Both states match, so try to combine the primitives. + if (DCAST(qpGeom, new_entry._geom)->copy_primitives_from + (DCAST(qpGeom, entry._geom))) { + // Successfully combined! + unified = true; + } + } + } + } + } + + if (!unified) { + // Couldn't unify this Geom with anything, so just add it to the + // output list. + new_geoms.push_back(entry); + } + } + + // Done! We'll keep whatever's left in the output list. + cdata->_geoms.swap(new_geoms); + new_geoms.clear(); + + // Finally, go back through and unify the resulting geom(s). + for (gi = cdata->_geoms.begin(); gi != cdata->_geoms.end(); ++gi) { + const GeomEntry &entry = (*gi); + if (entry._geom->is_of_type(qpGeom::get_class_type())) { + DCAST(qpGeom, entry._geom)->unify_in_place(); + } + } +} + //////////////////////////////////////////////////////////////////// // Function: GeomNode::write_geoms // Access: Published diff --git a/panda/src/pgraph/geomNode.h b/panda/src/pgraph/geomNode.h index 8457c2e25e..490bd214e7 100644 --- a/panda/src/pgraph/geomNode.h +++ b/panda/src/pgraph/geomNode.h @@ -68,6 +68,8 @@ PUBLISHED: INLINE void remove_geom(int n); INLINE void remove_all_geoms(); + void unify(); + void write_geoms(ostream &out, int indent_level) const; void write_verbose(ostream &out, int indent_level) const; diff --git a/panda/src/pgraph/geomTransformer.cxx b/panda/src/pgraph/geomTransformer.cxx index 6d06dde94a..998e11a4c9 100644 --- a/panda/src/pgraph/geomTransformer.cxx +++ b/panda/src/pgraph/geomTransformer.cxx @@ -617,7 +617,7 @@ collect_vertex_data(qpGeom *geom, int collect_bits) { // Access: Public // Description: Collects together individual GeomVertexData // structures that share the same format into one big -// GeomVertexData structure. This is designed to +// GeomVertexData structure. This is intended to // minimize context switches on the graphics card. //////////////////////////////////////////////////////////////////// int GeomTransformer:: diff --git a/panda/src/pgraph/sceneGraphReducer.I b/panda/src/pgraph/sceneGraphReducer.I index 0b1bfc96cd..f2dd44634c 100644 --- a/panda/src/pgraph/sceneGraphReducer.I +++ b/panda/src/pgraph/sceneGraphReducer.I @@ -133,3 +133,14 @@ INLINE int SceneGraphReducer:: make_nonindexed(PandaNode *root, int nonindexed_bits) { return r_make_nonindexed(root, nonindexed_bits); } + +//////////////////////////////////////////////////////////////////// +// Function: SceneGraphReducer::unify +// Access: Published +// Description: Calls unify() on every GeomNode at this level and +// below. +//////////////////////////////////////////////////////////////////// +INLINE void SceneGraphReducer:: +unify(PandaNode *root) { + r_unify(root); +} diff --git a/panda/src/pgraph/sceneGraphReducer.cxx b/panda/src/pgraph/sceneGraphReducer.cxx index dd0a66dbfa..817903bb1f 100644 --- a/panda/src/pgraph/sceneGraphReducer.cxx +++ b/panda/src/pgraph/sceneGraphReducer.cxx @@ -674,3 +674,22 @@ r_make_nonindexed(PandaNode *node, int nonindexed_bits) { return num_changed; } + +//////////////////////////////////////////////////////////////////// +// Function: SceneGraphReducer::r_unify +// Access: Private +// Description: The recursive implementation of unify(). +//////////////////////////////////////////////////////////////////// +void SceneGraphReducer:: +r_unify(PandaNode *node) { + if (node->is_geom_node()) { + GeomNode *geom_node = DCAST(GeomNode, node); + geom_node->unify(); + } + + PandaNode::Children children = node->get_children(); + int num_children = children.get_num_children(); + for (int i = 0; i < num_children; ++i) { + r_unify(children.get_child(i)); + } +} diff --git a/panda/src/pgraph/sceneGraphReducer.h b/panda/src/pgraph/sceneGraphReducer.h index 37281ae19e..f04f774e58 100644 --- a/panda/src/pgraph/sceneGraphReducer.h +++ b/panda/src/pgraph/sceneGraphReducer.h @@ -114,6 +114,7 @@ PUBLISHED: INLINE int collect_vertex_data(PandaNode *root, int collect_bits = ~0); INLINE int make_nonindexed(PandaNode *root, int nonindexed_bits = ~0); + INLINE void unify(PandaNode *root); protected: void r_apply_attribs(PandaNode *node, const AccumulatedAttribs &attribs, @@ -143,6 +144,7 @@ protected: int r_collect_vertex_data(PandaNode *node, int collect_bits, GeomTransformer &transformer); int r_make_nonindexed(PandaNode *node, int collect_bits); + void r_unify(PandaNode *node); private: GeomTransformer _transformer; diff --git a/panda/src/testbed/pview.cxx b/panda/src/testbed/pview.cxx index 12b8e62c86..42ace008fc 100644 --- a/panda/src/testbed/pview.cxx +++ b/panda/src/testbed/pview.cxx @@ -82,6 +82,12 @@ event_W(CPT_Event, void *) { } } +void +event_F(CPT_Event, void *) { + // shift-F: flatten the model hierarchy. + framework.get_models().flatten_strong(); +} + void event_Enter(CPT_Event, void *) { // alt-enter: toggle between window/fullscreen in the same scene. @@ -133,8 +139,9 @@ event_0(CPT_Event event, void *) { // 0: run hacky test. SceneGraphReducer gr; - gr.make_nonindexed(framework.get_models().node()); gr.collect_vertex_data(framework.get_models().node()); + gr.unify(framework.get_models().node()); + gr.make_nonindexed(framework.get_models().node()); /* static int count = 0; @@ -312,6 +319,7 @@ main(int argc, char *argv[]) { framework.enable_default_keys(); framework.define_key("shift-w", "open a new window", event_W, NULL); + framework.define_key("shift-f", "flatten hierarchy", event_F, NULL); framework.define_key("alt-enter", "toggle between window/fullscreen", event_Enter, NULL); framework.define_key("2", "split the window", event_2, NULL); if (pview_test_hack) {