Panda3D
eggXfmSAnim.cxx
1 // Filename: eggXfmSAnim.cxx
2 // Created by: drose (19Feb99)
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 "eggXfmSAnim.h"
16 #include "eggSAnimData.h"
17 #include "eggXfmAnimData.h"
18 #include "eggParameters.h"
19 #include "config_egg.h"
20 
21 #include "indent.h"
22 #include "compose_matrix.h"
23 #include "dcast.h"
24 
25 #include <math.h>
26 
27 TypeHandle EggXfmSAnim::_type_handle;
28 
29 
30 // For now, the standard order is sphrt. This matches the old,
31 // incorrect behavior of decompose_matrix(). When we have a new
32 // egg-optchar, we can safely remove the old decompose_matrix() and
33 // restore the correct standard order.
34 const string EggXfmSAnim::_standard_order_legacy = "sphrt";
35 const string EggXfmSAnim::_standard_order_hpr_fix = "srpht";
36 
37 
38 ////////////////////////////////////////////////////////////////////
39 // Function: EggXfmSAnim::Conversion constructor
40 // Access: Public
41 // Description: Converts the older-style XfmAnim table to the
42 // newer-style XfmSAnim table.
43 ////////////////////////////////////////////////////////////////////
44 EggXfmSAnim::
45 EggXfmSAnim(const EggXfmAnimData &convert_from)
46  : EggGroupNode(convert_from.get_name())
47 {
48  _has_fps = false;
49  _coordsys = convert_from.get_coordinate_system();
50 
51  if (convert_from.has_order()) {
52  set_order(convert_from.get_order());
53  }
54  if (convert_from.has_fps()) {
55  set_fps(convert_from.get_fps());
56  }
57 
58  const string &contents = convert_from.get_contents();
59  for (int col = 0; col < convert_from.get_num_cols(); col++) {
60  EggSAnimData *sanim = new EggSAnimData(contents.substr(col, 1));
61  add_child(sanim);
62  for (int row = 0; row < convert_from.get_num_rows(); row++) {
63  sanim->add_data(convert_from.get_value(row, col));
64  }
65  }
66 }
67 
68 ////////////////////////////////////////////////////////////////////
69 // Function: EggXfmSAnim::optimize
70 // Access: Public
71 // Description: Optimizes the table by collapsing redundant
72 // sub-tables.
73 ////////////////////////////////////////////////////////////////////
74 void EggXfmSAnim::
76  iterator ci = begin();
77  while (ci != end()) {
78  iterator ci_next = ci;
79  ++ci_next;
80 
81  if ((*ci)->is_of_type(EggSAnimData::get_class_type())) {
82  EggSAnimData *sanim = DCAST(EggSAnimData, *ci);
83  sanim->optimize();
84 
85  if (sanim->get_num_rows() == 1) {
86  // If we've optimized down to one value, check to see if it is
87  // a default value.
88  double value = sanim->get_value(0);
89  double default_value;
90  if (sanim->has_name() && strchr("ijk", sanim->get_name()[0]) != NULL) {
91  default_value = 1.0;
92  } else {
93  default_value = 0.0;
94  }
95 
96  if (fabs(value - default_value) < egg_parameters->_table_threshold) {
97  // It's a default-valued table, and therefore redundant:
98  // remove it.
99  erase(ci);
100  }
101  }
102  }
103 
104  ci = ci_next;
105  }
106 }
107 
108 ////////////////////////////////////////////////////////////////////
109 // Function: EggXfmSAnim::optimize_to_standard_order
110 // Access: Public
111 // Description: Optimizes the table by collapsing redundant
112 // sub-tables, and simultaneously ensures that the order
113 // string is the standard order (which is the same as
114 // that supported by compose_matrix() and
115 // decompose_matrix()).
116 ////////////////////////////////////////////////////////////////////
117 void EggXfmSAnim::
119  if (get_order() != get_standard_order()) {
120  normalize_by_rebuilding();
121  }
122  optimize();
123 }
124 
125 ////////////////////////////////////////////////////////////////////
126 // Function: EggXfmSAnim::normalize
127 // Access: Public
128 // Description: The inverse operation of optimize(), this ensures
129 // that all the sub-tables have the same length by
130 // duplicating rows as necessary. This is needed before
131 // doing operations like add_data() or set_value() on an
132 // existing table.
133 ////////////////////////////////////////////////////////////////////
134 void EggXfmSAnim::
136  if (get_order() != get_standard_order()) {
137  // If our order string is wrong, we must fix it now. This will
138  // incidentally also normalize the table, because we are totally
139  // rebuilding it.
140  normalize_by_rebuilding();
141 
142  } else {
143  // Otherwise, if the order string is already the standard order
144  // string, we can do this the easy way (from a computational
145  // standpoint), which is just to lengthen the tables directly.
146  normalize_by_expanding();
147  }
148 }
149 
150 ////////////////////////////////////////////////////////////////////
151 // Function: EggXfmSAnim::is_anim_matrix
152 // Access: Public, Virtual
153 // Description: Returns true if this node represents a table of
154 // animation transformation data, false otherwise.
155 ////////////////////////////////////////////////////////////////////
156 bool EggXfmSAnim::
157 is_anim_matrix() const {
158  return true;
159 }
160 
161 ////////////////////////////////////////////////////////////////////
162 // Function: EggXfmSAnim::write
163 // Access: Public, Virtual
164 // Description: Writes the data to the indicated output stream in Egg
165 // format.
166 ////////////////////////////////////////////////////////////////////
167 void EggXfmSAnim::
168 write(ostream &out, int indent_level) const {
169  test_under_integrity();
170 
171  write_header(out, indent_level, "<Xfm$Anim_S$>");
172 
173  if (has_fps()) {
174  indent(out, indent_level + 2) << "<Scalar> fps { " << get_fps() << " }\n";
175  }
176 
177  if (has_order()) {
178  indent(out, indent_level + 2)
179  << "<Char*> order { " << get_order() << " }\n";
180  }
181 
182  // Rather than calling EggGroupNode::write() to write out the
183  // children, we do it directly here so we can control the order. We
184  // write out all the non-table children first, then write out the
185  // table children in our expected order. (Normally there are only
186  // table children.)
187  EggSAnimData *tables[num_matrix_components];
188  memset(tables, 0, sizeof(EggSAnimData *) * num_matrix_components);
189 
190  const_iterator ci;
191  for (ci = begin(); ci != end(); ++ci) {
192  EggNode *child = (*ci);
193  if (child->is_of_type(EggSAnimData::get_class_type())) {
194  EggSAnimData *sanim = DCAST(EggSAnimData, *ci);
195 
196  // Each child SAnimData table should have a one-letter name.
197  nassertv(sanim->get_name().length() == 1);
198  char name = sanim->get_name()[0];
199  char *p = (char *)strchr(matrix_component_letters, name);
200  nassertv(p != (char *)NULL);
201  if (p != (char *)NULL) {
202  int index = p - matrix_component_letters;
203  nassertv(tables[index] == (EggSAnimData *)NULL);
204  tables[index] = sanim;
205  }
206  } else {
207  // Any non-table children are written directly.
208  child->write(out, indent_level + 2);
209  }
210  }
211 
212  // Now write out the table children in our normal order.
213  for (int i = 0; i < num_matrix_components; i++) {
214  if (tables[i] != (EggSAnimData *)NULL) {
215  tables[i]->write(out, indent_level + 2);
216  }
217  }
218 
219  indent(out, indent_level) << "}\n";
220 }
221 
222 
223 ////////////////////////////////////////////////////////////////////
224 // Function: EggXfmSAnim::compose_with_order
225 // Access: Public, Static
226 // Description: Composes a matrix out of the nine individual
227 // components, respecting the order string. The
228 // components will be applied in the order indicated by
229 // the string.
230 ////////////////////////////////////////////////////////////////////
231 void EggXfmSAnim::
233  const LVecBase3d &scale,
234  const LVecBase3d &shear,
235  const LVecBase3d &hpr,
236  const LVecBase3d &trans,
237  const string &order,
238  CoordinateSystem cs) {
239 
240  mat = LMatrix4d::ident_mat();
241 
242  bool reverse_roll = false;
243 
244  if (order == "sphrt" && egg_support_old_anims) {
245  // As a special case, if the order string is exactly "sphrt"
246  // (which is what all our legacy anim files used), we interpret
247  // roll in the opposite direction (as our legacy anim files did).
248  reverse_roll = true;
249  }
250 
251  string::const_iterator pi;
252  for (pi = order.begin(); pi != order.end(); ++pi) {
253  switch (*pi) {
254  case 's':
255  mat = mat * LMatrix4d::scale_shear_mat(scale, shear, cs);
256  break;
257 
258  case 'h':
259  mat = mat * LMatrix4d::rotate_mat_normaxis(hpr[0], LVector3d::up(cs), cs);
260  break;
261 
262  case 'p':
263  mat = mat * LMatrix4d::rotate_mat_normaxis(hpr[1], LVector3d::right(cs), cs);
264  break;
265 
266  case 'r':
267  if (reverse_roll) {
268  mat = mat * LMatrix4d::rotate_mat_normaxis(-hpr[2], LVector3d::forward(cs), cs);
269  } else {
270  mat = mat * LMatrix4d::rotate_mat_normaxis(hpr[2], LVector3d::forward(cs), cs);
271  }
272  break;
273 
274  case 't':
275  mat = mat * LMatrix4d::translate_mat(trans);
276  break;
277 
278  default:
279  egg_cat.warning()
280  << "Invalid letter in order string: " << *pi << "\n";
281  }
282  }
283 }
284 
285 ////////////////////////////////////////////////////////////////////
286 // Function: EggXfmSAnim::get_num_rows
287 // Access: Public
288 // Description: Returns the effective number of rows in the table.
289 // This is actually the number of rows of the smallest
290 // subtable larger than one row. This is a convenience
291 // function that treats the table of tables as if it
292 // were a single table of matrices.
293 ////////////////////////////////////////////////////////////////////
294 int EggXfmSAnim::
295 get_num_rows() const {
296  bool found_any = false;
297  int min_rows = 1;
298 
299  const_iterator ci;
300  for (ci = begin(); ci != end(); ++ci) {
301  if ((*ci)->is_of_type(EggSAnimData::get_class_type())) {
302  EggSAnimData *sanim = DCAST(EggSAnimData, *ci);
303  if (sanim->get_num_rows() > 1) {
304  if (!found_any) {
305  min_rows = sanim->get_num_rows();
306 
307  } else {
308  min_rows = min(min_rows, sanim->get_num_rows());
309  }
310  }
311  }
312  }
313 
314  return min_rows;
315 }
316 
317 ////////////////////////////////////////////////////////////////////
318 // Function: EggXfmSAnim::get_value
319 // Access: Public
320 // Description: Returns the value of the aggregate row of the table
321 // as a matrix. This is a convenience function that
322 // treats the table of tables as if it were a single
323 // table of matrices. It is an error to call this if
324 // any SAnimData children of this node have an improper
325 // name (e.g. not a single letter, or not one of
326 // "ijkabchprxyz").
327 ////////////////////////////////////////////////////////////////////
328 void EggXfmSAnim::
329 get_value(int row, LMatrix4d &mat) const {
330  LVector3d scale(1.0, 1.0, 1.0);
331  LVector3d shear(0.0, 0.0, 0.0);
332  LVector3d hpr(0.0, 0.0, 0.0);
333  LVector3d translate(0.0, 0.0, 0.0);
334 
335  const_iterator ci;
336  for (ci = begin(); ci != end(); ++ci) {
337  if ((*ci)->is_of_type(EggSAnimData::get_class_type())) {
338  EggSAnimData *sanim = DCAST(EggSAnimData, *ci);
339 
340  if (sanim->get_num_rows() == 0) {
341  // If the table is totally empty, let's keep the default
342  // value.
343  break;
344  }
345 
346  double value;
347  if (sanim->get_num_rows() == 1) {
348  value = sanim->get_value(0);
349  } else {
350  nassertv(row < sanim->get_num_rows());
351  value = sanim->get_value(row);
352  }
353 
354  // Each child SAnimData table should have a one-letter name.
355  nassertv(sanim->get_name().length() == 1);
356 
357  switch (sanim->get_name()[0]) {
358  case 'i':
359  scale[0] = value;
360  break;
361 
362  case 'j':
363  scale[1] = value;
364  break;
365 
366  case 'k':
367  scale[2] = value;
368  break;
369 
370  case 'a':
371  shear[0] = value;
372  break;
373 
374  case 'b':
375  shear[1] = value;
376  break;
377 
378  case 'c':
379  shear[2] = value;
380  break;
381 
382  case 'h':
383  hpr[0] = value;
384  break;
385 
386  case 'p':
387  hpr[1] = value;
388  break;
389 
390  case 'r':
391  hpr[2] = value;
392  break;
393 
394  case 'x':
395  translate[0] = value;
396  break;
397 
398  case 'y':
399  translate[1] = value;
400  break;
401 
402  case 'z':
403  translate[2] = value;
404  break;
405 
406  default:
407  // One of the child tables had an invalid name.
408  nassertv(false);
409  }
410  }
411  }
412 
413  // So now we've got the nine components; build a matrix.
414  compose_with_order(mat, scale, shear, hpr, translate, get_order(), _coordsys);
415 }
416 
417 ////////////////////////////////////////////////////////////////////
418 // Function: EggXfmSAnim::set_value
419 // Access: Public
420 // Description: Replaces the indicated row of the table with the
421 // given matrix.
422 //
423 // This function can only be called if all the
424 // constraints of add_data(), below, are met. Call
425 // normalize() first if you are not sure.
426 //
427 // The return value is true if the matrix can be
428 // decomposed and stored as scale, shear, rotate, and
429 // translate, or false otherwise. The data is set in
430 // either case.
431 ////////////////////////////////////////////////////////////////////
432 bool EggXfmSAnim::
433 set_value(int row, const LMatrix4d &mat) {
434  nassertr(get_order() == get_standard_order(), false);
435 
436  double components[num_matrix_components];
437  bool add_ok = decompose_matrix(mat, components, _coordsys);
438 
439  // Sanity check our sub-tables.
440 #ifndef NDEBUG
441  int table_length = -1;
442 #endif
443 
444  for (int i = 0; i < num_matrix_components; i++) {
445  string name(1, matrix_component_letters[i]);
446  EggNode *child = find_child(name);
447  nassertr(child != (EggNode *)NULL &&
448  child->is_of_type(EggSAnimData::get_class_type()), false);
449  EggSAnimData *sanim = DCAST(EggSAnimData, child);
450 
451 #ifndef NDEBUG
452  // Each table must have the same length.
453  if (table_length < 0) {
454  table_length = sanim->get_num_rows();
455  } else {
456  nassertr(sanim->get_num_rows() == table_length, false);
457  }
458 #endif
459  sanim->set_value(row, components[i]);
460  }
461 
462 #ifndef NDEBUG
463  // Sanity check the result.
464  LMatrix4d new_mat;
465  get_value(row, new_mat);
466  if (!new_mat.almost_equal(mat, 0.005)) {
467  egg_cat.warning()
468  << "After set_row(" << row << ", ...) to:\n";
469  mat.write(egg_cat.warning(false), 2);
470  egg_cat.warning(false)
471  << "which produces components:\n";
472  for (int i = 0; i < num_matrix_components; i += 3) {
473  egg_cat.warning(false)
474  << " "
475  << matrix_component_letters[i]
476  << matrix_component_letters[i + 1]
477  << matrix_component_letters[i + 2]
478  << ": "
479  << components[i] << " "
480  << components[i + 1] << " "
481  << components[i + 2] << "\n";
482  }
483  egg_cat.warning(false)
484  << "new mat set was:\n";
485  new_mat.write(egg_cat.warning(false), 2);
486  return false;
487  }
488 #endif
489 
490  return add_ok;
491 }
492 
493 ////////////////////////////////////////////////////////////////////
494 // Function: EggXfmSAnim::add_data
495 // Access: Public
496 // Description: Adds a new matrix to the table, by adding a new row
497 // to each of the subtables.
498 //
499 // This is a convenience function that
500 // treats the table of tables as if it were a single
501 // table of matrices. It is an error to call this if
502 // any SAnimData children of this node have an improper
503 // name (e.g. not a single letter, or not one of
504 // "ijkabchprxyz").
505 //
506 // This function has the further requirement that all
507 // nine of the subtables must exist and be of the same
508 // length. Furthermore, the order string must be the
509 // standard order string, which matches the system
510 // compose_matrix() and decompose_matrix() functions.
511 //
512 // Thus, you probably cannot take an existing
513 // EggXfmSAnim object and start adding matrices to the
514 // end; you must clear out the original data first. (As
515 // a special exception, if no tables exist, they will be
516 // created.) The method normalize() will do this for
517 // you on an existing EggXfmSAnim.
518 //
519 // This function may fail silently if the matrix cannot
520 // be decomposed into scale, shear, rotate, and
521 // translate. In this case, the closest approximation
522 // is added to the table, and false is returned.
523 ////////////////////////////////////////////////////////////////////
524 bool EggXfmSAnim::
525 add_data(const LMatrix4d &mat) {
526  double components[num_matrix_components];
527  bool add_ok = decompose_matrix(mat, components, _coordsys);
528 
529  if (empty()) {
530  // If we have no children, create all twelve tables now.
531  for (int i = 0; i < num_matrix_components; i++) {
532  char name = matrix_component_letters[i];
533  EggSAnimData *sanim = new EggSAnimData(string(1, name));
534  add_child(sanim);
535  }
536 
537  // Also insist on the correct ordering right off the bat.
538  set_order(get_standard_order());
539  }
540 
541  nassertr(get_order() == get_standard_order(), false);
542 
543 #ifndef NDEBUG
544  int table_length = -1;
545 #endif
546 
547  for (int i = 0; i < num_matrix_components; i++) {
548  string name(1, matrix_component_letters[i]);
549  EggNode *child = find_child(name);
550  nassertr(child != (EggNode *)NULL &&
551  child->is_of_type(EggSAnimData::get_class_type()), false);
552  EggSAnimData *sanim = DCAST(EggSAnimData, child);
553 
554 #ifndef NDEBUG
555  // Each table must have the same length.
556  if (table_length < 0) {
557  table_length = sanim->get_num_rows();
558  } else {
559  nassertr(sanim->get_num_rows() == table_length, false);
560  }
561 #endif
562  sanim->add_data(components[i]);
563  }
564 
565 #ifndef NDEBUG
566  // Sanity check the result.
567  LMatrix4d new_mat;
568  if (table_length >= 0) {
569  get_value(table_length, new_mat);
570  } else {
571  get_value(0, new_mat);
572  }
573  if (!new_mat.almost_equal(mat, 0.005)) {
574  egg_cat.warning()
575  << "After add_data():\n";
576  mat.write(egg_cat.warning(false), 2);
577  egg_cat.warning(false)
578  << "which produces components:\n";
579  for (int i = 0; i < num_matrix_components; i += 3) {
580  egg_cat.warning(false)
581  << " "
582  << matrix_component_letters[i]
583  << matrix_component_letters[i + 1]
584  << matrix_component_letters[i + 2]
585  << ": "
586  << components[i] << " "
587  << components[i + 1] << " "
588  << components[i + 2] << "\n";
589  }
590  egg_cat.warning(false)
591  << "new mat set was:\n";
592  new_mat.write(egg_cat.warning(false), 2);
593  return false;
594  }
595 #endif
596 
597  return add_ok;
598 }
599 
600 ////////////////////////////////////////////////////////////////////
601 // Function: EggXfmSAnim::add_component_data
602 // Access: Public
603 // Description: Adds a new row to the named component (one of
604 // matrix_component_letters) of the table.
605 ////////////////////////////////////////////////////////////////////
606 void EggXfmSAnim::
607 add_component_data(const string &component_name, double value) {
608  EggNode *child = find_child(component_name);
609  EggSAnimData *sanim;
610  if (child == (EggNode *)NULL) {
611  // We don't have this component yet; create it.
612  sanim = new EggSAnimData(component_name);
613  add_child(sanim);
614 
615  } else {
616  DCAST_INTO_V(sanim, child);
617  }
618 
619  sanim->add_data(value);
620 }
621 
622 ////////////////////////////////////////////////////////////////////
623 // Function: EggXfmSAnim::add_component_data
624 // Access: Public
625 // Description: Adds a new row to the indicated component (0-12) of
626 // the table.
627 ////////////////////////////////////////////////////////////////////
628 void EggXfmSAnim::
629 add_component_data(int component, double value) {
630  nassertv(component >= 0 && component < num_matrix_components);
631 
632  string name(1, matrix_component_letters[component]);
633  add_component_data(name, value);
634 }
635 
636 ////////////////////////////////////////////////////////////////////
637 // Function: EggXfmSAnim::r_transform
638 // Access: Protected, Virtual
639 // Description: Applies the indicated transform to all the rows of
640 // the table. This actually forces the generation of a
641 // totally new set of rows, and will quietly change the
642 // order to the standard order (if it is different).
643 ////////////////////////////////////////////////////////////////////
644 void EggXfmSAnim::
645 r_transform(const LMatrix4d &mat, const LMatrix4d &inv,
646  CoordinateSystem to_cs) {
647  // We need to build an inverse matrix that doesn't reflect the
648  // translation component.
649  LMatrix4d inv1 = inv;
650  inv1.set_row(3, LVector3d(0.0, 0.0, 0.0));
651 
652  // Save a temporary copy of the original data.
653  EggXfmSAnim original;
654  original.steal_children(*this);
655  original = (*this);
656 
657  // Now we have no children, so our data is clear. Rebuild it.
658  if (to_cs != CS_default) {
659  _coordsys = to_cs;
660  }
661 
662  int num_rows = original.get_num_rows();
663  LMatrix4d orig_mat;
664  for (int r = 0; r < num_rows; r++) {
665  original.get_value(r, orig_mat);
666  bool result = add_data(inv1 * orig_mat * mat);
667 
668  // If this assertion fails, we attempted to transform by a skew
669  // matrix or some such thing that cannot be represented in an anim
670  // file.
671  nassertv(result);
672  }
673 
674  // Now clean out the redundant columns we created.
675  optimize();
676 }
677 
678 ////////////////////////////////////////////////////////////////////
679 // Function: EggXfmSAnim::r_mark_coordsys
680 // Access: Protected, Virtual
681 // Description: This is only called immediately after loading an egg
682 // file from disk, to propagate the value found in the
683 // CoordinateSystem entry (or the default Y-up
684 // coordinate system) to all nodes that care about what
685 // the coordinate system is.
686 ////////////////////////////////////////////////////////////////////
687 void EggXfmSAnim::
688 r_mark_coordsys(CoordinateSystem cs) {
689  _coordsys = cs;
690 }
691 
692 ////////////////////////////////////////////////////////////////////
693 // Function: EggXfmSAnim::normalize_by_rebuilding
694 // Access: Private
695 // Description: One implementation of normalize() that rebuilds the
696 // entire table by composing and decomposing the rows.
697 // This has the advantage that it will also reset the
698 // order string to the standard order string, but it is
699 // more computationally intensive and is subject to
700 // roundoff error.
701 ////////////////////////////////////////////////////////////////////
702 void EggXfmSAnim::
703 normalize_by_rebuilding() {
704  // Save a temporary copy of the original data.
705  EggXfmSAnim original;
706  original.steal_children(*this);
707  original = (*this);
708 
709  // Now we have no children, so our data is clear. Rebuild it.
710  int num_rows = original.get_num_rows();
711  LMatrix4d orig_mat;
712  for (int r = 0; r < num_rows; r++) {
713  original.get_value(r, orig_mat);
714  bool result = add_data(orig_mat);
715 
716  // If this assertion fails, we somehow got a matrix out of the
717  // original table that we could not represent in the new table.
718  // That shouldn't be possible; there's probably something wrong
719  // in decompose_matrix().
720  nassertv(result);
721  }
722 }
723 
724 ////////////////////////////////////////////////////////////////////
725 // Function: EggXfmSAnim::normalize_by_expanding
726 // Access: Private
727 // Description: Another implementation of normalize() that simply
728 // expands any one-row tables and creates default-valued
729 // tables where none were before. This will not change
730 // the order string, but is much faster and does not
731 // introduce roundoff error.
732 ////////////////////////////////////////////////////////////////////
733 void EggXfmSAnim::
734 normalize_by_expanding() {
735  iterator ci;
736 
737  // First, determine which tables we already have, and how long they
738  // are.
739  int num_tables = 0;
740  int table_length = 1;
741  string remaining_tables = matrix_component_letters;
742 
743  for (ci = begin(); ci != end(); ++ci) {
744  if ((*ci)->is_of_type(EggSAnimData::get_class_type())) {
745  EggSAnimData *sanim = DCAST(EggSAnimData, *ci);
746 
747  nassertv(sanim->get_name().length() == 1);
748  char name = sanim->get_name()[0];
749  size_t p = remaining_tables.find(name);
750  nassertv(p != string::npos);
751  remaining_tables[p] = ' ';
752 
753  num_tables++;
754  if (sanim->get_num_rows() > 1) {
755  if (table_length == 1) {
756  table_length = sanim->get_num_rows();
757  } else {
758  nassertv(sanim->get_num_rows() == table_length);
759  }
760  }
761  }
762  }
763 
764  if (num_tables < num_matrix_components) {
765  // Create new, default, children for each table we lack.
766  for (size_t p = 0; p < remaining_tables.length(); p++) {
767  if (remaining_tables[p] != ' ') {
768  double default_value;
769  switch (remaining_tables[p]) {
770  case 'i':
771  case 'j':
772  case 'k':
773  default_value = 1.0;
774  break;
775 
776  default:
777  default_value = 0.0;
778  }
779 
780  string name(1, remaining_tables[p]);
781  EggSAnimData *sanim = new EggSAnimData(name);
782  add_child(sanim);
783  sanim->add_data(default_value);
784  }
785  }
786  }
787 
788  // Now expand any one-row tables as needed.
789  for (ci = begin(); ci != end(); ++ci) {
790  if ((*ci)->is_of_type(EggSAnimData::get_class_type())) {
791  EggSAnimData *sanim = DCAST(EggSAnimData, *ci);
792  if (sanim->get_num_rows() == 1) {
793  double value = sanim->get_value(0);
794  for (int i = 1; i < table_length; i++) {
795  sanim->add_data(value);
796  }
797  }
798  nassertv(sanim->get_num_rows() == table_length);
799  }
800  }
801 }
void optimize()
Optimizes the data by collapsing a long table of duplicate values into a single value.
int get_num_cols() const
Returns the number of columns in the table.
int get_num_rows() const
Returns the number of rows in the table.
bool add_data(const LMatrix4d &mat)
Adds a new matrix to the table, by adding a new row to each of the subtables.
This is a 4-by-4 transform matrix.
Definition: lmatrix.h:4716
void add_data(double value)
Adds a single element to the table.
Definition: eggAnimData.I:116
bool almost_equal(const LMatrix4d &other, double threshold) const
Returns true if two matrices are memberwise equal within a specified tolerance.
Definition: lmatrix.cxx:2058
void add_component_data(const string &component_name, double value)
Adds a new row to the named component (one of matrix_component_letters) of the table.
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:51
void normalize()
The inverse operation of optimize(), this ensures that all the sub-tables have the same length by dup...
virtual void write(ostream &out, int indent_level) const
Writes the data to the indicated output stream in Egg format.
Corresponding to an <S$Anim> entry, this stores a single column of numbers, for instance for a morph ...
Definition: eggSAnimData.h:28
CoordinateSystem get_coordinate_system() const
Returns the coordinate system this table believes it is defined within.
double get_value(int row) const
Returns the value at the indicated row.
Definition: eggSAnimData.I:68
static LMatrix4d translate_mat(const LVecBase3d &trans)
Returns a matrix that applies the indicated translation.
Definition: lmatrix.h:6662
void set_row(int row, const LVecBase4d &v)
Replaces the indicated row of the matrix.
Definition: lmatrix.h:5452
static const string & get_standard_order()
Returns the standard order of matrix component composition.
Definition: eggXfmSAnim.I:156
static LVector3d forward(CoordinateSystem cs=CS_default)
Returns the forward vector for the given coordinate system.
Definition: lvector3.h:1239
static const LMatrix4d & ident_mat()
Returns an identity matrix.
Definition: lmatrix.h:5168
double get_value(int row, int col) const
Returns the value at the indicated row.
This corresponds to an <Xfm$Anim_S$> entry, which is a collection of up to nine <S$Anim> entries that...
Definition: eggXfmSAnim.h:33
double get_fps() const
This is only valid if has_fps() returns true.
Definition: eggAnimData.I:94
void steal_children(EggGroupNode &other)
Moves all the children from the other node to this one.
static LMatrix4d rotate_mat_normaxis(double angle, const LVecBase3d &axis, CoordinateSystem cs=CS_default)
Returns a matrix that rotates by the given angle in degrees counterclockwise about the indicated vect...
Definition: lmatrix.h:6705
static LMatrix4d scale_shear_mat(const LVecBase3d &scale, const LVecBase3d &shear, CoordinateSystem cs=CS_default)
Returns a matrix that applies the indicated scale and shear.
Definition: lmatrix.h:6791
Corresponding to an <Xfm$Anim> entry, this stores a two-dimensional table with up to nine columns...
void optimize_to_standard_order()
Optimizes the table by collapsing redundant sub-tables, and simultaneously ensures that the order str...
void write_header(ostream &out, int indent_level, const char *egg_keyword) const
Writes the first line of the egg object, e.g.
This is the base class for all three-component vectors and points.
Definition: lvecBase3.h:1471
void optimize()
Optimizes the table by collapsing redundant sub-tables.
Definition: eggXfmSAnim.cxx:75
static void compose_with_order(LMatrix4d &mat, const LVecBase3d &scale, const LVecBase3d &shear, const LVecBase3d &hpr, const LVecBase3d &trans, const string &order, CoordinateSystem cs)
Composes a matrix out of the nine individual components, respecting the order string.
virtual void write(ostream &out, int indent_level) const
Writes the data to the indicated output stream in Egg format.
static LVector3d right(CoordinateSystem cs=CS_default)
Returns the right vector for the given coordinate system.
Definition: lvector3.h:1228
void get_value(int row, LMatrix4d &mat) const
Returns the value of the aggregate row of the table as a matrix.
This is a three-component vector distance (as opposed to a three-component point, which represents a ...
Definition: lvector3.h:760
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
int get_num_rows() const
Returns the effective number of rows in the table.
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:38
virtual bool is_anim_matrix() const
Returns true if this node represents a table of animation transformation data, false otherwise...
double get_fps() const
This is only valid if has_fps() returns true.
Definition: eggXfmSAnim.I:98
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:63
EggNode * find_child(const string &name) const
Returns the child of this node whose name is the indicated string, or NULL if there is no child of th...
int get_num_rows() const
Returns the number of rows in the table.
Definition: eggSAnimData.I:56
bool set_value(int row, const LMatrix4d &mat)
Replaces the indicated row of the table with the given matrix.
bool has_name() const
Returns true if the Namable has a nonempty name set, false if the name is empty.
Definition: namable.I:75
static LVector3d up(CoordinateSystem cs=CS_default)
Returns the up vector for the given coordinate system.
Definition: lvector3.h:1201
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85