소스 검색

SceneGraphReducer::doubleside()

David Rose 18 년 전
부모
커밋
eec731e417

+ 28 - 0
panda/src/gobj/geom.I

@@ -156,6 +156,34 @@ decompose() const {
   return new_geom;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::doubleside
+//       Access: Published
+//  Description: Doublesides all of the primitives within this Geom,
+//               returning the result.  See
+//               GeomPrimitive::doubleside().
+////////////////////////////////////////////////////////////////////
+INLINE PT(Geom) Geom::
+doubleside() const {
+  PT(Geom) new_geom = make_copy();
+  new_geom->doubleside_in_place();
+  return new_geom;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::reverse
+//       Access: Published
+//  Description: Reverses all of the primitives within this Geom,
+//               returning the result.  See
+//               GeomPrimitive::reverse().
+////////////////////////////////////////////////////////////////////
+INLINE PT(Geom) Geom::
+reverse() const {
+  PT(Geom) new_geom = make_copy();
+  new_geom->reverse_in_place();
+  return new_geom;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::rotate
 //       Access: Published

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

@@ -488,6 +488,82 @@ decompose_in_place() {
   nassertv(all_is_valid);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::doubleside_in_place
+//       Access: Published
+//  Description: Doublesides all of the primitives within this Geom,
+//               leaving the results in place.  See
+//               GeomPrimitive::doubleside().
+//
+//               Don't call this in a downstream thread unless you
+//               don't mind it blowing away other changes you might
+//               have recently made in an upstream thread.
+////////////////////////////////////////////////////////////////////
+void Geom::
+doubleside_in_place() {
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, true, current_thread);
+
+#ifndef NDEBUG
+  bool all_is_valid = true;
+#endif
+  Primitives::iterator pi;
+  for (pi = cdata->_primitives.begin(); pi != cdata->_primitives.end(); ++pi) {
+    CPT(GeomPrimitive) new_prim = (*pi).get_read_pointer()->doubleside();
+    (*pi) = (GeomPrimitive *)new_prim.p();
+
+#ifndef NDEBUG
+    if (!new_prim->check_valid(cdata->_data.get_read_pointer())) {
+      all_is_valid = false;
+    }
+#endif
+  }
+
+  cdata->_modified = Geom::get_next_modified();
+  reset_geom_rendering(cdata);
+  clear_cache_stage(current_thread);
+
+  nassertv(all_is_valid);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Geom::reverse_in_place
+//       Access: Published
+//  Description: Reverses all of the primitives within this Geom,
+//               leaving the results in place.  See
+//               GeomPrimitive::reverse().
+//
+//               Don't call this in a downstream thread unless you
+//               don't mind it blowing away other changes you might
+//               have recently made in an upstream thread.
+////////////////////////////////////////////////////////////////////
+void Geom::
+reverse_in_place() {
+  Thread *current_thread = Thread::get_current_thread();
+  CDWriter cdata(_cycler, true, current_thread);
+
+#ifndef NDEBUG
+  bool all_is_valid = true;
+#endif
+  Primitives::iterator pi;
+  for (pi = cdata->_primitives.begin(); pi != cdata->_primitives.end(); ++pi) {
+    CPT(GeomPrimitive) new_prim = (*pi).get_read_pointer()->reverse();
+    (*pi) = (GeomPrimitive *)new_prim.p();
+
+#ifndef NDEBUG
+    if (!new_prim->check_valid(cdata->_data.get_read_pointer())) {
+      all_is_valid = false;
+    }
+#endif
+  }
+
+  cdata->_modified = Geom::get_next_modified();
+  reset_geom_rendering(cdata);
+  clear_cache_stage(current_thread);
+
+  nassertv(all_is_valid);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Geom::rotate_in_place
 //       Access: Published

+ 4 - 0
panda/src/gobj/geom.h

@@ -98,10 +98,14 @@ PUBLISHED:
   void clear_primitives();
 
   INLINE PT(Geom) decompose() const;
+  INLINE PT(Geom) doubleside() const;
+  INLINE PT(Geom) reverse() const;
   INLINE PT(Geom) rotate() const;
   INLINE PT(Geom) unify(int max_indices) const;
 
   void decompose_in_place();
+  void doubleside_in_place();
+  void reverse_in_place();
   void rotate_in_place();
   void unify_in_place(int max_indices);
 

+ 74 - 0
panda/src/gobj/geomPrimitive.cxx

@@ -37,6 +37,8 @@ TypeHandle GeomPrimitive::CData::_type_handle;
 TypeHandle GeomPrimitivePipelineReader::_type_handle;
 
 PStatCollector GeomPrimitive::_decompose_pcollector("*:Munge:Decompose");
+PStatCollector GeomPrimitive::_doubleside_pcollector("*:Munge:Doubleside");
+PStatCollector GeomPrimitive::_reverse_pcollector("*:Munge:Reverse");
 PStatCollector GeomPrimitive::_rotate_pcollector("*:Munge:Rotate");
 
 ////////////////////////////////////////////////////////////////////
@@ -740,6 +742,58 @@ rotate() const {
   return new_prim;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::doubleside
+//       Access: Published
+//  Description: Duplicates triangles in the primitive so that each
+//               triangle is back-to-back with another triangle facing
+//               in the opposite direction.  Note that this doesn't
+//               affect vertex normals, so this operation alone won't
+//               work in the presence of lighting (but see
+//               SceneGraphReducer::doubleside()).
+//
+//               Also see CullFaceAttrib, which can enable rendering
+//               of both sides of a triangle without having to
+//               duplicate it (but which doesn't necessarily work in
+//               the presence of lighting).
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomPrimitive::
+doubleside() const {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "Doublesiding " << get_type() << ": " << (void *)this << "\n";
+  }
+
+  PStatTimer timer(_doubleside_pcollector);
+  return doubleside_impl();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::reverse
+//       Access: Published
+//  Description: Reverses the winding order in the primitive so that
+//               each triangle is facing in the opposite direction it
+//               was originally.  Note that this doesn't affect vertex
+//               normals, so this operation alone won't work in the
+//               presence of lighting (but see
+//               SceneGraphReducer::reverse()).
+//
+//               Also see CullFaceAttrib, which can change the visible
+//               direction of a triangle without having to duplicate
+//               it (but which doesn't necessarily work in the
+//               presence of lighting).
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomPrimitive::
+reverse() const {
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "Reversing " << get_type() << ": " << (void *)this << "\n";
+  }
+
+  PStatTimer timer(_reverse_pcollector);
+  return reverse_impl();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::match_shade_model
 //       Access: Published
@@ -1407,6 +1461,26 @@ rotate_impl() const {
   return NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::doubleside_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of doubleside().
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomPrimitive::
+doubleside_impl() const {
+  return this;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomPrimitive::reverse_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of reverse().
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomPrimitive::
+reverse_impl() const {
+  return this;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomPrimitive::requires_unused_vertices
 //       Access: Protected, Virtual

+ 6 - 0
panda/src/gobj/geomPrimitive.h

@@ -130,6 +130,8 @@ PUBLISHED:
 
   CPT(GeomPrimitive) decompose() const;
   CPT(GeomPrimitive) rotate() const;
+  CPT(GeomPrimitive) doubleside() const;
+  CPT(GeomPrimitive) reverse() const;
   CPT(GeomPrimitive) match_shade_model(ShadeModel shade_model) const;
 
   int get_num_bytes() const;
@@ -206,6 +208,8 @@ public:
 protected:
   virtual CPT(GeomPrimitive) decompose_impl() const;
   virtual CPT(GeomVertexArrayData) rotate_impl() const;
+  virtual CPT(GeomPrimitive) doubleside_impl() const;
+  virtual CPT(GeomPrimitive) reverse_impl() const;
   virtual bool requires_unused_vertices() const;
   virtual void append_unused_vertices(GeomVertexArrayData *vertices, 
                                       int vertex);
@@ -280,6 +284,8 @@ private:
   
 private:
   static PStatCollector _decompose_pcollector;
+  static PStatCollector _doubleside_pcollector;
+  static PStatCollector _reverse_pcollector;
   static PStatCollector _rotate_pcollector;
 
 public:

+ 79 - 1
panda/src/gobj/geomTriangles.cxx

@@ -110,10 +110,88 @@ draw(GraphicsStateGuardianBase *gsg, const GeomPrimitivePipelineReader *reader,
   return gsg->draw_triangles(reader, force);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomTriangles::doubleside_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of doubleside().
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomTriangles::
+doubleside_impl() const {
+  Thread *current_thread = Thread::get_current_thread();
+  PT(GeomTriangles) reversed = new GeomTriangles(*this);
+
+  GeomPrimitivePipelineReader from(this, current_thread);
+
+  // This is like reverse(), except we don't clear the vertices first.
+  // That way we double the vertices up.
+
+  // First, rotate the original copy, if necessary, so the
+  // flat-first/flat-last nature of the vertices is consistent
+  // throughout the primitive.
+  bool needs_rotate = false;
+  switch (from.get_shade_model()) {
+  case SM_flat_first_vertex:
+  case SM_flat_last_vertex:
+    reversed = (GeomTriangles *)DCAST(GeomTriangles, reversed->rotate());
+    needs_rotate = true;
+
+  default:
+    break;
+  }
+
+  // Now append all the new vertices, in reverse order.
+  for (int i = from.get_num_vertices() - 1; i >= 0; --i) {
+    reversed->add_vertex(from.get_vertex(i));
+  }
+
+  // Finally, re-rotate the whole thing to get back to the original
+  // shade model.
+  if (needs_rotate) {
+    reversed = (GeomTriangles *)DCAST(GeomTriangles, reversed->rotate());
+  }
+
+  return reversed.p();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomTriangles::reverse_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of reverse().
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomTriangles::
+reverse_impl() const {
+  Thread *current_thread = Thread::get_current_thread();
+  PT(GeomTriangles) reversed = new GeomTriangles(*this);
+
+  GeomPrimitivePipelineReader from(this, current_thread);
+  reversed->clear_vertices();
+
+  for (int i = from.get_num_vertices() - 1; i >= 0; --i) {
+    reversed->add_vertex(from.get_vertex(i));
+  }
+
+  switch (from.get_shade_model()) {
+  case SM_flat_first_vertex:
+    reversed->set_shade_model(SM_flat_last_vertex);
+    reversed = (GeomTriangles *)DCAST(GeomTriangles, reversed->rotate());
+    break;
+
+  case SM_flat_last_vertex:
+    reversed->set_shade_model(SM_flat_first_vertex);
+    reversed = (GeomTriangles *)DCAST(GeomTriangles, reversed->rotate());
+    break;
+
+  default:
+    break;
+  }
+
+  return reversed.p();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomTriangles::rotate_impl
 //       Access: Protected, Virtual
-//  Description: The virtual implementation of do_rotate().
+//  Description: The virtual implementation of rotate().
 ////////////////////////////////////////////////////////////////////
 CPT(GeomVertexArrayData) GeomTriangles::
 rotate_impl() const {

+ 2 - 0
panda/src/gobj/geomTriangles.h

@@ -44,6 +44,8 @@ public:
                     bool force) const;
 
 protected:
+  virtual CPT(GeomPrimitive) doubleside_impl() const;
+  virtual CPT(GeomPrimitive) reverse_impl() const;
   virtual CPT(GeomVertexArrayData) rotate_impl() const;
 
 public:

+ 24 - 0
panda/src/gobj/geomTristrips.cxx

@@ -239,6 +239,30 @@ decompose_impl() const {
   return triangles.p();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomTristrips::doubleside_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of doubleside().
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomTristrips::
+doubleside_impl() const {
+  // TODO: implement this properly as triangle strips, without
+  // requiring a decompose operation first.
+  return decompose_impl()->doubleside();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomTristrips::reverse_impl
+//       Access: Protected, Virtual
+//  Description: The virtual implementation of reverse().
+////////////////////////////////////////////////////////////////////
+CPT(GeomPrimitive) GeomTristrips::
+reverse_impl() const {
+  // TODO: implement this properly as triangle strips, without
+  // requiring a decompose operation first.
+  return decompose_impl()->reverse();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomTristrips::rotate_impl
 //       Access: Protected, Virtual

+ 2 - 0
panda/src/gobj/geomTristrips.h

@@ -45,6 +45,8 @@ public:
 
 protected:
   virtual CPT(GeomPrimitive) decompose_impl() const;
+  virtual CPT(GeomPrimitive) doubleside_impl() const;
+  virtual CPT(GeomPrimitive) reverse_impl() const;
   virtual CPT(GeomVertexArrayData) rotate_impl() const;
   virtual bool requires_unused_vertices() const;
   virtual void append_unused_vertices(GeomVertexArrayData *vertices, 

+ 28 - 0
panda/src/gobj/geomVertexData.cxx

@@ -923,6 +923,34 @@ set_color(const Colorf &color, int num_components,
   return new_data;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::reverse_normals
+//       Access: Published
+//  Description: Returns a new GeomVertexData object with the normal
+//               data modified in-place, so that each lighting normal
+//               is now facing in the opposite direction.
+//
+//               If the vertex data does not include a normal column,
+//               this returns the original GeomVertexData object,
+//               unchanged.
+////////////////////////////////////////////////////////////////////
+CPT(GeomVertexData) GeomVertexData::
+reverse_normals() const {
+  const GeomVertexColumn *old_column = 
+    get_format()->get_column(InternalName::get_normal());
+  if (old_column == (GeomVertexColumn *)NULL) {
+    return this;
+  }
+
+  PT(GeomVertexData) new_data = new GeomVertexData(*this);
+  GeomVertexRewriter to(new_data, InternalName::get_normal());
+  while (!to.is_at_end()) {
+    to.set_data3f(-to.get_data3f());
+  }
+
+  return new_data;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::animate_vertices
 //       Access: Published

+ 2 - 0
panda/src/gobj/geomVertexData.h

@@ -148,6 +148,8 @@ PUBLISHED:
     set_color(const Colorf &color, int num_components,
               NumericType numeric_type, Contents contents) const;
 
+  CPT(GeomVertexData) reverse_normals() const;
+
   CPT(GeomVertexData) animate_vertices(bool force, Thread *current_thread) const;
 
   PT(GeomVertexData) 

+ 25 - 0
panda/src/pgraph/geomTransformer.cxx

@@ -474,6 +474,31 @@ remove_column(GeomNode *node, const InternalName *column) {
   return any_changed;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomTransformer::reverse_normals
+//       Access: Public
+//  Description: Reverses the lighting normals on the vertex data, if
+//               any.  Returns true if the Geom was changed, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool GeomTransformer::
+reverse_normals(Geom *geom) {
+  nassertr(geom != (Geom *)NULL, false);
+  CPT(GeomVertexData) orig_data = geom->get_vertex_data();
+  CPT(GeomVertexData) &new_data = _reversed_normals[orig_data];
+  if (new_data.is_null()) {
+    new_data = orig_data->reverse_normals();
+  }
+
+  if (new_data == orig_data) {
+    // No change.
+    return false;
+  }
+
+  geom->set_vertex_data(new_data);
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomTransformer::collect_vertex_data
 //       Access: Public

+ 5 - 0
panda/src/pgraph/geomTransformer.h

@@ -75,6 +75,8 @@ public:
   bool remove_column(Geom *geom, const InternalName *column);
   bool remove_column(GeomNode *node, const InternalName *column);
 
+  bool reverse_normals(Geom *geom);
+
   int collect_vertex_data(Geom *geom, int collect_bits);
   int collect_vertex_data(GeomNode *node, int collect_bits);
 
@@ -131,6 +133,9 @@ private:
   typedef pmap<SourceFormat, PT(GeomVertexData) > NewFormat;
   NewFormat _format;
 
+  typedef pmap<CPT(GeomVertexData), CPT(GeomVertexData) > ReversedNormals;
+  ReversedNormals _reversed_normals;
+
   class AlreadyCollectedData {
   public:
     CPT(GeomVertexData) _data;

+ 126 - 1
panda/src/pgraph/sceneGraphReducer.cxx

@@ -29,6 +29,8 @@
 PStatCollector SceneGraphReducer::_flatten_collector("*:Flatten:flatten");
 PStatCollector SceneGraphReducer::_apply_collector("*:Flatten:apply");
 PStatCollector SceneGraphReducer::_remove_column_collector("*:Flatten:remove column");
+PStatCollector SceneGraphReducer::_doubleside_collector("*:Flatten:doubleside");
+PStatCollector SceneGraphReducer::_reverse_collector("*:Flatten:reverse");
 PStatCollector SceneGraphReducer::_collect_collector("*:Flatten:collect");
 PStatCollector SceneGraphReducer::_make_nonindexed_collector("*:Flatten:make nonindexed");
 PStatCollector SceneGraphReducer::_unify_collector("*:Flatten:unify");
@@ -143,6 +145,52 @@ remove_column(PandaNode *root, const InternalName *column) {
   return r_remove_column(root, column, _transformer);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SceneGraphReducer::doubleside
+//       Access: Published
+//  Description: Duplicates triangles in the GeomNodes at this level
+//               and below so that each triangle is back-to-back with
+//               another triangle facing in the opposite direction.
+//               If the geometry has vertex normals, this will also
+//               duplicate and reverse the normals, so that lighting
+//               will work correctly from both sides.  Note that
+//               calling this when the geometry is already doublesided
+//               (with back-to-back polygons) will result in multiple
+//               redundant coplanar polygons.
+//
+//               Also see CullFaceAttrib, which can enable rendering
+//               of both sides of a triangle without having to
+//               duplicate it (but which doesn't necessarily work in
+//               the presence of lighting).
+//
+//               Returns the number of GeomNodes modified.
+////////////////////////////////////////////////////////////////////
+int SceneGraphReducer::
+doubleside(PandaNode *root) {
+  PStatTimer timer(_doubleside_collector);
+  return r_doubleside(root, _transformer);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SceneGraphReducer::reverse
+//       Access: Published
+//  Description: Reverses the winding order in the GeomNodes at this
+//               level and below so that each triangle is facing in
+//               the opposite direction it was originally.  If the
+//               geometry has vertex normals, the normals will be
+//               reversed as well.
+//
+//               Also see CullFaceAttrib, which can change the visible
+//               direction of a triangle without having to duplicate
+//               it (but which doesn't necessarily work in the
+//               presence of lighting).
+////////////////////////////////////////////////////////////////////
+int SceneGraphReducer::
+reverse(PandaNode *root) {
+  PStatTimer timer(_reverse_collector);
+  return r_reverse(root, _transformer);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SceneGraphReducer::unify
 //       Access: Published
@@ -700,7 +748,6 @@ r_remove_column(PandaNode *node, const InternalName *column,
   int num_changed = 0;
 
   if (node->is_geom_node()) {
-    // When we come to a geom node, collect.
     if (transformer.remove_column(DCAST(GeomNode, node), column)) {
       ++num_changed;
     }
@@ -716,6 +763,84 @@ r_remove_column(PandaNode *node, const InternalName *column,
   return num_changed;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: SceneGraphReducer::r_doubleside
+//       Access: Private
+//  Description: The recursive implementation of doubleside().
+////////////////////////////////////////////////////////////////////
+int SceneGraphReducer::
+r_doubleside(PandaNode *node, GeomTransformer &transformer) {
+  int num_changed = 0;
+
+  if (node->is_geom_node()) {
+    PT(GeomNode) gnode = DCAST(GeomNode, node);
+    int num_geoms = gnode->get_num_geoms();
+    for (int i = 0; i < num_geoms; ++i) {
+      CPT(Geom) orig_geom = gnode->get_geom(i);
+      bool has_normals = (orig_geom->get_vertex_data()->has_column(InternalName::get_normal()));
+      if (has_normals) {
+        // If the geometry has normals, we have to duplicate it to
+        // reverse the normals on the duplicate copy.
+        PT(Geom) new_geom = orig_geom->reverse();
+        transformer.reverse_normals(new_geom);
+        gnode->add_geom(new_geom, gnode->get_geom_state(i));
+
+      } else {
+        // If there are no normals, we can just doubleside it in
+        // place.  This is preferable because we can share vertices.
+        orig_geom.clear();
+        gnode->modify_geom(i)->doubleside_in_place();
+      }
+    }
+
+    if (num_geoms != 0) {
+      ++num_changed;
+    }
+  }
+    
+  PandaNode::Children children = node->get_children();
+  int num_children = children.get_num_children();
+  for (int i = 0; i < num_children; ++i) {
+    num_changed +=
+      r_doubleside(children.get_child(i), transformer);
+  }
+
+  return num_changed;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SceneGraphReducer::r_reverse
+//       Access: Private
+//  Description: The recursive implementation of reverse().
+////////////////////////////////////////////////////////////////////
+int SceneGraphReducer::
+r_reverse(PandaNode *node, GeomTransformer &transformer) {
+  int num_changed = 0;
+
+  if (node->is_geom_node()) {
+    PT(GeomNode) gnode = DCAST(GeomNode, node);
+    int num_geoms = gnode->get_num_geoms();
+    for (int i = 0; i < num_geoms; ++i) {
+      PT(Geom) geom = gnode->modify_geom(i);
+      geom->reverse_in_place();
+      transformer.reverse_normals(geom);
+    }
+
+    if (num_geoms != 0) {
+      ++num_changed;
+    }
+  }
+    
+  PandaNode::Children children = node->get_children();
+  int num_children = children.get_num_children();
+  for (int i = 0; i < num_children; ++i) {
+    num_changed +=
+      r_reverse(children.get_child(i), transformer);
+  }
+
+  return num_changed;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: SceneGraphReducer::r_collect_vertex_data
 //       Access: Private

+ 6 - 0
panda/src/pgraph/sceneGraphReducer.h

@@ -136,6 +136,8 @@ PUBLISHED:
   int flatten(PandaNode *root, int combine_siblings_bits);
 
   int remove_column(PandaNode *root, const InternalName *column);
+  int doubleside(PandaNode *root);
+  int reverse(PandaNode *root);
 
   INLINE int collect_vertex_data(PandaNode *root, int collect_bits = ~0);
   INLINE int make_nonindexed(PandaNode *root, int nonindexed_bits = ~0);
@@ -170,6 +172,8 @@ protected:
 
   int r_remove_column(PandaNode *node, const InternalName *column,
                       GeomTransformer &transformer);
+  int r_doubleside(PandaNode *node, GeomTransformer &transformer);
+  int r_reverse(PandaNode *node, GeomTransformer &transformer);
 
   int r_collect_vertex_data(PandaNode *node, int collect_bits,
                             GeomTransformer &transformer);
@@ -186,6 +190,8 @@ private:
   static PStatCollector _flatten_collector;
   static PStatCollector _apply_collector;
   static PStatCollector _remove_column_collector;
+  static PStatCollector _doubleside_collector;
+  static PStatCollector _reverse_collector;
   static PStatCollector _collect_collector;
   static PStatCollector _make_nonindexed_collector;
   static PStatCollector _unify_collector;