Panda3D
Loading...
Searching...
No Matches
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"
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
37using std::cout;
38using std::setw;
39using std::string;
40
41/**
42 *
43 */
44EggOptchar::
45EggOptchar() {
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 */
215void EggOptchar::
216run() {
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 */
320bool EggOptchar::
321handle_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 */
334bool EggOptchar::
335dispatch_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 */
363bool EggOptchar::
364dispatch_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 */
410bool EggOptchar::
411dispatch_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 */
464bool EggOptchar::
465dispatch_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 */
508void EggOptchar::
509determine_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 */
623void EggOptchar::
624move_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 */
659bool EggOptchar::
660process_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 */
740EggJointData *EggOptchar::
741find_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 */
760EggJointData *EggOptchar::
761find_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 */
782bool EggOptchar::
783apply_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 */
863bool EggOptchar::
864zero_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 */
894bool EggOptchar::
895quantize_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 */
921void EggOptchar::
922analyze_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 */
996void EggOptchar::
997analyze_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 */
1061void EggOptchar::
1062list_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 */
1079void EggOptchar::
1080list_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 */
1113void EggOptchar::
1114list_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 */
1125void EggOptchar::
1126describe_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 */
1154void EggOptchar::
1155do_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 */
1175void EggOptchar::
1176quantize_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 */
1187void EggOptchar::
1188quantize_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 */
1208void EggOptchar::
1209quantize_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 */
1265void EggOptchar::
1266do_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 */
1310void EggOptchar::
1311rename_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 */
1344void EggOptchar::
1345change_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 */
1369void EggOptchar::
1370rename_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 */
1388void EggOptchar::
1389do_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 */
1445void EggOptchar::
1446do_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
1517int main(int argc, char *argv[]) {
1518 EggOptchar prog;
1519 prog.parse_command_line(argc, argv);
1520 prog.run();
1521 return 0;
1522}
This corresponds to an <AnimPreload> entry.
This stores a pointer from an EggJointData or EggSliderData object back to the referencing data in an...
virtual bool has_vertices() const
Returns true if there are any vertices referenced by the node this points to, false otherwise.
virtual void set_name(const std::string &name)
Applies the indicated name change to the egg file.
void add_normals_options()
Adds -no, -np, etc.
Definition eggBase.cxx:59
void add_transform_options()
Adds -TS, -TT, etc.
Definition eggBase.cxx:126
Represents a single character, as read and collected from several models and animation files.
EggJointData * get_root_joint() const
Returns the root joint of the character hierarchy.
int get_model_index(int n) const
Returns the model_index of the nth model associated with this character.
void choose_optimal_hierarchy()
Chooses the best possible parent joint for each of the joints in the hierarchy, based on the score co...
int get_num_components() const
Returns the total number of joints and sliders in the character.
double get_frame_rate(int model_index) const
Returns the stated frame rate of the specified model.
int get_num_sliders() const
Returns the number of sliders in the character slider list.
EggData * get_egg_data(int n) const
Returns the EggData representing the egg file that defined this particular model.
EggJointData * get_joint(int n) const
Returns the nth joint in the character joint hierarchy.
int get_num_frames(int model_index) const
Returns the number of frames of animation of the indicated model.
EggJointData * make_new_joint(const std::string &name, EggJointData *parent)
Creates a new joint as a child of the indicated joint and returns it.
EggNode * get_model_root(int n) const
Returns the model_root of the nth model associated with this character.
EggSliderData * get_slider(int n) const
Returns the nth slider in the character slider list.
int get_num_models() const
Returns the total number of models associated with this character.
bool do_reparent()
Begins the process of restructuring the joint hierarchy according to the previous calls to reparent_t...
int get_num_joints() const
Returns the total number of joints in the character joint hierarchy.
EggComponentData * get_component(int n) const
Returns the nth joint or slider in the character.
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.
This is the base class of both EggJointData and EggSliderData.
int get_num_frames(int model_index) const
Returns the number of frames of animation for this particular component in the indicated model.
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.
int get_num_models() const
Returns the maximum number of back pointers this component may have.
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,...
This is the primary interface into all the egg data, and the root of the egg file structure.
Definition eggData.h:37
A base class for nodes in the hierarchy that are not leaf nodes.
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition eggGroup.h:34
double get_vertex_membership(const EggVertex *vert) const
Returns the amount of membership of the indicated vertex in this group.
Definition eggGroup.cxx:677
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
This is one node of a hierarchy of EggJointData nodes, each of which represents a single joint of the...
void move_vertices_to(EggJointData *new_owner)
Moves the vertices assigned to this joint into the indicated joint, without changing their weight ass...
void expose(EggGroup::DCSType dcs_type=EggGroup::DC_default)
Calls expose() on all models for this joint, but does not recurse downwards.
void zero_channels(const std::string &components)
Calls zero_channels() on all models for this joint, but does not recurse downwards.
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...
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...
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 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...
A base class for things that may be directly added into the egg hierarchy.
Definition eggNode.h:36
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 set_user_data(EggUserData *user_data)
Sets the user data associated with this object.
Definition eggObject.cxx:74
This class contains extra user data which is piggybacked onto EggGroup objects for the purpose of the...
Performs basic optimizations of a character model and its associated animations, by analyzing the ani...
Definition eggOptchar.h:37
This corresponds to a single morph slider control.
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.
A collection of vertices.
iterator end() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
iterator begin() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition eggVertex.h:39
GroupRef::const_iterator gref_end() const
Returns an iterator that can, in conjunction with gref_begin(), be used to traverse the entire set of...
GroupRef::size_type gref_size() const
Returns the number of elements between gref_begin() and gref_end().
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...
static bool is_compression_available()
Returns true if the FFTW library is compiled in, so that this class is actually capable of doing usef...
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
Definition filename.I:386
This class can be used to test for string matches against standard Unix- shell filename globbing conv...
Definition globPattern.h:32
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_...
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 class is used to help EggOptchar quantize the membership of one vertex among its various groups.
This is our own Panda specialization on the default STL set.
Definition pset.h:49
This is our own Panda specialization on the default STL vector.
Definition pvector.h:42
This is an iterator adaptor that converts any iterator that returns a pair (e.g.
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.
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.
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.
string trim(const string &str)
Returns a new string representing the contents of the given string with both leading and trailing whi...
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.