Panda3D
geomTransformer.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file geomTransformer.cxx
10  * @author drose
11  * @date 2002-03-14
12  */
13 
14 #include "geomTransformer.h"
15 #include "sceneGraphReducer.h"
16 #include "geomNode.h"
17 #include "geom.h"
18 #include "geomVertexRewriter.h"
19 #include "renderState.h"
20 #include "transformTable.h"
21 #include "transformBlendTable.h"
22 #include "sliderTable.h"
23 #include "pStatCollector.h"
24 #include "pStatTimer.h"
25 #include "vector_int.h"
26 #include "userVertexTransform.h"
27 #include "geomMunger.h"
28 #include "texture.h"
29 #include "texturePeeker.h"
30 #include "textureAttrib.h"
31 #include "colorAttrib.h"
32 #include "config_pgraph.h"
33 
34 PStatCollector GeomTransformer::_apply_vertex_collector("*:Flatten:apply:vertex");
35 PStatCollector GeomTransformer::_apply_texcoord_collector("*:Flatten:apply:texcoord");
36 PStatCollector GeomTransformer::_apply_set_color_collector("*:Flatten:apply:set color");
37 PStatCollector GeomTransformer::_apply_scale_color_collector("*:Flatten:apply:scale color");
38 PStatCollector GeomTransformer::_apply_texture_color_collector("*:Flatten:apply:texture color");
39 PStatCollector GeomTransformer::_apply_set_format_collector("*:Flatten:apply:set format");
40 
41 TypeHandle GeomTransformer::NewCollectedData::_type_handle;
42 
43 /**
44  *
45  */
46 GeomTransformer::
47 GeomTransformer() :
48  // The default value here comes from the Config file.
49  _max_collect_vertices(max_collect_vertices)
50 {
51 }
52 
53 /**
54  *
55  */
56 GeomTransformer::
57 GeomTransformer(const GeomTransformer &copy) :
58  _max_collect_vertices(copy._max_collect_vertices)
59 {
60 }
61 
62 /**
63  *
64  */
65 GeomTransformer::
66 ~GeomTransformer() {
67  finish_collect(false);
68 }
69 
70 /**
71  * Records the association of the Geom with its GeomVertexData, for the
72  * purpose of later removing unused vertices.
73  */
75 register_vertices(Geom *geom, bool might_have_unused) {
76  VertexDataAssoc &assoc = _vdata_assoc[geom->get_vertex_data()];
77  assoc._geoms.push_back(geom);
78  if (might_have_unused) {
79  assoc._might_have_unused = true;
80  }
81 }
82 
83 /**
84  * Records the association of the Geom with its GeomVertexData, for the
85  * purpose of later removing unused vertices.
86  */
88 register_vertices(GeomNode *node, bool might_have_unused) {
89  Thread *current_thread = Thread::get_current_thread();
90  OPEN_ITERATE_CURRENT_AND_UPSTREAM(node->_cycler, current_thread) {
91  GeomNode::CDStageWriter cdata(node->_cycler, pipeline_stage, current_thread);
92  GeomNode::GeomList::iterator gi;
93  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
94  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
95  GeomNode::GeomEntry &entry = (*gi);
96  PT(Geom) geom = entry._geom.get_write_pointer();
97  register_vertices(geom, might_have_unused);
98  }
99  }
100  CLOSE_ITERATE_CURRENT_AND_UPSTREAM(node->_cycler);
101 }
102 
103 /**
104  * Transforms the vertices and the normals in the indicated Geom by the
105  * indicated matrix. Returns true if the Geom was changed, false otherwise.
106  */
108 transform_vertices(Geom *geom, const LMatrix4 &mat) {
109  PStatTimer timer(_apply_vertex_collector);
110 
111  nassertr(geom != nullptr, false);
112  SourceVertices sv;
113  sv._mat = mat;
114  sv._vertex_data = geom->get_vertex_data();
115 
116  NewVertexData &new_data = _vertices[sv];
117  if (new_data._vdata.is_null()) {
118  // We have not yet converted these vertices. Do so now.
119  PT(GeomVertexData) new_vdata = new GeomVertexData(*sv._vertex_data);
120  new_vdata->transform_vertices(mat);
121  new_data._vdata = new_vdata;
122  }
123 
124  geom->set_vertex_data(new_data._vdata);
125  if (sv._vertex_data->get_ref_count() > 1) {
126  _vdata_assoc[new_data._vdata]._might_have_unused = true;
127  _vdata_assoc[sv._vertex_data]._might_have_unused = true;
128  }
129 
130  return true;
131 }
132 
133 
134 /**
135  * Transforms the vertices and the normals in all of the Geoms within the
136  * indicated GeomNode by the indicated matrix. Does not destructively change
137  * Geoms; instead, a copy will be made of each Geom to be changed, in case
138  * multiple GeomNodes reference the same Geom. Returns true if the GeomNode
139  * was changed, false otherwise.
140  */
142 transform_vertices(GeomNode *node, const LMatrix4 &mat) {
143  bool any_changed = false;
144 
145  Thread *current_thread = Thread::get_current_thread();
146  OPEN_ITERATE_CURRENT_AND_UPSTREAM(node->_cycler, current_thread) {
147  GeomNode::CDStageWriter cdata(node->_cycler, pipeline_stage, current_thread);
148  GeomNode::GeomList::iterator gi;
149  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
150  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
151  GeomNode::GeomEntry &entry = (*gi);
152  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
153  if (transform_vertices(new_geom, mat)) {
154  entry._geom = std::move(new_geom);
155  any_changed = true;
156  }
157  }
158  }
159  CLOSE_ITERATE_CURRENT_AND_UPSTREAM(node->_cycler);
160 
161  if (any_changed) {
162  node->mark_internal_bounds_stale();
163  }
164 
165  return any_changed;
166 }
167 
168 
169 /**
170  * Transforms the texture coordinates in the indicated Geom by the indicated
171  * matrix. Returns true if the Geom was changed, false otherwise.
172  */
174 transform_texcoords(Geom *geom, const InternalName *from_name,
175  InternalName *to_name, const LMatrix4 &mat) {
176  PStatTimer timer(_apply_texcoord_collector);
177 
178  nassertr(geom != nullptr, false);
179 
180  SourceTexCoords st;
181  st._mat = mat;
182  st._from = from_name;
183  st._to = to_name;
184  st._vertex_data = geom->get_vertex_data();
185 
186  NewVertexData &new_data = _texcoords[st];
187  if (new_data._vdata.is_null()) {
188  if (!st._vertex_data->has_column(from_name)) {
189  // No from_name column; no change.
190  return false;
191  }
192 
193  PT(GeomVertexData) new_vdata;
194 
195  // We have not yet converted these texcoords. Do so now.
196  if (st._vertex_data->has_column(to_name)) {
197  new_vdata = new GeomVertexData(*st._vertex_data);
198  } else {
199  const GeomVertexColumn *old_column =
200  st._vertex_data->get_format()->get_column(from_name);
201  new_vdata = st._vertex_data->replace_column
202  (to_name, old_column->get_num_components(),
203  old_column->get_numeric_type(),
204  old_column->get_contents());
205  }
206 
207  CPT(GeomVertexFormat) format = new_vdata->get_format();
208 
209  GeomVertexWriter tdata(new_vdata, to_name);
210  GeomVertexReader fdata(new_vdata, from_name);
211 
212  while (!fdata.is_at_end()) {
213  const LPoint4 &coord = fdata.get_data4();
214  tdata.set_data4(coord * mat);
215  }
216  new_data._vdata = new_vdata;
217  }
218 
219  geom->set_vertex_data(new_data._vdata);
220  if (st._vertex_data->get_ref_count() > 1) {
221  _vdata_assoc[new_data._vdata]._might_have_unused = true;
222  _vdata_assoc[st._vertex_data]._might_have_unused = true;
223  }
224 
225  return true;
226 }
227 
228 
229 /**
230  * Transforms the texture coordinates in all of the Geoms within the indicated
231  * GeomNode by the indicated matrix. Does not destructively change Geoms;
232  * instead, a copy will be made of each Geom to be changed, in case multiple
233  * GeomNodes reference the same Geom. Returns true if the GeomNode was
234  * changed, false otherwise.
235  */
237 transform_texcoords(GeomNode *node, const InternalName *from_name,
238  InternalName *to_name, const LMatrix4 &mat) {
239  bool any_changed = false;
240 
241  GeomNode::CDWriter cdata(node->_cycler);
242  GeomNode::GeomList::iterator gi;
243  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
244  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
245  GeomNode::GeomEntry &entry = (*gi);
246  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
247  if (transform_texcoords(new_geom, from_name, to_name, mat)) {
248  entry._geom = new_geom;
249  any_changed = true;
250  }
251  }
252 
253  return any_changed;
254 }
255 
256 
257 /**
258  * Overrides the color indicated within the Geom with the given replacement
259  * color. Returns true if the Geom was changed, false otherwise.
260  */
262 set_color(Geom *geom, const LColor &color) {
263  PStatTimer timer(_apply_set_color_collector);
264 
265  SourceColors sc;
266  sc._color = color;
267  sc._vertex_data = geom->get_vertex_data();
268 
269  NewVertexData &new_data = _fcolors[sc];
270  if (new_data._vdata.is_null()) {
271  // We have not yet converted these colors. Do so now.
272  if (sc._vertex_data->has_column(InternalName::get_color())) {
273  new_data._vdata = sc._vertex_data->set_color(color);
274  } else {
275  new_data._vdata = sc._vertex_data->set_color
276  (color, 1, Geom::NT_packed_dabc, Geom::C_color);
277  }
278  }
279 
280  geom->set_vertex_data(new_data._vdata);
281  if (sc._vertex_data->get_ref_count() > 1) {
282  _vdata_assoc[new_data._vdata]._might_have_unused = true;
283  _vdata_assoc[sc._vertex_data]._might_have_unused = true;
284  }
285 
286  return true;
287 }
288 
289 
290 /**
291  * Overrides the color indicated within the GeomNode with the given
292  * replacement color. Returns true if any Geom in the GeomNode was changed,
293  * false otherwise.
294  */
296 set_color(GeomNode *node, const LColor &color) {
297  bool any_changed = false;
298 
299  GeomNode::CDWriter cdata(node->_cycler);
300  GeomNode::GeomList::iterator gi;
301  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
302  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
303  GeomNode::GeomEntry &entry = (*gi);
304  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
305  if (set_color(new_geom, color)) {
306  entry._geom = new_geom;
307  any_changed = true;
308  }
309  }
310 
311  return any_changed;
312 }
313 
314 /**
315  * Transforms the colors in the indicated Geom by the indicated scale.
316  * Returns true if the Geom was changed, false otherwise.
317  */
319 transform_colors(Geom *geom, const LVecBase4 &scale) {
320  PStatTimer timer(_apply_scale_color_collector);
321 
322  nassertr(geom != nullptr, false);
323 
324  SourceColors sc;
325  sc._color = scale;
326  sc._vertex_data = geom->get_vertex_data();
327 
328  NewVertexData &new_data = _tcolors[sc];
329  if (new_data._vdata.is_null()) {
330  // We have not yet converted these colors. Do so now.
331  if (sc._vertex_data->has_column(InternalName::get_color())) {
332  new_data._vdata = sc._vertex_data->scale_color(scale);
333  } else {
334  new_data._vdata = sc._vertex_data->set_color
335  (scale, 1, Geom::NT_packed_dabc, Geom::C_color);
336  }
337  }
338 
339  geom->set_vertex_data(new_data._vdata);
340  if (sc._vertex_data->get_ref_count() > 1) {
341  _vdata_assoc[new_data._vdata]._might_have_unused = true;
342  _vdata_assoc[sc._vertex_data]._might_have_unused = true;
343  }
344 
345  return true;
346 }
347 
348 
349 /**
350  * Transforms the colors in all of the Geoms within the indicated GeomNode by
351  * the indicated scale. Does not destructively change Geoms; instead, a copy
352  * will be made of each Geom to be changed, in case multiple GeomNodes
353  * reference the same Geom. Returns true if the GeomNode was changed, false
354  * otherwise.
355  */
357 transform_colors(GeomNode *node, const LVecBase4 &scale) {
358  bool any_changed = false;
359 
360  GeomNode::CDWriter cdata(node->_cycler);
361  GeomNode::GeomList::iterator gi;
362  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
363  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
364  GeomNode::GeomEntry &entry = (*gi);
365  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
366  if (transform_colors(new_geom, scale)) {
367  entry._geom = new_geom;
368  any_changed = true;
369  }
370  }
371 
372  return any_changed;
373 }
374 
375 
376 /**
377  * Removes textures from Geoms by applying the texture colors to the vertices.
378  *
379  * See apply_texure_colors(GeomNode *, RenderState *).
380  */
383  const TexMatrixAttrib *tma, const LColor &base_color,
384  bool keep_vertex_color) {
385  PStatTimer timer(_apply_texture_color_collector);
386 
387  nassertr(geom != nullptr, false);
388 
389  PT(TexturePeeker) peeker = tex->peek();
390  if (peeker == nullptr) {
391  return false;
392  }
393 
394  if (peeker->get_x_size() == 1 &&
395  peeker->get_y_size() == 1 &&
396  peeker->get_z_size() == 1) {
397  // If it's just a one-pixel texture (e.g. a simple ram image), don't
398  // bother scanning the UV's. Just extract the color and apply it.
399  LColor color;
400  peeker->lookup(color, 0.0f, 0.0f);
401  color.set(color[0] * base_color[0],
402  color[1] * base_color[1],
403  color[2] * base_color[2],
404  color[3] * base_color[3]);
405  if (keep_vertex_color) {
406  return transform_colors(geom, color);
407  } else {
408  return set_color(geom, color);
409  }
410  }
411 
412  bool got_mat = false;
413  LMatrix4 mat = LMatrix4::ident_mat();
414  if (tma != nullptr && tma->has_stage(ts)) {
415  mat = tma->get_mat(ts);
416  got_mat = !mat.almost_equal(LMatrix4::ident_mat());
417  }
418 
419  // This version of the code just applied one overall flat color to the
420  // entire mesh. Turned out not to be good enough. Instead, we'll look up
421  // each vertex in the texture map and apply the nearest color to the vertex.
422  /*
423  // Scan the UV's to get the used range. This is particularly necessary for
424  // palettized textures.
425 
426  LPoint3 min_point, max_point;
427  bool found_any = false;
428  geom->calc_tight_bounds(min_point, max_point, found_any,
429  geom->get_vertex_data(),
430  got_mat, mat,
431  ts->get_texcoord_name(),
432  Thread::get_current_thread());
433  if (found_any) {
434  // Now use that UV range to determine the overall color of the geom's
435  // texture.
436  LColor color;
437  peeker->filter_rect(color,
438  min_point[0], min_point[1], min_point[2],
439  max_point[0], max_point[1], max_point[2]);
440  color.set(color[0] * base_color[0],
441  color[1] * base_color[1],
442  color[2] * base_color[2],
443  color[3] * base_color[3]);
444  if (keep_vertex_color) {
445  return transform_colors(geom, color);
446  } else {
447  return set_color(geom, color);
448  }
449  }
450 
451  return false;
452  */
453 
454  SourceTextureColors stc;
455  stc._ts = ts;
456  stc._tex = tex;
457  stc._tma = tma;
458  stc._base_color = base_color;
459  stc._keep_vertex_color = keep_vertex_color;
460  stc._vertex_data = geom->get_vertex_data();
461 
462  NewVertexData &new_data = _tex_colors[stc];
463  if (new_data._vdata.is_null()) {
464  // We have not yet applied these texture colors. Do so now.
465 
466  PT(GeomVertexData) vdata;
467 
468  // Make sure the vdata has a color column.
469  if (stc._vertex_data->has_column(InternalName::get_color())) {
470  vdata = new GeomVertexData(*stc._vertex_data);
471  } else {
472  // Create a color column where there wasn't one before.
473  vdata = new GeomVertexData(*stc._vertex_data->set_color
474  (LColor(1.0f, 1.0f, 1.0f, 1.0f), 1, Geom::NT_packed_dabc, Geom::C_color));
475  keep_vertex_color = false;
476  }
477 
478  // Check whether it has 2-d or 3-d texture coordinates.
479  bool tex3d = false;
480  const GeomVertexColumn *column = vdata->get_format()->get_column(ts->get_texcoord_name());
481  if (column == nullptr) {
482  return false;
483  }
484  if (column->get_num_components() >= 3) {
485  tex3d = true;
486  }
487 
488  // Now walk through the vertices and apply each color from the texture as
489  // we go.
490  if (keep_vertex_color) {
491  // We want to modulate the existing vertex color.
492  GeomVertexReader gtexcoord(vdata, ts->get_texcoord_name());
493  GeomVertexRewriter gcolor(vdata, InternalName::get_color());
494 
495  if (got_mat || tex3d) {
496  while (!gtexcoord.is_at_end()) {
497  LTexCoord3 p = gtexcoord.get_data3();
498  LColor c = gcolor.get_data4();
499  p = p * mat;
500  LColor color;
501  peeker->lookup(color, p[0], p[1], p[2]);
502  color.set(color[0] * base_color[0] * c[0],
503  color[1] * base_color[1] * c[1],
504  color[2] * base_color[2] * c[2],
505  color[3] * base_color[3] * c[3]);
506  gcolor.set_data4(color);
507  }
508  } else {
509  while (!gtexcoord.is_at_end()) {
510  LTexCoord p = gtexcoord.get_data2();
511  LColor c = gcolor.get_data4();
512  LColor color;
513  peeker->lookup(color, p[0], p[1]);
514  color.set(color[0] * base_color[0] * c[0],
515  color[1] * base_color[1] * c[1],
516  color[2] * base_color[2] * c[2],
517  color[3] * base_color[3] * c[3]);
518  gcolor.set_data4(color);
519  }
520  }
521  } else {
522  // We want to replace any existing vertex color.
523  GeomVertexReader gtexcoord(vdata, ts->get_texcoord_name());
524  GeomVertexWriter gcolor(vdata, InternalName::get_color());
525 
526  if (got_mat || tex3d) {
527  while (!gtexcoord.is_at_end()) {
528  LTexCoord3 p = gtexcoord.get_data3();
529  p = p * mat;
530  LColor color;
531  peeker->lookup(color, p[0], p[1], p[2]);
532  color.set(color[0] * base_color[0],
533  color[1] * base_color[1],
534  color[2] * base_color[2],
535  color[3] * base_color[3]);
536  gcolor.set_data4(color);
537  }
538  } else {
539  while (!gtexcoord.is_at_end()) {
540  LTexCoord p = gtexcoord.get_data2();
541  LColor color;
542  peeker->lookup(color, p[0], p[1]);
543  color.set(color[0] * base_color[0],
544  color[1] * base_color[1],
545  color[2] * base_color[2],
546  color[3] * base_color[3]);
547  gcolor.set_data4(color);
548  }
549  }
550  }
551 
552  new_data._vdata = vdata;
553  }
554 
555  geom->set_vertex_data(new_data._vdata);
556  if (stc._vertex_data->get_ref_count() > 1) {
557  _vdata_assoc[new_data._vdata]._might_have_unused = true;
558  _vdata_assoc[stc._vertex_data]._might_have_unused = true;
559  }
560 
561  return true;
562 }
563 
564 /**
565  * Removes textures from Geoms by applying the texture colors to the vertices.
566  * This is primarily useful to simplify a low-LOD model.
567  *
568  * Only the bottommost texture is used (if there is more than one), and it is
569  * applied as if it were M_modulate, and WM_repeat, regardless of its actual
570  * settings. If the texture has a simple_ram_image, this may be used if the
571  * main image isn't resident.
572  *
573  * After this call, there will be no texturing specified on the GeomNode
574  * level. Of course, there might still be texturing inherited from above.
575  */
577 apply_texture_colors(GeomNode *node, const RenderState *state) {
578  bool any_changed = false;
579 
580  GeomNode::CDWriter cdata(node->_cycler);
581  GeomNode::GeomList::iterator gi;
582  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
583  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
584  GeomNode::GeomEntry &entry = (*gi);
585  CPT(RenderState) geom_state = state->compose(entry._state);
586 
587  const TextureAttrib *ta = DCAST(TextureAttrib, geom_state->get_attrib(TextureAttrib::get_class_slot()));
588  if (ta != nullptr) {
589  CPT(TextureAttrib) ta2 = ta->filter_to_max(1);
590  if (ta2->get_num_on_stages() > 0) {
591  TextureStage *ts = ta2->get_on_stage(0);
592  Texture *tex = ta2->get_on_texture(ts);
593  const TexMatrixAttrib *tma = DCAST(TexMatrixAttrib, geom_state->get_attrib(TexMatrixAttrib::get_class_slot()));
594 
595  const ColorAttrib *ca = DCAST(ColorAttrib, geom_state->get_attrib(ColorAttrib::get_class_slot()));
596  LColor base_color(1.0f, 1.0f, 1.0f, 1.0f);
597  bool keep_vertex_color = true;
598  if (ca != nullptr && ca->get_color_type() == ColorAttrib::T_flat) {
599  base_color = ca->get_color();
600  keep_vertex_color = false;
601  }
602 
603  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
604  if (apply_texture_colors(new_geom, ts, tex, tma, base_color, keep_vertex_color)) {
605  entry._geom = new_geom;
606  any_changed = true;
607 
608  if (new_geom->get_vertex_data()->has_column(InternalName::get_color())) {
609  // Ensure we have a ColorAttrib::make_vertex() attrib.
610  CPT(RenderState) color_state = entry._state->set_attrib(ColorAttrib::make_vertex());
611  if (entry._state != color_state) {
612  entry._state = color_state;
613  any_changed = true;
614  }
615  }
616  }
617 
618  // Also remove any texture references from the GeomState.
619  CPT(RenderState) no_tex_state = entry._state->remove_attrib(TextureAttrib::get_class_slot());
620  if (entry._state != no_tex_state) {
621  entry._state = no_tex_state;
622  any_changed = true;
623  }
624  }
625  }
626  }
627 
628  return any_changed;
629 }
630 
631 /**
632  * Applies the indicated render state to all the of Geoms. Returns true if
633  * the GeomNode was changed, false otherwise.
634  */
636 apply_state(GeomNode *node, const RenderState *state) {
637  bool any_changed = false;
638 
639  GeomNode::CDWriter cdata(node->_cycler);
640  GeomNode::GeomList::iterator gi;
641  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
642  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
643  GeomNode::GeomEntry &entry = (*gi);
644  CPT(RenderState) new_state = state->compose(entry._state);
645  if (entry._state != new_state) {
646  entry._state = new_state;
647  any_changed = true;
648  }
649  }
650 
651  return any_changed;
652 }
653 
654 /**
655  * Changes the GeomVertexData of the indicated Geom to use the specified
656  * format.
657  */
659 set_format(Geom *geom, const GeomVertexFormat *new_format) {
660  PStatTimer timer(_apply_set_format_collector);
661 
662  nassertr(geom != nullptr, false);
663 
664  SourceFormat sf;
665  sf._format = new_format;
666  sf._vertex_data = geom->get_vertex_data();
667 
668  NewVertexData &new_data = _format[sf];
669  if (new_data._vdata.is_null()) {
670  if (sf._vertex_data->get_format() == new_format) {
671  // No change.
672  return false;
673  }
674 
675  // We have not yet converted this vertex data. Do so now.
676  PT(GeomVertexData) new_vdata = new GeomVertexData(*sf._vertex_data);
677  new_vdata->set_format(new_format);
678  new_data._vdata = new_vdata;
679  }
680 
681  geom->set_vertex_data(new_data._vdata);
682  if (sf._vertex_data->get_ref_count() > 1) {
683  _vdata_assoc[new_data._vdata]._might_have_unused = true;
684  _vdata_assoc[sf._vertex_data]._might_have_unused = true;
685  }
686 
687  return true;
688 }
689 
690 /**
691  * Removes the named column from the vertex data in the Geom. Returns true if
692  * the Geom was changed, false otherwise.
693  */
695 remove_column(Geom *geom, const InternalName *column) {
696  CPT(GeomVertexFormat) format = geom->get_vertex_data()->get_format();
697  if (!format->has_column(column)) {
698  return false;
699  }
700 
701  PT(GeomVertexFormat) new_format = new GeomVertexFormat(*format);
702  new_format->remove_column(column);
703  new_format->pack_columns();
704  format = GeomVertexFormat::register_format(new_format);
705 
706  return set_format(geom, format);
707 }
708 
709 
710 /**
711  * Removes the named column from the vertex datas within the GeomNode.
712  * Returns true if the GeomNode was changed, false otherwise.
713  */
715 remove_column(GeomNode *node, const InternalName *column) {
716  bool any_changed = false;
717 
718  GeomNode::CDWriter cdata(node->_cycler);
719  GeomNode::GeomList::iterator gi;
720  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
721  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
722  GeomNode::GeomEntry &entry = (*gi);
723  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
724  if (remove_column(new_geom, column)) {
725  entry._geom = new_geom;
726  any_changed = true;
727  }
728  }
729 
730  return any_changed;
731 }
732 
733 /**
734  * Checks if the different geoms in the GeomNode have different RenderStates.
735  * If so, tries to make the RenderStates the same. It does this by
736  * canonicalizing the ColorAttribs, and in the future, possibly other attribs.
737  */
740  if (node->get_num_geoms() < 2) {
741  return false;
742  }
743 
744  GeomNode::CDWriter cdata(node->_cycler);
745  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
746 
747  // For each geom, calculate a canonicalized RenderState, and classify all
748  // the geoms according to that. By "canonicalize" here, we simply mean
749  // removing the ColorAttrib.
750 
751  typedef pmap <CPT(RenderState), pvector<int> > StateTable;
752  StateTable state_table;
753 
754  for (int i = 0; i < (int)geoms->size(); i++) {
755  GeomNode::GeomEntry &entry = (*geoms)[i];
756  CPT(RenderState) canon = entry._state->remove_attrib(ColorAttrib::get_class_slot());
757  state_table[canon].push_back(i);
758  }
759 
760  // For each group of geoms, check for mismatch.
761 
762  bool any_changed = false;
763  StateTable::iterator si;
764  for (si = state_table.begin(); si != state_table.end(); si++) {
765 
766  // If the geoms in the group already have the same RenderStates, then
767  // nothing needs to be done to this group.
768 
769  const pvector<int> &indices = (*si).second;
770  bool mismatch = false;
771  for (int i = 1; i < (int)indices.size(); i++) {
772  if ((*geoms)[indices[i]]._state != (*geoms)[indices[0]]._state) {
773  mismatch = true;
774  break;
775  }
776  }
777  if (!mismatch) {
778  continue;
779  }
780 
781  // The geoms do not have the same RenderState, but they could, since their
782  // canonicalized states are the same. Canonicalize them, by applying the
783  // colors to the vertices.
784 
785  const RenderState *canon_state = (*si).first;
786  for (int i = 0; i < (int)indices.size(); i++) {
787  GeomNode::GeomEntry &entry = (*geoms)[indices[i]];
788  const RenderAttrib *ra = entry._state->get_attrib_def(ColorAttrib::get_class_slot());
789  const ColorAttrib *ca = DCAST(ColorAttrib, ra);
790  if (ca->get_color_type() == ColorAttrib::T_vertex) {
791  // All we need to do is ensure that the geom has a color column.
792  if (!entry._geom.get_read_pointer()->get_vertex_data()->has_column(InternalName::get_color())) {
793  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
794  if (set_color(new_geom, LColor(1,1,1,1))) {
795  entry._geom = new_geom;
796  }
797  }
798  } else {
799  // A flat color (or "off", which is white). Set the vertices to the
800  // indicated flat color.
801  LColor c = ca->get_color();
802  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
803  if (set_color(new_geom, c)) {
804  entry._geom = new_geom;
805  }
806  }
807  entry._state = canon_state->add_attrib(ColorAttrib::make_vertex());
808  any_changed = true;
809  }
810  }
811 
812  return any_changed;
813 }
814 
815 /**
816  * Reverses the lighting normals on the vertex data, if any. Returns true if
817  * the Geom was changed, false otherwise.
818  */
820 reverse_normals(Geom *geom) {
821  nassertr(geom != nullptr, false);
822  CPT(GeomVertexData) orig_data = geom->get_vertex_data();
823  NewVertexData &new_data = _reversed_normals[orig_data];
824  if (new_data._vdata.is_null()) {
825  new_data._vdata = orig_data->reverse_normals();
826  }
827 
828  if (new_data._vdata == orig_data) {
829  // No change.
830  return false;
831  }
832 
833  geom->set_vertex_data(new_data._vdata);
834  if (orig_data->get_ref_count() > 1) {
835  _vdata_assoc[new_data._vdata]._might_have_unused = true;
836  _vdata_assoc[orig_data]._might_have_unused = true;
837  }
838 
839  return true;
840 }
841 
842 /**
843  * Duplicates triangles in this GeomNode so that each triangle is back-to-back
844  * with another triangle facing in the opposite direction. If the geometry
845  * has vertex normals, this will also duplicate and reverse the normals, so
846  * that lighting will work correctly from both sides. Note that calling this
847  * when the geometry is already doublesided (with back-to-back polygons) will
848  * result in multiple redundant coplanar polygons.
849  *
850  * Also see CullFaceAttrib, which can enable rendering of both sides of a
851  * triangle without having to duplicate it (but which doesn't necessarily work
852  * in the presence of lighting).
853  *
854  * Returns true if any Geoms are modified, false otherwise.
855  */
857 doubleside(GeomNode *node) {
858  int num_geoms = node->get_num_geoms();
859  for (int i = 0; i < num_geoms; ++i) {
860  CPT(Geom) orig_geom = node->get_geom(i);
861  bool has_normals = (orig_geom->get_vertex_data()->has_column(InternalName::get_normal()));
862  if (has_normals) {
863  // If the geometry has normals, we have to duplicate it to reverse the
864  // normals on the duplicate copy.
865  PT(Geom) new_geom = orig_geom->reverse();
866  reverse_normals(new_geom);
867  node->add_geom(new_geom, node->get_geom_state(i));
868 
869  } else {
870  // If there are no normals, we can just doubleside it in place. This is
871  // preferable because we can share vertices.
872  orig_geom.clear();
873  node->modify_geom(i)->doubleside_in_place();
874  }
875  }
876 
877  return (num_geoms != 0);
878 }
879 
880 
881 /**
882  * Reverses the winding order of triangles in this GeomNode so that each
883  * triangle is facing in the opposite direction. If the geometry has vertex
884  * normals, this will also reverse the normals, so that lighting will work
885  * correctly.
886  *
887  * Also see CullFaceAttrib, which can effectively change the facing of a
888  * triangle having to modify its vertices (but which doesn't necessarily work
889  * in the presence of lighting).
890  *
891  * Returns true if any Geoms are modified, false otherwise.
892  */
894 reverse(GeomNode *node) {
895  int num_geoms = node->get_num_geoms();
896  for (int i = 0; i < num_geoms; ++i) {
897  PT(Geom) geom = node->modify_geom(i);
898  geom->reverse_in_place();
899  reverse_normals(geom);
900  }
901 
902  return (num_geoms != 0);
903 }
904 
905 /**
906  * Should be called after performing any operations--particularly
907  * PandaNode::apply_attribs_to_vertices()--that might result in new
908  * GeomVertexData objects being duplicated and modified. This walks through
909  * those newly duplicated objects and ensures that redundant unused vertices
910  * have not been created, removing them if they have.
911  */
913 finish_apply() {
914  VertexDataAssocMap::iterator vi;
915  for (vi = _vdata_assoc.begin(); vi != _vdata_assoc.end(); ++vi) {
916  const GeomVertexData *vdata = (*vi).first;
917  VertexDataAssoc &assoc = (*vi).second;
918  if (assoc._might_have_unused) {
919  assoc.remove_unused_vertices(vdata);
920  }
921  }
922  _vdata_assoc.clear();
923 
924  _texcoords.clear();
925  _fcolors.clear();
926  _tcolors.clear();
927  _format.clear();
928  _reversed_normals.clear();
929 }
930 
931 /**
932  * Collects together GeomVertexDatas from different geoms into one big (or
933  * several big) GeomVertexDatas. Returns the number of unique GeomVertexDatas
934  * created.
935  *
936  * If format_only is true, this only makes GeomVertexFormats compatible; it
937  * does not otherwise combine vertices.
938  *
939  * You should follow this up with a call to finish_collect(), but you probably
940  * don't want to call this method directly anyway. Call
941  * SceneGraphReducer::collect_vertex_data() instead.
942  */
944 collect_vertex_data(Geom *geom, int collect_bits, bool format_only) {
945  CPT(GeomVertexData) vdata = geom->get_vertex_data();
946  if (vdata->get_num_rows() > _max_collect_vertices) {
947  // Don't even bother.
948  return 0;
949  }
950 
951  CPT(GeomVertexFormat) format = vdata->get_format();
952 
953  NewCollectedKey key;
954  if ((collect_bits & SceneGraphReducer::CVD_name) != 0) {
955  key._name = vdata->get_name();
956  }
957  if ((collect_bits & SceneGraphReducer::CVD_format) != 0) {
958  key._format = format;
959  }
960  if ((collect_bits & SceneGraphReducer::CVD_usage_hint) != 0) {
961  key._usage_hint = vdata->get_usage_hint();
962  } else {
963  key._usage_hint = Geom::UH_unspecified;
964  }
965  if ((collect_bits & SceneGraphReducer::CVD_animation_type) != 0) {
966  key._animation_type = format->get_animation().get_animation_type();
967  } else {
968  key._animation_type = Geom::AT_none;
969  }
970 
971  AlreadyCollectedMap::const_iterator ai;
972  ai = _already_collected_map.find(vdata);
973  if (ai != _already_collected_map.end()) {
974  // We've previously collected this vertex data; reuse it.
975  const AlreadyCollectedData &acd = (*ai).second;
976  SourceGeom source_geom;
977  source_geom._geom = geom;
978  source_geom._vertex_offset = acd._vertex_offset;
979  acd._ncd->_source_geoms.push_back(source_geom);
980  return 0;
981  }
982 
983  // We haven't collected this vertex data yet; associate it with a new data.
984  NewCollectedMap::iterator ni = _new_collected_map.find(key);
985  NewCollectedData *ncd;
986  if (ni != _new_collected_map.end()) {
987  ncd = (*ni).second;
988 
989  } else {
990  // We haven't encountered a compatible GeomVertexData before. Create a
991  // new entry.
992  ncd = new NewCollectedData(vdata);
993  _new_collected_list.push_back(ncd);
994  _new_collected_map[key] = ncd;
995  }
996 
997  if (ncd->_new_format != format) {
998  ncd->_new_format = format->get_union_format(ncd->_new_format);
999  }
1000 
1001  int this_num_vertices = vdata->get_num_rows();
1002 
1003  if (!format_only &&
1004  ncd->_num_vertices + this_num_vertices > _max_collect_vertices) {
1005  // Whoa, hold the phone! Too many vertices going into this one
1006  // GeomVertexData object; we'd better start over.
1007  ncd = new NewCollectedData(vdata);
1008  _new_collected_list.push_back(ncd);
1009  _new_collected_map[key] = ncd;
1010  }
1011 
1012  int vertex_offset = ncd->_num_vertices;
1013 
1014  AlreadyCollectedData &acd = _already_collected_map[vdata];
1015  acd._ncd = ncd;
1016  acd._vertex_offset = vertex_offset;
1017 
1018  SourceGeom source_geom;
1019  source_geom._geom = geom;
1020  source_geom._vertex_offset = vertex_offset;
1021  ncd->_source_geoms.push_back(source_geom);
1022 
1023  SourceData source_data;
1024  source_data._vdata = vdata;
1025  source_data._num_vertices = this_num_vertices;
1026 
1027  ncd->_source_datas.push_back(source_data);
1028  ncd->_num_vertices += this_num_vertices;
1029 
1030  return 0;
1031 }
1032 
1033 
1034 /**
1035  * Collects together individual GeomVertexData structures that share the same
1036  * format into one big GeomVertexData structure. This is intended to minimize
1037  * context switches on the graphics card.
1038  *
1039  * If format_only is true, this only makes GeomVertexFormats compatible; it
1040  * does not otherwise combine vertices.
1041  *
1042  * You should follow this up with a call to finish_collect(), but you probably
1043  * don't want to call this method directly anyway. Call
1044  * SceneGraphReducer::collect_vertex_data() instead.
1045  */
1047 collect_vertex_data(GeomNode *node, int collect_bits, bool format_only) {
1048  int num_adjusted = 0;
1049  GeomTransformer *dynamic = nullptr;
1050 
1051  GeomNode::CDWriter cdata(node->_cycler);
1052  GeomNode::GeomList::iterator gi;
1053  PT(GeomNode::GeomList) geoms = cdata->modify_geoms();
1054  for (gi = geoms->begin(); gi != geoms->end(); ++gi) {
1055  GeomNode::GeomEntry &entry = (*gi);
1056  PT(Geom) new_geom = entry._geom.get_read_pointer()->make_copy();
1057  entry._geom = new_geom;
1058 
1059  if ((collect_bits & SceneGraphReducer::CVD_avoid_dynamic) != 0 &&
1060  new_geom->get_vertex_data()->get_usage_hint() < Geom::UH_static) {
1061  // This one has some dynamic properties. Collect it independently of
1062  // the outside world.
1063  if (dynamic == nullptr) {
1064  dynamic = new GeomTransformer(*this);
1065  }
1066  num_adjusted += dynamic->collect_vertex_data(new_geom, collect_bits, format_only);
1067 
1068  } else {
1069  num_adjusted += collect_vertex_data(new_geom, collect_bits, format_only);
1070  }
1071  }
1072 
1073  if (dynamic != nullptr) {
1074  num_adjusted += dynamic->finish_collect(format_only);
1075  delete dynamic;
1076  }
1077 
1078  return num_adjusted;
1079 }
1080 
1081 /**
1082  * This should be called after a call to collect_vertex_data() to finalize the
1083  * changes and apply them to the vertices in the graph. If this is not
1084  * called, it will be called automatically by the GeomTransformer destructor.
1085  *
1086  * If format_only is true, this returns the number of GeomVertexDatas modified
1087  * to use a new format. If false, it returns the number of GeomVertexDatas
1088  * created.
1089  */
1091 finish_collect(bool format_only) {
1092  int num_adjusted = 0;
1093 
1094  NewCollectedList::iterator nci;
1095  for (nci = _new_collected_list.begin();
1096  nci != _new_collected_list.end();
1097  ++nci) {
1098  NewCollectedData *ncd = (*nci);
1099  if (format_only) {
1100  num_adjusted += ncd->apply_format_only_changes();
1101  } else {
1102  num_adjusted += ncd->apply_collect_changes();
1103  }
1104  delete ncd;
1105  }
1106 
1107  _new_collected_list.clear();
1108  _new_collected_map.clear();
1109  _already_collected_map.clear();
1110 
1111  return num_adjusted;
1112 }
1113 
1114 /**
1115  * Uses the indicated munger to premunge the given Geom to optimize it for
1116  * eventual rendering. See SceneGraphReducer::premunge().
1117  */
1118 PT(Geom) GeomTransformer::
1119 premunge_geom(const Geom *geom, GeomMunger *munger) {
1120  // This method had been originally provided to cache the result for a
1121  // particular geommunger and vdatamunger combination, similar to the way
1122  // other GeomTransformer methods work. On reflection, this additional
1123  // caching is not necessary, since the GeomVertexFormat does its own
1124  // caching, and there's no danger of that cache filling up during the span
1125  // of one frame.
1126 
1127  CPT(GeomVertexData) vdata = geom->get_vertex_data();
1128  vdata = munger->premunge_data(vdata);
1129  CPT(Geom) pgeom = geom;
1130  munger->premunge_geom(pgeom, vdata);
1131 
1132  PT(Geom) geom_copy = pgeom->make_copy();
1133  geom_copy->set_vertex_data(vdata);
1134 
1135  return geom_copy;
1136 }
1137 
1138 /**
1139  *
1140  */
1141 GeomTransformer::NewCollectedData::
1142 NewCollectedData(const GeomVertexData *source_data) {
1143  _new_format = source_data->get_format();
1144  _vdata_name = source_data->get_name();
1145  _usage_hint = source_data->get_usage_hint();
1146  _num_vertices = 0;
1147 }
1148 
1149 /**
1150  * Actually adjusts the GeomVertexDatas found in a collect_vertex_data()
1151  * format-only call to have the same vertex format. Returns the number of
1152  * vdatas modified.
1153  */
1154 int GeomTransformer::NewCollectedData::
1155 apply_format_only_changes() {
1156  int num_modified = 0;
1157 
1158  // We probably don't need to use a map, since GeomVertexData::convert_to()
1159  // already caches its result, but we do it anyway just in case there's
1160  // danger of overflowing the cache. What the heck, it's easy to do.
1161  typedef pmap< CPT(GeomVertexData), CPT(GeomVertexData) > VDataMap;
1162  VDataMap vdata_map;
1163 
1164  SourceGeoms::iterator sgi;
1165  for (sgi = _source_geoms.begin(); sgi != _source_geoms.end(); ++sgi) {
1166  SourceGeom &sg = (*sgi);
1167  CPT(GeomVertexData) orig_data = sg._geom->get_vertex_data();
1168 
1169  if (orig_data->get_format() != _new_format) {
1170  VDataMap::iterator mi = vdata_map.find(orig_data);
1171  if (mi != vdata_map.end()) {
1172  // Already modified this vdata.
1173  sg._geom->set_vertex_data((*mi).second);
1174 
1175  } else {
1176  // Modify this vdata to the new format.
1177  CPT(GeomVertexData) new_data = orig_data->convert_to(_new_format);
1178  vdata_map[orig_data] = new_data;
1179  ++num_modified;
1180 
1181  sg._geom->set_vertex_data(new_data);
1182  }
1183  }
1184  }
1185 
1186  return num_modified;
1187 }
1188 
1189 /**
1190  * Actually combines all of the vertex datas found in a previous call to
1191  * collect_vertex_data().
1192  */
1193 int GeomTransformer::NewCollectedData::
1194 apply_collect_changes() {
1195  if (_num_vertices == 0) {
1196  return 0;
1197  }
1198 
1199  _new_data =
1200  new GeomVertexData(_vdata_name, _new_format, _usage_hint);
1201 
1202  _new_data->unclean_set_num_rows(_num_vertices);
1203 
1204  // Copy each source data into the new GeomVertexData, one at a time.
1205  int vertex_offset = 0;
1206  SourceDatas::iterator sdi;
1207  for (sdi = _source_datas.begin(); sdi != _source_datas.end(); ++sdi) {
1208  SourceData &sd = (*sdi);
1209  CPT(GeomVertexData) vdata = sd._vdata;
1210 
1211  if (_new_format != vdata->get_format()) {
1212  // Convert (non-destructively) the current Geom's vertex data to the new
1213  // format, so we can just blindly append the vertices to _new_data,
1214  // within append_vdata().
1215  vdata = vdata->convert_to(_new_format);
1216  }
1217 
1218  append_vdata(vdata, vertex_offset);
1219  vertex_offset += sd._num_vertices;
1220  }
1221 
1222  nassertr(vertex_offset == _num_vertices, 0);
1223 
1224  if (_new_btable != nullptr) {
1225  _new_btable->set_rows(_new_btable_rows);
1226  _new_data->set_transform_blend_table(_new_btable);
1227  }
1228 
1229  update_geoms();
1230 
1231  _new_data.clear();
1232  _new_btable.clear();
1233  _new_btable_rows.clear();
1234 
1235  return 1;
1236 }
1237 
1238 /**
1239  * Appends the vertices from the indicated source GeomVertexData to the end of
1240  * the working data.
1241  */
1242 void GeomTransformer::NewCollectedData::
1243 append_vdata(const GeomVertexData *vdata, int vertex_offset) {
1244  for (size_t i = 0; i < vdata->get_num_arrays(); ++i) {
1245  PT(GeomVertexArrayDataHandle) new_handle = _new_data->modify_array_handle(i);
1246  CPT(GeomVertexArrayDataHandle) old_handle = vdata->get_array_handle(i);
1247  size_t stride = (size_t)_new_format->get_array(i)->get_stride();
1248  size_t start_byte = (size_t)vertex_offset * stride;
1249  size_t copy_bytes = old_handle->get_data_size_bytes();
1250  nassertv(start_byte + copy_bytes <= new_handle->get_data_size_bytes());
1251 
1252  new_handle->copy_subdata_from(start_byte, copy_bytes, old_handle, 0, copy_bytes);
1253  }
1254 
1255  // Also, copy the animation data (if any). This means combining transform
1256  // andor slider tables, and might therefore mean remapping transform indices
1257  // in the vertices. Each of these has a slightly different way to handle
1258  // the remapping, because they have slightly different kinds of data.
1259 
1260  if (vdata->get_transform_table() != nullptr ||
1261  _new_data->get_transform_table() != nullptr) {
1262  // The TransformTable.
1263  CPT(TransformTable) old_table;
1264  if (vdata->get_transform_table() != nullptr) {
1265  old_table = vdata->get_transform_table();
1266  } else {
1267  PT(TransformTable) temp_table = new TransformTable;
1268  // There's an implicit identity transform for all nodes.
1269  PT(VertexTransform) identity_transform = new UserVertexTransform("identity");
1270  temp_table->add_transform(identity_transform);
1271  old_table = TransformTable::register_table(temp_table);
1272  }
1273 
1274  // First, build a mapping of the transforms we already have in the current
1275  // table. We must do this because the TransformTable doesn't
1276  // automatically unquify index numbers for us (it doesn't store an index).
1277  typedef pmap<const VertexTransform *, int> AddedTransforms;
1278  AddedTransforms added_transforms;
1279 
1280  int num_old_transforms = old_table->get_num_transforms();
1281  for (int i = 0; i < num_old_transforms; i++) {
1282  added_transforms[old_table->get_transform(i)] = i;
1283  }
1284 
1285  // Now create a new table. We have to create a new table instead of
1286  // modifying the existing one, since a registered TransformTable cannot be
1287  // modified.
1288  PT(TransformTable) new_table;
1289  if (_new_data->get_transform_table() != nullptr) {
1290  new_table = new TransformTable(*_new_data->get_transform_table());
1291  } else {
1292  new_table = new TransformTable;
1293  }
1294 
1295  // Now walk through the old table and copy over its transforms. We will
1296  // build up an IndexMap of old index numbers to new index numbers while we
1297  // go, which we can use to modify the vertices.
1298  IndexMap transform_map;
1299 
1300  int num_transforms = old_table->get_num_transforms();
1301  transform_map.reserve(num_transforms);
1302  for (int ti = 0; ti < num_transforms; ++ti) {
1303  const VertexTransform *transform = old_table->get_transform(ti);
1304  AddedTransforms::iterator ai = added_transforms.find(transform);
1305  if (ai != added_transforms.end()) {
1306  // Already got this one in the table.
1307  transform_map.push_back((*ai).second);
1308  } else {
1309  // This is a new one.
1310  int tj = new_table->add_transform(transform);
1311  transform_map.push_back(tj);
1312  added_transforms[transform] = tj;
1313  }
1314  }
1315  _new_data->set_transform_table(TransformTable::register_table(new_table));
1316 
1317  // And now modify the vertices to update the indices to their new values
1318  // in the new table. This requires a nested loop, since each column of
1319  // transform_index might define multiple index values.
1320  GeomVertexRewriter index(_new_data, InternalName::get_transform_index());
1321  if (index.has_column()) {
1322  int num_values = index.get_column()->get_num_values();
1323  int num_rows = vdata->get_num_rows();
1324 
1325  index.set_row_unsafe(vertex_offset);
1326  for (int ci = 0; ci < num_rows; ++ci) {
1327  LVecBase4i indices = index.get_data4i();
1328  for (int i = 0; i < num_values; i++) {
1329  nassertv(indices[i] >= 0 && indices[i] < (int)transform_map.size());
1330  indices[i] = transform_map[indices[i]];
1331  }
1332  index.set_data4i(indices);
1333  }
1334  }
1335  }
1336 
1337  if (vdata->get_transform_blend_table() != nullptr) {
1338  // The TransformBlendTable. This one is the easiest, because we can
1339  // modify it directly, and it will uniquify blend objects for us.
1340 
1341  // We have a few special optimizations to handle the TransformBlendTable,
1342  // since it's a very common case and therefore worth spending a bit of
1343  // effort to optimize deeply.
1344 
1345  CPT(TransformBlendTable) old_btable = vdata->get_transform_blend_table();
1346 
1347  if (_new_btable == nullptr) {
1348  _new_btable = new TransformBlendTable;
1349  _new_btable->add_blend(TransformBlend());
1350  }
1351 
1352  SparseArray new_rows = old_btable->get_rows();
1353  new_rows <<= vertex_offset;
1354  _new_btable_rows |= new_rows;
1355 
1356  // We still need to build up the IndexMap.
1357  IndexMap blend_map;
1358 
1359  int num_blends = old_btable->get_num_blends();
1360  blend_map.reserve(num_blends);
1361  for (int bi = 0; bi < num_blends; ++bi) {
1362  int bj = _new_btable->add_blend(old_btable->get_blend(bi));
1363  blend_map.push_back(bj);
1364  }
1365 
1366  // Modify the indices. This is simpler than the transform_index, above,
1367  // because each column of transform_blend may only define one index value.
1368  GeomVertexRewriter index(_new_data, InternalName::get_transform_blend());
1369  if (index.has_column()) {
1370  int num_rows = vdata->get_num_rows();
1371  index.set_row_unsafe(vertex_offset);
1372 
1373  for (int ci = 0; ci < num_rows; ++ci) {
1374  int orig_index = index.get_data1i();
1375  nassertv(orig_index >= 0 && orig_index < (int)blend_map.size());
1376  int new_index = blend_map[orig_index];
1377  index.set_data1i(new_index);
1378  }
1379  }
1380  }
1381 
1382  if (vdata->get_slider_table() != nullptr) {
1383  // The SliderTable. This one requires making a copy, like the
1384  // TransformTable (since it can't be modified once registered either), but
1385  // at least it uniquifies sliders added to it. Also, it doesn't require
1386  // indexing into it, so we don't have to build an IndexMap to modify the
1387  // vertices with.
1388  const SliderTable *old_sliders = vdata->get_slider_table();
1389  PT(SliderTable) new_sliders;
1390  if (_new_data->get_slider_table() != nullptr) {
1391  new_sliders = new SliderTable(*_new_data->get_slider_table());
1392  } else {
1393  new_sliders = new SliderTable;
1394  }
1395  int num_sliders = old_sliders->get_num_sliders();
1396  for (int si = 0; si < num_sliders; ++si) {
1397  SparseArray new_rows = old_sliders->get_slider_rows(si);
1398  new_rows <<= vertex_offset;
1399  new_sliders->add_slider(old_sliders->get_slider(si), new_rows);
1400  }
1401  _new_data->set_slider_table(SliderTable::register_table(new_sliders));
1402  }
1403 }
1404 
1405 /**
1406  * Updates all of the source Geoms to reference the new vertex data.
1407  */
1408 void GeomTransformer::NewCollectedData::
1409 update_geoms() {
1410  SourceGeoms::iterator sgi;
1411  for (sgi = _source_geoms.begin(); sgi != _source_geoms.end(); ++sgi) {
1412  SourceGeom &sg = (*sgi);
1413  sg._geom->offset_vertices(_new_data, sg._vertex_offset);
1414  }
1415 }
1416 
1417 /**
1418  *
1419  */
1420 void GeomTransformer::VertexDataAssoc::
1421 remove_unused_vertices(const GeomVertexData *vdata) {
1422  if (_geoms.empty()) {
1423  // Trivial case.
1424  return;
1425  }
1426 
1427  PT(Thread) current_thread = Thread::get_current_thread();
1428 
1429  BitArray referenced_vertices;
1430  bool any_referenced = false;
1431  GeomList::iterator gi;
1432  for (gi = _geoms.begin(); gi != _geoms.end(); ++gi) {
1433  Geom *geom = (*gi);
1434  if (geom->get_vertex_data() != vdata) {
1435  continue;
1436  }
1437 
1438  any_referenced = true;
1439  int num_primitives = geom->get_num_primitives();
1440  for (int i = 0; i < num_primitives; ++i) {
1441  GeomPrimitivePipelineReader reader(geom->get_primitive(i), current_thread);
1442  reader.get_referenced_vertices(referenced_vertices);
1443  }
1444  }
1445 
1446  if (!any_referenced) {
1447  return;
1448  }
1449 
1450  int num_vertices = vdata->get_num_rows();
1451  int new_num_vertices = referenced_vertices.get_num_on_bits();
1452  if (num_vertices <= new_num_vertices) {
1453  // All vertices are used.
1454  nassertv(num_vertices == new_num_vertices);
1455  return;
1456  }
1457 
1458  // Remap the vertices.
1459  int *remap_array = (int *)alloca(sizeof(int) * num_vertices);
1460  int new_index = 0;
1461  int index;
1462  int next_index = 0;
1463  for (index = 0; index < num_vertices; ++index) {
1464  if (referenced_vertices.get_bit(index)) {
1465  while (next_index <= index) {
1466  remap_array[next_index] = new_index;
1467  ++next_index;
1468  }
1469  ++new_index;
1470  }
1471  }
1472  while (next_index < num_vertices) {
1473  remap_array[next_index] = new_num_vertices - 1;
1474  ++next_index;
1475  }
1476 
1477  // Now recopy the actual vertex data, one array at a time.
1478  PT(GeomVertexData) new_vdata = new GeomVertexData(*vdata);
1479  new_vdata->unclean_set_num_rows(new_num_vertices);
1480 
1481  size_t num_arrays = vdata->get_num_arrays();
1482  nassertv(num_arrays == new_vdata->get_num_arrays());
1483 
1484  GeomVertexDataPipelineReader reader(vdata, current_thread);
1485  reader.check_array_readers();
1486  GeomVertexDataPipelineWriter writer(new_vdata, true, current_thread);
1487  writer.check_array_writers();
1488 
1489  for (size_t a = 0; a < num_arrays; ++a) {
1490  const GeomVertexArrayDataHandle *array_reader = reader.get_array_reader(a);
1491  GeomVertexArrayDataHandle *array_writer = writer.get_array_writer(a);
1492 
1493  int stride = array_reader->get_array_format()->get_stride();
1494  nassertv(stride == array_writer->get_array_format()->get_stride());
1495 
1496  int new_index = 0;
1497  int index;
1498  for (index = 0; index < num_vertices; ++index) {
1499  if (referenced_vertices.get_bit(index)) {
1500  array_writer->copy_subdata_from(new_index * stride, stride,
1501  array_reader,
1502  index * stride, stride);
1503  ++new_index;
1504  }
1505  }
1506  }
1507 
1508  // Update the subranges in the TransformBlendTable, if any.
1509  PT(TransformBlendTable) tbtable = new_vdata->modify_transform_blend_table();
1510  if (!tbtable.is_null()) {
1511  const SparseArray &rows = tbtable->get_rows();
1512  SparseArray new_rows;
1513  int num_subranges = rows.get_num_subranges();
1514  for (int si = 0; si < num_subranges; ++si) {
1515  int from = rows.get_subrange_begin(si);
1516  int to = rows.get_subrange_end(si);
1517  nassertv(from >= 0 && from < num_vertices && to > from && to <= num_vertices);
1518  int new_from = remap_array[from];
1519  int new_to = remap_array[to - 1] + 1;
1520  nassertv(new_from >= 0 && new_from < new_num_vertices && new_to >= new_from && new_to <= new_num_vertices);
1521  new_rows.set_range(new_from, new_to - new_from);
1522  }
1523  tbtable->set_rows(new_rows);
1524  }
1525 
1526  // Finally, reindex the Geoms.
1527  for (gi = _geoms.begin(); gi != _geoms.end(); ++gi) {
1528  Geom *geom = (*gi);
1529  if (geom->get_vertex_data() != vdata) {
1530  continue;
1531  }
1532 
1533  int num_primitives = geom->get_num_primitives();
1534  for (int i = 0; i < num_primitives; ++i) {
1535  PT(GeomPrimitive) prim = geom->modify_primitive(i);
1536  prim->make_indexed();
1537  PT(GeomVertexArrayData) vertices = prim->modify_vertices();
1538  GeomVertexRewriter rewriter(vertices, 0, current_thread);
1539 
1540  while (!rewriter.is_at_end()) {
1541  index = rewriter.get_data1i();
1542  nassertv(index >= 0 && index < num_vertices);
1543  new_index = remap_array[index];
1544  nassertv(new_index >= 0 && new_index < new_num_vertices);
1545  rewriter.set_data1i(new_index);
1546  }
1547  }
1548 
1549  geom->set_vertex_data(new_vdata);
1550  }
1551 }
Geom
A container for geometry primitives.
Definition: geom.h:54
SliderTable
Stores the total set of VertexSliders that the vertices in a particular GeomVertexData object might d...
Definition: sliderTable.h:37
GeomVertexArrayData
This is the data for one array of a GeomVertexData structure.
Definition: geomVertexArrayData.h:58
GeomVertexWriter::set_data4
void set_data4(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z, PN_stdfloat w)
Sets the write row to a particular 4-component value, and advances the write row.
Definition: geomVertexWriter.I:670
GeomTransformer::transform_colors
bool transform_colors(Geom *geom, const LVecBase4 &scale)
Transforms the colors in the indicated Geom by the indicated scale.
Definition: geomTransformer.cxx:319
sliderTable.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
BitArray::get_num_on_bits
int get_num_on_bits() const
Returns the number of bits that are set to 1 in the array.
Definition: bitArray.cxx:296
pvector< int >
Geom::set_vertex_data
void set_vertex_data(const GeomVertexData *data)
Replaces the Geom's underlying vertex data table with a completely new table.
Definition: geom.cxx:171
GeomVertexRewriter
This object provides the functionality of both a GeomVertexReader and a GeomVertexWriter,...
Definition: geomVertexRewriter.h:33
GeomVertexData
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
Definition: geomVertexData.h:68
TransformBlend
This defines a single entry in a TransformBlendTable.
Definition: transformBlend.h:32
vector_int.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomTransformer
An object specifically designed to transform the vertices of a Geom without disturbing indexing or af...
Definition: geomTransformer.h:42
geomMunger.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pmap
This is our own Panda specialization on the default STL map.
Definition: pmap.h:49
sceneGraphReducer.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TransformTable
Stores the total set of VertexTransforms that the vertices in a particular GeomVertexData object migh...
Definition: transformTable.h:38
GeomVertexData::get_format
get_format
Returns a pointer to the GeomVertexFormat structure that defines this data.
Definition: geomVertexData.h:98
GeomVertexData::get_transform_table
get_transform_table
Returns a const pointer to the TransformTable assigned to this data.
Definition: geomVertexData.h:120
GeomTransformer::finish_collect
int finish_collect(bool format_only)
This should be called after a call to collect_vertex_data() to finalize the changes and apply them to...
Definition: geomTransformer.cxx:1091
pStatTimer.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
colorAttrib.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexWriter
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
Definition: geomVertexWriter.h:55
RenderAttrib
This is the base class for a number of render attributes (other than transform) that may be set on sc...
Definition: renderAttrib.h:51
GeomVertexArrayDataHandle::copy_subdata_from
void copy_subdata_from(size_t to_start, size_t to_size, const GeomVertexArrayDataHandle *other, size_t from_start, size_t from_size)
Copies a portion of the data array from the other object into a portion of the data array of this obj...
Definition: geomVertexArrayData.cxx:755
InternalName
Encodes a string name in a hash table, mapping it to a pointer.
Definition: internalName.h:38
GeomVertexReader
This object provides a high-level interface for quickly reading a sequence of numeric values from a v...
Definition: geomVertexReader.h:47
GeomTransformer::doubleside
bool doubleside(GeomNode *node)
Duplicates triangles in this GeomNode so that each triangle is back-to-back with another triangle fac...
Definition: geomTransformer.cxx:857
Texture
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
SliderTable::get_slider_rows
const SparseArray & get_slider_rows(size_t n) const
Returns the set of rows (vertices) governed by the nth slider in the table.
Definition: sliderTable.I:70
UserVertexTransform
This is a specialization on VertexTransform that allows the user to specify any arbitrary transform m...
Definition: userVertexTransform.h:31
GeomTransformer::reverse
bool reverse(GeomNode *node)
Reverses the winding order of triangles in this GeomNode so that each triangle is facing in the oppos...
Definition: geomTransformer.cxx:894
GeomTransformer::apply_texture_colors
bool apply_texture_colors(Geom *geom, TextureStage *ts, Texture *tex, const TexMatrixAttrib *tma, const LColor &base_color, bool keep_vertex_color)
Removes textures from Geoms by applying the texture colors to the vertices.
Definition: geomTransformer.cxx:382
GeomTransformer::set_format
bool set_format(Geom *geom, const GeomVertexFormat *new_format)
Changes the GeomVertexData of the indicated Geom to use the specified format.
Definition: geomTransformer.cxx:659
GeomNode::get_num_geoms
get_num_geoms
Returns the number of geoms in the node.
Definition: geomNode.h:71
RenderState
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
GeomTransformer::apply_state
bool apply_state(GeomNode *node, const RenderState *state)
Applies the indicated render state to all the of Geoms.
Definition: geomTransformer.cxx:636
Thread::get_current_thread
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
GeomTransformer::remove_column
bool remove_column(Geom *geom, const InternalName *column)
Removes the named column from the vertex data in the Geom.
Definition: geomTransformer.cxx:695
PStatTimer
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
GeomTransformer::make_compatible_state
bool make_compatible_state(GeomNode *node)
Checks if the different geoms in the GeomNode have different RenderStates.
Definition: geomTransformer.cxx:739
renderState.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
GeomVertexReader::is_at_end
bool is_at_end() const
Returns true if the reader is currently at the end of the list of vertices, false otherwise.
Definition: geomVertexReader.I:362
GeomNode
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
CopyOnWriteObj
This is similar to RefCountObj, but it implements a CopyOnWriteObject inheritance instead of a Refere...
Definition: copyOnWriteObject.h:102
GeomVertexData::get_num_rows
int get_num_rows() const
Returns the number of rows stored within all the arrays.
Definition: geomVertexData.I:62
GeomVertexData::get_usage_hint
get_usage_hint
Returns the usage hint that was passed to the constructor, and which will be passed to each array dat...
Definition: geomVertexData.h:93
CycleDataWriter
This template class calls PipelineCycler::write() in the constructor and PipelineCycler::release_writ...
Definition: cycleDataWriter.h:34
transformTable.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomNode::GeomEntry
Definition: geomNode.h:113
SparseArray
This class records a set of integers, where each integer is either present or not present in the set.
Definition: sparseArray.h:42
SparseArray::get_num_subranges
size_t get_num_subranges() const
Returns the number of separate subranges stored in the SparseArray.
Definition: sparseArray.I:394
PStatCollector
A lightweight class that represents a single element that may be timed and/or counted via stats.
Definition: pStatCollector.h:43
geomVertexRewriter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
SliderTable::get_num_sliders
get_num_sliders
Returns the number of sliders in the table.
Definition: sliderTable.h:49
SparseArray::set_range
void set_range(int low_bit, int size)
Sets the indicated range of bits on.
Definition: sparseArray.I:218
GeomVertexReader::get_data4
const LVecBase4 & get_data4()
Returns the data associated with the read row, expressed as a 4-component value, and advances the rea...
Definition: geomVertexReader.I:590
GeomVertexData::get_name
get_name
Returns the name passed to the constructor, if any.
Definition: geomVertexData.h:89
TransformTable::get_num_transforms
get_num_transforms
Returns the number of transforms in the table.
Definition: transformTable.h:50
geom.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomTransformer::transform_texcoords
bool transform_texcoords(Geom *geom, const InternalName *from_name, InternalName *to_name, const LMatrix4 &mat)
Transforms the texture coordinates in the indicated Geom by the indicated matrix.
Definition: geomTransformer.cxx:174
GeomVertexColumn::get_numeric_type
NumericType get_numeric_type() const
Returns the token representing the numeric type of the data storage.
Definition: geomVertexColumn.I:116
GeomVertexColumn::get_contents
Contents get_contents() const
Returns the token representing the semantic meaning of the stored value.
Definition: geomVertexColumn.I:124
GeomVertexArrayDataHandle
This data object is returned by GeomVertexArrayData::get_handle() or modify_handle().
Definition: geomVertexArrayData.h:250
GeomVertexReader::get_data3
const LVecBase3 & get_data3()
Returns the data associated with the read row, expressed as a 3-component value, and advances the rea...
Definition: geomVertexReader.I:577
GeomVertexFormat
This class defines the physical layout of the vertex data stored within a Geom.
Definition: geomVertexFormat.h:55
ColorAttrib::get_color_type
get_color_type
Returns the type of color specified by this ColorAttrib.
Definition: colorAttrib.h:46
CycleDataStageWriter
This class is similar to CycleDataWriter, except it allows writing to a particular stage of the pipel...
Definition: cycleDataStageWriter.h:31
texturePeeker.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
BitArray::get_bit
bool get_bit(int index) const
Returns true if the nth bit is set, false if it is cleared.
Definition: bitArray.I:99
textureAttrib.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomTransformer::collect_vertex_data
int collect_vertex_data(Geom *geom, int collect_bits, bool format_only)
Collects together GeomVertexDatas from different geoms into one big (or several big) GeomVertexDatas.
Definition: geomTransformer.cxx:944
GeomVertexData::get_slider_table
get_slider_table
Returns a const pointer to the SliderTable assigned to this data.
Definition: geomVertexData.h:130
GeomVertexColumn::get_num_components
int get_num_components() const
Returns the number of components of the column: the number of instances of the NumericType in each el...
Definition: geomVertexColumn.I:87
texture.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomVertexData::get_num_arrays
get_num_arrays
Returns the number of individual arrays stored within the data.
Definition: geomVertexData.h:111
TexMatrixAttrib
Applies a transform matrix to UV's before they are rendered.
Definition: texMatrixAttrib.h:30
TextureStage::get_texcoord_name
get_texcoord_name
See set_texcoord_name.
Definition: textureStage.h:192
SparseArray::get_subrange_end
int get_subrange_end(size_t n) const
Returns the last numeric element, plus one, in the nth subrange.
Definition: sparseArray.I:415
GeomTransformer::set_color
bool set_color(Geom *geom, const LColor &color)
Overrides the color indicated within the Geom with the given replacement color.
Definition: geomTransformer.cxx:262
SparseArray::get_subrange_begin
int get_subrange_begin(size_t n) const
Returns the first numeric element in the nth subrange.
Definition: sparseArray.I:404
GeomVertexColumn
This defines how a single column is interleaved within a vertex array stored within a Geom.
Definition: geomVertexColumn.h:37
geomTransformer.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TexturePeeker
An instance of this object is returned by Texture::peek().
Definition: texturePeeker.h:27
GeomTransformer::register_vertices
void register_vertices(Geom *geom, bool might_have_unused)
Records the association of the Geom with its GeomVertexData, for the purpose of later removing unused...
Definition: geomTransformer.cxx:75
GeomNode::get_geom_state
get_geom_state
Returns the RenderState associated with the nth geom of the node.
Definition: geomNode.h:75
TransformBlendTable::add_blend
size_t add_blend(const TransformBlend &blend)
Adds a new blend to the table, and returns its index number.
Definition: transformBlendTable.cxx:87
geomNode.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GeomTransformer::reverse_normals
bool reverse_normals(Geom *geom)
Reverses the lighting normals on the vertex data, if any.
Definition: geomTransformer.cxx:820
GeomTransformer::transform_vertices
bool transform_vertices(Geom *geom, const LMatrix4 &mat)
Transforms the vertices and the normals in the indicated Geom by the indicated matrix.
Definition: geomTransformer.cxx:108
GeomPrimitivePipelineReader
Encapsulates the data from a GeomPrimitive, pre-fetched for one stage of the pipeline.
Definition: geomPrimitive.h:352
GeomVertexReader::get_data2
const LVecBase2 & get_data2()
Returns the data associated with the read row, expressed as a 2-component value, and advances the rea...
Definition: geomVertexReader.I:564
SliderTable::get_slider
get_slider
Returns the nth slider in the table.
Definition: sliderTable.h:49
TextureAttrib
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
Definition: textureAttrib.h:31
GeomNode::add_geom
void add_geom(Geom *geom, const RenderState *state=RenderState::make_empty())
Adds a new Geom to the node.
Definition: geomNode.cxx:586
userVertexTransform.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
ColorAttrib
Indicates what color should be applied to renderable geometry.
Definition: colorAttrib.h:27
Thread
A thread; that is, a lightweight process.
Definition: thread.h:46
config_pgraph.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
transformBlendTable.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TextureStage
Defines the properties of a named stage of the multitexture pipeline.
Definition: textureStage.h:35
GeomVertexDataPipelineWriter
Encapsulates the data from a GeomVertexData, pre-fetched for one stage of the pipeline.
Definition: geomVertexData.h:508
TransformBlendTable
This structure collects together the different combinations of transforms and blend amounts used by a...
Definition: transformBlendTable.h:45
VertexTransform
This is an abstract base class that holds a pointer to some transform, computed in some arbitrary way...
Definition: vertexTransform.h:35
BitArray
A dynamic array with an unlimited number of bits.
Definition: bitArray.h:39
GeomPrimitive
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
ColorAttrib::get_color
get_color
If the type is T_flat or T_off, this returns the color that will be applied to geometry.
Definition: colorAttrib.h:47
GeomVertexDataPipelineReader
Encapsulates the data from a GeomVertexData, pre-fetched for one stage of the pipeline.
Definition: geomVertexData.h:442
GeomTransformer::finish_apply
void finish_apply()
Should be called after performing any operations–particularly PandaNode::apply_attribs_to_vertices()–...
Definition: geomTransformer.cxx:913
GeomMunger
Objects of this class are used to convert vertex data from a Geom into a format suitable for passing ...
Definition: geomMunger.h:50
pStatCollector.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.