Panda3D
 All Classes Functions Variables Enumerations
sceneGraphAnalyzer.cxx
1 // Filename: sceneGraphAnalyzer.cxx
2 // Created by: drose (02Jul00)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "sceneGraphAnalyzer.h"
16 #include "config_pgraph.h"
17 
18 #include "indent.h"
19 #include "lodNode.h"
20 #include "geomNode.h"
21 #include "geomVertexData.h"
22 #include "geom.h"
23 #include "geomPrimitive.h"
24 #include "geomPoints.h"
25 #include "geomLines.h"
26 #include "geomLinestrips.h"
27 #include "geomTriangles.h"
28 #include "geomTristrips.h"
29 #include "geomTrifans.h"
30 #include "geomPatches.h"
31 #include "transformState.h"
32 #include "textureAttrib.h"
33 #include "pta_ushort.h"
34 #include "geomVertexReader.h"
35 
36 ////////////////////////////////////////////////////////////////////
37 // Function: SceneGraphAnalyzer::Constructor
38 // Access: Published
39 // Description:
40 ////////////////////////////////////////////////////////////////////
41 SceneGraphAnalyzer::
42 SceneGraphAnalyzer() {
43  _lod_mode = LM_all;
44  clear();
45 }
46 
47 ////////////////////////////////////////////////////////////////////
48 // Function: SceneGraphAnalyzer::Destructor
49 // Access: Published
50 // Description:
51 ////////////////////////////////////////////////////////////////////
52 SceneGraphAnalyzer::
53 ~SceneGraphAnalyzer() {
54 }
55 
56 ////////////////////////////////////////////////////////////////////
57 // Function: SceneGraphAnalyzer::clear
58 // Access: Published
59 // Description: Resets all of the data in the analyzer in preparation
60 // for a new run.
61 ////////////////////////////////////////////////////////////////////
63 clear() {
64  _nodes.clear();
65  _vdatas.clear();
66  _vformats.clear();
67  _vadatas.clear();
68  _unique_vdatas.clear();
69  _unique_vadatas.clear();
70  _textures.clear();
71 
72  _num_nodes = 0;
73  _num_instances = 0;
74  _num_transforms = 0;
75  _num_nodes_with_attribs = 0;
76  _num_lod_nodes = 0;
77  _num_geom_nodes = 0;
78  _num_geoms = 0;
79  _num_geom_vertex_datas = 0;
80  _num_geom_vertex_formats = 0;
81  _vertex_data_size = 0;
82  _prim_data_size = 0;
83 
84  _num_vertices = 0;
85  _num_vertices_64 = 0;
86  _num_normals = 0;
87  _num_colors = 0;
88  _num_texcoords = 0;
89  _num_tris = 0;
90  _num_lines = 0;
91  _num_points = 0;
92  _num_patches = 0;
93 
94  _num_individual_tris = 0;
95  _num_tristrips = 0;
96  _num_triangles_in_strips = 0;
97  _num_trifans = 0;
98  _num_triangles_in_fans = 0;
99  _num_vertices_in_patches = 0;
100 
101  _texture_bytes = 0;
102 
103  _num_long_normals = 0;
104  _num_short_normals = 0;
105  _total_normal_length = 0.0f;
106 }
107 
108 ////////////////////////////////////////////////////////////////////
109 // Function: SceneGraphAnalyzer::add_node
110 // Access: Published
111 // Description: Adds a new node to the set of data for analysis.
112 // Normally, this would only be called once, and passed
113 // the top of the scene graph, but it's possible to
114 // repeatedly pass in subgraphs to get an analysis of
115 // all the graphs together.
116 ////////////////////////////////////////////////////////////////////
119  collect_statistics(node, false);
120 }
121 
122 ////////////////////////////////////////////////////////////////////
123 // Function: SceneGraphAnalyzer::write
124 // Access: Published
125 // Description: Describes all the data collected.
126 ////////////////////////////////////////////////////////////////////
128 write(ostream &out, int indent_level) const {
129  indent(out, indent_level)
130  << _num_nodes << " total nodes (including "
131  << _num_instances << " instances); " << _num_lod_nodes << " LODNodes.\n";
132 
133  indent(out, indent_level)
134  << _num_transforms << " transforms";
135 
136  if (_num_nodes != 0) {
137  out << "; " << 100 * _num_nodes_with_attribs / _num_nodes
138  << "% of nodes have some render attribute.";
139  }
140  out << "\n";
141 
142  indent(out, indent_level)
143  << _num_geoms << " Geoms, with " << _num_geom_vertex_datas
144  << " GeomVertexDatas and " << _num_geom_vertex_formats
145  << " GeomVertexFormats, appear on " << _num_geom_nodes
146  << " GeomNodes.\n";
147 
148  indent(out, indent_level);
149  if (_num_vertices_64 != 0) {
150  out << _num_vertices_64 << " 64-bit vertices, ";
151  if (_num_vertices != _num_vertices_64) {
152  out << _num_vertices - _num_vertices_64 << " 32-bit vertices, ";
153  }
154  } else {
155  out << _num_vertices << " vertices, ";
156  }
157 
158  out << _num_normals << " normals, "
159  << _num_colors << " colors, "
160  << _num_texcoords << " texture coordinates.\n";
161 
162  if (_num_long_normals != 0 || _num_short_normals != 0) {
163  indent(out, indent_level)
164  << _num_long_normals << " normals are too long, "
165  << _num_short_normals << " are too short. Average normal length is "
166  << _total_normal_length / (PN_stdfloat)_num_normals << "\n";
167  }
168 
169  indent(out, indent_level)
170  << "GeomVertexData arrays occupy " << (_vertex_data_size + 1023) / 1024
171  << "K memory.\n";
172 
173  indent(out, indent_level)
174  << "GeomPrimitive arrays occupy " << (_prim_data_size + 1023) / 1024
175  << "K memory.\n";
176 
177  int unreferenced_vertices = 0;
178  VDatas::const_iterator vdi;
179  for (vdi = _vdatas.begin(); vdi != _vdatas.end(); ++vdi) {
180  CPT(GeomVertexData) vdata = (*vdi).first;
181  const VDataTracker &tracker = (*vdi).second;
182  int num_unreferenced = vdata->get_num_rows() - tracker._referenced_vertices.get_num_on_bits();
183  nassertv(num_unreferenced >= 0);
184  unreferenced_vertices += num_unreferenced;
185  }
186  if (unreferenced_vertices != 0) {
187  indent(out, indent_level)
188  << unreferenced_vertices << " vertices are unreferenced by any GeomPrimitives.\n";
189  }
190  if (_unique_vdatas.size() != _vdatas.size()) {
191  indent(out, indent_level)
192  << _vdatas.size() - _unique_vdatas.size()
193  << " GeomVertexDatas are redundantly duplicated\n";
194  }
195  if (_unique_vadatas.size() != _vadatas.size()) {
196  int wasted_bytes = 0;
197 
198  UniqueVADatas::const_iterator uvai;
199  for (uvai = _unique_vadatas.begin();
200  uvai != _unique_vadatas.end();
201  ++uvai) {
202  const GeomVertexArrayData *gvad = (*uvai).first;
203  int dup_count = (*uvai).second;
204  if (dup_count > 1) {
205  wasted_bytes += (dup_count - 1) * gvad->get_data_size_bytes();
206  }
207  }
208  indent(out, indent_level)
209  << _vadatas.size() - _unique_vadatas.size()
210  << " GeomVertexArrayDatas are redundant, wasting "
211  << (wasted_bytes + 1023) / 1024 << "K.\n";
212  }
213  if (_unique_prim_vadatas.size() != _prim_vadatas.size()) {
214  int wasted_bytes = 0;
215 
216  UniqueVADatas::const_iterator uvai;
217  for (uvai = _unique_prim_vadatas.begin();
218  uvai != _unique_prim_vadatas.end();
219  ++uvai) {
220  const GeomVertexArrayData *gvad = (*uvai).first;
221  int dup_count = (*uvai).second;
222  if (dup_count > 1) {
223  wasted_bytes += (dup_count - 1) * gvad->get_data_size_bytes();
224  }
225  }
226  indent(out, indent_level)
227  << _prim_vadatas.size() - _unique_prim_vadatas.size()
228  << " GeomPrimitive arrays are redundant, wasting "
229  << (wasted_bytes + 1023) / 1024 << "K.\n";
230  }
231 
232  indent(out, indent_level)
233  << _num_tris << " triangles:\n";
234  indent(out, indent_level + 2)
235  << _num_triangles_in_strips
236  << " of these are on " << _num_tristrips << " tristrips";
237  if (_num_tristrips != 0) {
238  out << " ("
239  << (double)_num_triangles_in_strips / (double)_num_tristrips
240  << " average tris per strip)";
241  }
242  out << ".\n";
243 
244  if (_num_trifans != 0) {
245  indent(out, indent_level + 2)
246  << _num_triangles_in_fans
247  << " of these are on " << _num_trifans << " trifans";
248  if (_num_trifans != 0) {
249  out << " ("
250  << (double)_num_triangles_in_fans / (double)_num_trifans
251  << " average tris per fan)";
252  }
253  out << ".\n";
254  }
255 
256  indent(out, indent_level + 2)
257  << _num_individual_tris
258  << " of these are independent triangles.\n";
259 
260  if (_num_patches != 0) {
261  indent(out, indent_level)
262  << _num_patches << " patches ("
263  << (double)_num_vertices_in_patches / (double)_num_patches
264  << " average verts per patch).\n";
265  }
266 
267  if (_num_lines != 0 || _num_points != 0) {
268  indent(out, indent_level)
269  << _num_lines << " lines, " << _num_points << " points.\n";
270  }
271 
272  indent(out, indent_level)
273  << _textures.size() << " textures, estimated minimum "
274  << (_texture_bytes + 1023) / 1024 << "K texture memory required.\n";
275 }
276 
277 ////////////////////////////////////////////////////////////////////
278 // Function: SceneGraphAnalyzer::collect_statistics
279 // Access: Private
280 // Description: Recursively visits each node, counting up the
281 // statistics.
282 ////////////////////////////////////////////////////////////////////
283 void SceneGraphAnalyzer::
284 collect_statistics(PandaNode *node, bool under_instance) {
285  _num_nodes++;
286 
287  if (!under_instance) {
288  Nodes::iterator ni = _nodes.find(node);
289  if (ni == _nodes.end()) {
290  // This is the first time this node has been encountered.
291  _nodes.insert(Nodes::value_type(node, 1));
292  } else {
293  // This node has been encountered before; that makes it an
294  // instance.
295  (*ni).second++;
296  _num_instances++;
297  under_instance = true;
298  }
299  }
300 
301  if (!node->get_state()->is_empty()) {
302  _num_nodes_with_attribs++;
303  const RenderAttrib *attrib =
304  node->get_attrib(TextureAttrib::get_class_slot());
305  if (attrib != (RenderAttrib *)NULL) {
306  const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
307  for (int i = 0; i < ta->get_num_on_stages(); i++) {
308  collect_statistics(ta->get_on_texture(ta->get_on_stage(i)));
309  }
310  }
311  }
312  if (!node->get_transform()->is_identity()) {
313  _num_transforms++;
314  }
315 
316  if (node->is_geom_node()) {
317  collect_statistics(DCAST(GeomNode, node));
318  }
319 
320  if (node->is_lod_node()) {
321  LODNode *lod_node = DCAST(LODNode, node);
322  ++_num_lod_nodes;
323 
324  switch (_lod_mode) {
325  case LM_lowest:
326  case LM_highest:
327  {
328  int sw = (_lod_mode == LM_lowest) ? lod_node->get_lowest_switch() : lod_node->get_highest_switch();
329  if (sw >= 0 && sw < node->get_num_children()) {
330  PandaNode *child = node->get_child(sw);
331  collect_statistics(child, under_instance);
332  }
333  return;
334  }
335 
336  case LM_none:
337  return;
338 
339  case LM_all:
340  // fall through to the loop below.
341  break;
342  }
343  }
344 
345  int num_children = node->get_num_children();
346  for (int i = 0; i < num_children; i++) {
347  PandaNode *child = node->get_child(i);
348  collect_statistics(child, under_instance);
349  }
350 }
351 
352 ////////////////////////////////////////////////////////////////////
353 // Function: SceneGraphAnalyzer::collect_statistics
354 // Access: Private
355 // Description: Recursively visits each node, counting up the
356 // statistics.
357 ////////////////////////////////////////////////////////////////////
358 void SceneGraphAnalyzer::
359 collect_statistics(GeomNode *geom_node) {
360  nassertv(geom_node != (GeomNode *)NULL);
361 
362  ++_num_geom_nodes;
363 
364  int num_geoms = geom_node->get_num_geoms();
365  _num_geoms += num_geoms;
366 
367  for (int i = 0; i < num_geoms; i++) {
368  const Geom *geom = geom_node->get_geom(i);
369  collect_statistics(geom);
370 
371  const RenderState *geom_state = geom_node->get_geom_state(i);
372 
373  const RenderAttrib *attrib =
374  geom_state->get_attrib(TextureAttrib::get_class_slot());
375  if (attrib != (RenderAttrib *)NULL) {
376  const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
377  for (int i = 0; i < ta->get_num_on_stages(); i++) {
378  collect_statistics(ta->get_on_texture(ta->get_on_stage(i)));
379  }
380  }
381  }
382 }
383 
384 ////////////////////////////////////////////////////////////////////
385 // Function: SceneGraphAnalyzer::collect_statistics
386 // Access: Private
387 // Description: Recursively visits each node, counting up the
388 // statistics.
389 ////////////////////////////////////////////////////////////////////
390 void SceneGraphAnalyzer::
391 collect_statistics(const Geom *geom) {
392  CPT(GeomVertexData) vdata = geom->get_vertex_data();
393  pair<VDatas::iterator, bool> result = _vdatas.insert(VDatas::value_type(vdata, VDataTracker()));
394  if (result.second) {
395  // This is the first time we've encountered this vertex data.
396  ++_num_geom_vertex_datas;
397 
398  CPT(GeomVertexFormat) vformat = vdata->get_format();
399  bool format_inserted = _vformats.insert(vformat).second;
400  if (format_inserted) {
401  // This is the first time we've encountered this vertex format.
402  ++_num_geom_vertex_formats;
403  }
404 
405  int &dup_count = (*(_unique_vdatas.insert(UniqueVDatas::value_type(vdata, 0)).first)).second;
406  ++dup_count;
407 
408  int num_rows = vdata->get_num_rows();
409  const GeomVertexFormat *format = vdata->get_format();
410  if (format->has_column(InternalName::get_vertex())) {
411  _num_vertices += num_rows;
412  const GeomVertexColumn *vcolumn = format->get_column(InternalName::get_vertex());
413  if (vcolumn->get_numeric_type() == GeomEnums::NT_float64) {
414  _num_vertices_64 += num_rows;
415  }
416  }
417  if (format->has_column(InternalName::get_normal())) {
418  _num_normals += num_rows;
419  GeomVertexReader rnormal(vdata, InternalName::get_normal());
420  while (!rnormal.is_at_end()) {
421  LVector3f normal = rnormal.get_data3f();
422  float length = normal.length();
423  if (IS_NEARLY_EQUAL(length, 1.0f)) {
424  // Correct length normal.
425  } else if (length > 1.0f) {
426  ++_num_long_normals;
427  } else { // length < 1.0f
428  ++_num_short_normals;
429  }
430  _total_normal_length += length;
431  }
432  }
433  if (format->has_column(InternalName::get_color())) {
434  _num_colors += num_rows;
435  }
436  int num_texcoords = format->get_num_texcoords();
437  _num_texcoords += num_rows * num_texcoords;
438 
439  int num_arrays = vdata->get_num_arrays();
440  for (int i = 0; i < num_arrays; ++i) {
441  collect_statistics(vdata->get_array(i));
442  }
443  }
444  VDataTracker &tracker = (*(result.first)).second;
445 
446  // Now consider the primitives in the Geom.
447  int num_primitives = geom->get_num_primitives();
448  for (int i = 0; i < num_primitives; ++i) {
449  CPT(GeomPrimitive) prim = geom->get_primitive(i);
450 
451  int num_vertices = prim->get_num_vertices();
452  for (int vi = 0; vi < num_vertices; ++vi) {
453  tracker._referenced_vertices.set_bit(prim->get_vertex(vi));
454  }
455 
456  if (prim->is_indexed()) {
457  collect_prim_statistics(prim->get_vertices());
458  if (prim->is_composite()) {
459  collect_statistics(prim->get_mins());
460  collect_statistics(prim->get_maxs());
461  }
462  }
463 
464  if (prim->is_of_type(GeomPoints::get_class_type())) {
465  _num_points += prim->get_num_primitives();
466 
467  } else if (prim->is_of_type(GeomLines::get_class_type())) {
468  _num_lines += prim->get_num_primitives();
469 
470  } else if (prim->is_of_type(GeomLinestrips::get_class_type())) {
471  _num_lines += prim->get_num_faces();
472 
473  } else if (prim->is_of_type(GeomTriangles::get_class_type())) {
474  _num_tris += prim->get_num_primitives();
475  _num_individual_tris += prim->get_num_primitives();
476 
477  } else if (prim->is_of_type(GeomTristrips::get_class_type())) {
478  _num_tris += prim->get_num_faces();
479  _num_tristrips += prim->get_num_primitives();
480  _num_triangles_in_strips += prim->get_num_faces();
481 
482  } else if (prim->is_of_type(GeomTrifans::get_class_type())) {
483  _num_tris += prim->get_num_faces();
484  _num_trifans += prim->get_num_primitives();
485  _num_triangles_in_fans += prim->get_num_faces();
486 
487  } else if (prim->is_of_type(GeomPatches::get_class_type())) {
488  _num_patches += prim->get_num_primitives();
489  _num_vertices_in_patches += prim->get_num_vertices();
490 
491  } else {
492  pgraph_cat.warning()
493  << "Unknown GeomPrimitive type in SceneGraphAnalyzer: "
494  << prim->get_type() << "\n";
495  }
496  }
497 }
498 
499 ////////////////////////////////////////////////////////////////////
500 // Function: SceneGraphAnalyzer::collect_statistics
501 // Access: Private
502 // Description: Recursively visits each node, counting up the
503 // statistics.
504 ////////////////////////////////////////////////////////////////////
505 void SceneGraphAnalyzer::
506 collect_statistics(Texture *texture) {
507  nassertv(texture != (Texture *)NULL);
508 
509  Textures::iterator ti = _textures.find(texture);
510  if (ti == _textures.end()) {
511  // This is the first time this texture has been encountered.
512  _textures.insert(Textures::value_type(texture, 1));
513 
514  // Attempt to guess how many bytes of texture memory this one
515  // requires.
516  int bytes =
517  texture->get_x_size() * texture->get_y_size() *
518  texture->get_num_components() * texture->get_component_width();
519 
520  if (texture->uses_mipmaps()) {
521  bytes *= 4/3;
522  }
523 
524  _texture_bytes += bytes;
525 
526  } else {
527  // This texture has been encountered before; don't count it again.
528  (*ti).second++;
529  }
530 }
531 
532 ////////////////////////////////////////////////////////////////////
533 // Function: SceneGraphAnalyzer::collect_statistics
534 // Access: Private
535 // Description: Recursively visits each node, counting up the
536 // statistics.
537 ////////////////////////////////////////////////////////////////////
538 void SceneGraphAnalyzer::
539 collect_statistics(const GeomVertexArrayData *vadata) {
540  nassertv(vadata != NULL);
541  bool inserted = _vadatas.insert(vadata).second;
542  if (inserted) {
543  // This is the first time we've encountered this vertex array.
544  _vertex_data_size += vadata->get_data_size_bytes();
545  int &dup_count = (*(_unique_vadatas.insert(UniqueVADatas::value_type(vadata, 0)).first)).second;
546  ++dup_count;
547  }
548 }
549 
550 ////////////////////////////////////////////////////////////////////
551 // Function: SceneGraphAnalyzer::collect_prim_statistics
552 // Access: Private
553 // Description: Recursively visits each node, counting up the
554 // statistics. This one records the vertex index array
555 // associated with a GeomPrimitive, as opposed to the
556 // vertex data array, component of a GeomVertexData.
557 ////////////////////////////////////////////////////////////////////
558 void SceneGraphAnalyzer::
559 collect_prim_statistics(const GeomVertexArrayData *vadata) {
560  nassertv(vadata != NULL);
561  bool inserted = _prim_vadatas.insert(vadata).second;
562  if (inserted) {
563  // This is the first time we've encountered this vertex array.
564  _prim_data_size += vadata->get_data_size_bytes();
565  int &dup_count = (*(_unique_prim_vadatas.insert(UniqueVADatas::value_type(vadata, 0)).first)).second;
566  ++dup_count;
567  }
568 }
A basic node of the scene graph or data graph.
Definition: pandaNode.h:72
This is the base class for a number of render attributes (other than transform) that may be set on sc...
Definition: renderAttrib.h:60
void add_node(PandaNode *node)
Adds a new node to the set of data for analysis.
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:75
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:63
int get_data_size_bytes() const
Returns the number of bytes stored in the array.
int get_num_on_stages() const
Returns the number of stages that are turned on by the attribute.
Definition: textureAttrib.I:91
This is a three-component vector distance (as opposed to a three-component point, which represents a ...
Definition: lvector3.h:100
virtual bool is_lod_node() const
A simple downcast check.
Definition: pandaNode.cxx:2501
TextureStage * get_on_stage(int n) const
Returns the nth stage turned on by the attribute, sorted in render order.
void clear()
Resets all of the data in the analyzer in preparation for a new run.
bool uses_mipmaps() const
Returns true if the minfilter settings on this texture indicate the use of mipmapping, false otherwise.
Definition: texture.I:1319
This defines how a single column is interleaved within a vertex array stored within a Geom...
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
Definition: textureAttrib.h:34
float length() const
Returns the length of the vector, by the Pythagorean theorem.
Definition: lvecBase3.h:765
A Level-of-Detail node.
Definition: lodNode.h:31
int get_num_components() const
Returns the number of color components for each texel of the texture image.
Definition: texture.I:835
int get_num_children(Thread *current_thread=Thread::get_current_thread()) const
Returns the number of child nodes this node has.
Definition: pandaNode.I:68
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
A container for geometry primitives.
Definition: geom.h:58
int get_lowest_switch() const
Returns the index number of the child with the lowest level of detail; that is, the one that is desig...
Definition: lodNode.I:183
Texture * get_on_texture(TextureStage *stage) const
Returns the texture associated with the indicated stage, or NULL if no texture is associated...
void write(ostream &out, int indent_level=0) const
Describes all the data collected.
const RenderAttrib * get_attrib(TypeHandle type) const
Looks for a RenderAttrib of the indicated type in the state, and returns it if it is found...
Definition: renderState.I:134
const RenderState * get_geom_state(int n) const
Returns the RenderState associated with the nth geom of the node.
Definition: geomNode.I:106
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:53
PandaNode * get_child(int n, Thread *current_thread=Thread::get_current_thread()) const
Returns the nth child node of this node.
Definition: pandaNode.I:82
This object provides a high-level interface for quickly reading a sequence of numeric values from a v...
NumericType get_numeric_type() const
Returns the token representing the numeric type of the data storage.
int get_component_width() const
Returns the number of bytes stored for each color component of a texel.
Definition: texture.I:848
virtual bool is_geom_node() const
A simple downcast check.
Definition: pandaNode.cxx:2486
int get_y_size() const
Returns the height of the texture image in texels.
Definition: texture.I:650
int get_x_size() const
Returns the width of the texture image in texels.
Definition: texture.I:638
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:37
int get_highest_switch() const
Returns the index number of the child with the highest level of detail; that is, the one that is desi...
Definition: lodNode.I:197
int get_num_geoms() const
Returns the number of geoms in the node.
Definition: geomNode.I:46
This is the data for one array of a GeomVertexData structure.
int get_num_primitives() const
Returns the number of GeomPrimitive objects stored within the Geom, each of which represents a number...
Definition: geom.I:110
int get_num_rows() const
Returns the number of rows stored within all the arrays.