Panda3D
eggOptchar.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 eggOptchar.cxx
10  * @author drose
11  * @date 2003-07-18
12  */
13 
14 #include "eggOptchar.h"
15 #include "eggOptcharUserData.h"
16 #include "vertexMembership.h"
17 
18 #include "eggJointData.h"
19 #include "eggSliderData.h"
20 #include "eggCharacterCollection.h"
21 #include "eggCharacterData.h"
22 #include "eggBackPointer.h"
23 #include "eggGroupNode.h"
24 #include "eggPrimitive.h"
25 #include "eggVertexPool.h"
26 #include "eggTable.h"
27 #include "eggGroup.h"
28 #include "eggAnimPreload.h"
29 #include "string_utils.h"
30 #include "dcast.h"
31 #include "pset.h"
32 #include "compose_matrix.h"
33 #include "fftCompressor.h"
34 
35 #include <algorithm>
36 
37 using std::cout;
38 using std::setw;
39 using std::string;
40 
41 /**
42  *
43  */
44 EggOptchar::
45 EggOptchar() {
46  add_path_replace_options();
47  add_path_store_options();
50  add_fixrest_option();
51 
52  set_program_brief("optimizes character models and animations in .egg files");
53  set_program_description
54  ("egg-optchar performs basic optimizations of a character model "
55  "and its associated animations, primarily by analyzing the "
56  "animation tables and removing unneeded joints and/or morphs. "
57  "It can also perform basic restructuring operations on the "
58  "character hierarchy.");
59 
60  add_option
61  ("ls", "", 0,
62  "List the joint hierarchy instead of performing any operations.",
63  &EggOptchar::dispatch_none, &_list_hierarchy);
64 
65  add_option
66  ("lsv", "", 0,
67  "List the joint hierarchy along with an indication of the properties "
68  "each joint.",
69  &EggOptchar::dispatch_none, &_list_hierarchy_v);
70 
71  add_option
72  ("lsp", "", 0,
73  "List the existing joint hierarchy as a series of -p joint,parent "
74  "commands, suitable for pasting into an egg-optchar command line.",
75  &EggOptchar::dispatch_none, &_list_hierarchy_p);
76 
77  add_option
78  ("keep", "joint[,joint...]", 0,
79  "Keep the named joints (or sliders) in the character, even if they do "
80  "not appear to be needed by the animation.",
81  &EggOptchar::dispatch_vector_string_comma, nullptr, &_keep_components);
82 
83  add_option
84  ("drop", "joint[,joint...]", 0,
85  "Removes the named joints or sliders, even if they appear to be needed.",
86  &EggOptchar::dispatch_vector_string_comma, nullptr, &_drop_components);
87 
88  add_option
89  ("expose", "joint[,joint...]", 0,
90  "Expose the named joints by flagging them with a DCS attribute, so "
91  "each one can be found in the scene graph when the character is loaded, "
92  "and objects can be parented to it. This implies -keep.",
93  &EggOptchar::dispatch_vector_string_comma, nullptr, &_expose_components);
94 
95  add_option
96  ("suppress", "joint[,joint...]", 0,
97  "The opposite of suppress, this prevents the named joints from "
98  "being created with an implicit DCS attribute, even if they contain "
99  "rigid geometry. The default is to create an implicit node for any "
100  "joint that contains rigid geometry, to take advantage of display "
101  "list and/or vertex buffer caching. This does not imply -keep.",
102  &EggOptchar::dispatch_vector_string_comma, nullptr, &_suppress_components);
103 
104  add_option
105  ("flag", "node[,node...][=name]", 0,
106  "Assign the indicated name to the geometry within the given nodes. "
107  "This will make the geometry visible as a node in the resulting "
108  "character model when it is loaded in the scene graph (normally, "
109  "the node hierarchy is suppressed when loading characters). This "
110  "is different from -expose in that it reveals geometry rather than "
111  "joints; the revealed node can be hidden or its attributes changed "
112  "at runtime, but it will be animated by its vertices, not the node, so "
113  "objects parented to this node will not inherit its animation.",
114  &EggOptchar::dispatch_flag_groups, nullptr, &_flag_groups);
115 
116  add_option
117  ("defpose", "anim.egg,frame", 0,
118  "Specify the model's default pose. The pose is taken "
119  "from the indicated frame of the named animation file (which must "
120  "also be named separately on the command line). The "
121  "pose will be held by the model in "
122  "the absence of any animation, and need not be the same "
123  "pose in which the model was originally skinned.",
124  &EggOptchar::dispatch_string, nullptr, &_defpose);
125 
126  add_option
127  ("preload", "", 0,
128  "Add an <AnimPreload> entry for each animation to the model file(s). "
129  "This can be used at runtime to support asynchronous "
130  "loading and binding of animation channels.",
131  &EggOptchar::dispatch_none, &_preload);
132 
133  add_option
134  ("zero", "joint[,hprxyzijkabc]", 0,
135  "Zeroes out the animation channels for the named joint. If "
136  "a subset of the component letters hprxyzijkabc is included, the "
137  "operation is restricted to just those components; otherwise the "
138  "entire transform is cleared.",
139  &EggOptchar::dispatch_name_components, nullptr, &_zero_channels);
140 
141  add_option
142  ("keepall", "", 0,
143  "Keep all joints and sliders in the character, except those named "
144  "explicitly by -drop.",
145  &EggOptchar::dispatch_none, &_keep_all);
146 
147  add_option
148  ("p", "joint,parent", 0,
149  "Moves the named joint under the named parent joint. Use "
150  "\"-p joint,\" to reparent a joint to the root. The joint transform "
151  "is recomputed appropriately under its new parent so that the animation "
152  "is not affected (the effect is similar to NodePath::wrt_reparent_to).",
153  &EggOptchar::dispatch_vector_string_pair, nullptr, &_reparent_joints);
154 
155  add_option
156  ("new", "joint,source", 0,
157  "Creates a new joint under the named parent joint. The new "
158  "joint will inherit the same net transform as its parent.",
159  &EggOptchar::dispatch_vector_string_pair, nullptr, &_new_joints);
160 
161  add_option
162  ("rename", "joint,newjoint", 0,
163  "Renames the indicated joint, if present, to the given name.",
164  &EggOptchar::dispatch_vector_string_pair, nullptr, &_rename_joints);
165 
167  add_option
168  ("optimal", "", 0,
169  "Computes the optimal joint hierarchy for the character by analyzing "
170  "all of the joint animation and reparenting joints to minimize "
171  "transformations. This can repair skeletons that have been flattened "
172  "or whose hierarchy was otherwise damaged in conversion; it can also "
173  "detect joints that are constrained to follow other joints and should "
174  "therefore be parented to the master joints. The result is a file "
175  "from which more joints may be successfully removed, that generally "
176  "compresses better and with fewer artifacts. However, this is a "
177  "fairly expensive operation.",
178  &EggOptchar::dispatch_none, &_optimal_hierarchy);
179  }
180 
181  add_option
182  ("q", "quantum", 0,
183  "Quantize joint membership values to the given unit. This is "
184  "the smallest significant change in joint membership. There can "
185  "be a significant performance (and memory utilization) runtime "
186  "benefit for eliminating small differences in joint memberships "
187  "between neighboring vertices. The default is 0.01; specifying "
188  "0 means to preserve the original values.",
189  &EggOptchar::dispatch_double, nullptr, &_vref_quantum);
190 
191  add_option
192  ("qa", "quantum[,hprxyzijkabc]", 0,
193  "Quantizes animation channels to the given unit. This rounds each "
194  "of the named components of all joints to the nearest multiple of unit. "
195  "There is no performance benefit, and little compression benefit, "
196  "for doing this; and this may introduce visible artifacts to the "
197  "animation. However, sometimes it is a useful tool for animation "
198  "analysis and comparison. This option may be repeated several times "
199  "to quantize different channels by a different amount.",
200  &EggOptchar::dispatch_double_components, nullptr, &_quantize_anims);
201 
202  add_option
203  ("dart", "[default, sync, nosync, or structured]", 0,
204  "change the dart value in the given eggs",
205  &EggOptchar::dispatch_string, nullptr, &_dart_type);
206 
207 
208  _optimal_hierarchy = false;
209  _vref_quantum = 0.01;
210 }
211 
212 /**
213  *
214  */
215 void EggOptchar::
216 run() {
217  // We have to apply the user-specified reparent requests first, before we
218  // even analyze the joints. This is because reparenting the joints may
219  // change their properties.
220  if (apply_user_reparents()) {
221  nout << "Reparenting hierarchy.\n";
222  // So we'll have to call do_reparent() twice. It seems wasteful, but it
223  // really is necessary, and it's not that bad.
224  do_reparent();
225  }
226 
227  if (!_zero_channels.empty()) {
228  zero_channels();
229  }
230 
231  int num_characters = _collection->get_num_characters();
232  int ci;
233 
234  // Now we can analyze the joints for their properties.
235  for (ci = 0; ci < num_characters; ci++) {
236  EggCharacterData *char_data = _collection->get_character(ci);
237  analyze_joints(char_data->get_root_joint(), 0);
238  analyze_sliders(char_data);
239  }
240 
241  if (_list_hierarchy || _list_hierarchy_v) {
242  for (ci = 0; ci < num_characters; ci++) {
243  EggCharacterData *char_data = _collection->get_character(ci);
244  nout << "Character: " << char_data->get_name() << "\n";
245  list_joints(char_data->get_root_joint(), 0, _list_hierarchy_v);
246  list_scalars(char_data, _list_hierarchy_v);
247  nout << char_data->get_num_joints() << " joints.\n";
248  }
249 
250  } else if (_list_hierarchy_p) {
251  for (ci = 0; ci < num_characters; ci++) {
252  EggCharacterData *char_data = _collection->get_character(ci);
253  nout << "Character: " << char_data->get_name() << "\n";
254  int col = 0;
255  list_joints_p(char_data->get_root_joint(), col);
256  // A newline to cout is needed after the above call.
257  cout << "\n";
258  nout << char_data->get_num_joints() << " joints.\n";
259  }
260 
261  } else {
262  // The meat of the program: determine which joints are to be removed, and
263  // then actually remove them.
264  determine_removed_components();
265  move_vertices();
266  if (process_joints()) {
267  do_reparent();
268  }
269 
270  // We currently do not implement optimizing morph sliders. Need to add
271  // this at some point; it's quite easy. Identity and empty morph sliders
272  // can simply be removed, while static sliders need to be applied to the
273  // vertices and then removed.
274 
275  rename_joints();
276 
277  // Quantize the vertex memberships. We call this even if _vref_quantum is
278  // 0, because this also normalizes the vertex memberships.
279  quantize_vertices();
280 
281  // Also quantize the animation channels, if the user so requested.
282  quantize_channels();
283 
284  // flag all the groups as the user requested.
285  if (!_flag_groups.empty()) {
286  Eggs::iterator ei;
287  for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
288  do_flag_groups(*ei);
289  }
290  }
291 
292  // Add the AnimPreload entries.
293  if (_preload) {
294  do_preload();
295  }
296 
297 
298  // Finally, set the default poses. It's important not to do this until
299  // after we have adjusted all of the transforms for the various joints.
300  if (!_defpose.empty()) {
301  do_defpose();
302  }
303 
304  if (!_dart_type.empty()) {
305  Eggs::iterator ei;
306  for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
307  change_dart_type(*ei, _dart_type);
308  }
309  }
310 
311  write_eggs();
312  }
313 }
314 
315 /**
316  * Does something with the additional arguments on the command line (after all
317  * the -options have been parsed). Returns true if the arguments are good,
318  * false otherwise.
319  */
320 bool EggOptchar::
321 handle_args(ProgramBase::Args &args) {
322  if (_list_hierarchy || _list_hierarchy_v || _list_hierarchy_p) {
323  _read_only = true;
324  }
325 
326  return EggCharacterFilter::handle_args(args);
327 }
328 
329 /**
330  * Standard dispatch function for an option that takes a pair of string
331  * parameters. The data pointer is to StringPairs vector; the pair will be
332  * pushed onto the end of the vector.
333  */
334 bool EggOptchar::
335 dispatch_vector_string_pair(const string &opt, const string &arg, void *var) {
336  StringPairs *ip = (StringPairs *)var;
337 
338  vector_string words;
339  tokenize(arg, words, ",");
340 
341  if (words.size() == 2) {
342  StringPair sp;
343  sp._a = words[0];
344  sp._b = words[1];
345  ip->push_back(sp);
346 
347  } else {
348  nout << "-" << opt
349  << " requires a pair of strings separated by a comma.\n";
350  return false;
351  }
352 
353  return true;
354 }
355 
356 /**
357  * Accepts a name optionally followed by a comma and some of the nine standard
358  * component letters,
359  *
360  * The data pointer is to StringPairs vector; the pair will be pushed onto the
361  * end of the vector.
362  */
363 bool EggOptchar::
364 dispatch_name_components(const string &opt, const string &arg, void *var) {
365  StringPairs *ip = (StringPairs *)var;
366 
367  vector_string words;
368  tokenize(arg, words, ",");
369 
370  StringPair sp;
371  if (words.size() == 1) {
372  sp._a = words[0];
373 
374  } else if (words.size() == 2) {
375  sp._a = words[0];
376  sp._b = words[1];
377 
378  } else {
379  nout << "-" << opt
380  << " requires a pair of strings separated by a comma.\n";
381  return false;
382  }
383 
384  if (sp._b.empty()) {
385  sp._b = matrix_component_letters;
386  } else {
387  for (string::const_iterator si = sp._b.begin(); si != sp._b.end(); ++si) {
388  if (strchr(matrix_component_letters, *si) == nullptr) {
389  nout << "Not a standard matrix component: \"" << *si << "\"\n"
390  << "-" << opt << " requires a joint name followed by a set "
391  << "of component names. The standard component names are \""
392  << matrix_component_letters << "\".\n";
393  return false;
394  }
395  }
396  }
397 
398  ip->push_back(sp);
399 
400  return true;
401 }
402 
403 /**
404  * Accepts a double value optionally followed by a comma and some of the nine
405  * standard component letters,
406  *
407  * The data pointer is to a DoubleStrings vector; the pair will be pushed onto
408  * the end of the vector.
409  */
410 bool EggOptchar::
411 dispatch_double_components(const string &opt, const string &arg, void *var) {
412  DoubleStrings *ip = (DoubleStrings *)var;
413 
414  vector_string words;
415  tokenize(arg, words, ",");
416 
417  bool valid_double = false;
418 
419  DoubleString sp;
420  if (words.size() == 1) {
421  valid_double = string_to_double(words[0], sp._a);
422 
423  } else if (words.size() == 2) {
424  valid_double = string_to_double(words[0], sp._a);
425  sp._b = words[1];
426 
427  } else {
428  nout << "-" << opt
429  << " requires a numeric value followed by a string.\n";
430  return false;
431  }
432 
433  if (!valid_double) {
434  nout << "-" << opt
435  << " requires a numeric value followed by a string.\n";
436  return false;
437  }
438 
439  if (sp._b.empty()) {
440  sp._b = matrix_component_letters;
441  } else {
442  for (string::const_iterator si = sp._b.begin(); si != sp._b.end(); ++si) {
443  if (strchr(matrix_component_letters, *si) == nullptr) {
444  nout << "Not a standard matrix component: \"" << *si << "\"\n"
445  << "-" << opt << " requires a joint name followed by a set "
446  << "of component names. The standard component names are \""
447  << matrix_component_letters << "\".\n";
448  return false;
449  }
450  }
451  }
452 
453  ip->push_back(sp);
454 
455  return true;
456 }
457 
458 /**
459  * Accepts a set of comma-delimited group names followed by an optional name
460  * separated with an equal sign.
461  *
462  * The data pointer is to a FlagGroups object.
463  */
464 bool EggOptchar::
465 dispatch_flag_groups(const string &opt, const string &arg, void *var) {
466  FlagGroups *ip = (FlagGroups *)var;
467 
468  vector_string words;
469 
470  tokenize(arg, words, ",");
471 
472  if (words.empty()) {
473  nout << "-" << opt
474  << " requires a series of words separated by a comma.\n";
475  return false;
476  }
477 
478  FlagGroupsEntry entry;
479 
480  // Check for an equal sign in the last word. This marks the name to assign.
481  string &last_word = words.back();
482  size_t equals = last_word.rfind('=');
483  if (equals != string::npos) {
484  entry._name = last_word.substr(equals + 1);
485  last_word = last_word.substr(0, equals);
486 
487  } else {
488  // If there's no equal sign, the default is to name all groups after the
489  // group itself. We leave the name empty to indicate that.
490  }
491 
492  // Convert the words to GlobPatterns.
493  vector_string::const_iterator si;
494  for (si = words.begin(); si != words.end(); ++si) {
495  const string &word = (*si);
496  entry._groups.push_back(GlobPattern(word));
497  }
498 
499  ip->push_back(entry);
500 
501  return true;
502 }
503 
504 /**
505  * Flag all joints and sliders that should be removed for optimization
506  * purposes.
507  */
508 void EggOptchar::
509 determine_removed_components() {
510  typedef pset<string> Names;
511  Names keep_names;
512  Names drop_names;
513  Names expose_names;
514  Names suppress_names;
515  Names names_used;
516 
517  vector_string::const_iterator si;
518  for (si = _keep_components.begin(); si != _keep_components.end(); ++si) {
519  keep_names.insert(*si);
520  }
521  for (si = _drop_components.begin(); si != _drop_components.end(); ++si) {
522  drop_names.insert(*si);
523  }
524  for (si = _expose_components.begin(); si != _expose_components.end(); ++si) {
525  keep_names.insert(*si);
526  expose_names.insert(*si);
527  }
528  for (si = _suppress_components.begin(); si != _suppress_components.end(); ++si) {
529  suppress_names.insert(*si);
530  }
531 
532  // We always keep the root joint, which has no name.
533  keep_names.insert("");
534 
535  int num_characters = _collection->get_num_characters();
536  for (int ci = 0; ci < num_characters; ci++) {
537  EggCharacterData *char_data = _collection->get_character(ci);
538  int num_components = char_data->get_num_components();
539  nout << char_data->get_name() << " has " << num_components << " components.\n";
540  for (int i = 0; i < num_components; i++) {
541  EggComponentData *comp_data = char_data->get_component(i);
542  nassertv(comp_data != nullptr);
543 
544  EggOptcharUserData *user_data =
545  DCAST(EggOptcharUserData, comp_data->get_user_data());
546  nassertv(user_data != nullptr);
547 
548  const string &name = comp_data->get_name();
549  if (suppress_names.find(name) != suppress_names.end()) {
550  // If this component is not dropped, it will not be implicitly
551  // exposed.
552  names_used.insert(name);
553  user_data->_flags |= EggOptcharUserData::F_suppress;
554  }
555 
556  if (drop_names.find(name) != drop_names.end()) {
557  // Remove this component by user request.
558  names_used.insert(name);
559  user_data->_flags |= EggOptcharUserData::F_remove;
560 
561  } else if (_keep_all || keep_names.find(name) != keep_names.end()) {
562  // Keep this component.
563  names_used.insert(name);
564 
565  if (expose_names.find(name) != expose_names.end()) {
566  // In fact, expose it.
567  user_data->_flags |= EggOptcharUserData::F_expose;
568  }
569 
570  } else {
571  // Remove this component if it's unanimated or empty.
572  if ((user_data->_flags & (EggOptcharUserData::F_static | EggOptcharUserData::F_empty)) != 0) {
573  if ((user_data->_flags & (EggOptcharUserData::F_top | EggOptcharUserData::F_empty)) == EggOptcharUserData::F_top) {
574  // Actually, we can't remove it if it's a top joint, unless it's
575  // also empty. That's because vertices that are partially
576  // assigned to this joint would then have no joint to represent
577  // the same partial assignment, and they would then appear to be
578  // wholly assigned to their other joint, which would be incorrect.
579 
580  } else {
581  // But joints that aren't top joints (or that are empty) are o.k.
582  // to remove.
583  user_data->_flags |= EggOptcharUserData::F_remove;
584  }
585  }
586  }
587  }
588  }
589 
590  // Go back and tell the user about component names we didn't use, just to be
591  // helpful.
592  for (si = _keep_components.begin(); si != _keep_components.end(); ++si) {
593  const string &name = (*si);
594  if (names_used.find(name) == names_used.end()) {
595  nout << "No such component: " << name << "\n";
596  }
597  }
598  for (si = _drop_components.begin(); si != _drop_components.end(); ++si) {
599  const string &name = (*si);
600  if (names_used.find(name) == names_used.end()) {
601  nout << "No such component: " << name << "\n";
602  }
603  }
604  for (si = _expose_components.begin(); si != _expose_components.end(); ++si) {
605  const string &name = (*si);
606  if (names_used.find(name) == names_used.end()) {
607  nout << "No such component: " << name << "\n";
608  }
609  }
610  for (si = _suppress_components.begin(); si != _suppress_components.end(); ++si) {
611  const string &name = (*si);
612  if (names_used.find(name) == names_used.end()) {
613  nout << "No such component: " << name << "\n";
614  }
615  }
616 }
617 
618 /**
619  * Moves the vertices from joints that are about to be removed into the first
620  * suitable parent. This might result in fewer joints being removed (because
621  * the parent might suddenly no longer be empty).
622  */
623 void EggOptchar::
624 move_vertices() {
625  int num_characters = _collection->get_num_characters();
626  for (int ci = 0; ci < num_characters; ci++) {
627  EggCharacterData *char_data = _collection->get_character(ci);
628  int num_joints = char_data->get_num_joints();
629 
630  for (int i = 0; i < num_joints; i++) {
631  EggJointData *joint_data = char_data->get_joint(i);
632  EggOptcharUserData *user_data =
633  DCAST(EggOptcharUserData, joint_data->get_user_data());
634 
635  if ((user_data->_flags & EggOptcharUserData::F_empty) == 0 &&
636  (user_data->_flags & EggOptcharUserData::F_remove) != 0) {
637  // This joint has vertices, but is scheduled to be removed; find a
638  // suitable home for its vertices.
639  EggJointData *best_joint = find_best_vertex_joint(joint_data->get_parent());
640  joint_data->move_vertices_to(best_joint);
641 
642  // Now we can't remove the joint.
643  if (best_joint != nullptr) {
644  EggOptcharUserData *best_user_data =
645  DCAST(EggOptcharUserData, best_joint->get_user_data());
646  best_user_data->_flags &= ~(EggOptcharUserData::F_empty | EggOptcharUserData::F_remove);
647  }
648  }
649  }
650  }
651 }
652 
653 
654 /**
655  * Effects the actual removal of joints flagged for removal by reparenting the
656  * hierarchy appropriately. Returns true if any joints are removed, false
657  * otherwise.
658  */
659 bool EggOptchar::
660 process_joints() {
661  bool removed_any = false;
662  int num_characters = _collection->get_num_characters();
663  for (int ci = 0; ci < num_characters; ci++) {
664  EggCharacterData *char_data = _collection->get_character(ci);
665  int num_joints = char_data->get_num_joints();
666 
667  int num_static = 0;
668  int num_empty = 0;
669  int num_identity = 0;
670  int num_other = 0;
671  int num_kept = 0;
672 
673  for (int i = 0; i < num_joints; i++) {
674  EggJointData *joint_data = char_data->get_joint(i);
675  EggOptcharUserData *user_data =
676  DCAST(EggOptcharUserData, joint_data->get_user_data());
677 
678  if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) {
679  // This joint will be removed, so reparent it to nothing.
680  joint_data->reparent_to(nullptr);
681 
682  // Determine what kind of node it is we're removing, for the user's
683  // information.
684  if ((user_data->_flags & EggOptcharUserData::F_identity) != 0) {
685  num_identity++;
686  } else if ((user_data->_flags & EggOptcharUserData::F_static) != 0) {
687  num_static++;
688  } else if ((user_data->_flags & EggOptcharUserData::F_empty) != 0) {
689  num_empty++;
690  } else {
691  num_other++;
692  }
693  removed_any = true;
694 
695  } else {
696  // This joint will be preserved, but maybe its parent will change.
697  EggJointData *best_parent = find_best_parent(joint_data->get_parent());
698  joint_data->reparent_to(best_parent);
699  if ((user_data->_flags & EggOptcharUserData::F_expose) != 0) {
700  joint_data->expose();
701  } else if ((user_data->_flags & EggOptcharUserData::F_suppress) != 0) {
702  joint_data->expose(EggGroup::DC_none);
703  }
704  num_kept++;
705  }
706  }
707 
708  if (num_joints == num_kept) {
709  nout << char_data->get_name() << ": keeping " << num_joints
710  << " joints.\n";
711  } else {
712  nout << setw(5) << num_joints
713  << " original joints in " << char_data->get_name()
714  << "\n";
715  if (num_identity != 0) {
716  nout << setw(5) << num_identity << " identity joints\n";
717  }
718  if (num_static != 0) {
719  nout << setw(5) << num_static << " unanimated joints\n";
720  }
721  if (num_empty != 0) {
722  nout << setw(5) << num_empty << " empty joints\n";
723  }
724  if (num_other != 0) {
725  nout << setw(5) << num_other << " other joints\n";
726  }
727  nout << " ----\n"
728  << setw(5) << num_kept << " joints remaining\n\n";
729  }
730  }
731 
732  return removed_any;
733 }
734 
735 /**
736  * Searches for the first joint at this level or above that is not scheduled
737  * to be removed. This is the joint that the first child of this joint should
738  * be reparented to.
739  */
740 EggJointData *EggOptchar::
741 find_best_parent(EggJointData *joint_data) const {
742  EggOptcharUserData *user_data =
743  DCAST(EggOptcharUserData, joint_data->get_user_data());
744 
745  if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) {
746  // Keep going.
747  if (joint_data->get_parent() != nullptr) {
748  return find_best_parent(joint_data->get_parent());
749  }
750  }
751 
752  // This is the one!
753  return joint_data;
754 }
755 
756 /**
757  * Searches for the first joint at this level or above that is not static.
758  * This is the joint that the vertices of this joint should be moved into.
759  */
760 EggJointData *EggOptchar::
761 find_best_vertex_joint(EggJointData *joint_data) const {
762  if (joint_data == nullptr) {
763  return nullptr;
764  }
765 
766  EggOptcharUserData *user_data =
767  DCAST(EggOptcharUserData, joint_data->get_user_data());
768 
769  if ((user_data->_flags & EggOptcharUserData::F_static) != 0) {
770  // Keep going.
771  return find_best_vertex_joint(joint_data->get_parent());
772  }
773 
774  // This is the one!
775  return joint_data;
776 }
777 
778 /**
779  * Reparents all the joints that the user suggested on the command line.
780  * Returns true if any operations were performed, false otherwise.
781  */
782 bool EggOptchar::
783 apply_user_reparents() {
784  bool did_anything = false;
785 
786  int num_characters = _collection->get_num_characters();
787 
788  // First, get the new joints.
789  StringPairs::const_iterator spi;
790  for (spi = _new_joints.begin(); spi != _new_joints.end(); ++spi) {
791  const StringPair &p = (*spi);
792 
793  for (int ci = 0; ci < num_characters; ci++) {
794  EggCharacterData *char_data = _collection->get_character(ci);
795  EggJointData *node_a = char_data->find_joint(p._a);
796  EggJointData *node_b = char_data->get_root_joint();
797  if (!p._b.empty()) {
798  node_b = char_data->find_joint(p._b);
799  }
800 
801  if (node_b == nullptr) {
802  nout << "No joint named " << p._b << " in " << char_data->get_name()
803  << ".\n";
804 
805  } else if (node_a != nullptr) {
806  nout << "Joint " << p._a << " already exists in "
807  << char_data->get_name() << ".\n";
808 
809  } else {
810  nout << "Creating new joint " << p._a << " in "
811  << char_data->get_name() << ".\n";
812  node_a = char_data->make_new_joint(p._a, node_b);
813  did_anything = true;
814  }
815  }
816  }
817 
818  // Now get the user reparents.
819  for (spi = _reparent_joints.begin(); spi != _reparent_joints.end(); ++spi) {
820  const StringPair &p = (*spi);
821 
822  for (int ci = 0; ci < num_characters; ci++) {
823  EggCharacterData *char_data = _collection->get_character(ci);
824  EggJointData *node_a = char_data->find_joint(p._a);
825  EggJointData *node_b = char_data->get_root_joint();
826  if (!p._b.empty()) {
827  node_b = char_data->find_joint(p._b);
828  }
829 
830  if (node_b == nullptr) {
831  nout << "No joint named " << p._b << " in " << char_data->get_name()
832  << ".\n";
833  } else if (node_a == nullptr) {
834  nout << "No joint named " << p._a << " in " << char_data->get_name()
835  << ".\n";
836  } else {
837  node_a->reparent_to(node_b);
838  did_anything = true;
839  }
840  }
841  }
842 
843  if (_optimal_hierarchy) {
844  did_anything = true;
845  for (int ci = 0; ci < num_characters; ci++) {
846  EggCharacterData *char_data = _collection->get_character(ci);
847  nout << "Computing optimal hierarchy for "
848  << char_data->get_name() << ".\n";
849  char_data->choose_optimal_hierarchy();
850  nout << "Done computing optimal hierarchy for "
851  << char_data->get_name() << ".\n";
852  }
853  }
854 
855  return did_anything;
856 }
857 
858 /**
859  * Zeroes out the channels specified by the user on the command line.
860  *
861  * Returns true if any operation was performed, false otherwise.
862  */
863 bool EggOptchar::
864 zero_channels() {
865  bool did_anything = false;
866  int num_characters = _collection->get_num_characters();
867 
868  StringPairs::const_iterator spi;
869  for (spi = _zero_channels.begin(); spi != _zero_channels.end(); ++spi) {
870  const StringPair &p = (*spi);
871 
872  for (int ci = 0; ci < num_characters; ci++) {
873  EggCharacterData *char_data = _collection->get_character(ci);
874  EggJointData *joint_data = char_data->find_joint(p._a);
875 
876  if (joint_data == nullptr) {
877  nout << "No joint named " << p._a << " in " << char_data->get_name()
878  << ".\n";
879  } else {
880  joint_data->zero_channels(p._b);
881  did_anything = true;
882  }
883  }
884  }
885 
886  return did_anything;
887 }
888 
889 /**
890  * Quantizes the channels specified by the user on the command line.
891  *
892  * Returns true if any operation was performed, false otherwise.
893  */
894 bool EggOptchar::
895 quantize_channels() {
896  bool did_anything = false;
897  int num_characters = _collection->get_num_characters();
898 
899  DoubleStrings::const_iterator spi;
900  for (spi = _quantize_anims.begin(); spi != _quantize_anims.end(); ++spi) {
901  const DoubleString &p = (*spi);
902 
903  for (int ci = 0; ci < num_characters; ci++) {
904  EggCharacterData *char_data = _collection->get_character(ci);
905  EggJointData *joint_data = char_data->get_root_joint();
906 
907  if (joint_data != nullptr) {
908  joint_data->quantize_channels(p._b, p._a);
909  did_anything = true;
910  }
911  }
912  }
913 
914  return did_anything;
915 }
916 
917 /**
918  * Recursively walks the joint hierarchy for a particular character,
919  * indentifying properties of each joint.
920  */
921 void EggOptchar::
922 analyze_joints(EggJointData *joint_data, int level) {
923  PT(EggOptcharUserData) user_data = new EggOptcharUserData;
924  joint_data->set_user_data(user_data);
925 
926  if (level == 1) {
927  // The child joints of the root joint are deemed "top" joints. These may
928  // not be removed unless they are empty (because their vertices have no
929  // joint to be moved into).
930  user_data->_flags |= EggOptcharUserData::F_top;
931  }
932 
933  // Analyze the table of matrices for this joint, checking to see if they're
934  // all the same across all frames, or if any of them are different; also
935  // look for empty joints (that control no vertices).
936  int num_mats = 0;
937  bool different_mat = false;
938  bool has_vertices = false;
939 
940  int num_models = joint_data->get_num_models();
941  int i;
942  for (i = 0; i < num_models; i++) {
943  if (joint_data->has_model(i)) {
944  EggBackPointer *model = joint_data->get_model(i);
945  if (model->has_vertices()) {
946  has_vertices = true;
947  }
948 
949  int num_frames = joint_data->get_num_frames(i);
950 
951  int f;
952  for (f = 0; f < num_frames && !different_mat; f++) {
953  LMatrix4d mat = joint_data->get_frame(i, f);
954  num_mats++;
955  if (num_mats == 1) {
956  // This is the first matrix.
957  user_data->_static_mat = mat;
958 
959  } else {
960  // This is a second or later matrix.
961  if (!mat.almost_equal(user_data->_static_mat, 0.0001)) {
962  // It's different than the first one.
963  different_mat = true;
964  }
965  }
966  }
967  }
968  }
969 
970  if (!different_mat) {
971  // All the mats are the same for this joint.
972  user_data->_flags |= EggOptcharUserData::F_static;
973 
974  if (num_mats == 0 ||
975  user_data->_static_mat.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
976  // It's not only static, but it's the identity matrix.
977  user_data->_flags |= EggOptcharUserData::F_identity;
978  }
979  }
980 
981  if (!has_vertices) {
982  // There are no vertices in this joint.
983  user_data->_flags |= EggOptcharUserData::F_empty;
984  }
985 
986  int num_children = joint_data->get_num_children();
987  for (i = 0; i < num_children; i++) {
988  analyze_joints(joint_data->get_child(i), level + 1);
989  }
990 }
991 
992 /**
993  * Linearly walks the slider list for a particular character, indentifying
994  * properties of each slider.
995  */
996 void EggOptchar::
997 analyze_sliders(EggCharacterData *char_data) {
998  int num_sliders = char_data->get_num_sliders();
999  for (int si = 0; si < num_sliders; si++) {
1000  EggSliderData *slider_data = char_data->get_slider(si);
1001 
1002  PT(EggOptcharUserData) user_data = new EggOptcharUserData;
1003  slider_data->set_user_data(user_data);
1004 
1005  // Analyze the table of values for this slider, checking to see if they're
1006  // all the same across all frames, or if any of them are different; also
1007  // look for empty sliders (that control no vertices).
1008  int num_values = 0;
1009  bool different_value = false;
1010  bool has_vertices = false;
1011 
1012  int num_models = slider_data->get_num_models();
1013  for (int i = 0; i < num_models; i++) {
1014  if (slider_data->has_model(i)) {
1015  EggBackPointer *model = slider_data->get_model(i);
1016  if (model->has_vertices()) {
1017  has_vertices = true;
1018  }
1019 
1020  int num_frames = slider_data->get_num_frames(i);
1021 
1022  int f;
1023  for (f = 0; f < num_frames && !different_value; f++) {
1024  double value = slider_data->get_frame(i, f);
1025  num_values++;
1026  if (num_values == 1) {
1027  // This is the first value.
1028  user_data->_static_value = value;
1029 
1030  } else {
1031  // This is a second or later value.
1032  if (!IS_THRESHOLD_EQUAL(value, user_data->_static_value, 0.0001)) {
1033  // It's different than the first one.
1034  different_value = true;
1035  }
1036  }
1037  }
1038  }
1039  }
1040 
1041  if (!different_value) {
1042  // All the values are the same for this slider.
1043  user_data->_flags |= EggOptcharUserData::F_static;
1044 
1045  if (num_values == 0 || IS_THRESHOLD_ZERO(user_data->_static_value, 0.0001)) {
1046  // It's not only static, but it's the identity value.
1047  user_data->_flags |= EggOptcharUserData::F_identity;
1048  }
1049  }
1050 
1051  if (!has_vertices) {
1052  // There are no vertices in this slider.
1053  user_data->_flags |= EggOptcharUserData::F_empty;
1054  }
1055  }
1056 }
1057 
1058 /**
1059  * Outputs a list of the joint hierarchy.
1060  */
1061 void EggOptchar::
1062 list_joints(EggJointData *joint_data, int indent_level, bool verbose) {
1063  // Don't list the root joint, which is artificially created when the
1064  // character is loaded. Instead, list each child as it is encountered.
1065 
1066  int num_children = joint_data->get_num_children();
1067  for (int i = 0; i < num_children; i++) {
1068  EggJointData *child_data = joint_data->get_child(i);
1069  describe_component(child_data, indent_level, verbose);
1070 
1071  list_joints(child_data, indent_level + 2, verbose);
1072  }
1073 }
1074 
1075 /**
1076  * Outputs a list of the joint hierarchy as a series of -p joint,parent
1077  * commands.
1078  */
1079 void EggOptchar::
1080 list_joints_p(EggJointData *joint_data, int &col) {
1081  // As above, don't list the root joint.
1082 
1083  int num_children = joint_data->get_num_children();
1084  static const int max_col = 72;
1085 
1086  for (int i = 0; i < num_children; i++) {
1087  EggJointData *child_data = joint_data->get_child(i);
1088  // We send output to cout instead of nout to avoid the word-wrapping, and
1089  // also to allow the user to redirect this easily to a file.
1090 
1091  string text = string(" -p ") + child_data->get_name() +
1092  string(",") + joint_data->get_name();
1093  if (col == 0) {
1094  cout << " " << text;
1095  col = 4 + text.length();
1096  } else {
1097  col += text.length();
1098  if (col >= max_col) {
1099  cout << " \\\n " << text;
1100  col = 4 + text.length();
1101  } else {
1102  cout << text;
1103  }
1104  }
1105 
1106  list_joints_p(child_data, col);
1107  }
1108 }
1109 
1110 /**
1111  * Outputs a list of the scalars.
1112  */
1113 void EggOptchar::
1114 list_scalars(EggCharacterData *char_data, bool verbose) {
1115  int num_sliders = char_data->get_num_sliders();
1116  for (int si = 0; si < num_sliders; si++) {
1117  EggSliderData *slider_data = char_data->get_slider(si);
1118  describe_component(slider_data, 0, verbose);
1119  }
1120 }
1121 
1122 /**
1123  * Describes one particular slider or joint.
1124  */
1125 void EggOptchar::
1126 describe_component(EggComponentData *comp_data, int indent_level,
1127  bool verbose) {
1128  // We use cout instead of nout so the user can easily redirect this to a
1129  // file.
1130  indent(cout, indent_level)
1131  << comp_data->get_name();
1132 
1133  if (verbose) {
1134  EggOptcharUserData *user_data =
1135  DCAST(EggOptcharUserData, comp_data->get_user_data());
1136  if (user_data->is_identity()) {
1137  cout << " (identity)";
1138  } else if (user_data->is_static()) {
1139  cout << " (static)";
1140  }
1141  if (user_data->is_empty()) {
1142  cout << " (empty)";
1143  }
1144  if (user_data->is_top()) {
1145  cout << " (top)";
1146  }
1147  }
1148  cout << "\n";
1149 }
1150 
1151 /**
1152  * Performs all of the queued up reparenting operations.
1153  */
1154 void EggOptchar::
1155 do_reparent() {
1156  bool all_ok = true;
1157 
1158  int num_characters = _collection->get_num_characters();
1159  for (int ci = 0; ci < num_characters; ci++) {
1160  EggCharacterData *char_data = _collection->get_character(ci);
1161  if (!char_data->do_reparent()) {
1162  all_ok = false;
1163  }
1164  }
1165 
1166  if (!all_ok) {
1167  exit(1);
1168  }
1169 }
1170 
1171 /**
1172  * Walks through all of the loaded egg files, looking for vertices whose joint
1173  * memberships are then quantized according to _vref_quantum.
1174  */
1175 void EggOptchar::
1176 quantize_vertices() {
1177  Eggs::iterator ei;
1178  for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
1179  quantize_vertices(*ei);
1180  }
1181 }
1182 
1183 /**
1184  * Recursively walks through the indicated egg hierarchy, looking for vertices
1185  * whose joint memberships are then quantized according to _vref_quantum.
1186  */
1187 void EggOptchar::
1188 quantize_vertices(EggNode *egg_node) {
1189  if (egg_node->is_of_type(EggVertexPool::get_class_type())) {
1190  EggVertexPool *vpool = DCAST(EggVertexPool, egg_node);
1192  for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
1193  quantize_vertex(*vi);
1194  }
1195 
1196  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
1197  EggGroupNode *group = DCAST(EggGroupNode, egg_node);
1198  EggGroupNode::iterator ci;
1199  for (ci = group->begin(); ci != group->end(); ++ci) {
1200  quantize_vertices(*ci);
1201  }
1202  }
1203 }
1204 
1205 /**
1206  * Quantizes the indicated vertex's joint membership.
1207  */
1208 void EggOptchar::
1209 quantize_vertex(EggVertex *egg_vertex) {
1210  if (egg_vertex->gref_size() == 0) {
1211  // Never mind on this vertex.
1212  return;
1213  }
1214 
1215  // First, get a copy of the existing membership.
1216  VertexMemberships memberships;
1217  EggVertex::GroupRef::const_iterator gi;
1218  double net_membership = 0.0;
1219  for (gi = egg_vertex->gref_begin(); gi != egg_vertex->gref_end(); ++gi) {
1220  EggGroup *group = (*gi);
1221  double membership = group->get_vertex_membership(egg_vertex);
1222  memberships.push_back(VertexMembership(group, membership));
1223  net_membership += membership;
1224  }
1225  nassertv(net_membership != 0.0);
1226 
1227  // Now normalize all the memberships so the net membership is 1.0, and then
1228  // quantize the result (if the user so requested).
1229  double factor = 1.0 / net_membership;
1230  net_membership = 0.0;
1231  VertexMemberships::iterator mi;
1232  VertexMemberships::iterator largest = memberships.begin();
1233 
1234  for (mi = memberships.begin(); mi != memberships.end(); ++mi) {
1235  if ((*largest) < (*mi)) {
1236  // Remember the largest membership value, so we can readjust it at the
1237  // end.
1238  largest = mi;
1239  }
1240 
1241  double value = (*mi)._membership * factor;
1242  if (_vref_quantum != 0.0) {
1243  value = floor(value / _vref_quantum + 0.5) * _vref_quantum;
1244  }
1245  (*mi)._membership = value;
1246 
1247  net_membership += value;
1248  }
1249 
1250  // The the largest membership value gets corrected again by the roundoff
1251  // error.
1252  (*largest)._membership += 1.0 - net_membership;
1253 
1254  // Finally, walk back through and apply these computed values to the vertex.
1255  for (mi = memberships.begin(); mi != memberships.end(); ++mi) {
1256  (*mi)._group->set_vertex_membership(egg_vertex, (*mi)._membership);
1257  }
1258 }
1259 
1260 /**
1261  * Recursively walks the indicated egg hierarchy, looking for groups that
1262  * match one of the group names in _flag_groups, and renaming geometry
1263  * appropriately.
1264  */
1265 void EggOptchar::
1266 do_flag_groups(EggGroupNode *egg_group) {
1267  bool matched = false;
1268  string name;
1269  FlagGroups::const_iterator fi;
1270  for (fi = _flag_groups.begin();
1271  fi != _flag_groups.end() && !matched;
1272  ++fi) {
1273  const FlagGroupsEntry &entry = (*fi);
1274  Globs::const_iterator si;
1275  for (si = entry._groups.begin();
1276  si != entry._groups.end() && !matched;
1277  ++si) {
1278  if ((*si).matches(egg_group->get_name())) {
1279  matched = true;
1280  if (!entry._name.empty()) {
1281  name = entry._name;
1282  } else {
1283  name = egg_group->get_name();
1284  }
1285  }
1286  }
1287  }
1288 
1289  if (matched) {
1290  // Ok, this group matched one of the user's command-line renames. Rename
1291  // all the primitives in this group and below to the indicated name; this
1292  // will expose the primitives through the character loader.
1293  rename_primitives(egg_group, name);
1294  }
1295 
1296  // Now recurse on children.
1297  EggGroupNode::iterator gi;
1298  for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) {
1299  EggNode *child = (*gi);
1300  if (child->is_of_type(EggGroupNode::get_class_type())) {
1301  EggGroupNode *group = DCAST(EggGroupNode, child);
1302  do_flag_groups(group);
1303  }
1304  }
1305 }
1306 
1307 /**
1308  * Rename all the joints named with the -rename command-line option.
1309  */
1310 void EggOptchar::
1311 rename_joints() {
1312  for (StringPairs::iterator spi = _rename_joints.begin();
1313  spi != _rename_joints.end();
1314  ++spi) {
1315  const StringPair &sp = (*spi);
1316  int num_characters = _collection->get_num_characters();
1317  int ci;
1318  for (ci = 0; ci < num_characters; ++ci) {
1319  EggCharacterData *char_data = _collection->get_character(ci);
1320  EggJointData *joint = char_data->find_joint(sp._a);
1321  if (joint != nullptr) {
1322  nout << "Renaming joint " << sp._a << " to " << sp._b << "\n";
1323  joint->set_name(sp._b);
1324 
1325  int num_models = joint->get_num_models();
1326  for (int mn = 0; mn < num_models; ++mn) {
1327  if (joint->has_model(mn)) {
1328  EggBackPointer *model = joint->get_model(mn);
1329  model->set_name(sp._b);
1330  }
1331  }
1332 
1333  } else {
1334  nout << "Couldn't find joint " << sp._a << "\n";
1335  }
1336  }
1337  }
1338 }
1339 
1340 /**
1341  * Recursively walks the indicated egg hierarchy, renaming geometry to the
1342  * indicated name.
1343  */
1344 void EggOptchar::
1345 change_dart_type(EggGroupNode *egg_group, const string &new_dart_type) {
1346  EggGroupNode::iterator gi;
1347  for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) {
1348  EggNode *child = (*gi);
1349  if (child->is_of_type(EggGroupNode::get_class_type())) {
1350  EggGroupNode *group = DCAST(EggGroupNode, child);
1351  if (child->is_of_type(EggGroup::get_class_type())) {
1352  EggGroup *gr = DCAST(EggGroup, child);
1353  EggGroup::DartType dt = gr->get_dart_type();
1354  if(dt != EggGroup::DT_none) {
1355  EggGroup::DartType newDt = gr->string_dart_type(new_dart_type);
1356  gr->set_dart_type(newDt);
1357  }
1358  }
1359  change_dart_type(group, new_dart_type);
1360  }
1361  }
1362 }
1363 
1364 
1365 /**
1366  * Recursively walks the indicated egg hierarchy, renaming geometry to the
1367  * indicated name.
1368  */
1369 void EggOptchar::
1370 rename_primitives(EggGroupNode *egg_group, const string &name) {
1371  EggGroupNode::iterator gi;
1372  for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) {
1373  EggNode *child = (*gi);
1374 
1375  if (child->is_of_type(EggGroupNode::get_class_type())) {
1376  EggGroupNode *group = DCAST(EggGroupNode, child);
1377  rename_primitives(group, name);
1378 
1379  } else if (child->is_of_type(EggPrimitive::get_class_type())) {
1380  child->set_name(name);
1381  }
1382  }
1383 }
1384 
1385 /**
1386  * Generates the preload tables for each model.
1387  */
1388 void EggOptchar::
1389 do_preload() {
1390  // First, build up the list of AnimPreload entries, one for each animation
1391  // file.
1392  PT(EggGroup) anim_group = new EggGroup("preload");
1393 
1394  int num_characters = _collection->get_num_characters();
1395  int ci;
1396  for (ci = 0; ci < num_characters; ++ci) {
1397  EggCharacterData *char_data = _collection->get_character(ci);
1398 
1399  int num_models = char_data->get_num_models();
1400  for (int mn = 0; mn < num_models; ++mn) {
1401  EggNode *root = char_data->get_model_root(mn);
1402  if (root->is_of_type(EggTable::get_class_type())) {
1403  // This model represents an animation.
1404  EggData *data = char_data->get_egg_data(mn);
1405  string basename = data->get_egg_filename().get_basename_wo_extension();
1406  PT(EggAnimPreload) anim_preload = new EggAnimPreload(basename);
1407 
1408  int mi = char_data->get_model_index(mn);
1409  anim_preload->set_num_frames(char_data->get_num_frames(mi));
1410  double frame_rate = char_data->get_frame_rate(mi);
1411  if (frame_rate != 0.0) {
1412  anim_preload->set_fps(frame_rate);
1413  }
1414 
1415  anim_group->add_child(anim_preload);
1416  }
1417  }
1418  }
1419 
1420  // Now go back through and copy the preload tables into each of the model
1421  // files.
1422  for (ci = 0; ci < num_characters; ++ci) {
1423  EggCharacterData *char_data = _collection->get_character(ci);
1424 
1425  int num_models = char_data->get_num_models();
1426  for (int mn = 0; mn < num_models; ++mn) {
1427  EggNode *root = char_data->get_model_root(mn);
1428  if (root->is_of_type(EggGroup::get_class_type())) {
1429  // This is a model file. Copy in the table.
1430  EggGroup *model_root = DCAST(EggGroup, root);
1431  EggGroup::const_iterator ci;
1432  for (ci = anim_group->begin(); ci != anim_group->end(); ++ci) {
1433  EggAnimPreload *anim_preload = DCAST(EggAnimPreload, *ci);
1434  PT(EggAnimPreload) new_anim_preload = new EggAnimPreload(*anim_preload);
1435  model_root->add_child(new_anim_preload);
1436  }
1437  }
1438  }
1439  }
1440 }
1441 
1442 /**
1443  * Sets the initial pose for the character(s).
1444  */
1445 void EggOptchar::
1446 do_defpose() {
1447  // Split out the defpose parameter.
1448  Filename egg_filename;
1449  size_t comma = _defpose.find(',');
1450  egg_filename = _defpose.substr(0, comma);
1451 
1452  string frame_str;
1453  if (comma != string::npos) {
1454  frame_str = _defpose.substr(comma + 1);
1455  }
1456  frame_str = trim(frame_str);
1457  int frame = 0;
1458  if (!frame_str.empty()) {
1459  if (!string_to_int(frame_str, frame)) {
1460  nout << "Invalid integer in -defpose: " << frame_str << "\n";
1461  return;
1462  }
1463  }
1464 
1465  // Now find the named animation file in our egg list.
1466  int egg_index = -1;
1467  int num_eggs = _collection->get_num_eggs();
1468  int i;
1469 
1470  // First, look for an exact match.
1471  for (i = 0; i < num_eggs && egg_index == -1; ++i) {
1472  if (_collection->get_egg(i)->get_egg_filename() == egg_filename) {
1473  egg_index = i;
1474  }
1475  }
1476 
1477  // Then, look for an inexact match.
1478  string egg_basename = egg_filename.get_basename_wo_extension();
1479  for (i = 0; i < num_eggs && egg_index == -1; ++i) {
1480  if (_collection->get_egg(i)->get_egg_filename().get_basename_wo_extension() == egg_basename) {
1481  egg_index = i;
1482  }
1483  }
1484 
1485  if (egg_index == -1) {
1486  // No joy.
1487  nout << "Egg file " << egg_filename << " named in -defpose, but does not appear on command line.\n";
1488  return;
1489  }
1490 
1491  EggData *egg_data = _collection->get_egg(egg_index);
1492 
1493  if (_collection->get_num_models(egg_index) == 0) {
1494  nout << "Egg file " << egg_filename << " does not include any model or animation.\n";
1495  return;
1496  }
1497 
1498  // Now get the first model (or animation) named by this egg file.
1499  int mi = _collection->get_first_model_index(egg_index);
1500  EggCharacterData *ch = _collection->get_character_by_model_index(mi);
1501  EggJointData *root_joint = ch->get_root_joint();
1502 
1503  int anim_index = -1;
1504  for (i = 0; i < ch->get_num_models() && anim_index == -1; ++i) {
1505  if (ch->get_egg_data(i) == egg_data) {
1506  anim_index = i;
1507  }
1508  }
1509 
1510  // This couldn't possibly fail, since we already checked this above.
1511  nassertv(anim_index != -1);
1512 
1513  // Now we can recursively apply the default pose to the hierarchy.
1514  root_joint->apply_default_pose(anim_index, frame);
1515 }
1516 
1517 int main(int argc, char *argv[]) {
1518  EggOptchar prog;
1519  prog.parse_command_line(argc, argv);
1520  prog.run();
1521  return 0;
1522 }
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
void reparent_to(EggJointData *new_parent)
Indicates an intention to change the parent of this joint to the indicated joint, or NULL to remove i...
Definition: eggJointData.I:91
GroupRef::size_type gref_size() const
Returns the number of elements between gref_begin() and gref_end().
Definition: eggVertex.cxx:724
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static DartType string_dart_type(const std::string &strval)
Returns the DartType value associated with the given string representation, or DT_none if the string ...
Definition: eggGroup.cxx:825
int get_num_frames(int model_index) const
Returns the number of frames of animation for this particular component in the indicated model.
GroupRef::const_iterator gref_end() const
Returns an iterator that can, in conjunction with gref_begin(), be used to traverse the entire set of...
Definition: eggVertex.cxx:714
This is an iterator adaptor that converts any iterator that returns a pair (e.g.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_num_joints() const
Returns the total number of joints in the character joint hierarchy.
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
EggJointData * get_root_joint() const
Returns the root joint of the character hierarchy.
bool do_reparent()
Begins the process of restructuring the joint hierarchy according to the previous calls to reparent_t...
int get_num_frames(int model_index) const
Returns the number of frames of animation of the indicated model.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void parse_command_line(int argc, char **argv)
Dispatches on each of the options on the command line, and passes the remaining parameters to handle_...
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:46
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
Definition: filename.I:386
void add_normals_options()
Adds -no, -np, etc.
Definition: eggBase.cxx:59
int get_num_models() const
Returns the maximum number of back pointers this component may have.
virtual void set_name(const std::string &name)
Applies the indicated name change to the egg file.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
GroupRef::const_iterator gref_begin() const
Returns an iterator that can, in conjunction with gref_end(), be used to traverse the entire set of g...
Definition: eggVertex.cxx:702
This corresponds to a single morph slider control.
Definition: eggSliderData.h:28
int get_num_models() const
Returns the total number of models associated with this character.
EggBackPointer * get_model(int model_index) const
Returns the back pointer to an egg file for the indicated model if it exists, or NULL if it does not.
This is the primary interface into all the egg data, and the root of the egg file structure.
Definition: eggData.h:37
EggJointData * make_new_joint(const std::string &name, EggJointData *parent)
Creates a new joint as a child of the indicated joint and returns it.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
iterator end() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
EggData * get_egg_data(int n) const
Returns the EggData representing the egg file that defined this particular model.
This is the base class of both EggJointData and EggSliderData.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual bool has_vertices() const
Returns true if there are any vertices referenced by the node this points to, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
iterator begin() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
void apply_default_pose(int source_model, int frame)
Applies the pose from the indicated frame of the indicated source model_index as the initial pose for...
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:34
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
This corresponds to an <AnimPreload> entry.
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition: eggVertex.h:39
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.
EggSliderData * get_slider(int n) const
Returns the nth slider in the character slider list.
Performs basic optimizations of a character model and its associated animations, by analyzing the ani...
Definition: eggOptchar.h:37
EggComponentData * get_component(int n) const
Returns the nth joint or slider in the character.
EggUserData * get_user_data() const
Returns the user data pointer most recently stored on this object, or NULL if nothing was previously ...
Definition: eggObject.cxx:84
void move_vertices_to(EggJointData *new_owner)
Moves the vertices assigned to this joint into the indicated joint, without changing their weight ass...
double get_vertex_membership(const EggVertex *vert) const
Returns the amount of membership of the indicated vertex in this group.
Definition: eggGroup.cxx:677
This class contains extra user data which is piggybacked onto EggGroup objects for the purpose of the...
string trim(const string &str)
Returns a new string representing the contents of the given string with both leading and trailing whi...
bool has_model(int model_index) const
Returns true if the component has a back pointer to an egg file somewhere for the indicated model,...
int get_num_components() const
Returns the total number of joints and sliders in the character.
void choose_optimal_hierarchy()
Chooses the best possible parent joint for each of the joints in the hierarchy, based on the score co...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggNode * get_model_root(int n) const
Returns the model_root of the nth model associated with this character.
Represents a single character, as read and collected from several models and animation files.
This class is used to help EggOptchar quantize the membership of one vertex among its various groups.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_user_data(EggUserData *user_data)
Sets the user data associated with this object.
Definition: eggObject.cxx:74
void tokenize(const string &str, vector_string &words, const string &delimiters, bool discard_repeated_delimiters)
Chops the source string up into pieces delimited by any of the characters specified in delimiters.
This is one node of a hierarchy of EggJointData nodes, each of which represents a single joint of the...
Definition: eggJointData.h:31
EggJointData * find_joint(const std::string &name) const
Returns the first joint found with the indicated name, or NULL if no joint has that name.
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
EggJointData * get_joint(int n) const
Returns the nth joint in the character joint hierarchy.
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:35
int get_num_sliders() const
Returns the number of sliders in the character slider list.
LMatrix4d get_frame(int model_index, int n) const
Returns the local transform matrix corresponding to this joint position in the nth frame in the indic...
void add_transform_options()
Adds -TS, -TT, etc.
Definition: eggBase.cxx:126
void zero_channels(const std::string &components)
Calls zero_channels() on all models for this joint, but does not recurse downwards.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void quantize_channels(const std::string &components, double quantum)
Calls quantize_channels() on all models for this joint, and then recurses downwards to all joints bel...
double get_frame(int model_index, int n) const
Returns the value corresponding to this slider position in the nth frame in the indicated model.
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
This is our own Panda specialization on the default STL set.
Definition: pset.h:49
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double get_frame_rate(int model_index) const
Returns the stated frame rate of the specified model.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This stores a pointer from an EggJointData or EggSliderData object back to the referencing data in an...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void expose(EggGroup::DCSType dcs_type=EggGroup::DC_default)
Calls expose() on all models for this joint, but does not recurse downwards.
A collection of vertices.
Definition: eggVertexPool.h:41
static bool is_compression_available()
Returns true if the FFTW library is compiled in, so that this class is actually capable of doing usef...
This class can be used to test for string matches against standard Unix- shell filename globbing conv...
Definition: globPattern.h:32
int get_model_index(int n) const
Returns the model_index of the nth model associated with this character.