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