diff --git a/panda/src/egg/eggGroupNode.cxx b/panda/src/egg/eggGroupNode.cxx index 29665f437f..507fe33342 100644 --- a/panda/src/egg/eggGroupNode.cxx +++ b/panda/src/egg/eggGroupNode.cxx @@ -493,9 +493,14 @@ strip_normals() { // the scene graph and below with triangles. Returns // the total number of new triangles produced, less // degenerate polygons removed. +// +// If convex_also is true, both concave and convex +// polygons will be subdivided into triangles; +// otherwise, only concave polygons will be subdivided, +// and convex polygons will be largely unchanged. //////////////////////////////////////////////////////////////////// int EggGroupNode:: -triangulate_polygons() { +triangulate_polygons(bool convex_also) { int num_produced = 0; Children children_copy = _children; @@ -508,10 +513,10 @@ triangulate_polygons() { if (child->is_of_type(EggPolygon::get_class_type())) { EggPolygon *poly = DCAST(EggPolygon, child); - poly->triangulate_in_place(); + poly->triangulate_in_place(convex_also); } else if (child->is_of_type(EggGroupNode::get_class_type())) { - num_produced += DCAST(EggGroupNode, child)->triangulate_polygons(); + num_produced += DCAST(EggGroupNode, child)->triangulate_polygons(convex_also); } } diff --git a/panda/src/egg/eggGroupNode.h b/panda/src/egg/eggGroupNode.h index 0c6a13f0ae..23e3458d81 100644 --- a/panda/src/egg/eggGroupNode.h +++ b/panda/src/egg/eggGroupNode.h @@ -101,7 +101,7 @@ public: void recompute_polygon_normals(); void strip_normals(); - int triangulate_polygons(); + int triangulate_polygons(bool convex_also); int remove_unused_vertices(); int remove_invalid_primitives(); diff --git a/panda/src/egg/eggPolygon.I b/panda/src/egg/eggPolygon.I index c375a00601..ad082930aa 100644 --- a/panda/src/egg/eggPolygon.I +++ b/panda/src/egg/eggPolygon.I @@ -65,8 +65,15 @@ recompute_polygon_normal() { // Returns true if the triangulation is successful, or // false if there was some error (in which case the // container may contain some partial triangulation). +// +// If convex_also is true, both concave and convex +// polygons will be subdivided into triangles; +// otherwise, only concave polygons will be subdivided, +// and convex polygons will be copied unchanged into the +// container. //////////////////////////////////////////////////////////////////// INLINE bool EggPolygon:: -triangulate_into(EggGroupNode *container) const { - return triangulate_poly(container); +triangulate_into(EggGroupNode *container, bool convex_also) const { + EggPolygon *copy = new EggPolygon(*this); + return copy->triangulate_poly(container, convex_also); } diff --git a/panda/src/egg/eggPolygon.cxx b/panda/src/egg/eggPolygon.cxx index 29ed5ccbde..e42d8ec972 100644 --- a/panda/src/egg/eggPolygon.cxx +++ b/panda/src/egg/eggPolygon.cxx @@ -95,18 +95,21 @@ calculate_normal(Normald &result) const { // triangles to the parent group node in place of the // original polygon. Returns a pointer to the original // polygon, which is likely about to be destructed. +// +// If convex_also is true, both concave and convex +// polygons will be subdivided into triangles; +// otherwise, only concave polygons will be subdivided, +// and convex polygons will be copied unchanged into the +// container. //////////////////////////////////////////////////////////////////// PT(EggPolygon) EggPolygon:: -triangulate_in_place() { +triangulate_in_place(bool convex_also) { EggGroupNode *parent = get_parent(); nassertr(parent != (EggGroupNode *)NULL, this); PT(EggPolygon) save_me = this; parent->remove_child(this); - - if (cleanup()) { - triangulate_into(parent); - } + triangulate_poly(parent, convex_also); return save_me; } @@ -253,13 +256,16 @@ decomp_concave(EggGroupNode *container, int asum, int x, int y) const { } else { // Here's a triangle to output. - EggPolygon *triangle = new EggPolygon(*this); - container->add_child(triangle); + PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(p0->index)); triangle->add_vertex(get_vertex(p1->index)); triangle->add_vertex(get_vertex(p2->index)); + if (triangle->cleanup()) { + container->add_child(triangle.p()); + } + p0->next = p1->next; p1 = p2; p2 = p2->next; @@ -273,13 +279,16 @@ decomp_concave(EggGroupNode *container, int asum, int x, int y) const { } // One more triangle to output. - EggPolygon *triangle = new EggPolygon(*this); - container->add_child(triangle); + PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(p0->index)); triangle->add_vertex(get_vertex(p1->index)); triangle->add_vertex(get_vertex(p2->index)); + if (triangle->cleanup()) { + container->add_child(triangle.p()); + } + return true; } @@ -292,18 +301,27 @@ decomp_concave(EggGroupNode *container, int asum, int x, int y) const { // container up with EggPolygons that represent the // triangles. Returns true if successful, false on // failure. +// +// If convex_also is true, both concave and convex +// polygons will be subdivided into triangles; +// otherwise, only concave polygons will be subdivided, +// and convex polygons will be copied unchanged into the +// container. //////////////////////////////////////////////////////////////////// bool EggPolygon:: -triangulate_poly(EggGroupNode *container) const { +triangulate_poly(EggGroupNode *container, bool convex_also) { LPoint3d p0, p1, as; double dx1, dy1, dx2, dy2, max; int i, flag, asum, csum, index, x, y, v0, v1, v, even; + + if (!cleanup()) { + return false; + } // First see if the polygon is already a triangle int num_verts = size(); if (num_verts == 3) { - EggPolygon *triangle = new EggPolygon(*this); - container->add_child(triangle); + container->add_child(this); return true; @@ -386,10 +404,18 @@ triangulate_poly(EggGroupNode *container) const { csum = ((dx1 * dy2 - dx2 * dy1 >= 0.0) ? 1 : 0); if (csum ^ asum) { + // It's a concave polygon. This is a little harder. return decomp_concave(container, flag, x, y); } } + // It's a convex polygon. + if (!convex_also) { + container->add_child(this); + + return true; + } + v0 = 0; v1 = 1; v = num_verts - 1; @@ -402,24 +428,30 @@ triangulate_poly(EggGroupNode *container) const { */ for (i = 0; i < num_verts - 2; i++) { if (even) { - EggPolygon *triangle = new EggPolygon(*this); - container->add_child(triangle); + PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(v0)); triangle->add_vertex(get_vertex(v1)); triangle->add_vertex(get_vertex(v)); + if (triangle->cleanup()) { + container->add_child(triangle.p()); + } + v0 = v1; v1 = v; v = v0 + 1; } else { - EggPolygon *triangle = new EggPolygon(*this); - container->add_child(triangle); + PT(EggPolygon) triangle = new EggPolygon(*this); triangle->clear(); triangle->add_vertex(get_vertex(v1)); triangle->add_vertex(get_vertex(v0)); triangle->add_vertex(get_vertex(v)); + if (triangle->cleanup()) { + container->add_child(triangle.p()); + } + v0 = v1; v1 = v; v = v0 - 1; diff --git a/panda/src/egg/eggPolygon.h b/panda/src/egg/eggPolygon.h index ce868fe4af..1bdce86dc2 100644 --- a/panda/src/egg/eggPolygon.h +++ b/panda/src/egg/eggPolygon.h @@ -25,14 +25,14 @@ public: bool calculate_normal(Normald &result) const; INLINE bool recompute_polygon_normal(); - INLINE bool triangulate_into(EggGroupNode *container) const; - PT(EggPolygon) triangulate_in_place(); + INLINE bool triangulate_into(EggGroupNode *container, bool convex_also) const; + PT(EggPolygon) triangulate_in_place(bool convex_also); virtual void write(ostream &out, int indent_level) const; private: bool decomp_concave(EggGroupNode *container, int asum, int x, int y) const; - bool triangulate_poly(EggGroupNode *container) const; + bool triangulate_poly(EggGroupNode *container, bool convex_also); public: diff --git a/panda/src/egg2sg/config_egg2sg.cxx b/panda/src/egg2sg/config_egg2sg.cxx index 1324bb1837..cb986ae289 100644 --- a/panda/src/egg2sg/config_egg2sg.cxx +++ b/panda/src/egg2sg/config_egg2sg.cxx @@ -55,6 +55,12 @@ bool egg_keep_texture_pathnames = config_egg2sg.GetBool("egg-keep-texture-pathna // actually a NurbsPPCurve object. bool egg_load_classic_nurbs_curves = config_egg2sg.GetBool("egg-load-classic-nurbs-curves", false); +// When this is true, certain kinds of recoverable errors (not syntax +// errors) in an egg file will be allowed and ignored when an egg file +// is loaded. When it is false, only perfectly pristine egg files may +// be loaded. +bool egg_accept_errors = config_egg2sg.GetBool("egg-accept-errors", true); + CoordinateSystem egg_coordinate_system; ConfigureFn(config_egg2sg) { diff --git a/panda/src/egg2sg/config_egg2sg.h b/panda/src/egg2sg/config_egg2sg.h index f2fa1013ce..f2fe600ac7 100644 --- a/panda/src/egg2sg/config_egg2sg.h +++ b/panda/src/egg2sg/config_egg2sg.h @@ -41,6 +41,7 @@ extern EXPCL_PANDAEGG bool egg_flatten_siblings; extern EXPCL_PANDAEGG bool egg_show_collision_solids; extern EXPCL_PANDAEGG bool egg_keep_texture_pathnames; extern EXPCL_PANDAEGG bool egg_load_classic_nurbs_curves; +extern EXPCL_PANDAEGG bool egg_accept_errors; extern EXPCL_PANDAEGG void init_libegg2sg(); diff --git a/panda/src/egg2sg/eggLoader.cxx b/panda/src/egg2sg/eggLoader.cxx index d74dd512d4..622706c31a 100644 --- a/panda/src/egg2sg/eggLoader.cxx +++ b/panda/src/egg2sg/eggLoader.cxx @@ -99,6 +99,7 @@ EggLoader:: EggLoader() { // We need to enforce whatever coordinate system the user asked for. _data.set_coordinate_system(egg_coordinate_system); + _error = false; } //////////////////////////////////////////////////////////////////// @@ -110,6 +111,7 @@ EggLoader:: EggLoader(const EggData &data) : _data(data) { + _error = false; } @@ -1578,7 +1580,7 @@ make_collision_plane(EggGroup *egg_group, CollisionNode *cnode, for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { CollisionPlane *csplane = - create_collision_plane(DCAST(EggPolygon, *ci)); + create_collision_plane(DCAST(EggPolygon, *ci), egg_group); if (csplane != (CollisionPlane *)NULL) { apply_collision_flags(csplane, flags); cnode->add_solid(csplane); @@ -1604,13 +1606,8 @@ make_collision_polygon(EggGroup *egg_group, CollisionNode *cnode, EggGroup::const_iterator ci; for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { - CollisionPolygon *cspoly = - create_collision_polygon(DCAST(EggPolygon, *ci)); - if (cspoly != (CollisionPolygon *)NULL) { - apply_collision_flags(cspoly, flags); - cnode->add_solid(cspoly); - return; - } + create_collision_polygons(cnode, DCAST(EggPolygon, *ci), + egg_group, flags); } } } @@ -1630,12 +1627,8 @@ make_collision_polyset(EggGroup *egg_group, CollisionNode *cnode, EggGroup::const_iterator ci; for (ci = geom_group->begin(); ci != geom_group->end(); ++ci) { if ((*ci)->is_of_type(EggPolygon::get_class_type())) { - CollisionPolygon *cspoly = - create_collision_polygon(DCAST(EggPolygon, *ci)); - if (cspoly != (CollisionPolygon *)NULL) { - apply_collision_flags(cspoly, flags); - cnode->add_solid(cspoly); - } + create_collision_polygons(cnode, DCAST(EggPolygon, *ci), + egg_group, flags); } } } @@ -1775,8 +1768,12 @@ find_collision_geometry(EggGroup *egg_group) { // EggPolygon. //////////////////////////////////////////////////////////////////// CollisionPlane *EggLoader:: -create_collision_plane(EggPolygon *egg_poly) { +create_collision_plane(EggPolygon *egg_poly, EggGroup *parent_group) { if (!egg_poly->cleanup()) { + egg2sg_cat.warning() + << "Degenerate collision plane in " << parent_group->get_name() + << "\n"; + _error = true; return NULL; } @@ -1809,44 +1806,68 @@ create_collision_plane(EggPolygon *egg_poly) { } //////////////////////////////////////////////////////////////////// -// Function: EggLoader::create_collision_polygon +// Function: EggLoader::create_collision_polygons // Access: Private -// Description: Creates a single CollisionPolygon from the indicated -// EggPolygon. +// Description: Creates one or more CollisionPolygons from the +// indicated EggPolygon, and adds them to the indicated +// CollisionNode. //////////////////////////////////////////////////////////////////// -CollisionPolygon *EggLoader:: -create_collision_polygon(EggPolygon *egg_poly) { - if (!egg_poly->cleanup()) { - return NULL; +void EggLoader:: +create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly, + EggGroup *parent_group, + EggGroup::CollideFlags flags) { + + PT(EggGroup) group = new EggGroup; + if (!egg_poly->triangulate_into(group, false)) { + egg2sg_cat.warning() + << "Degenerate collision polygon in " << parent_group->get_name() + << "\n"; + _error = true; + return; } - vector vertices; - if (!egg_poly->empty()) { - EggPolygon::const_iterator vi; - vi = egg_poly->begin(); + if (group->size() != 1) { + egg2sg_cat.warning() + << "Concave collision polygon in " << parent_group->get_name() + << "\n"; + _error = true; + } - Vertexd vert = (*vi)->get_pos3(); - vertices.push_back(LCAST(float, vert)); - - Vertexd last_vert = vert; - ++vi; - while (vi != egg_poly->end()) { - vert = (*vi)->get_pos3(); - if (!vert.almost_equal(last_vert)) { - vertices.push_back(LCAST(float, vert)); - } + EggGroup::iterator ci; + for (ci = group->begin(); ci != group->end(); ++ci) { + EggPolygon *poly = DCAST(EggPolygon, *ci); - last_vert = vert; + vector vertices; + if (!poly->empty()) { + EggPolygon::const_iterator vi; + vi = poly->begin(); + + Vertexd vert = (*vi)->get_pos3(); + vertices.push_back(LCAST(float, vert)); + + Vertexd last_vert = vert; ++vi; + while (vi != poly->end()) { + vert = (*vi)->get_pos3(); + if (!vert.almost_equal(last_vert)) { + vertices.push_back(LCAST(float, vert)); + } + + last_vert = vert; + ++vi; + } + } + + if (vertices.size() >= 3) { + CollisionPolygon *cspoly = + new CollisionPolygon(vertices.begin(), vertices.end()); + apply_collision_flags(cspoly, flags); + cnode->add_solid(cspoly); } } - - if (vertices.size() < 3) { - return NULL; - } - return new CollisionPolygon(vertices.begin(), vertices.end()); } + //////////////////////////////////////////////////////////////////// // Function: EggLoader::apply_deferred_arcs // Access: Private diff --git a/panda/src/egg2sg/eggLoader.h b/panda/src/egg2sg/eggLoader.h index 9981ac3e64..3b3f2fbdb6 100644 --- a/panda/src/egg2sg/eggLoader.h +++ b/panda/src/egg2sg/eggLoader.h @@ -53,7 +53,6 @@ public: void reparent_decals(); void reset_directs(); - void make_nonindexed_primitive(EggPrimitive *egg_prim, NamedNode *parent, const LMatrix4d *transform = NULL); @@ -100,8 +99,11 @@ private: void apply_collision_flags(CollisionSolid *solid, EggGroup::CollideFlags flags); EggGroup *find_collision_geometry(EggGroup *egg_group); - CollisionPlane *create_collision_plane(EggPolygon *egg_poly); - CollisionPolygon *create_collision_polygon(EggPolygon *egg_poly); + CollisionPlane *create_collision_plane(EggPolygon *egg_poly, + EggGroup *parent_group); + void create_collision_polygons(CollisionNode *cnode, EggPolygon *egg_poly, + EggGroup *parent_group, + EggGroup::CollideFlags flags); void apply_deferred_arcs(Node *root); @@ -125,6 +127,7 @@ private: public: PT_NamedNode _root; EggData _data; + bool _error; }; diff --git a/panda/src/egg2sg/load_egg_file.cxx b/panda/src/egg2sg/load_egg_file.cxx index 18fd5fb548..25e8856b6a 100644 --- a/panda/src/egg2sg/load_egg_file.cxx +++ b/panda/src/egg2sg/load_egg_file.cxx @@ -16,6 +16,12 @@ load_from_loader(EggLoader &loader) { loader.build_graph(); + if (loader._error && !egg_accept_errors) { + egg2sg_cat.error() + << "Errors in egg file.\n"; + return NULL; + } + if (loader._root != (NamedNode *)NULL && egg_flatten) { SceneGraphReducer gr(RenderRelation::get_class_type()); int num_reduced = gr.flatten(loader._root, egg_flatten_siblings); diff --git a/panda/src/glgsg/glGraphicsStateGuardian.cxx b/panda/src/glgsg/glGraphicsStateGuardian.cxx index 3ae9be9307..41c7b9526d 100644 --- a/panda/src/glgsg/glGraphicsStateGuardian.cxx +++ b/panda/src/glgsg/glGraphicsStateGuardian.cxx @@ -126,11 +126,12 @@ issue_transformed_color_gl(const Geom *geom, Geom::ColorIterator &citerator, const GLGraphicsStateGuardian *glgsg = DCAST(GLGraphicsStateGuardian, gsg); const Colorf &color = geom->get_next_color(citerator); - //This is a little cheat here just for a slight bit of - //efficiency. We don't want/need to transform the alpha by the color - //matrix, so use a Vertexf (which is only 3 component) and multiply - //this by the matrix, that will by us 1 dot product - Vertexf temp(color[0], color[1], color[2]); + // To be truly general, we really need a 5x5 matrix to transform a + // 4-component color. Rather than messing with that, we instead + // treat the color as a 3-component RGB, which can be transformed by + // the ordinary 4x4 matrix, and a separate alpha value, which can be + // scaled and offsetted. + LPoint3f temp(color[0], color[1], color[2]); temp = temp * glgsg->get_current_color_mat(); float alpha = (color[3] * glgsg->get_current_alpha_scale()) + glgsg->get_current_alpha_offset(); diff --git a/panda/src/graph/matrixTransition.I b/panda/src/graph/matrixTransition.I index bba9829bf2..542f23ddb9 100644 --- a/panda/src/graph/matrixTransition.I +++ b/panda/src/graph/matrixTransition.I @@ -112,7 +112,9 @@ invert() const { if (_matrix.almost_equal(Matrix::ident_mat())) { return (MatrixTransition *)this; } - return make_with_matrix(::invert(_matrix)); + Matrix inverted; + inverted.invert_from(_matrix); + return make_with_matrix(inverted); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/sgmanip/nodePath.I b/panda/src/sgmanip/nodePath.I index 0e62ce9d73..ca2442cedc 100644 --- a/panda/src/sgmanip/nodePath.I +++ b/panda/src/sgmanip/nodePath.I @@ -136,18 +136,7 @@ INLINE NodePath:: //////////////////////////////////////////////////////////////////// INLINE bool NodePath:: operator == (const NodePath &other) const { - // If either is a singleton, they must both be the same singleton. - if (_head == (ArcComponent *)NULL || - other._head == (ArcComponent *)NULL) { - if (_head != (ArcComponent *)NULL || - other._head != (ArcComponent *)NULL || - _top_node != other._top_node) { - return false; - } - } - - // If both has at least one arc, check the list of arcs. - return r_equiv(_head, other._head); + return compare_to(other) == 0; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/sgmanip/nodePath.cxx b/panda/src/sgmanip/nodePath.cxx index 98f052851c..469a3e93aa 100644 --- a/panda/src/sgmanip/nodePath.cxx +++ b/panda/src/sgmanip/nodePath.cxx @@ -47,6 +47,27 @@ public: GraphicsStateGuardianBase *_gsg; }; +//////////////////////////////////////////////////////////////////// +// Function: NodePath::compare_to +// Access: Public +// Description: Returns a number less than zero if this NodePath +// sorts before the indicated NodePath in an arbitrary +// lexicographical comparision, greater than zero if +// this one sorts after the other one, or zero if the +// two NodePaths are equivalent. +//////////////////////////////////////////////////////////////////// +int NodePath:: +compare_to(const NodePath &other) const { + if (_head == (ArcComponent *)NULL && other._head == (ArcComponent *)NULL) { + // If both are singletons, compare the pointers. + return _top_node - other._top_node; + + } else { + // Otherwise, compare the arc chains. + return r_compare_to(_head, other._head); + } +} + //////////////////////////////////////////////////////////////////// // Function: NodePath::extend_by // Access: Public @@ -2430,24 +2451,29 @@ write_bounds(ostream &out) const { } //////////////////////////////////////////////////////////////////// -// Function: NodePath::r_equiv -// Access: Private -// Description: The recursive implementation of operator ==. Returns -// true if the list of arcs is equivalent, false -// otherwise. +// Function: NodePath::r_compare_to +// Access: Private, Static +// Description: The recursive implementation of compare_to(). Returns +// < 0 if a sorts before b, > 0 if b sorts before a, or +// == 0 if they are equivalent. //////////////////////////////////////////////////////////////////// -bool NodePath:: -r_equiv(const ArcComponent *next, const ArcComponent *other) const { - if (next == (const ArcComponent *)NULL) { - nassertr(other == (const ArcComponent *)NULL, false); - return true; +int NodePath:: +r_compare_to(const ArcComponent *a, const ArcComponent *b) { + if (a == b) { + return 0; + + } else if (a == (const ArcComponent *)NULL) { + return -1; + + } else if (b == (const ArcComponent *)NULL) { + return 1; + + } else if (a->_arc != b->_arc) { + return a->_arc - b->_arc; + + } else { + return r_compare_to(a->_next, b->_next); } - - nassertr(next != (const ArcComponent *)NULL, false); - nassertr(other != (const ArcComponent *)NULL, false); - - return (next == other || - (next->_arc == other->_arc && r_equiv(next->_next, other->_next))); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/sgmanip/nodePath.h b/panda/src/sgmanip/nodePath.h index 6d747f3a68..e03e3cacfc 100644 --- a/panda/src/sgmanip/nodePath.h +++ b/panda/src/sgmanip/nodePath.h @@ -122,6 +122,7 @@ PUBLISHED: INLINE bool operator == (const NodePath &other) const; INLINE bool operator != (const NodePath &other) const; + int compare_to(const NodePath &other) const; INLINE void set_graph_type(TypeHandle graph_type); INLINE TypeHandle get_graph_type() const; @@ -465,7 +466,7 @@ public: }; private: - bool r_equiv(const ArcComponent *next, const ArcComponent *other) const; + static int r_compare_to(const ArcComponent *a, const ArcComponent *v); bool r_extend_by(const ArcComponent *other); int r_as_string(const ArcComponent *comp, string &result, int skip_nodes) const; diff --git a/panda/src/sgmanip/nodePathCollection.cxx b/panda/src/sgmanip/nodePathCollection.cxx index 27175de074..ef3dab5b83 100644 --- a/panda/src/sgmanip/nodePathCollection.cxx +++ b/panda/src/sgmanip/nodePathCollection.cxx @@ -221,6 +221,20 @@ get_path(int index) const { return NodePath(_node_paths[index]); } +//////////////////////////////////////////////////////////////////// +// Function: NodePathCollection::operator [] +// Access: Published +// Description: Returns the nth NodePath in the collection. This is +// the same as get_path(), but it may be a more +// convenient way to access it. +//////////////////////////////////////////////////////////////////// +NodePath NodePathCollection:: +operator [] (int index) const { + nassertr(index >= 0 && index < (int)_node_paths.size(), NodePath()); + + return NodePath(_node_paths[index]); +} + //////////////////////////////////////////////////////////////////// // Function: NodePathCollection::ls // Access: Published diff --git a/panda/src/sgmanip/nodePathCollection.h b/panda/src/sgmanip/nodePathCollection.h index ed993bf6d1..3a5b58db12 100644 --- a/panda/src/sgmanip/nodePathCollection.h +++ b/panda/src/sgmanip/nodePathCollection.h @@ -41,6 +41,7 @@ PUBLISHED: bool is_empty() const; int get_num_paths() const; NodePath get_path(int index) const; + NodePath operator [] (int index) const; // Handy operations on many NodePaths at once. INLINE void ls() const;