Browse Source

*** empty log message ***

David Rose 25 years ago
parent
commit
66e460dbaf

+ 8 - 3
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);
     }
   }
 

+ 1 - 1
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();

+ 9 - 2
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);
 }

+ 48 - 16
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;

+ 3 - 3
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:
 

+ 6 - 0
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) {

+ 1 - 0
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();
 

+ 62 - 41
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<Vertexf> 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<Vertexf> 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) {
-    return NULL;
+    if (vertices.size() >= 3) {
+      CollisionPolygon *cspoly = 
+	new CollisionPolygon(vertices.begin(), vertices.end());
+      apply_collision_flags(cspoly, flags);
+      cnode->add_solid(cspoly);
+    }
   }
-  return new CollisionPolygon(vertices.begin(), vertices.end());
 }
 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggLoader::apply_deferred_arcs
 //       Access: Private

+ 6 - 3
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;
 };
 
 

+ 6 - 0
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);

+ 6 - 5
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();

+ 3 - 1
panda/src/graph/matrixTransition.I

@@ -112,7 +112,9 @@ invert() const {
   if (_matrix.almost_equal(Matrix::ident_mat())) {
     return (MatrixTransition<Matrix> *)this;
   }
-  return make_with_matrix(::invert(_matrix));
+  Matrix inverted;
+  inverted.invert_from(_matrix);
+  return make_with_matrix(inverted);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 12
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;
 }
 
 ////////////////////////////////////////////////////////////////////

+ 41 - 15
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;
 
-  nassertr(next != (const ArcComponent *)NULL, false);
-  nassertr(other != (const ArcComponent *)NULL, false);
+  } else if (a == (const ArcComponent *)NULL) {
+    return -1;
 
-  return (next == other ||
-	  (next->_arc == other->_arc && r_equiv(next->_next, other->_next)));
+  } 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);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

+ 2 - 1
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;

+ 14 - 0
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

+ 1 - 0
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;