Panda3D
Loading...
Searching...
No Matches
mayaToEggConverter.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 mayaToEggConverter.cxx
10 * @author drose
11 * @date 1999-11-10
12 * Modified 19Mar10 by ETC PandaSE team
13 * Added set_vertex_color_modern to fix Phong shader bug; also see
14 * header comment for mayaToEgg.cxx for more details
15 */
16
17#include "mayaToEggConverter.h"
18#include "mayaShader.h"
19#include "maya_funcs.h"
20#include "config_mayaegg.h"
22
23#include "eggData.h"
24#include "eggGroup.h"
25#include "eggTable.h"
26#include "eggVertex.h"
27#include "eggVertexPool.h"
28#include "eggNurbsSurface.h"
29#include "eggNurbsCurve.h"
30#include "eggPolygon.h"
31#include "eggPrimitive.h"
32#include "eggTexture.h"
34#include "eggXfmSAnim.h"
35#include "eggSAnimData.h"
36#include "string_utils.h"
37#include "dcast.h"
38
39#include "pre_maya_include.h"
40#include <maya/MArgList.h>
41#include <maya/MColor.h>
42#include <maya/MDagPath.h>
43#include <maya/MFnCamera.h>
44#include <maya/MFnDagNode.h>
45#include <maya/MFnTransform.h>
46#include <maya/MFnLight.h>
47#include <maya/MFnNurbsSurface.h>
48#include <maya/MFnNurbsCurve.h>
49#include <maya/MFnMesh.h>
50#include <maya/MFnMeshData.h>
51#include <maya/MFnPlugin.h>
52#include <maya/MItDag.h>
53#include <maya/MMatrix.h>
54#include <maya/MObject.h>
55#include <maya/MPoint.h>
56#include <maya/MPointArray.h>
57#include <maya/MDoubleArray.h>
58#include <maya/MIntArray.h>
59#include <maya/MPxCommand.h>
60#include <maya/MStatus.h>
61#include <maya/MString.h>
62#include <maya/MTransformationMatrix.h>
63#include <maya/MVector.h>
64#include <maya/MTesselationParams.h>
65#include <maya/MAnimControl.h>
66#include <maya/MGlobal.h>
67#include <maya/MAnimUtil.h>
68#include <maya/MFnSkinCluster.h>
69#include <maya/MFnWeightGeometryFilter.h>
70#include <maya/MFnIkJoint.h>
71#include <maya/MFnSingleIndexedComponent.h>
72#include <maya/MFnDoubleIndexedComponent.h>
73#include <maya/MFnBlendShapeDeformer.h>
74#include <maya/MItDependencyGraph.h>
75#include <maya/MDagPathArray.h>
76#include <maya/MSelectionList.h>
77#include "post_maya_include.h"
78
79using std::endl;
80using std::string;
81
82
83/**
84 *
85 */
86MayaToEggConverter::
87MayaToEggConverter(const string &program_name) :
88 _program_name(program_name),
89 _tree(this)
90{
91 // Make sure the library is properly initialized.
93
94 _from_selection = false;
95
96 _polygon_output = false;
97 _polygon_tolerance = 0.01;
98 _respect_maya_double_sided = maya_default_double_sided;
99 _always_show_vertex_color = maya_default_vertex_color;
100 _keep_all_uvsets = false;
101 _round_uvs = false;
102 _legacy_shader = false;
103 _convert_cameras = false;
104 _convert_lights = false;
105
106 _transform_type = TT_model;
107}
108
109/**
110 *
111 */
112MayaToEggConverter::
113MayaToEggConverter(const MayaToEggConverter &copy) :
114 _program_name(copy._program_name),
115 _from_selection(copy._from_selection),
116 _subsets(copy._subsets),
117 _subroots(copy._subroots),
118 _excludes(copy._excludes),
119 _ignore_sliders(copy._ignore_sliders),
120 _force_joints(copy._force_joints),
121 _tree(this),
122 _maya(copy._maya),
123 _polygon_output(copy._polygon_output),
124 _polygon_tolerance(copy._polygon_tolerance),
125 _respect_maya_double_sided(copy._respect_maya_double_sided),
126 _always_show_vertex_color(copy._always_show_vertex_color),
127 _keep_all_uvsets(copy._keep_all_uvsets),
128 _convert_cameras(copy._convert_cameras),
129 _convert_lights(copy._convert_lights),
130 _round_uvs(copy._round_uvs),
131 _legacy_shader(copy._legacy_shader),
132 _transform_type(copy._transform_type)
133{
134}
135
136/**
137 *
138 */
139MayaToEggConverter::
140~MayaToEggConverter() {
141 close_api();
142}
143
144/**
145 * Allocates and returns a new copy of the converter.
146 */
151
152/**
153 * Returns the English name of the file type this converter supports.
154 */
156get_name() const {
157 return "Maya";
158}
159
160/**
161 * Returns the common extension of the file type this converter supports.
162 */
164get_extension() const {
165 return "mb";
166}
167
168/**
169 * Returns a space-separated list of extension, in addition to the one
170 * returned by get_extension(), that are recognized by this converter.
171 */
174 return "ma";
175}
176
177/**
178 * Handles the reading of the input file and converting it to egg. Returns
179 * true if successful, false otherwise.
180 *
181 * This is designed to be as generic as possible, generally in support of run-
182 * time loading. Also see convert_maya().
183 */
185convert_file(const Filename &filename) {
186 if (!open_api()) {
187 mayaegg_cat.error()
188 << "Maya is not available.\n";
189 return false;
190 }
191
192 // We must ensure our Maya pointers are cleared before we reset the Maya
193 // scene, because resetting the Maya scene will invalidate all the Maya
194 // pointers we are holding and cause a crash if we try to free them later.
195 clear();
196
197 if (!_maya->read(filename)) {
198 mayaegg_cat.error()
199 << "Unable to read " << filename << "\n";
200 return false;
201 }
202
203 if (_character_name.empty()) {
204 _character_name = filename.get_basename_wo_extension();
205 }
206
207 return convert_maya();
208}
209
210/**
211 * Empties the list of subroot nodes added via add_subroot(). The entire file
212 * will once again be converted.
213 */
216 _subroots.clear();
217}
218
219/**
220 * Adds a name pattern to the list of subroot nodes. If the list of subroot
221 * nodes is not empty, then only a subroot of the nodes in the maya file will
222 * be converted: those whose names match one of the patterns given on this
223 * list.
224 */
226add_subroot(const GlobPattern &glob) {
227 _subroots.push_back(glob);
228}
229
230/**
231 * Empties the list of subset nodes added via add_subset(). The entire file
232 * will once again be converted.
233 */
236 _subsets.clear();
237}
238
239/**
240 * Adds a name pattern to the list of subset nodes. If the list of subset
241 * nodes is not empty, then only a subset of the nodes in the maya file will
242 * be converted: those whose names match one of the patterns given on this
243 * list.
244 */
246add_subset(const GlobPattern &glob) {
247 _subsets.push_back(glob);
248}
249
250/**
251 * Empties the list of excluded nodes added via add_exclude().
252 */
255 _excludes.clear();
256}
257
258/**
259 * Adds a name pattern to the list of excluded nodes.
260 */
262add_exclude(const GlobPattern &glob) {
263 _excludes.push_back(glob);
264}
265
266/**
267 * Empties the list of ignore_sliders added via add_ignore_slider(). No
268 * sliders will be ignored.
269 */
272 _ignore_sliders.clear();
273}
274
275/**
276 * Adds a name pattern to the list of ignore_sliders. Any slider (blend shape
277 * deformer) that matches a name on the list will not be converted or
278 * otherwise molested by the converter. This is occasionally necessary to
279 * filter out automatically-created sliders that are not intended to be used
280 * directly, but instead have an indirect effect on other sliders.
281 */
283add_ignore_slider(const GlobPattern &glob) {
284 _ignore_sliders.push_back(glob);
285}
286
287/**
288 * Returns true if the indicated name is on the list of sliders to ignore,
289 * false otherwise.
290 */
292ignore_slider(const string &name) const {
293 Globs::const_iterator gi;
294 for (gi = _ignore_sliders.begin(); gi != _ignore_sliders.end(); ++gi) {
295 if ((*gi).matches(name)) {
296 return true;
297 }
298 }
299
300 return false;
301}
302
303/**
304 * Empties the list of force_joints added via add_force_joint(). No joints
305 * will be forced.
306 */
309 _force_joints.clear();
310}
311
312/**
313 * Adds a name pattern to the list of force_joints.
314 *
315 * Any DAG node that matches a name on the list will be treated as if it were
316 * a joint during the conversion process; it will receive animation and
317 * position information. Normally, a true Maya joint, as well as any DAG
318 * nodes whose transforms are animated, will automatically be flagged as a
319 * Panda joint.
320 */
322add_force_joint(const GlobPattern &glob) {
323 _force_joints.push_back(glob);
324}
325
326/**
327 * Returns true if the indicated name is on the list of DAG nodes to treat as
328 * a joint, false otherwise.
329 */
331force_joint(const string &name) const {
332 Globs::const_iterator gi;
333 for (gi = _force_joints.begin(); gi != _force_joints.end(); ++gi) {
334 if ((*gi).matches(name)) {
335 return true;
336 }
337 }
338
339 return false;
340}
341
342/**
343 * Sets the flag that indicates whether the currently selected Maya geometry
344 * will be converted. If this is true, and the selection is nonempty, then
345 * only the selected geometry will be converted. If this is false, the entire
346 * file will be converted.
347 */
349set_from_selection(bool from_selection) {
350 _from_selection = from_selection;
351}
352
353/**
354 * This may be called after convert_file() has been called and returned true,
355 * indicating a successful conversion. It will return the distance units
356 * represented by the converted egg file, if known, or DU_invalid if not
357 * known.
358 */
361 return _maya->get_units();
362}
363
364/**
365 * Fills up the egg_data structure according to the global maya model data.
366 * Returns true if successful, false if there is an error.
367 */
369convert_maya() {
370 clear();
371 clear_error();
372
373 if (!open_api()) {
374 mayaegg_cat.error()
375 << "Maya is not available.\n";
376 return false;
377 }
378
379 if (_egg_data->get_coordinate_system() == CS_default) {
380 _egg_data->set_coordinate_system(_maya->get_coordinate_system());
381 }
382
383 mayaegg_cat.info()
384 << "Converting from Maya.\n";
385
386 // Figure out the animation parameters.
387 double start_frame, end_frame, frame_inc, input_frame_rate, output_frame_rate;
388 if (has_start_frame()) {
389 start_frame = get_start_frame();
390 } else {
391 start_frame = MAnimControl::minTime().value();
392 }
393 if (has_end_frame()) {
394 end_frame = get_end_frame();
395 } else {
396 end_frame = MAnimControl::maxTime().value();
397 // end_frame = MAnimControl::animationEndTime().value(); masad: we could
398 // use this
399 }
400 if (has_frame_inc()) {
401 frame_inc = get_frame_inc();
402 } else {
403 frame_inc = 1.0;
404 }
405 if (has_input_frame_rate()) {
406 input_frame_rate = get_input_frame_rate();
407 } else {
408 MTime time(1.0, MTime::kSeconds);
409 input_frame_rate = time.as(MTime::uiUnit());
410 }
411 if (has_output_frame_rate()) {
412 output_frame_rate = get_output_frame_rate();
413 } else {
414 output_frame_rate = input_frame_rate;
415 }
416
417 frame_inc = frame_inc * input_frame_rate / output_frame_rate;
418
419 bool all_ok = _tree.build_hierarchy();
420
421 if (all_ok) {
422 if (!_subroots.empty()) {
423 Globs::const_iterator gi;
424 for (gi = _subroots.begin(); gi != _subroots.end(); ++gi) {
425 if (!_tree.tag_joint_named(*gi)) {
426 mayaegg_cat.info()
427 << "No node matching " << *gi << " found.\n";
428 }
429 }
430
431 } else {
432 // This call makes every node a potential joint; but it does not
433 // necessarily force nodes to be joints.
434 _tree.tag_joint_all();
435 }
436 }
437
438 if (all_ok) {
439 if (_from_selection) {
440 all_ok = _tree.tag_selected();
441 } else if (!_subsets.empty()) {
442 Globs::const_iterator gi;
443 for (gi = _subsets.begin(); gi != _subsets.end(); ++gi) {
444 if (!_tree.tag_named(*gi)) {
445 mayaegg_cat.info()
446 << "No node matching " << *gi << " found.\n";
447 }
448 }
449
450 } else {
451 _tree.tag_all();
452 }
453 }
454
455 if (all_ok) {
456 if (!_excludes.empty()) {
457 Globs::const_iterator gi;
458 for (gi = _excludes.begin(); gi != _excludes.end(); ++gi) {
459 if (!_tree.untag_named(*gi)) {
460 mayaegg_cat.info()
461 << "No node matching " << *gi << " found.\n";
462 }
463 }
464 }
465 }
466
467 if (all_ok) {
468 switch (get_animation_convert()) {
469 case AC_pose:
470 // pose: set to a specific frame, then get out the static geometry.
471 mayaegg_cat.info(false)
472 << "frame " << start_frame << "\n";
473 MGlobal::viewFrame(MTime(start_frame, MTime::uiUnit()));
474 // fall through
475
476 case AC_none:
477 // none: just get out a static model, no animation.
478 mayaegg_cat.info() << "ac_none" << endl;
479 all_ok = convert_hierarchy(get_egg_data());
480 break;
481
482 case AC_flip:
483 case AC_strobe:
484 // flip or strobe: get out a series of static models, one per frame,
485 // under a sequence node for AC_flip.
486 all_ok = convert_flip(start_frame, end_frame, frame_inc,
487 output_frame_rate);
488 break;
489
490 case AC_model:
491 // model: get out an animatable model with joints and vertex membership.
492 all_ok = convert_char_model();
493 break;
494
495 case AC_chan:
496 // chan: get out a series of animation tables.
497 all_ok = convert_char_chan(start_frame, end_frame, frame_inc,
498 output_frame_rate);
499 break;
500
501 case AC_both:
502 // both: Put a model and its animation into the same egg file.
503 _animation_convert = AC_model;
504 if (!convert_char_model()) {
505 all_ok = false;
506 }
507 _animation_convert = AC_chan;
508 if (!convert_char_chan(start_frame, end_frame, frame_inc,
509 output_frame_rate)) {
510 all_ok = false;
511 }
512 break;
513
514 case AC_invalid:
515 break;
516 };
517
518 reparent_decals(get_egg_data());
519 }
520
521 if (had_error()) {
522 all_ok = false;
523 }
524
525 if (all_ok) {
526 mayaegg_cat.info()
527 << "Converted, no errors.\n";
528 } else {
529 mayaegg_cat.info()
530 << "Errors encountered in conversion.\n";
531 }
532
533 return all_ok;
534}
535
536/**
537 * Attempts to open the Maya API if it was not already open, and returns true
538 * if successful, or false if there is an error.
539 */
541open_api(bool revert_directory) {
542
543 if (_maya == nullptr || !_maya->is_valid()) {
544 // maya to egg converter only needs a read license. only egg2maya need
545 // write lisences.
546 _maya = MayaApi::open_api(_program_name, true, revert_directory);
547 }
548 return _maya->is_valid();
549}
550
551/**
552 * Closes the Maya API, if it was previously opened. Caution! Maya appears
553 * to call exit() when its API is closed.
554 */
556close_api() {
557 // We have to clear the shaders, at least, before we release the Maya API.
558 clear();
559 _maya.clear();
560}
561
562/**
563 * Frees all of the Maya pointers kept within this object, in preparation for
564 * loading a new scene or releasing the Maya API.
565 */
567clear() {
568 _tree.clear();
569 _textures.clear();
570 _shaders.clear();
571}
572
573/**
574 * Converts the animation as a series of models that cycle (flip) from one to
575 * the next at the appropriate frame rate. This is the most likely to convert
576 * precisely (since we ask Maya to tell us the vertex position each time) but
577 * it is the most wasteful in terms of memory utilization (since a complete of
578 * the model is stored for each frame).
579 */
580bool MayaToEggConverter::
581convert_flip(double start_frame, double end_frame, double frame_inc,
582 double output_frame_rate) {
583 bool all_ok = true;
584
585 EggGroup *sequence_node = new EggGroup(_character_name);
586 get_egg_data()->add_child(sequence_node);
587 if (_animation_convert == AC_flip) {
588 sequence_node->set_switch_flag(true);
589 sequence_node->set_switch_fps(output_frame_rate);
590 }
591
592 MTime frame(start_frame, MTime::uiUnit());
593 MTime frame_stop(end_frame, MTime::uiUnit());
594 while (frame <= frame_stop) {
595 mayaegg_cat.info(false)
596 << "frame " << frame.value() << "\n";
597 std::ostringstream name_strm;
598 name_strm << "frame" << frame.value();
599 EggGroup *frame_root = new EggGroup(name_strm.str());
600 sequence_node->add_child(frame_root);
601
602 MGlobal::viewFrame(frame);
603 if (!convert_hierarchy(frame_root)) {
604 all_ok = false;
605 }
606
607 frame += frame_inc;
608 }
609
610 return all_ok;
611}
612
613/**
614 * Converts the file as an animatable character model, with joints and vertex
615 * membership.
616 */
617bool MayaToEggConverter::
618convert_char_model() {
619 if (has_neutral_frame()) {
620 MTime frame(get_neutral_frame(), MTime::uiUnit());
621 mayaegg_cat.info(false)
622 << "neutral frame " << frame.value() << "\n";
623 MGlobal::viewFrame(frame);
624 }
625
626 // It's also important for us to reset all the blend shape sliders to 0
627 // before we get out the model. Otherwise, the model we convert will have
628 // the current positions of the sliders baked in.
629 _tree.reset_sliders();
630
631 EggGroup *char_node = new EggGroup(_character_name);
632 get_egg_data()->add_child(char_node);
633 char_node->set_dart_type(EggGroup::DT_default);
634
635 return convert_hierarchy(char_node);
636}
637
638/**
639 * Converts the animation as a series of tables to apply to the character
640 * model, as retrieved earlier via AC_model.
641 */
642bool MayaToEggConverter::
643convert_char_chan(double start_frame, double end_frame, double frame_inc,
644 double output_frame_rate) {
645 // MStatus status;
646
647 EggTable *root_table_node = new EggTable();
648 get_egg_data()->add_child(root_table_node);
649 EggTable *bundle_node = new EggTable(_character_name);
650 bundle_node->set_table_type(EggTable::TT_bundle);
651 root_table_node->add_child(bundle_node);
652 EggTable *skeleton_node = new EggTable("<skeleton>");
653 bundle_node->add_child(skeleton_node);
654 EggTable *morph_node = new EggTable("morph");
655 bundle_node->add_child(morph_node);
656
657 // Set the frame rate before we start asking for anim tables to be created.
658 _tree._fps = output_frame_rate;
659 _tree.clear_egg(get_egg_data(), nullptr, skeleton_node, morph_node);
660
661 // Now we can get the animation data by walking through all of the frames,
662 // one at a time, and getting the joint angles at each frame.
663
664 // This is just a temporary EggGroup to receive the transform for each joint
665 // each frame.
666 PT(EggGroup) tgroup = new EggGroup;
667
668 int num_nodes = _tree.get_num_nodes();
669 int num_sliders = _tree.get_num_blend_descs();
670 int i;
671
672 MTime frame(start_frame, MTime::uiUnit());
673 MTime frame_stop(end_frame, MTime::uiUnit());
674 while (frame <= frame_stop) {
675 if (mayaegg_cat.is_spam()) {
676 mayaegg_cat.spam(false)
677 << "frame " << frame.value() << "\n";
678 } else {
679 // We have to write to cerr instead of mayaegg_cat to allow flushing
680 // without writing a newline.
681 std::cerr << "." << std::flush;
682 }
683 MGlobal::viewFrame(frame);
684
685 for (i = 0; i < num_nodes; i++) {
686 MayaNodeDesc *node_desc = _tree.get_node(i);
687 if (node_desc->is_joint()) {
688 if (mayaegg_cat.is_spam()) {
689 mayaegg_cat.spam()
690 << "joint " << node_desc->get_name() << "\n";
691 }
692 get_joint_transform(node_desc->get_dag_path(), tgroup);
693 EggXfmSAnim *anim = _tree.get_egg_anim(node_desc);
694 if (!anim->add_data(tgroup->get_transform3d())) {
695 mayaegg_cat.error()
696 << "Invalid transform on " << node_desc->get_name()
697 << " frame " << frame.value() << ".\n";
698 }
699 }
700 }
701
702 for (i = 0; i < num_sliders; i++) {
703 MayaBlendDesc *blend_desc = _tree.get_blend_desc(i);
704 if (mayaegg_cat.is_spam()) {
705 mayaegg_cat.spam()
706 << "slider " << blend_desc->get_name() << "\n";
707 }
708 EggSAnimData *anim = _tree.get_egg_slider(blend_desc);
709 anim->add_data(blend_desc->get_slider());
710 }
711
712 frame += frame_inc;
713 }
714
715 // Now optimize all of the tables we just filled up, for no real good
716 // reason, except that it makes the resulting egg file a little easier to
717 // read.
718 for (i = 0; i < num_nodes; i++) {
719 MayaNodeDesc *node_desc = _tree.get_node(i);
720 if (node_desc->is_joint()) {
721 _tree.get_egg_anim(node_desc)->optimize();
722 }
723 }
724
725 for (i = 0; i < num_sliders; i++) {
726 MayaBlendDesc *blend_desc = _tree.get_blend_desc(i);
727 EggSAnimData *anim = _tree.get_egg_slider(blend_desc);
728 anim->optimize();
729 }
730
731 mayaegg_cat.info(false)
732 << "\n";
733
734 return true;
735}
736
737/**
738 * Generates egg structures for each node in the Maya hierarchy.
739 */
740bool MayaToEggConverter::
741convert_hierarchy(EggGroupNode *egg_root) {
742 int num_nodes = _tree.get_num_nodes();
743
744 if (_round_uvs) {
745 mayaegg_cat.info() << "will round up uv coordinates" << endl;
746 }
747
748 if (_keep_all_uvsets) {
749 mayaegg_cat.info() << "will keep_all_uvsets" << endl;
750 }
751 if (_polygon_output) {
752 mayaegg_cat.info() << "will convert NURBS to polys" << endl;
753 }
754 if (_convert_cameras) {
755 mayaegg_cat.info() << "will convert camera nodes to locators" << endl;
756 }
757 if (_convert_lights) {
758 mayaegg_cat.info() << "will convert light nodes to locators" << endl;
759 }
760 // give some feedback about whether special options are on
761 if (_legacy_shader) {
762 mayaegg_cat.info() << "will disable modern Phong shader path. using legacy" << endl;
763 }
764 _tree.clear_egg(get_egg_data(), egg_root, nullptr, nullptr);
765 for (int i = 0; i < num_nodes; i++) {
766 MayaNodeDesc *node = _tree.get_node(i);
767 if (!process_model_node(node)) {
768 return false;
769 }
770 }
771 return true;
772}
773
774/**
775 * Converts the indicated Maya node (given a MDagPath, similar in concept to
776 * Panda's NodePath) to the corresponding Egg structure. Returns true if
777 * successful, false if an error was encountered.
778 */
779bool MayaToEggConverter::
780process_model_node(MayaNodeDesc *node_desc) {
781 if (!node_desc->has_dag_path()) {
782 // If the node has no Maya equivalent, never mind.
783 return true;
784 }
785
786 MDagPath dag_path = node_desc->get_dag_path();
787
788 MStatus status;
789 MFnDagNode dag_node(dag_path, &status);
790 if (!status) {
791 status.perror("MFnDagNode constructor");
792 mayaegg_cat.error() << dag_path.fullPathName().asChar() << "\n";
793 return false;
794 }
795
796 MObject node = dag_path.transform(&status);
797 if (!status) {
798 status.perror("dag_path.transform()");
799 return false;
800 }
801
802 string path = dag_path.fullPathName().asChar();
803
804 if (mayaegg_cat.is_debug()) {
805 mayaegg_cat.debug()
806 << path << ": " << dag_node.typeName().asChar();
807
808 if (MAnimUtil::isAnimated(dag_path)) {
809 mayaegg_cat.debug(false)
810 << " (animated)";
811 }
812
813 mayaegg_cat.debug(false) << "\n";
814 }
815
816 if (dag_node.inUnderWorld()) {
817 if (mayaegg_cat.is_debug()) {
818 mayaegg_cat.debug()
819 << "Ignoring underworld node " << path
820 << "\n";
821 }
822
823 } else if (dag_node.isIntermediateObject()) {
824 if (mayaegg_cat.is_debug()) {
825 mayaegg_cat.debug()
826 << "Ignoring intermediate object " << path
827 << "\n";
828 }
829
830 } else if (dag_path.hasFn(MFn::kCamera)) {
831 if (_convert_cameras) {
832 MFnCamera camera (dag_path, &status);
833 if ( !status ) {
834 status.perror("MFnCamera constructor");
835 return false;
836 }
837
838 // Extract some interesting Camera data
839 if (mayaegg_cat.is_spam()) {
840 MPoint eyePoint = camera.eyePoint(MSpace::kWorld);
841 MVector upDirection = camera.upDirection(MSpace::kWorld);
842 MVector viewDirection = camera.viewDirection(MSpace::kWorld);
843 mayaegg_cat.spam() << " eyePoint: " << eyePoint.x << " "
844 << eyePoint.y << " " << eyePoint.z << endl;
845 mayaegg_cat.spam() << " upDirection: " << upDirection.x << " "
846 << upDirection.y << " " << upDirection.z << endl;
847 mayaegg_cat.spam() << " viewDirection: " << viewDirection.x << " "
848 << viewDirection.y << " " << viewDirection.z << endl;
849 mayaegg_cat.spam() << " aspectRatio: " << camera.aspectRatio() << endl;
850 mayaegg_cat.spam() << " horizontalFilmAperture: "
851 << camera.horizontalFilmAperture() << endl;
852 mayaegg_cat.spam() << " verticalFilmAperture: "
853 << camera.verticalFilmAperture() << endl;
854 }
855
856 EggGroup *egg_group = _tree.get_egg_group(node_desc);
857
858 if (mayaegg_cat.is_debug()) {
859 mayaegg_cat.warning()
860 << "Saving camera nodes as a locator: " << path << "\n";
861 }
862
863 if (node_desc->is_tagged()) {
864 // Presumably, the camera's position has some meaning to the end-user,
865 // so we will implicitly tag it with the DCS flag so it won't get
866 // flattened out.
867 if (_animation_convert != AC_model) {
868 // For now, don't set the DCS flag on cameras within character
869 // models, since egg-optchar doesn't understand this. Perhaps
870 // there's no reason to ever change this, since cameras within
871 // character models may not be meaningful.
872 egg_group->set_dcs_type(EggGroup::DC_net);
873 }
874 get_transform(node_desc, dag_path, egg_group);
875 make_camera_locator(dag_path, dag_node, egg_group);
876 } else {
877 if (mayaegg_cat.is_debug()) {
878 mayaegg_cat.debug()
879 << "Ignoring camera node " << path
880 << "\n";
881 }
882 }
883 }
884
885 } else if (dag_path.hasFn(MFn::kLight)) {
886 if (_convert_lights) {
887 MFnLight light (dag_path, &status);
888 if ( !status ) {
889 status.perror("MFnLight constructor");
890 return false;
891 }
892
893 EggGroup *egg_group = _tree.get_egg_group(node_desc);
894
895 if (mayaegg_cat.is_debug()) {
896 mayaegg_cat.warning() << "Saving light node as a locator: " << path << endl;
897 }
898
899 if (node_desc->is_tagged()) {
900 // Presumably, the lighht's position has some meaning to the end-user,
901 // so we will implicitly tag it with the DCS flag so it won't get
902 // flattened out.
903 if (_animation_convert != AC_model) {
904 // For now, don't set the DCS flag on lights within character
905 // models, since egg-optchar doesn't understand this. Perhaps
906 // there's no reason to ever change this, since lights within
907 // character models may not be meaningful.
908 egg_group->set_dcs_type(EggGroup::DC_net);
909 }
910 get_transform(node_desc, dag_path, egg_group);
911 make_light_locator(dag_path, dag_node, egg_group);
912 } else {
913 if (mayaegg_cat.is_debug()) {
914 mayaegg_cat.debug()
915 << "Ignoring light node " << path
916 << "\n";
917 }
918 }
919 }
920
921 MFnLight light (dag_path, &status);
922 if ( !status ) {
923 status.perror("MFnLight constructor");
924 mayaegg_cat.error() << "light extraction failed" << endl;
925 return false;
926 }
927
928 if (mayaegg_cat.is_info()) {
929 MString name = dag_path.partialPathName();
930 mayaegg_cat.info() << "-- Light found -- tranlations in cm, rotations in rads\n";
931 mayaegg_cat.info() << "\"" << name.asChar() << "\" : \n";
932 }
933
934 // Get the translationrotationscale data
935 MObject transformNode = dag_path.transform(&status);
936 // This node has no transform - i.e., it's the world node
937 if (!status && status.statusCode () == MStatus::kInvalidParameter)
938 return false;
939 MFnDagNode transform (transformNode, &status);
940 if (!status) {
941 status.perror("MFnDagNode constructor");
942 return false;
943 }
944 MTransformationMatrix matrix (transform.transformationMatrix());
945 MVector tl = matrix.translation(MSpace::kWorld);
946 // Stop rediculously small values like -4.43287e-013
947 if (tl.x < 0.0001) {
948 tl.x = 0;
949 }
950 if (tl.y < 0.0001) {
951 tl.y = 0;
952 }
953 if (tl.z < 0.0001) {
954 tl.z = 0;
955 }
956 // We swap Y and Z in the next few bits cuz Panda is Z-up by default and
957 // Maya is Y-up
958 mayaegg_cat.info() << " \"translation\" : (" << tl.x << ", " << tl.z << ", " << tl.y << ")"
959 << endl;
960 double threeDoubles[3];
961 MTransformationMatrix::RotationOrder rOrder;
962
963 matrix.getRotation (threeDoubles, rOrder, MSpace::kWorld);
964 mayaegg_cat.info() << " \"rotation\": ("
965 << threeDoubles[0] << ", "
966 << threeDoubles[2] << ", "
967 << threeDoubles[1] << ")\n";
968 matrix.getScale (threeDoubles, MSpace::kWorld);
969 mayaegg_cat.info() << " \"scale\" : ("
970 << threeDoubles[0] << ", "
971 << threeDoubles[2] << ", "
972 << threeDoubles[1] << ")\n";
973
974 // Extract some interesting Light data
975 MColor color;
976 color = light.color();
977 mayaegg_cat.info() << " \"color\" : ("
978 << color.r << ", "
979 << color.g << ", "
980 << color.b << ")\n";
981 color = light.shadowColor();
982 mayaegg_cat.info() << " \"intensity\" : " << light.intensity() << endl;
983
984 } else if (dag_path.hasFn(MFn::kNurbsSurface)) {
985 EggGroup *egg_group = _tree.get_egg_group(node_desc);
986 get_transform(node_desc, dag_path, egg_group);
987
988 if (node_desc->is_tagged()) {
989 MFnNurbsSurface surface(dag_path, &status);
990 if (!status) {
991 mayaegg_cat.info()
992 << "Error in node " << path
993 << ":\n"
994 << " it appears to have a NURBS surface, but does not.\n";
995 } else {
996 make_nurbs_surface(node_desc, dag_path, surface, egg_group);
997 }
998 }
999 } else if (dag_path.hasFn(MFn::kNurbsCurve)) {
1000 // Only convert NurbsCurves if we aren't making an animated model.
1001 // Animated models, as a general rule, don't want these sorts of things in
1002 // them.
1003 if (_animation_convert != AC_model) {
1004 EggGroup *egg_group = _tree.get_egg_group(node_desc);
1005 get_transform(node_desc, dag_path, egg_group);
1006
1007 if (node_desc->is_tagged()) {
1008 MFnNurbsCurve curve(dag_path, &status);
1009 if (!status) {
1010 mayaegg_cat.info()
1011 << "Error in node " << path << ":\n"
1012 << " it appears to have a NURBS curve, but does not.\n";
1013 } else {
1014 make_nurbs_curve(dag_path, curve, egg_group);
1015 }
1016 }
1017 }
1018
1019 } else if (dag_path.hasFn(MFn::kMesh)) {
1020 if (node_desc->is_tagged()) {
1021 EggGroup *egg_group = _tree.get_egg_group(node_desc);
1022 get_transform(node_desc, dag_path, egg_group);
1023 MFnMesh mesh(dag_path, &status);
1024 if (!status) {
1025 mayaegg_cat.info()
1026 << "Error in node " << path << ":\n"
1027 << " it appears to have a polygon mesh, but does not.\n";
1028 } else {
1029 make_polyset(node_desc, dag_path, mesh, egg_group);
1030 }
1031 }
1032 /*
1033 EggGroup *egg_group = _tree.get_egg_group(node_desc);
1034 get_transform(node_desc, dag_path, egg_group);
1035
1036 if (node_desc->is_tagged()) {
1037 MFnMesh mesh(dag_path, &status);
1038 if (!status) {
1039 mayaegg_cat.info()
1040 << "Error in node " << path << ":\n"
1041 << " it appears to have a polygon mesh, but does not.\n";
1042 } else {
1043 make_polyset(node_desc, dag_path, mesh, egg_group);
1044 }
1045 }
1046 */
1047 } else if (dag_path.hasFn(MFn::kLocator)) {
1048 if (_animation_convert == AC_none) {
1049 if (!node_desc->is_tagged()) {
1050 return true;
1051 }
1052 }
1053 EggGroup *egg_group = _tree.get_egg_group(node_desc);
1054
1055 if (mayaegg_cat.is_debug()) {
1056 mayaegg_cat.debug()
1057 << "Locator at " << path << "\n";
1058 }
1059
1060 if (node_desc->is_tagged()) {
1061 // Presumably, the locator's position has some meaning to the end-user,
1062 // so we will implicitly tag it with the DCS flag so it won't get
1063 // flattened out.
1064 if (_animation_convert != AC_model) {
1065 // For now, don't set the DCS flag on locators within character
1066 // models, since egg-optchar doesn't understand this. Perhaps there's
1067 // no reason to ever change this, since locators within character
1068 // models may not be meaningful.
1069 egg_group->set_dcs_type(EggGroup::DC_net);
1070 }
1071 get_transform(node_desc, dag_path, egg_group);
1072 make_locator(dag_path, dag_node, egg_group);
1073 }
1074
1075 } else {
1076 // Just a generic node.
1077 if (_animation_convert == AC_none) {
1078 if (!node_desc->is_tagged()) {
1079 return true;
1080 }
1081 }
1082 EggGroup *egg_group = _tree.get_egg_group(node_desc);
1083 get_transform(node_desc, dag_path, egg_group);
1084 }
1085
1086 return true;
1087}
1088
1089/**
1090 * Extracts the transform on the indicated Maya node, and applies it to the
1091 * corresponding Egg node.
1092 */
1093void MayaToEggConverter::
1094get_transform(MayaNodeDesc *node_desc, const MDagPath &dag_path,
1095 EggGroup *egg_group) {
1096 if (_animation_convert == AC_model) {
1097 // When we're getting an animated model, we only get transforms for
1098 // joints, and they get converted in a special way.
1099
1100 if (node_desc->is_joint()) {
1101 if (mayaegg_cat.is_spam()) {
1102 mayaegg_cat.spam()
1103 << "gt: joint " << node_desc->get_name() << "\n";
1104 }
1105 get_joint_transform(dag_path, egg_group);
1106 }
1107 return;
1108 }
1109
1110 MStatus status;
1111 MObject transformNode = dag_path.transform(&status);
1112 if (!status && status.statusCode() == MStatus::kInvalidParameter) {
1113 // This node has no transform - i.e., it's the world node
1114 return;
1115 }
1116
1117 // Billboards always get the transform set.
1118 if (egg_group->get_billboard_type() == EggGroup::BT_none) {
1119 switch (_transform_type) {
1120 case TT_all:
1121 break;
1122
1123 case TT_model:
1124 if (!egg_group->get_model_flag() && !egg_group->has_dcs_type()) {
1125 return;
1126 }
1127 break;
1128
1129 case TT_dcs:
1130 if (!egg_group->has_dcs_type()) {
1131 return;
1132 }
1133 break;
1134
1135 case TT_none:
1136 case TT_invalid:
1137 return;
1138 }
1139 }
1140
1141 // Extract the matrix from the dag path.
1142 MMatrix mat = dag_path.inclusiveMatrix(&status);
1143 if (!status) {
1144 status.perror("Can't get transform matrix");
1145 return;
1146 }
1147 LMatrix4d m4d(mat[0][0], mat[0][1], mat[0][2], mat[0][3],
1148 mat[1][0], mat[1][1], mat[1][2], mat[1][3],
1149 mat[2][0], mat[2][1], mat[2][2], mat[2][3],
1150 mat[3][0], mat[3][1], mat[3][2], mat[3][3]);
1151
1152 // Maya has a rotate pivot, separate from its transform. Usually we care
1153 // more about the rotate pivot than we do about the transform, so get the
1154 // rotate pivot too.
1155 MFnTransform transform(transformNode, &status);
1156 if (!status) {
1157 status.perror("MFnTransform constructor");
1158 return;
1159 }
1160 MPoint pivot = transform.rotatePivot(MSpace::kObject, &status);
1161 if (!status) {
1162 status.perror("Can't get rotate pivot");
1163 return;
1164 }
1165
1166 // We need to convert the pivot to world coordinates. (Maya can only tell
1167 // it to us in local coordinates.)
1168 LPoint3d p3d(pivot[0], pivot[1], pivot[2]);
1169 p3d = p3d * m4d;
1170
1171 // Now recenter the matrix about the pivot point.
1172 m4d.set_row(3, p3d);
1173
1174 // Convert the recentered matrix into the group's space and store it.
1175 m4d = m4d * egg_group->get_node_frame_inv();
1176 if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
1177 egg_group->add_matrix4(m4d);
1178 }
1179 return;
1180}
1181
1182/**
1183 * Extracts the transform on the indicated Maya node, as appropriate for a
1184 * joint in an animated character, and applies it to the indicated node. This
1185 * is different from get_transform() in that it does not respect the
1186 * _transform_type flag, and it does not consider the relative transforms
1187 * within the egg file.
1188 */
1189void MayaToEggConverter::
1190get_joint_transform(const MDagPath &dag_path, EggGroup *egg_group) {
1191 // First, make sure there's not a transform on the group already.
1192 egg_group->clear_transform();
1193
1194 MStatus status;
1195 MObject transformNode = dag_path.transform(&status);
1196 // This node has no transform - i.e., it's the world node
1197 if (!status && status.statusCode() == MStatus::kInvalidParameter) {
1198 return;
1199 }
1200
1201 MFnDagNode transform(transformNode, &status);
1202 if (!status) {
1203 status.perror("MFnDagNode constructor");
1204 return;
1205 }
1206
1207 MTransformationMatrix matrix(transform.transformationMatrix());
1208
1209 if (mayaegg_cat.is_spam()) {
1210 MVector t = matrix.translation(MSpace::kWorld);
1211 mayaegg_cat.spam()
1212 << " translation: ["
1213 << t[0] << ", "
1214 << t[1] << ", "
1215 << t[2] << "]\n";
1216 double d[3];
1217 MTransformationMatrix::RotationOrder rOrder;
1218
1219 matrix.getRotation(d, rOrder, MSpace::kWorld);
1220 mayaegg_cat.spam()
1221 << " rotation: ["
1222 << d[0] << ", "
1223 << d[1] << ", "
1224 << d[2] << "]\n";
1225 matrix.getScale(d, MSpace::kWorld);
1226 mayaegg_cat.spam()
1227 << " scale: ["
1228 << d[0] << ", "
1229 << d[1] << ", "
1230 << d[2] << "]\n";
1231 matrix.getShear(d, MSpace::kWorld);
1232 mayaegg_cat.spam()
1233 << " shear: ["
1234 << d[0] << ", "
1235 << d[1] << ", "
1236 << d[2] << "]\n";
1237 }
1238
1239 MMatrix mat = matrix.asMatrix();
1240 MMatrix ident_mat;
1241 ident_mat.setToIdentity();
1242
1243 if (!mat.isEquivalent(ident_mat, 0.0001)) {
1244 egg_group->set_transform3d
1245 (LMatrix4d(mat[0][0], mat[0][1], mat[0][2], mat[0][3],
1246 mat[1][0], mat[1][1], mat[1][2], mat[1][3],
1247 mat[2][0], mat[2][1], mat[2][2], mat[2][3],
1248 mat[3][0], mat[3][1], mat[3][2], mat[3][3]));
1249 }
1250}
1251
1252/**
1253 * Converts the indicated Maya NURBS surface to a corresponding egg structure,
1254 * and attaches it to the indicated egg group.
1255 */
1256void MayaToEggConverter::
1257make_nurbs_surface(MayaNodeDesc *node_desc, const MDagPath &dag_path,
1258 MFnNurbsSurface &surface, EggGroup *egg_group) {
1259 MStatus status;
1260 string name = surface.name().asChar();
1261
1262 if (mayaegg_cat.is_spam()) {
1263 mayaegg_cat.spam()
1264 << " numCVs: "
1265 << surface.numCVsInU()
1266 << " * "
1267 << surface.numCVsInV()
1268 << "\n";
1269 mayaegg_cat.spam()
1270 << " numKnots: "
1271 << surface.numKnotsInU()
1272 << " * "
1273 << surface.numKnotsInV()
1274 << "\n";
1275 mayaegg_cat.spam()
1276 << " numSpans: "
1277 << surface.numSpansInU()
1278 << " * "
1279 << surface.numSpansInV()
1280 << "\n";
1281 }
1282 MayaShader *shader = _shaders.find_shader_for_node(surface.object(), _legacy_shader);
1283
1284 if (_polygon_output) {
1285 // If we want polygon output only, tesselate the NURBS and output that.
1286 MTesselationParams params;
1287 params.setFormatType(MTesselationParams::kStandardFitFormat);
1288 params.setOutputType(MTesselationParams::kQuads);
1289 params.setStdFractionalTolerance(_polygon_tolerance);
1290
1291 // We'll create the tesselation as a sibling of the NURBS surface. That
1292 // way we inherit all of the transformations.
1293 MDagPath polyset_path = dag_path;
1294 MObject polyset_parent = polyset_path.node();
1295 MObject polyset =
1296 surface.tesselate(params, polyset_parent, &status);
1297 if (!status) {
1298 status.perror("MFnNurbsSurface::tesselate");
1299 return;
1300 }
1301
1302 status = polyset_path.push(polyset);
1303 if (!status) {
1304 status.perror("MDagPath::push");
1305 }
1306
1307 MFnMesh polyset_fn(polyset, &status);
1308 if (!status) {
1309 status.perror("MFnMesh constructor");
1310 return;
1311 }
1312 make_polyset(node_desc, polyset_path, polyset_fn, egg_group, shader);
1313
1314 // Now remove the polyset we created.
1315 MFnDagNode parent_node(polyset_parent, &status);
1316 if (!status) {
1317 status.perror("MFnDagNode constructor");
1318 return;
1319 }
1320 status = parent_node.removeChild(polyset);
1321 if (!status) {
1322 status.perror("MFnDagNode::removeChild");
1323 }
1324
1325 return;
1326 }
1327
1328 MPointArray cv_array;
1329 status = surface.getCVs(cv_array, MSpace::kWorld);
1330 if (!status) {
1331 status.perror("MFnNurbsSurface::getCVs");
1332 return;
1333 }
1334
1335 // Also get out all the alternate blend shapes for the surface by applying
1336 // each morph slider one at a time.
1337 pvector<MPointArray> morph_cvs;
1338 if (_animation_convert == AC_model) {
1339 int num_sliders = node_desc->get_num_blend_descs();
1340 morph_cvs.reserve(num_sliders);
1341 for (int i = 0; i < num_sliders; i++) {
1342 MayaBlendDesc *blend_desc = node_desc->get_blend_desc(i);
1343
1344 // Temporarily push the slider up to 1.0 so we can see what the surface
1345 // looks like at that value.
1346 blend_desc->set_slider(1.0);
1347 MPointArray cv_array;
1348 status = surface.getCVs(cv_array, MSpace::kWorld);
1349 blend_desc->set_slider(0.0);
1350
1351 if (!status) {
1352 status.perror("MFnNurbsSurface::getCVs");
1353 return;
1354 }
1355 morph_cvs.push_back(cv_array);
1356 }
1357 }
1358
1359 MDoubleArray u_knot_array, v_knot_array;
1360 status = surface.getKnotsInU(u_knot_array);
1361 if (!status) {
1362 status.perror("MFnNurbsSurface::getKnotsInU");
1363 return;
1364 }
1365 status = surface.getKnotsInV(v_knot_array);
1366 if (!status) {
1367 status.perror("MFnNurbsSurface::getKnotsInV");
1368 return;
1369 }
1370
1371 MFnNurbsSurface::Form u_form = surface.formInU();
1372 MFnNurbsSurface::Form v_form = surface.formInV();
1373
1374 int u_degree = surface.degreeU();
1375 int v_degree = surface.degreeV();
1376
1377 int u_cvs = surface.numCVsInU();
1378 int v_cvs = surface.numCVsInV();
1379
1380 // Maya repeats CVS at the end for a periodic surface, and doesn't count
1381 // them in the joint weight array, below.
1382 int maya_u_cvs = (u_form == MFnNurbsSurface::kPeriodic) ? u_cvs - u_degree : u_cvs;
1383 int maya_v_cvs = (v_form == MFnNurbsSurface::kPeriodic) ? v_cvs - v_degree : v_cvs;
1384
1385 int u_knots = surface.numKnotsInU();
1386 int v_knots = surface.numKnotsInV();
1387
1388 assert(u_knots == u_cvs + u_degree - 1);
1389 assert(v_knots == v_cvs + v_degree - 1);
1390
1391 string vpool_name = name + ".cvs";
1392 EggVertexPool *vpool = new EggVertexPool(vpool_name);
1393 egg_group->add_child(vpool);
1394
1395 EggNurbsSurface *egg_nurbs = new EggNurbsSurface(name);
1396 egg_nurbs->setup(u_degree + 1, v_degree + 1,
1397 u_knots + 2, v_knots + 2);
1398
1399 int i;
1400
1401 egg_nurbs->set_u_knot(0, u_knot_array[0]);
1402 for (i = 0; i < u_knots; i++) {
1403 egg_nurbs->set_u_knot(i + 1, u_knot_array[i]);
1404 }
1405 egg_nurbs->set_u_knot(u_knots + 1, u_knot_array[u_knots - 1]);
1406
1407 egg_nurbs->set_v_knot(0, v_knot_array[0]);
1408 for (i = 0; i < v_knots; i++) {
1409 egg_nurbs->set_v_knot(i + 1, v_knot_array[i]);
1410 }
1411 egg_nurbs->set_v_knot(v_knots + 1, v_knot_array[v_knots - 1]);
1412
1413 LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv();
1414
1415 for (i = 0; i < egg_nurbs->get_num_cvs(); i++) {
1416 int ui = egg_nurbs->get_u_index(i);
1417 int vi = egg_nurbs->get_v_index(i);
1418 int maya_vi = v_cvs * ui + vi;
1419
1420 double v[4];
1421 status = cv_array[maya_vi].get(v);
1422 if (!status) {
1423 status.perror("MPoint::get");
1424 } else {
1425 EggVertex *vert = vpool->add_vertex(new EggVertex, i);
1426 LPoint4d p4d(v[0], v[1], v[2], v[3]);
1427 p4d = p4d * vertex_frame_inv;
1428 vert->set_pos(p4d);
1429
1430 // Now generate the morph targets for the vertex.
1431 if (!morph_cvs.empty()) {
1432 // Morph deltas are given in 3-d space, not in 4-d homogenous space.
1433 LPoint3d p3d(v[0] / v[3], v[1] / v[3], v[2] / v[3]);
1434
1435 for (unsigned int si = 0; si < morph_cvs.size(); si++) {
1436 MayaBlendDesc *blend_desc = node_desc->get_blend_desc(si);
1437 status = morph_cvs[si][maya_vi].get(v);
1438 if (!status) {
1439 status.perror("MPoint::get");
1440 } else {
1441 LPoint3d m3d(v[0] / v[3], v[1] / v[3], v[2] / v[3]);
1442 LVector3d delta = m3d - p3d;
1443 if (!delta.almost_equal(LVector3d::zero())) {
1444 EggMorphVertex dxyz(blend_desc->get_name(), delta);
1445 vert->_dxyzs.insert(dxyz);
1446 }
1447 }
1448 }
1449 }
1450
1451 egg_nurbs->add_vertex(vert);
1452 }
1453 }
1454
1455 // Now consider the trim curves, if any.
1456 unsigned num_trims = surface.numRegions();
1457 int trim_curve_index = 0;
1458 for (unsigned ti = 0; ti < num_trims; ti++) {
1459 unsigned num_loops = surface.numBoundaries(ti);
1460
1461 if (num_loops > 0) {
1462 egg_nurbs->_trims.push_back(EggNurbsSurface::Trim());
1463 EggNurbsSurface::Trim &egg_trim = egg_nurbs->_trims.back();
1464
1465 for (unsigned li = 0; li < num_loops; li++) {
1466 egg_trim.push_back(EggNurbsSurface::Loop());
1467 EggNurbsSurface::Loop &egg_loop = egg_trim.back();
1468
1469 MFnNurbsSurface::BoundaryType type =
1470 surface.boundaryType(ti, li, &status);
1471 bool keep_loop = false;
1472
1473 if (!status) {
1474 status.perror("MFnNurbsSurface::BoundaryType");
1475 } else {
1476 keep_loop = (type == MFnNurbsSurface::kInner ||
1477 type == MFnNurbsSurface::kOuter);
1478 }
1479
1480 if (keep_loop) {
1481 unsigned num_edges = surface.numEdges(ti, li);
1482 for (unsigned ei = 0; ei < num_edges; ei++) {
1483 MObjectArray edge = surface.edge(ti, li, ei, true, &status);
1484 if (!status) {
1485 status.perror("MFnNurbsSurface::edge");
1486 } else {
1487 unsigned num_segs = edge.length();
1488 for (unsigned si = 0; si < num_segs; si++) {
1489 MObject segment = edge[si];
1490 if (segment.hasFn(MFn::kNurbsCurve)) {
1491 MFnNurbsCurve curve(segment, &status);
1492 if (!status) {
1493 mayaegg_cat.error()
1494 << "Trim curve appears to be a nurbs curve, but isn't.\n";
1495 } else {
1496 // Finally, we have a valid curve!
1497 EggNurbsCurve *egg_curve =
1498 make_trim_curve(curve, name, egg_group, trim_curve_index);
1499 trim_curve_index++;
1500 if (egg_curve != nullptr) {
1501 egg_loop.push_back(egg_curve);
1502 }
1503 }
1504 } else {
1505 mayaegg_cat.error()
1506 << "Trim curve segment is not a nurbs curve.\n";
1507 }
1508 }
1509 }
1510 }
1511 }
1512 }
1513 }
1514 }
1515
1516 // We add the NURBS to the group down here, after all of the vpools for the
1517 // trim curves have been added.
1518 egg_group->add_child(egg_nurbs);
1519
1520 if (shader != nullptr) {
1521 set_shader_attributes(*egg_nurbs, *shader);
1522 }
1523
1524 // Now try to find the skinning information for the surface.
1525 bool got_weights = false;
1526
1527 pvector<EggGroup *> joints;
1528 MFloatArray weights;
1529 if (_animation_convert == AC_model) {
1530 got_weights =
1531 get_vertex_weights(dag_path, surface, joints, weights);
1532 }
1533
1534 if (got_weights && !joints.empty()) {
1535 int num_joints = joints.size();
1536 int num_weights = (int)weights.length();
1537 int num_verts = num_weights / num_joints;
1538 // The number of weights should be an even multiple of verts * joints.
1539 nassertv(num_weights == num_verts * num_joints);
1540
1541 for (i = 0; i < egg_nurbs->get_num_cvs(); i++) {
1542 int ui = egg_nurbs->get_u_index(i) % maya_u_cvs;
1543 int vi = egg_nurbs->get_v_index(i) % maya_v_cvs;
1544
1545 int maya_vi = maya_v_cvs * ui + vi;
1546 nassertv(maya_vi < num_verts);
1547 EggVertex *vert = vpool->get_vertex(i);
1548
1549 for (int ji = 0; ji < num_joints; ++ji) {
1550 PN_stdfloat weight = weights[maya_vi * num_joints + ji];
1551 if (weight != 0.0f) {
1552 EggGroup *joint = joints[ji];
1553 if (joint != nullptr) {
1554 joint->ref_vertex(vert, weight);
1555 }
1556 }
1557 }
1558 }
1559 }
1560}
1561
1562/**
1563 * Converts the indicated Maya NURBS trim curve to a corresponding egg
1564 * structure, and returns it, or NULL if there is a problem.
1565 */
1566EggNurbsCurve *MayaToEggConverter::
1567make_trim_curve(const MFnNurbsCurve &curve, const string &nurbs_name,
1568 EggGroupNode *egg_group, int trim_curve_index) {
1569 if (mayaegg_cat.is_spam()) {
1570 mayaegg_cat.spam()
1571 << "Trim curve:\n";
1572 mayaegg_cat.spam()
1573 << " numCVs: "
1574 << curve.numCVs()
1575 << "\n";
1576 mayaegg_cat.spam()
1577 << " numKnots: "
1578 << curve.numKnots()
1579 << "\n";
1580 mayaegg_cat.spam()
1581 << " numSpans: "
1582 << curve.numSpans()
1583 << "\n";
1584 }
1585
1586 MStatus status;
1587
1588 MPointArray cv_array;
1589 status = curve.getCVs(cv_array, MSpace::kWorld);
1590 if (!status) {
1591 status.perror("MFnNurbsCurve::getCVs");
1592 return nullptr;
1593 }
1594 MDoubleArray knot_array;
1595 status = curve.getKnots(knot_array);
1596 if (!status) {
1597 status.perror("MFnNurbsCurve::getKnots");
1598 return nullptr;
1599 }
1600
1601 /*
1602 MFnNurbsCurve::Form form = curve.form();
1603 */
1604
1605 int degree = curve.degree();
1606 int cvs = curve.numCVs();
1607 int knots = curve.numKnots();
1608
1609 assert(knots == cvs + degree - 1);
1610
1611 string trim_name = "trim" + format_string(trim_curve_index);
1612
1613 string vpool_name = nurbs_name + "." + trim_name;
1614 EggVertexPool *vpool = new EggVertexPool(vpool_name);
1615 egg_group->add_child(vpool);
1616
1617 EggNurbsCurve *egg_curve = new EggNurbsCurve(trim_name);
1618 egg_curve->setup(degree + 1, knots + 2);
1619
1620 int i;
1621
1622 egg_curve->set_knot(0, knot_array[0]);
1623 for (i = 0; i < knots; i++) {
1624 egg_curve->set_knot(i + 1, knot_array[i]);
1625 }
1626 egg_curve->set_knot(knots + 1, knot_array[knots - 1]);
1627
1628 for (i = 0; i < egg_curve->get_num_cvs(); i++) {
1629 double v[4];
1630 MStatus status = cv_array[i].get(v);
1631 if (!status) {
1632 status.perror("MPoint::get");
1633 } else {
1634 EggVertex vert;
1635 vert.set_pos(LPoint3d(v[0], v[1], v[3]));
1636 egg_curve->add_vertex(vpool->create_unique_vertex(vert));
1637 }
1638 }
1639
1640 return egg_curve;
1641}
1642
1643/**
1644 * Converts the indicated Maya NURBS curve (a standalone curve, not a trim
1645 * curve) to a corresponding egg structure and attaches it to the indicated
1646 * egg group.
1647 */
1648void MayaToEggConverter::
1649make_nurbs_curve(const MDagPath &, const MFnNurbsCurve &curve,
1650 EggGroup *egg_group) {
1651 MStatus status;
1652 string name = curve.name().asChar();
1653
1654 if (mayaegg_cat.is_spam()) {
1655 mayaegg_cat.spam()
1656 << " numCVs: "
1657 << curve.numCVs()
1658 << "\n";
1659 mayaegg_cat.spam()
1660 << " numKnots: "
1661 << curve.numKnots()
1662 << "\n";
1663 mayaegg_cat.spam()
1664 << " numSpans: "
1665 << curve.numSpans()
1666 << "\n";
1667 }
1668
1669 MPointArray cv_array;
1670 status = curve.getCVs(cv_array, MSpace::kWorld);
1671 if (!status) {
1672 status.perror("MFnNurbsCurve::getCVs");
1673 return;
1674 }
1675 MDoubleArray knot_array;
1676 status = curve.getKnots(knot_array);
1677 if (!status) {
1678 status.perror("MFnNurbsCurve::getKnots");
1679 return;
1680 }
1681
1682 /*
1683 MFnNurbsCurve::Form form = curve.form();
1684 */
1685
1686 int degree = curve.degree();
1687 int cvs = curve.numCVs();
1688 int knots = curve.numKnots();
1689
1690 assert(knots == cvs + degree - 1);
1691
1692 string vpool_name = name + ".cvs";
1693 EggVertexPool *vpool = new EggVertexPool(vpool_name);
1694 egg_group->add_child(vpool);
1695
1696 EggNurbsCurve *egg_curve = new EggNurbsCurve(name);
1697 egg_group->add_child(egg_curve);
1698 egg_curve->setup(degree + 1, knots + 2);
1699
1700 int i;
1701
1702 egg_curve->set_knot(0, knot_array[0]);
1703 for (i = 0; i < knots; i++) {
1704 egg_curve->set_knot(i + 1, knot_array[i]);
1705 }
1706 egg_curve->set_knot(knots + 1, knot_array[knots - 1]);
1707
1708 LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv();
1709
1710 for (i = 0; i < egg_curve->get_num_cvs(); i++) {
1711 double v[4];
1712 MStatus status = cv_array[i].get(v);
1713 if (!status) {
1714 status.perror("MPoint::get");
1715 } else {
1716 EggVertex vert;
1717 LPoint4d p4d(v[0], v[1], v[2], v[3]);
1718 p4d = p4d * vertex_frame_inv;
1719 vert.set_pos(p4d);
1720 egg_curve->add_vertex(vpool->create_unique_vertex(vert));
1721 }
1722 }
1723 MayaShader *shader = _shaders.find_shader_for_node(curve.object(), _legacy_shader);
1724 if (shader != nullptr) {
1725 set_shader_attributes(*egg_curve, *shader);
1726 }
1727}
1728
1729/**
1730 * given uvsets, round them up or down
1731 */
1732int MayaToEggConverter::
1733round(double value) {
1734 if (value < 0)
1735 return -(floor(-value + 0.5));
1736 // or as an alternate use: return ceil ( value - 0.5);
1737 else
1738 return floor( value + 0.5);
1739}
1740
1741/**
1742 * Converts the indicated Maya polyset to a bunch of EggPolygons and parents
1743 * them to the indicated egg group.
1744 */
1745void MayaToEggConverter::
1746make_polyset(MayaNodeDesc *node_desc, const MDagPath &dag_path,
1747 const MFnMesh &mesh, EggGroup *egg_group,
1748 MayaShader *default_shader) {
1749 MStatus status;
1750 string name = mesh.name().asChar();
1751
1752 MObject mesh_object = mesh.object();
1753 bool maya_double_sided = false;
1754 get_bool_attribute(mesh_object, "doubleSided", maya_double_sided);
1755
1756 if (mayaegg_cat.is_spam()) {
1757 mayaegg_cat.spam()
1758 << " numPolygons: "
1759 << mesh.numPolygons()
1760 << "\n";
1761 mayaegg_cat.spam()
1762 << " numVertices: "
1763 << mesh.numVertices()
1764 << "\n";
1765 }
1766
1767 if (mesh.numPolygons() == 0) {
1768 if (mayaegg_cat.is_debug()) {
1769 mayaegg_cat.debug()
1770 << "Ignoring empty mesh " << name << "\n";
1771 }
1772 return;
1773 }
1774
1775 string vpool_name = name + ".verts";
1776 EggVertexPool *vpool = new EggVertexPool(vpool_name);
1777 egg_group->add_child(vpool);
1778
1779 // One way to convert the mesh would be to first get out all the vertices in
1780 // the mesh and add them into the vpool, then when we traverse the polygons
1781 // we would only have to index them into the vpool according to their Maya
1782 // vertex index.
1783
1784 // Unfortunately, since Maya may store multiple normals andor colors for
1785 // each vertex according to which polygon it is in, that approach won't
1786 // necessarily work. In egg, those split-property vertices have to become
1787 // separate vertices. So instead of adding all the vertices up front, we'll
1788 // start with an empty vpool, and add vertices to it on the fly.
1789
1790 MObject component_obj;
1791 MItMeshPolygon pi(dag_path, component_obj, &status);
1792 if (!status) {
1793 status.perror("MItMeshPolygon constructor");
1794 return;
1795 }
1796
1797 MObjectArray shaders;
1798 MIntArray poly_shader_indices;
1799
1800 status = mesh.getConnectedShaders(dag_path.instanceNumber(),
1801 shaders, poly_shader_indices);
1802 if (!status) {
1803 status.perror("MFnMesh::getConnectedShaders");
1804 }
1805
1806 // We will need to transform all vertices from world coordinate space into
1807 // the vertex space appropriate to this node. Usually, this is the same
1808 // thing as world coordinate space, and this matrix will be identity; but if
1809 // the node is under an instance (particularly, for instance, a billboard)
1810 // then the vertex space will be different from world space.
1811 LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv();
1812
1813 // Save these modeling flags for the check below.
1814 bool egg_vertex_color = false;
1815 bool egg_double_sided = false;
1816 if (egg_group->has_user_data(MayaEggGroupUserData::get_class_type())) {
1817 MayaEggGroupUserData *user_data =
1818 DCAST(MayaEggGroupUserData, egg_group->get_user_data());
1819 egg_vertex_color = user_data->_vertex_color;
1820 egg_double_sided = user_data->_double_sided;
1821 }
1822
1823 bool double_sided = maya_double_sided;
1824 if (!_respect_maya_double_sided) {
1825 // If this flag is false, we respect the maya double-sided settings only
1826 // if the egg "double-sided" flag is also set.
1827 if (!egg_double_sided) {
1828 double_sided = false;
1829 }
1830 }
1831
1832 bool keep_all_uvsets = _keep_all_uvsets || node_desc->has_object_type("keep-all-uvsets");
1833 if (node_desc->has_object_type("keep-all-uvsets")) {
1834 mayaegg_cat.info() << "will keep_all_uvsets" << endl;
1835 }
1836
1837 _shaders.bind_uvsets(mesh.object());
1838
1839 while (!pi.isDone()) {
1840 EggPolygon *egg_poly = new EggPolygon;
1841 egg_group->add_child(egg_poly);
1842
1843 egg_poly->set_bface_flag(double_sided);
1844
1845 // Determine the MayaShader for this particular polygon. There appears to
1846 // be two diverging paths for any Maya node with a Material (MayaShader)
1847 // on it This next bit kicks us out into mayaShader et al. to pull
1848 // textures and everything else.
1849 MayaShader *shader = nullptr;
1850 int index = pi.index();
1851 nassertv(index >= 0 && index < (int)poly_shader_indices.length());
1852 int shader_index = poly_shader_indices[index];
1853
1854 if (shader_index != -1) {
1855 nassertv(shader_index >= 0 && shader_index < (int)shaders.length());
1856 MObject engine = shaders[shader_index];
1857 shader =
1858 _shaders.find_shader_for_shading_engine(engine, _legacy_shader); //head out to the other classes
1859 // does this mean if we didn't find a Maya shader give it a default
1860 // value anyway?
1861 } else if (default_shader != nullptr) {
1862 shader = default_shader;
1863 }
1864
1865 const MayaShaderColorDef *default_color_def = nullptr;
1866
1867 // And apply the shader properties to the polygon.
1868 if (shader != nullptr) {
1869 set_shader_attributes(*egg_poly, *shader, true);
1870 default_color_def = shader->get_color_def();
1871 }
1872
1873 // Should we extract the color from the vertices? Normally, in Maya a
1874 // texture completely replaces the vertex color, so we should ignore the
1875 // vertex color if we have a texture.
1876
1877 // However, this is an inconvenient property of Maya; sometimes we really
1878 // do want both vertex color and texture applied to the same object. To
1879 // allow this, we define the special egg flag "vertex-color", which when
1880 // set indicates that we should respect the vertex color anyway.
1881
1882 // Furthermore, if _always_show_vertex_color is true, we pretend that the
1883 // "vertex-color" flag is always set.
1884 bool ignore_vertex_color = false;
1885 if ( default_color_def != nullptr) {
1886 ignore_vertex_color = default_color_def->_has_texture && !(egg_vertex_color || _always_show_vertex_color);
1887 }
1888
1889 LColor poly_color(1.0f, 1.0f, 1.0f, 1.0f);
1890 if (!ignore_vertex_color) {
1891 // If we're respecting the vertex color, then remove the color
1892 // specification from the polygon (so we can apply it to the vertices).
1893 poly_color = egg_poly->get_color();
1894 egg_poly->clear_color();
1895 }
1896
1897 // Get the vertices for the polygon.
1898 long num_verts = pi.polygonVertexCount();
1899 long i;
1900 LPoint3d centroid(0.0, 0.0, 0.0);
1901
1902 if (default_color_def != nullptr && default_color_def->has_projection()) {
1903 // If the shader has a projection, we may need to compute the polygon's
1904 // centroid to avoid seams at the edges.
1905 for (i = 0; i < num_verts; i++) {
1906 MPoint p = pi.point(i, MSpace::kWorld);
1907 LPoint3d p3d(p[0], p[1], p[2]);
1908 p3d = p3d * vertex_frame_inv;
1909 centroid += p3d;
1910 }
1911 centroid /= (double)num_verts;
1912 }
1913 for (i = 0; i < num_verts; i++) {
1914 EggVertex vert;
1915
1916 MPoint p = pi.point(i, MSpace::kWorld);
1917 LPoint3d p3d(p[0] / p[3], p[1] / p[3], p[2] / p[3]);
1918 p3d = p3d * vertex_frame_inv;
1919 vert.set_pos(p3d);
1920
1921 MVector n;
1922 status = pi.getNormal(i, n, MSpace::kWorld);
1923 if (!status) {
1924 status.perror("MItMeshPolygon::getNormal");
1925 } else {
1926 LNormald n3d(n[0], n[1], n[2]);
1927 n3d = n3d * vertex_frame_inv;
1928 vert.set_normal(n3d);
1929 }
1930
1931 // Go thru all the texture references for this primitive and set uvs
1932 if (mayaegg_cat.is_spam()) {
1933 if (shader != nullptr) {
1934 mayaegg_cat.spam() << "shader->_color.size is " << shader->_color.size() << endl;
1935 }
1936 mayaegg_cat.spam() << "primitive->tref.size is " << egg_poly->get_num_textures() << endl;
1937 }
1938 for (size_t ti=0; ti< _shaders._uvset_names.size(); ++ti) {
1939 // get the eggTexture pointer
1940 string uvset_name(_shaders._uvset_names[ti]);
1941 string panda_uvset_name = uvset_name;
1942 if (panda_uvset_name == "map1") {
1943 panda_uvset_name = "default";
1944 }
1945 if (mayaegg_cat.is_spam()) {
1946 mayaegg_cat.spam() << "--uvset_name :" << uvset_name << endl;
1947 }
1948
1949 // get the shader color def that matches this EggTexture Asad:
1950 // optimizing uvset: to discard unused uvsets. This for loop figures
1951 // out which ones are unused.
1952
1953 bool keep_uv = keep_all_uvsets;
1954 bool project_uv = false;
1955 LTexCoordd uv_projection;
1956
1957 if (shader != nullptr) {
1958 for (size_t tj = 0; tj < shader->_all_maps.size(); ++tj) {
1959 MayaShaderColorDef *def = shader->_all_maps[tj];
1960 if (def->_uvset_name == uvset_name) {
1961 if (mayaegg_cat.is_spam()) {
1962 mayaegg_cat.spam() << "matched colordef idx: " << tj << endl;
1963 }
1964 keep_uv = true;
1965 if (def->has_projection()) {
1966 project_uv = true;
1967 uv_projection = def->project_uv(p3d, centroid);
1968 }
1969 break;
1970 }
1971 }
1972 }
1973
1974 // if uvset is not used don't add it to the vertex
1975 if (!keep_uv) {
1976 if (mayaegg_cat.is_spam()) {
1977 mayaegg_cat.spam() << "discarding unused uvset " << uvset_name << endl;
1978 }
1979 continue;
1980 }
1981
1982 if (project_uv) {
1983 // If the shader has a projection, use it instead of the polygon's
1984 // built-in UV's.
1985 vert.set_uv(panda_uvset_name, uv_projection);
1986 } else {
1987 // Get the UV's from the polygon.
1988 float2 uvs;
1989 MString uv_mstring(uvset_name.c_str());
1990 if (pi.hasUVs(uv_mstring, &status)) {
1991 status = pi.getUV(i, uvs, &uv_mstring);
1992 if (!status) {
1993 status.perror("MItMeshPolygon::getUV");
1994 } else {
1995 if (_round_uvs) {
1996 if (uvs[0] > 1.0 || uvs[0] < -1.0) {
1997 // apply upto 11000th precision, but round up
1998 uvs[0] = (long)(uvs[0]*1000);
1999 mayaegg_cat.debug() << "before rounding uvs[0]: " << uvs[0] << endl;
2000 uvs[0] = (double)(round((double)uvs[0]/10.0)*10.0)/1000.0;
2001 mayaegg_cat.debug() << "after rounding uvs[0]: " << uvs[0] << endl;
2002 }
2003 if (uvs[1] > 1.0 || uvs[1] < -1.0) {
2004 uvs[1] = (long)(uvs[1]*1000);
2005 mayaegg_cat.debug() << "before rounding uvs[1]: " << uvs[1] << endl;
2006 uvs[1] = (double)(round((double)uvs[1]/10.0)*10.0)/1000.0;
2007 mayaegg_cat.debug() << "after rounding uvs[1]: " << uvs[1] << endl;
2008 }
2009 }
2010 vert.set_uv(panda_uvset_name, LTexCoordd(uvs[0], uvs[1]));
2011 }
2012 }
2013 }
2014 }
2015
2016 if (!ignore_vertex_color) {
2017 if (mayaegg_cat.is_spam()) {
2018 mayaegg_cat.spam() << "poly_color = " << poly_color << endl;
2019 }
2020 set_vertex_color(vert,pi,i,shader,poly_color);
2021 }
2022
2023 vert.set_external_index(pi.vertexIndex(i, &status));
2024 vert.set_external_index2(pi.normalIndex(i, &status));
2025
2026 egg_poly->add_vertex(vpool->create_unique_vertex(vert));
2027 }
2028
2029 // Also get the face normal for the polygon.
2030 LNormald face_normal;
2031 bool got_face_normal = false;
2032
2033 MVector n;
2034 status = pi.getNormal(n, MSpace::kWorld);
2035 if (!status) {
2036 status.perror("MItMeshPolygon::getNormal face");
2037 } else {
2038 face_normal.set(n[0], n[1], n[2]);
2039 face_normal = face_normal * vertex_frame_inv;
2040 got_face_normal = true;
2041 egg_poly->set_normal(face_normal);
2042 }
2043
2044 // Now, check that the vertex ordering is consistent with the direction of
2045 // the normals. If not, reverse the vertex ordering (since we have seen
2046 // cases where Maya sets this in contradiction to its normals).
2047 LNormald order_normal;
2048 if (got_face_normal && egg_poly->calculate_normal(order_normal)) {
2049 if (order_normal.dot(face_normal) < 0.0) {
2050 egg_poly->reverse_vertex_ordering();
2051 if (mayaegg_cat.is_debug()) {
2052 mayaegg_cat.debug()
2053 << "reversing polygon\n";
2054 }
2055 }
2056 }
2057
2058 pi.next();
2059 }
2060 if (mayaegg_cat.is_spam()) {
2061 mayaegg_cat.spam() << "done traversing polys" << endl;
2062 }
2063
2064 // Now that we've added all the polygons (and created all the vertices), go
2065 // back through the vertex pool and set up the appropriate joint membership
2066 // for each of the vertices.
2067 bool got_weights = false;
2068
2069 pvector<EggGroup *> joints;
2070 MFloatArray weights;
2071 if (_animation_convert == AC_model) {
2072 got_weights =
2073 get_vertex_weights(dag_path, mesh, joints, weights);
2074 }
2075
2076 if (got_weights && !joints.empty()) {
2077 int num_joints = joints.size();
2078 int num_weights = (int)weights.length();
2079 int num_verts = num_weights / num_joints;
2080 // The number of weights should be an even multiple of verts * joints.
2081 nassertv(num_weights == num_verts * num_joints);
2082
2084 for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
2085 EggVertex *vert = (*vi);
2086 int maya_vi = vert->get_external_index();
2087 nassertv(maya_vi >= 0 && maya_vi < num_verts);
2088
2089 for (int ji = 0; ji < num_joints; ++ji) {
2090 PN_stdfloat weight = weights[maya_vi * num_joints + ji];
2091 if (weight != 0.0f) {
2092 EggGroup *joint = joints[ji];
2093 if (joint != nullptr) {
2094 joint->ref_vertex(vert, weight);
2095 }
2096 }
2097 }
2098 }
2099 }
2100
2101
2102 // We also need to compute the vertex morphs for the polyset, based on
2103 // whatever blend shapes may be present. This is similar to the code in
2104 // make_nurbs_surface(), except that since we don't have a one-to-one
2105 // relationship of egg vertices to Maya vertices, we have to get the morphs
2106 // down here, after we have added all of the egg vertices.
2107
2108 if (_animation_convert == AC_model) {
2109 int num_orig_mesh_verts = mesh.numVertices();
2110
2111 int num_sliders = node_desc->get_num_blend_descs();
2112 for (int i = 0; i < num_sliders; i++) {
2113 MayaBlendDesc *blend_desc = node_desc->get_blend_desc(i);
2114
2115 // Temporarily push the slider up to 1.0 so we can see what the surface
2116 // looks like at that value.
2117 blend_desc->set_slider(1.0);
2118
2119 // We have to get the mesh object from the dag again after fiddling with
2120 // the slider.
2121 MFnMesh blend_mesh(dag_path, &status);
2122 if (!status) {
2123 mayaegg_cat.warning()
2124 << name << " no longer has a mesh after applying "
2125 << blend_desc->get_name() << "\n";
2126
2127 } else {
2128 if (blend_mesh.numVertices() != num_orig_mesh_verts) {
2129 mayaegg_cat.warning()
2130 << "Ignoring " << blend_desc->get_name() << " for "
2131 << name << "; blend shape has " << blend_mesh.numVertices()
2132 << " vertices while original shape has "
2133 << num_orig_mesh_verts << ".\n";
2134
2135 } else {
2136 MPointArray verts;
2137 status = blend_mesh.getPoints(verts, MSpace::kWorld);
2138 if (!status) {
2139 status.perror("MFnMesh::getPoints");
2140 } else {
2141 int num_verts = (int)verts.length();
2143 for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
2144 EggVertex *vert = (*vi);
2145 int maya_vi = vert->get_external_index();
2146 nassertv(maya_vi >= 0 && maya_vi < num_verts);
2147
2148 const MPoint &m = verts[maya_vi];
2149 LPoint3d m3d(m[0] / m[3], m[1] / m[3], m[2] / m[3]);
2150 m3d = m3d * vertex_frame_inv;
2151
2152 LVector3d delta = m3d - vert->get_pos3();
2153 if (!delta.almost_equal(LVector3d::zero())) {
2154 EggMorphVertex dxyz(blend_desc->get_name(), delta);
2155 vert->_dxyzs.insert(dxyz);
2156 }
2157 }
2158 }
2159
2160 MFloatVectorArray norms;
2161 status = blend_mesh.getNormals(norms, MSpace::kWorld);
2162 if (!status) {
2163 status.perror("MFnMesh::getNormals");
2164 } else {
2165 int num_norms = (int)norms.length();
2167 for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
2168 EggVertex *vert = (*vi);
2169 int maya_vi = vert->get_external_index2();
2170 nassertv(maya_vi >= 0 && maya_vi < num_norms);
2171
2172 const MFloatVector &m = norms[maya_vi];
2173 LVector3d m3d(m[0], m[1], m[2]);
2174 m3d = m3d * vertex_frame_inv;
2175
2176 LNormald delta = m3d - vert->get_normal();
2177 if (!delta.almost_equal(LVector3d::zero())) {
2178 EggMorphNormal dnormal(blend_desc->get_name(), delta);
2179 vert->_dnormals.insert(dnormal);
2180 }
2181 }
2182 }
2183 }
2184 }
2185
2186 blend_desc->set_slider(0.0);
2187 }
2188 }
2189}
2190
2191/**
2192 * Locators are used in Maya to indicate a particular position in space to the
2193 * user or the modeler. We represent that in egg with an ordinary Group node,
2194 * which we transform by the locator's position, so that the indicated point
2195 * becomes the origin at this node and below.
2196 */
2197void MayaToEggConverter::
2198make_locator(const MDagPath &dag_path, const MFnDagNode &dag_node,
2199 EggGroup *egg_group) {
2200 MStatus status;
2201
2202 unsigned int num_children = dag_node.childCount();
2203 MObject locator;
2204 bool found_locator = false;
2205 for (unsigned int ci = 0; ci < num_children && !found_locator; ci++) {
2206 locator = dag_node.child(ci);
2207 found_locator = (locator.apiType() == MFn::kLocator);
2208 }
2209
2210 if (!found_locator) {
2211 mayaegg_cat.error()
2212 << "Couldn't find locator within locator node "
2213 << dag_path.fullPathName().asChar() << "\n";
2214 return;
2215 }
2216
2217 LPoint3d p3d;
2218 if (!get_vec3d_attribute(locator, "localPosition", p3d)) {
2219 mayaegg_cat.error()
2220 << "Couldn't get position of locator "
2221 << dag_path.fullPathName().asChar() << "\n";
2222 return;
2223 }
2224
2225 // We need to convert the position to world coordinates. For some reason,
2226 // Maya can only tell it to us in local coordinates.
2227 MMatrix mat = dag_path.inclusiveMatrix(&status);
2228 if (!status) {
2229 status.perror("Can't get coordinate space for locator");
2230 return;
2231 }
2232 LMatrix4d n2w(mat[0][0], mat[0][1], mat[0][2], mat[0][3],
2233 mat[1][0], mat[1][1], mat[1][2], mat[1][3],
2234 mat[2][0], mat[2][1], mat[2][2], mat[2][3],
2235 mat[3][0], mat[3][1], mat[3][2], mat[3][3]);
2236 p3d = p3d * n2w;
2237
2238 // Now convert the locator point into the group's space.
2239 p3d = p3d * egg_group->get_node_frame_inv();
2240
2241 egg_group->add_translate3d(p3d);
2242}
2243
2244/**
2245 * Locators are used in Maya to indicate a particular position in space to the
2246 * user or the modeler. We represent that in egg with an ordinary Group node,
2247 * which we transform by the locator's position, so that the indicated point
2248 * becomes the origin at this node and below.
2249 */
2250void MayaToEggConverter::
2251make_camera_locator(const MDagPath &dag_path, const MFnDagNode &dag_node,
2252 EggGroup *egg_group) {
2253 MStatus status;
2254
2255 unsigned int num_children = dag_node.childCount();
2256 MObject locator;
2257 bool found_camera = false;
2258 for (unsigned int ci = 0; ci < num_children && !found_camera; ci++) {
2259 locator = dag_node.child(ci);
2260 found_camera = (locator.apiType() == MFn::kCamera);
2261 }
2262
2263 if (!found_camera) {
2264 mayaegg_cat.error()
2265 << "Couldn't find camera"
2266 << dag_path.fullPathName().asChar() << "\n";
2267 return;
2268 }
2269 MFnCamera camera (dag_path, &status);
2270 if ( !status ) {
2271 status.perror("MFnCamera constructor");
2272 return;
2273 }
2274 MPoint eyePoint = camera.eyePoint(MSpace::kWorld);
2275 LPoint3d p3d (eyePoint.x, eyePoint.y, eyePoint.z);
2276
2277 // Now convert the locator point into the group's space.
2278 p3d = p3d * egg_group->get_node_frame_inv();
2279
2280 egg_group->add_translate3d(p3d);
2281}
2282
2283
2284/**
2285 * Locators are used in Maya to indicate a particular position in space to the
2286 * user or the modeler. We represent that in egg with an ordinary Group node,
2287 * which we transform by the locator's position, so that the indicated point
2288 * becomes the origin at this node and below.
2289 */
2290void MayaToEggConverter::
2291make_light_locator(const MDagPath &dag_path, const MFnDagNode &dag_node,
2292 EggGroup *egg_group) {
2293 MStatus status;
2294
2295 unsigned int num_children = dag_node.childCount();
2296 MObject locator;
2297 bool found_alight = false;
2298 bool found_dlight = false;
2299 bool found_plight = false;
2300 for (unsigned int ci = 0; ci < num_children && !found_alight && !found_dlight && !found_plight; ci++) {
2301 locator = dag_node.child(ci);
2302 found_alight = (locator.apiType() == MFn::kAmbientLight);
2303 found_dlight = (locator.apiType() == MFn::kDirectionalLight);
2304 found_plight = (locator.apiType() == MFn::kPointLight);
2305 }
2306
2307 if (!found_alight && !found_dlight && !found_plight) {
2308 mayaegg_cat.error()
2309 << "Couldn't find light within locator node "
2310 << dag_path.fullPathName().asChar() << "\n";
2311 return;
2312 }
2313
2314 LPoint3d p3d;
2315
2316 // We need to convert the position to world coordinates. For some reason,
2317 // Maya can only tell it to us in local coordinates.
2318 MMatrix mat = dag_path.inclusiveMatrix(&status);
2319 if (!status) {
2320 status.perror("Can't get coordinate space for light");
2321 return;
2322 }
2323 LMatrix4d n2w(mat[0][0], mat[0][1], mat[0][2], mat[0][3],
2324 mat[1][0], mat[1][1], mat[1][2], mat[1][3],
2325 mat[2][0], mat[2][1], mat[2][2], mat[2][3],
2326 mat[3][0], mat[3][1], mat[3][2], mat[3][3]);
2327 p3d = p3d * n2w;
2328
2329 // Now convert the locator point into the group's space.
2330 p3d = p3d * egg_group->get_node_frame_inv();
2331
2332 egg_group->add_translate3d(p3d);
2333}
2334
2335
2336/**
2337 *
2338 */
2339bool MayaToEggConverter::
2340get_vertex_weights(const MDagPath &dag_path, const MFnMesh &mesh,
2341 pvector<EggGroup *> &joints, MFloatArray &weights) {
2342 MStatus status;
2343
2344 // Since we are working with a mesh the input attribute that creates the
2345 // mesh is named "inMesh"
2346 MObject attr = mesh.attribute("inMesh");
2347
2348 // Create the plug to the "inMesh" attribute then use the DG iterator to
2349 // walk through the DG, at the node level.
2350 MPlug history(mesh.object(), attr);
2351 MItDependencyGraph it(history, MFn::kDependencyNode,
2352 MItDependencyGraph::kUpstream,
2353 MItDependencyGraph::kDepthFirst,
2354 MItDependencyGraph::kNodeLevel);
2355
2356 while (!it.isDone()) {
2357 // We will walk along the node level of the DG until we spot a skinCluster
2358 // node.
2359 MObject c_node = it.thisNode();
2360 if (c_node.hasFn(MFn::kSkinClusterFilter)) {
2361 // We've found the cluster handle. Try to get the weight data.
2362 MFnSkinCluster cluster(c_node, &status);
2363 if (!status) {
2364 status.perror("MFnSkinCluster constructor");
2365 return false;
2366 }
2367
2368 // Get the set of objects that influence the vertices of this mesh.
2369 // Hopefully these will all be joints.
2370 MDagPathArray influence_objects;
2371 cluster.influenceObjects(influence_objects, &status);
2372 if (!status) {
2373 status.perror("MFnSkinCluster::influenceObjects");
2374
2375 } else {
2376 // Fill up the vector with the corresponding table of egg groups for
2377 // each joint.
2378 joints.clear();
2379 for (unsigned oi = 0; oi < influence_objects.length(); oi++) {
2380 MDagPath joint_dag_path = influence_objects[oi];
2381 MayaNodeDesc *joint_node_desc = _tree.build_node(joint_dag_path);
2382 EggGroup *joint = _tree.get_egg_group(joint_node_desc);
2383 joints.push_back(joint);
2384 }
2385
2386 // Now use a component object to retrieve all of the weight data in
2387 // one API call.
2388 MFnSingleIndexedComponent sic;
2389 MObject sic_object = sic.create(MFn::kMeshVertComponent);
2390 sic.setCompleteData(mesh.numVertices());
2391 unsigned influence_count;
2392
2393 status = cluster.getWeights(dag_path, sic_object,
2394 weights, influence_count);
2395 if (!status) {
2396 status.perror("MFnSkinCluster::getWeights");
2397 } else {
2398 if (influence_count != influence_objects.length()) {
2399 mayaegg_cat.error()
2400 << "MFnSkinCluster::influenceObjects() returns "
2401 << influence_objects.length()
2402 << " objects, but MFnSkinCluster::getWeights() reports "
2403 << influence_count << " objects.\n";
2404
2405 } else {
2406 // We've got the weights and the set of objects. That's all we
2407 // need.
2408 return true;
2409 }
2410 }
2411 }
2412 } else if (c_node.hasFn(MFn::kWeightGeometryFilt)) {
2413 // We've found the joint cluster handle. (rigid Binding)
2414 MFnWeightGeometryFilter cluster(c_node, &status);
2415 if (!status) {
2416 status.perror("MFnWeightGeometryFilter constructor");
2417 return false;
2418 }
2419
2420 MPlug matrix_plug = cluster.findPlug("matrix");
2421 if (!matrix_plug.isNull()) {
2422 MPlugArray matrix_pa;
2423 matrix_plug.connectedTo(matrix_pa, true, false, &status);
2424 if (!status) {
2425 status.perror("Can't find connected Joint");
2426 } else {
2427 MObject jointObj = matrix_pa[0].node();
2428 MFnIkJoint jointFn(jointObj, &status);
2429 if (!status) {
2430 status.perror("Can't find connected JointDag");
2431 } else {
2432 joints.clear();
2433 MDagPath joint_dag_path = MDagPath();
2434 status = jointFn.getPath(joint_dag_path);
2435 if (!status) {
2436 status.perror("MFnIkJoint::dagPath");
2437 } else {
2438 MayaNodeDesc *joint_node_desc = _tree.build_node(joint_dag_path);
2439 EggGroup *joint = _tree.get_egg_group(joint_node_desc);
2440 joints.push_back(joint);
2441
2442 // Now use a component object to retrieve all of the weight data
2443 // in one API call.
2444 MFnSingleIndexedComponent sic;
2445 MObject sic_object = sic.create(MFn::kMeshVertComponent);
2446 sic.setCompleteData(mesh.numVertices());
2447
2448 status = cluster.getWeights(dag_path, sic_object,
2449 weights);
2450 if (!status) {
2451 status.perror("MFnWeightGeometryFilter::getWeights");
2452 } else {
2453 // We've got the weights and the set of objects. That's all
2454 // we need.
2455 return true;
2456 }
2457 }
2458 }
2459 }
2460 }
2461 }
2462
2463 it.next();
2464 }
2465
2466 // The mesh was not soft-skinned.
2467 return false;
2468}
2469
2470/**
2471 * As above, for a NURBS surface instead of a polygon mesh.
2472 */
2473bool MayaToEggConverter::
2474get_vertex_weights(const MDagPath &dag_path, const MFnNurbsSurface &surface,
2475 pvector<EggGroup *> &joints, MFloatArray &weights) {
2476 MStatus status;
2477
2478 // Since we are working with a NURBS surface the input attribute that
2479 // creates the surface is named "create"
2480 MObject attr = surface.attribute("create");
2481
2482 // Create the plug to the "create" attribute then use the DG iterator to
2483 // walk through the DG, at the node level.
2484 MPlug history(surface.object(), attr);
2485 MItDependencyGraph it(history, MFn::kDependencyNode,
2486 MItDependencyGraph::kUpstream,
2487 MItDependencyGraph::kDepthFirst,
2488 MItDependencyGraph::kNodeLevel);
2489
2490 while (!it.isDone()) {
2491 // We will walk along the node level of the DG until we spot a skinCluster
2492 // node.
2493 MObject c_node = it.thisNode();
2494 if (c_node.hasFn(MFn::kSkinClusterFilter)) {
2495 // We've found the cluster handle. Try to get the weight data.
2496 MFnSkinCluster cluster(c_node, &status);
2497 if (!status) {
2498 status.perror("MFnSkinCluster constructor");
2499 return false;
2500 }
2501
2502 // Get the set of objects that influence the vertices of this surface.
2503 // Hopefully these will all be joints.
2504 MDagPathArray influence_objects;
2505 cluster.influenceObjects(influence_objects, &status);
2506 if (!status) {
2507 status.perror("MFnSkinCluster::influenceObjects");
2508
2509 } else {
2510 // Fill up the vector with the corresponding table of egg groups for
2511 // each joint.
2512 joints.clear();
2513 for (unsigned oi = 0; oi < influence_objects.length(); oi++) {
2514 MDagPath joint_dag_path = influence_objects[oi];
2515 MayaNodeDesc *joint_node_desc = _tree.build_node(joint_dag_path);
2516 EggGroup *joint = _tree.get_egg_group(joint_node_desc);
2517 joints.push_back(joint);
2518 }
2519
2520 // Now use a component object to retrieve all of the weight data in
2521 // one API call.
2522 MFnDoubleIndexedComponent dic;
2523 MObject dic_object = dic.create(MFn::kSurfaceCVComponent);
2524 dic.setCompleteData(surface.numCVsInU(), surface.numCVsInV());
2525 unsigned influence_count;
2526
2527 status = cluster.getWeights(dag_path, dic_object,
2528 weights, influence_count);
2529 if (!status) {
2530 status.perror("MFnSkinCluster::getWeights");
2531 } else {
2532 if (influence_count != influence_objects.length()) {
2533 mayaegg_cat.error()
2534 << "MFnSkinCluster::influenceObjects() returns "
2535 << influence_objects.length()
2536 << " objects, but MFnSkinCluster::getWeights() reports "
2537 << influence_count << " objects.\n";
2538
2539 } else {
2540 // We've got the weights and the set of objects. That's all we
2541 // need.
2542 return true;
2543 }
2544 }
2545 }
2546 }
2547
2548 it.next();
2549 }
2550
2551 // The surface was not soft-skinned.
2552 return false;
2553}
2554
2555/**
2556 * Applies the known shader attributes to the indicated egg primitive. Note:
2557 * For multi-textures, Maya lists the top most texture in slot 0. But Panda
2558 * puts the base texture at slot 0. Hence I parse the list of textures from
2559 * last to first.
2560 */
2561void MayaToEggConverter::
2562set_shader_attributes(EggPrimitive &primitive, const MayaShader &shader,
2563 bool mesh) {
2564 if (shader._legacy_mode) {
2565 set_shader_legacy(primitive, shader, mesh);
2566 } else {
2567 set_shader_modern(primitive, shader, mesh);
2568 }
2569}
2570
2571/**
2572 * The modern implementation of set_shader_attributes.
2573 *
2574 * In the modern codepath, the MayaShader is a direct, literal representation
2575 * of a list of EggTextures. All this exporter has to do is translate the
2576 * list without interpretation. All the complex interpretation is handled
2577 * elsewhere, in the MayaShader module.
2578 */
2579void MayaToEggConverter::
2580set_shader_modern(EggPrimitive &primitive, const MayaShader &shader,
2581 bool mesh) {
2582
2583 for (size_t idx=0; idx < shader._all_maps.size(); idx++) {
2584 MayaShaderColorDef *def = shader._all_maps[idx];
2585 if ((def->_is_alpha)&&(def->_opposite != 0)) {
2586 // This texture represents an alpha-filename. It doesn't get its own
2587 // <Texture>
2588 continue;
2589 }
2590
2591 EggTexture tex(shader.get_name(), "");
2592 tex.set_format(def->_is_alpha ? EggTexture::F_alpha : EggTexture::F_rgb);
2593 apply_texture_filename(tex, *def);
2594 if (def->_opposite) {
2595 apply_texture_alpha_filename(tex, *def);
2596 }
2597 apply_texture_uvprops(tex, *def);
2598 apply_texture_blendtype(tex, *def);
2599 tex.set_uv_name(def->get_panda_uvset_name());
2600
2601 EggTexture *new_tex =
2602 _textures.create_unique_texture(tex, ~0);
2603 primitive.add_texture(new_tex);
2604 }
2605}
2606
2607/**
2608 * The legacy implementation of set_shader_attributes. The old behavior of
2609 * the exporter is just plain weird. It seems to be a result of an
2610 * inexperienced coder who made some core mistakes, and then patched them up
2611 * with kludges. It seems to produce plausible results in certain specific
2612 * cases, but overall, it doesn't make any sense. Unfortunately, this weird
2613 * behavior cannot be discarded - vast numbers of 3D models have been created
2614 * that rely on this behavior. The solution is to compartmentalize the
2615 * weirdness. The legacy codepath, when activated, implements the old weird
2616 * behavior. A brand-new codepath that shares almost nothing with the legacy
2617 * codepath implements a much more straightforward behavior.
2618 */
2619void MayaToEggConverter::
2620set_shader_legacy(EggPrimitive &primitive, const MayaShader &shader,
2621 bool mesh) {
2622
2623 // determine if the base texture or any of the top texture need to be rgb
2624 // only
2625 MayaShaderColorDef *color_def = nullptr;
2626 bool is_rgb = false;
2627 bool is_decal = false;
2628 bool is_interpolate = false;
2629 int i;
2630 // last shader is the base so lets skip it
2631 for (i=0; i<(int)shader._color.size()-1; ++i) {
2632 color_def = shader.get_color_def(i);
2633 if (color_def->_has_texture) {
2634 if ((EggTexture::EnvType)color_def->_interpolate) {
2635 is_interpolate = true;
2636 }
2637 else if ((EggTexture::EnvType)color_def->_blend_type == EggTexture::ET_modulate) {
2638 // Maya's multiply is slightly different than panda's. Unless,
2639 // _keep_alpha is set, we are dropping the alpha.
2640 if (!color_def->_keep_alpha)
2641 is_rgb = true; // modulate forces the alpha to be ignored
2642 }
2643 else if ((EggTexture::EnvType)color_def->_blend_type == EggTexture::ET_decal) {
2644 is_decal = true;
2645 }
2646 }
2647 }
2648
2649 // we don't want an extra light stage for interpolate mode, it takes care of
2650 // automatically
2651 if (is_interpolate)
2652 is_decal = false;
2653
2654 // new decal mode needs an extra dummy layers of textureStage
2655 EggTexture *dummy_tex = nullptr;
2656 string dummy_uvset_name;
2657
2658 // In Maya, a polygon is either textured or colored. The texture, if
2659 // present, replaces the color. Also now there could be multiple textures
2660 const MayaShaderColorDef &trans_def = shader._transparency;
2661 for (i=shader._color.size()-1; i>=0; --i) {
2662 color_def = shader.get_color_def(i);
2663 if (mayaegg_cat.is_spam()) {
2664 mayaegg_cat.spam() << "slot " << i << ":got color_def: " << color_def << endl;
2665 }
2666 if (color_def->_has_texture || trans_def._has_texture) {
2667 EggTexture tex(shader.get_name(), "");
2668 if (mayaegg_cat.is_spam()) {
2669 mayaegg_cat.spam() << "got shader name:" << shader.get_name() << endl;
2670 mayaegg_cat.spam() << "ssa:texture name[" << i << "]: " << color_def->_texture_name << endl;
2671 }
2672
2673 string uvset_name = _shaders.find_uv_link(color_def->_texture_name);
2674 if (mayaegg_cat.is_spam()) {
2675 mayaegg_cat.spam() << "ssa:corresponding uvset name is " << uvset_name << endl;
2676 }
2677
2678 if (color_def->_has_texture) {
2679 // If we have a texture on color, apply it as the filename. if
2680 // (mayaegg_cat.is_debug()) { mayaegg_cat.debug() << "ssa:got texture
2681 // name" << color_def->_texture_filename << endl; }
2682 Filename filename = Filename::from_os_specific(color_def->_texture_filename);
2683 Filename fullpath, outpath;
2684 _path_replace->full_convert_path(filename, get_model_path(), fullpath, outpath);
2685 tex.set_filename(outpath);
2686 tex.set_fullpath(fullpath);
2687 apply_texture_uvprops(tex, *color_def);
2688
2689 // If we also have a texture on transparency, apply it as the alpha
2690 // filename.
2691 if (trans_def._has_texture) {
2692 if (color_def->_wrap_u != trans_def._wrap_u ||
2693 color_def->_wrap_u != trans_def._wrap_u) {
2694 mayaegg_cat.warning()
2695 << "Shader " << shader.get_name()
2696 << " has contradictory wrap modes on color and texture.\n";
2697 }
2698
2699 if (!compare_texture_uvprops(tex, trans_def)) {
2700 // Only report each broken shader once.
2701 static pset<string> bad_shaders;
2702 if (bad_shaders.insert(shader.get_name()).second) {
2703 mayaegg_cat.error()
2704 << "Color and transparency texture properties differ on shader "
2705 << shader.get_name() << "\n";
2706 }
2707 }
2708 // tex.set_format(EggTexture::F_rgba);
2709
2710 // We should try to be smarter about whether the transparency value
2711 // is connected to the texture's alpha channel or to its grayscale
2712 // channel. However, I'm not sure how to detect this at the moment;
2713 // rather than spending days trying to figure out, for now I'll just
2714 // assume that if the same texture image is used for both color and
2715 // transparency, then the artist meant to use the alpha channel for
2716 // transparency.
2717 if (trans_def._texture_filename == color_def->_texture_filename) {
2718 // That means that we don't need to do anything special: use all
2719 // the channels of the texture.
2720
2721 } else {
2722 // Otherwise, pull the alpha channel from the other image file.
2723 // Ideally, we should figure out which channel from the other
2724 // image supplies alpha (and specify this via
2725 // set_alpha_file_channel()), but for now we assume it comes from
2726 // the grayscale data.
2727 filename = Filename::from_os_specific(trans_def._texture_filename);
2728 _path_replace->full_convert_path(filename, get_model_path(),
2729 fullpath, outpath);
2730 tex.set_alpha_filename(outpath);
2731 tex.set_alpha_fullpath(fullpath);
2732 }
2733 } else {
2734 // If there is no transparency texture specified, we don't have any
2735 // transparency, so tell the egg format to ignore any alpha channel
2736 // that might be on the color texture.
2737 // tex.set_format(EggTexture::F_rgb);
2738 }
2739
2740 if (shader._color.size() > 1) {
2741 // if multi-textured, first texture in maya is on top, so last
2742 // shader on the list is the base one, which should always pick up
2743 // the alpha from the texture file. But the top textures may have
2744 // to strip the alpha
2745 if ((size_t)i != shader._color.size() - 1) {
2746 if (!i && is_interpolate) {
2747 // this is the grass path mode where alpha on this texture
2748 // determines whether to show layer1 or layer2. Since by now
2749 // other layers are set lets change those to get this effect
2750 tex.set_combine_mode(EggTexture::CC_rgb, EggTexture::CM_interpolate);
2751 tex.set_combine_source(EggTexture::CC_rgb, 0, EggTexture::CS_previous);
2752 tex.set_combine_operand(EggTexture::CC_rgb, 0, EggTexture::CO_src_color);
2753 tex.set_combine_source(EggTexture::CC_rgb, 1, EggTexture::CS_last_saved_result);
2754 tex.set_combine_operand(EggTexture::CC_rgb, 1, EggTexture::CO_src_color);
2755 tex.set_combine_source(EggTexture::CC_rgb, 2, EggTexture::CS_texture);
2756 tex.set_combine_operand(EggTexture::CC_rgb, 2, EggTexture::CO_src_alpha);
2757
2758 tex.set_combine_mode(EggTexture::CC_alpha, EggTexture::CM_interpolate);
2759 tex.set_combine_source(EggTexture::CC_alpha, 0, EggTexture::CS_previous);
2760 tex.set_combine_operand(EggTexture::CC_alpha, 0, EggTexture::CO_src_alpha);
2761 tex.set_combine_source(EggTexture::CC_alpha, 1, EggTexture::CS_last_saved_result);
2762 tex.set_combine_operand(EggTexture::CC_alpha, 1, EggTexture::CO_src_alpha);
2763 tex.set_combine_source(EggTexture::CC_alpha, 2, EggTexture::CS_texture);
2764 tex.set_combine_operand(EggTexture::CC_alpha, 2, EggTexture::CO_src_alpha);
2765 }
2766 else {
2767 if (is_interpolate) {
2768 tex.set_combine_mode(EggTexture::CC_rgb, EggTexture::CM_modulate);
2769 tex.set_combine_source(EggTexture::CC_rgb, 0, EggTexture::CS_primary_color);
2770 tex.set_combine_operand(EggTexture::CC_rgb, 0, EggTexture::CO_src_color);
2771 tex.set_combine_source(EggTexture::CC_rgb, 1, EggTexture::CS_texture);
2772 tex.set_combine_operand(EggTexture::CC_rgb, 1, EggTexture::CO_src_color);
2773 }
2774 else {
2775 tex.set_env_type((EggTexture::EnvType)color_def->_blend_type);
2776 if (tex.get_env_type() == EggTexture::ET_modulate) {
2777 if (color_def->_has_alpha_channel) {
2778 // lets caution the artist that they should not be using a
2779 // alpha channel on this texture.
2780 if (mayaegg_cat.is_spam()) {
2781 maya_cat.spam()
2782 << color_def->_texture_name
2783 << " should not have alpha channel in multiply mode: ignoring\n";
2784 }
2785 }
2786 if (is_rgb) {
2787 // tex.set_alpha_mode(EggRenderMode::AM_off); force
2788 // alpha off
2789 tex.set_format(EggTexture::F_rgb); // Change the format to be rgb only
2790 }
2791 }
2792 }
2793 }
2794 }
2795 else {
2796 if (is_interpolate) {
2797 // base shader need to save result
2798 tex.set_saved_result(true);
2799 }
2800 else if (is_decal) {
2801 // decal in classic time, always overwrote the base color. That
2802 // causes problem when the polygon wants to be lit or wants to
2803 // retain vertexpolygon color In the new decal mode, we achieve
2804 // this with a third dummy layer copy this layer to a new dummy
2805 // layer
2806 EggTexture texDummy(shader.get_name()+".dummy", "");
2807 if (mayaegg_cat.is_debug()) {
2808 mayaegg_cat.debug() << "creating dummy shader: " << texDummy.get_name() << endl;
2809 }
2810 texDummy.set_filename(outpath);
2811 texDummy.set_fullpath(fullpath);
2812 apply_texture_uvprops(texDummy, *color_def);
2813 texDummy.set_combine_mode(EggTexture::CC_rgb, EggTexture::CM_modulate);
2814 texDummy.set_combine_source(EggTexture::CC_rgb, 0, EggTexture::CS_primary_color);
2815 texDummy.set_combine_operand(EggTexture::CC_rgb, 0, EggTexture::CO_src_color);
2816 texDummy.set_combine_source(EggTexture::CC_rgb, 1, EggTexture::CS_previous);
2817 texDummy.set_combine_operand(EggTexture::CC_rgb, 1, EggTexture::CO_src_color);
2818 dummy_tex = _textures.create_unique_texture(texDummy, ~0);
2819
2820 // make this layer ET_replace
2821 tex.set_env_type(EggTexture::ET_replace);
2822 }
2823 }
2824 }
2825 } else { // trans_def._has_texture
2826 // We have a texture on transparency only. Apply it as the primary
2827 // filename, and set the format accordingly.
2828 Filename filename = Filename::from_os_specific(trans_def._texture_filename);
2829 Filename fullpath,outpath;
2830 _path_replace->full_convert_path(filename, get_model_path(),
2831 fullpath, outpath);
2832 tex.set_filename(outpath);
2833 tex.set_fullpath(fullpath);
2834 tex.set_format(EggTexture::F_alpha);
2835 apply_texture_uvprops(tex, trans_def);
2836 }
2837
2838 if (mayaegg_cat.is_debug()) {
2839 mayaegg_cat.debug() << "ssa:tref_name:" << tex.get_name() << endl;
2840 }
2841 if (is_rgb && i == (int)shader._color.size()-1) {
2842 // make base layer rgb only
2843 tex.set_format(EggTexture::F_rgb); // Change the format to be rgb only
2844 }
2845 EggTexture *new_tex =
2846 _textures.create_unique_texture(tex, ~0);
2847
2848 if (mesh) {
2849 if (uvset_name.find("not found") == string::npos) {
2850 primitive.add_texture(new_tex);
2851 color_def->_uvset_name.assign(uvset_name.c_str());
2852 if (uvset_name != "map1") {
2853 new_tex->set_uv_name(uvset_name);
2854 }
2855 if (i == (int)shader._color.size()-1 && is_decal) {
2856 dummy_uvset_name.assign(color_def->_uvset_name);
2857 }
2858 }
2859 } else {
2860 primitive.add_texture(new_tex);
2861 if (color_def->_uvset_name != "map1") {
2862 new_tex->set_uv_name(color_def->_uvset_name);
2863 }
2864 }
2865 }
2866 }
2867 if (dummy_tex != nullptr) {
2868 primitive.add_texture(dummy_tex);
2869 dummy_tex->set_uv_name(dummy_uvset_name);
2870 }
2871 // Also apply an overall color to the primitive.
2872 LColor rgba = shader.get_rgba();
2873 if (mayaegg_cat.is_spam()) {
2874 mayaegg_cat.spam() << "ssa:rgba = " << rgba << endl;
2875 }
2876
2877 // The existence of a texture on either color channel completely replaces
2878 // the corresponding flat color.
2879 if (color_def && color_def->_has_texture) {
2880 rgba[0] = 1.0f;
2881 rgba[1] = 1.0f;
2882 rgba[2] = 1.0f;
2883 }
2884 if (trans_def._has_texture) {
2885 rgba[3] = 1.0f;
2886 }
2887
2888 // But the color gain always gets applied.
2889 if (color_def) {
2890 rgba[0] *= color_def->_color_gain[0];
2891 rgba[1] *= color_def->_color_gain[1];
2892 rgba[2] *= color_def->_color_gain[2];
2893 rgba[3] *= color_def->_color_gain[3];
2894 if (mayaegg_cat.is_spam()) {
2895 mayaegg_cat.spam() << "ssa:rgba = " << rgba << endl;
2896 }
2897 }
2898
2899 primitive.set_color(rgba);
2900
2901 if (mayaegg_cat.is_spam()) {
2902 mayaegg_cat.spam() << " set_shader_attributes : end\n";
2903 }
2904}
2905
2906/**
2907 * Applies all the appropriate texture properties to the EggTexture object,
2908 * including wrap modes and texture matrix.
2909 */
2910void MayaToEggConverter::
2911apply_texture_uvprops(EggTexture &tex, const MayaShaderColorDef &color_def) {
2912 // Let's mipmap all textures by default.
2913 tex.set_minfilter(EggTexture::FT_linear_mipmap_linear);
2914 tex.set_magfilter(EggTexture::FT_linear);
2915
2916 EggTexture::WrapMode wrap_u = color_def._wrap_u ? EggTexture::WM_repeat : EggTexture::WM_clamp;
2917 EggTexture::WrapMode wrap_v = color_def._wrap_v ? EggTexture::WM_repeat : EggTexture::WM_clamp;
2918
2919 tex.set_wrap_u(wrap_u);
2920 tex.set_wrap_v(wrap_v);
2921
2922 LMatrix3d mat = color_def.compute_texture_matrix();
2923 if (!mat.almost_equal(LMatrix3d::ident_mat())) {
2924 tex.set_transform2d(mat);
2925 }
2926}
2927
2928/**
2929 * Applies the blendtype to the EggTexture.
2930 */
2931void MayaToEggConverter::
2932apply_texture_blendtype(EggTexture &tex, const MayaShaderColorDef &color_def) {
2933 switch (color_def._blend_type) {
2934 case MayaShaderColorDef::BT_unspecified:
2935 tex.set_env_type(EggTexture::ET_unspecified);
2936 return;
2937 case MayaShaderColorDef::BT_modulate:
2938 tex.set_env_type(EggTexture::ET_modulate);
2939 return;
2940 case MayaShaderColorDef::BT_decal:
2941 tex.set_env_type(EggTexture::ET_decal);
2942 return;
2943 case MayaShaderColorDef::BT_blend:
2944 tex.set_env_type(EggTexture::ET_blend);
2945 return;
2946 case MayaShaderColorDef::BT_replace:
2947 tex.set_env_type(EggTexture::ET_replace);
2948 return;
2949 case MayaShaderColorDef::BT_add:
2950 tex.set_env_type(EggTexture::ET_add);
2951 return;
2952 case MayaShaderColorDef::BT_blend_color_scale:
2953 tex.set_env_type(EggTexture::ET_blend_color_scale);
2954 return;
2955 case MayaShaderColorDef::BT_modulate_glow:
2956 tex.set_env_type(EggTexture::ET_modulate_glow);
2957 return;
2958 case MayaShaderColorDef::BT_modulate_gloss:
2959 tex.set_env_type(EggTexture::ET_modulate_gloss);
2960 return;
2961 case MayaShaderColorDef::BT_normal:
2962 tex.set_env_type(EggTexture::ET_normal);
2963 return;
2964 case MayaShaderColorDef::BT_normal_height:
2965 tex.set_env_type(EggTexture::ET_normal_height);
2966 return;
2967 case MayaShaderColorDef::BT_glow:
2968 tex.set_env_type(EggTexture::ET_glow);
2969 return;
2970 case MayaShaderColorDef::BT_gloss:
2971 tex.set_env_type(EggTexture::ET_gloss);
2972 return;
2973 case MayaShaderColorDef::BT_height:
2974 tex.set_env_type(EggTexture::ET_height);
2975 return;
2976 case MayaShaderColorDef::BT_selector:
2977 tex.set_env_type(EggTexture::ET_selector);
2978 return;
2979 }
2980}
2981
2982/**
2983 * Applies the filename to the EggTexture.
2984 */
2985void MayaToEggConverter::
2986apply_texture_filename(EggTexture &tex, const MayaShaderColorDef &def) {
2987 Filename filename = Filename::from_os_specific(def._texture_filename);
2988 Filename fullpath, outpath;
2989 _path_replace->full_convert_path(filename, get_model_path(), fullpath, outpath);
2990 tex.set_filename(outpath);
2991 tex.set_fullpath(fullpath);
2992}
2993
2994/**
2995 * Applies the alpha filename to the EggTexture.
2996 */
2997void MayaToEggConverter::
2998apply_texture_alpha_filename(EggTexture &tex, const MayaShaderColorDef &def) {
2999 if (def._opposite) {
3000 tex.set_format(EggTexture::F_rgba);
3001 if (def._opposite->_texture_filename != def._texture_filename) {
3002 Filename filename = Filename::from_os_specific(def._opposite->_texture_filename);
3003 Filename fullpath, outpath;
3004 _path_replace->full_convert_path(filename, get_model_path(), fullpath, outpath);
3005 tex.set_alpha_filename(outpath);
3006 tex.set_alpha_fullpath(fullpath);
3007 }
3008 }
3009}
3010
3011/**
3012 * Compares the texture properties already on the texture (presumably set by a
3013 * previous call to apply_texture_uvprops()) and returns false if they differ
3014 * from that specified by the indicated color_def object, or true if they
3015 * match.
3016 */
3017bool MayaToEggConverter::
3018compare_texture_uvprops(EggTexture &tex,
3019 const MayaShaderColorDef &color_def) {
3020 bool okflag = true;
3021
3022 EggTexture::WrapMode wrap_u = color_def._wrap_u ? EggTexture::WM_repeat : EggTexture::WM_clamp;
3023 EggTexture::WrapMode wrap_v = color_def._wrap_v ? EggTexture::WM_repeat : EggTexture::WM_clamp;
3024
3025 if (wrap_u != tex.determine_wrap_u()) {
3026 // Choose the more general of the two.
3027 if (wrap_u == EggTexture::WM_repeat) {
3028 tex.set_wrap_u(wrap_u);
3029 }
3030 okflag = false;
3031 }
3032 if (wrap_v != tex.determine_wrap_v()) {
3033 if (wrap_v == EggTexture::WM_repeat) {
3034 tex.set_wrap_v(wrap_v);
3035 }
3036 okflag = false;
3037 }
3038
3039 LMatrix3d m = color_def.compute_texture_matrix();
3040 LMatrix4d mat4(m(0, 0), m(0, 1), 0.0, m(0, 2),
3041 m(1, 0), m(1, 1), 0.0, m(1, 2),
3042 0.0, 0.0, 1.0, 0.0,
3043 m(2, 0), m(2, 1), 0.0, m(2, 2));
3044 if (!mat4.almost_equal(tex.get_transform3d())) {
3045 okflag = false;
3046 }
3047
3048 return okflag;
3049}
3050
3051/**
3052 * Recursively walks the egg hierarchy, reparenting "decal" type nodes below
3053 * their corresponding "decalbase" type nodes, and setting the flags.
3054 *
3055 * Returns true on success, false if some nodes were incorrect.
3056 */
3057bool MayaToEggConverter::
3058reparent_decals(EggGroupNode *egg_parent) {
3059 bool okflag = true;
3060
3061 // First, walk through all children of this node, looking for the one decal
3062 // base, if any.
3063 EggGroup *decal_base = nullptr;
3064 pvector<EggGroup *> decal_children;
3065
3066 EggGroupNode::iterator ci;
3067 for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) {
3068 EggNode *child = (*ci);
3069 if (child->is_of_type(EggGroup::get_class_type())) {
3070 EggGroup *child_group = DCAST(EggGroup, child);
3071 if (child_group->has_object_type("decalbase")) {
3072 if (decal_base != nullptr) {
3073 mayaegg_cat.error()
3074 << "Two children of " << egg_parent->get_name()
3075 << " both have decalbase set: " << decal_base->get_name()
3076 << " and " << child_group->get_name() << "\n";
3077 okflag = false;
3078 }
3079 child_group->remove_object_type("decalbase");
3080 decal_base = child_group;
3081
3082 } else if (child_group->has_object_type("decal")) {
3083 child_group->remove_object_type("decal");
3084 decal_children.push_back(child_group);
3085 }
3086 }
3087 }
3088
3089 if (decal_base == nullptr) {
3090 if (!decal_children.empty()) {
3091 mayaegg_cat.warning()
3092 << decal_children.front()->get_name()
3093 << " has decal, but no sibling node has decalbase.\n";
3094 }
3095
3096 } else {
3097 if (decal_children.empty()) {
3098 mayaegg_cat.warning()
3099 << decal_base->get_name()
3100 << " has decalbase, but no sibling nodes have decal.\n";
3101
3102 } else {
3103 // All the decal children get moved to be a child of decal base. This
3104 // usually will not affect the vertex positions, but it could if the
3105 // decal base has a transform and the decal child is an instance node.
3106 // So don't do that.
3108 for (di = decal_children.begin(); di != decal_children.end(); ++di) {
3109 EggGroup *child_group = (*di);
3110 decal_base->add_child(child_group);
3111 }
3112
3113 // Also set the decal state on the base.
3114 decal_base->set_decal_flag(true);
3115 }
3116 }
3117
3118 // Now recurse on each of the child nodes.
3119 for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) {
3120 EggNode *child = (*ci);
3121 if (child->is_of_type(EggGroupNode::get_class_type())) {
3122 EggGroupNode *child_group = DCAST(EggGroupNode, child);
3123 if (!reparent_decals(child_group)) {
3124 okflag = false;
3125 }
3126 }
3127 }
3128
3129 return okflag;
3130}
3131
3132/**
3133 * Returns the TransformType value corresponding to the indicated string, or
3134 * TT_invalid.
3135 */
3136MayaToEggConverter::TransformType MayaToEggConverter::
3137string_transform_type(const string &arg) {
3138 if (cmp_nocase(arg, "all") == 0) {
3139 return TT_all;
3140 } else if (cmp_nocase(arg, "model") == 0) {
3141 return TT_model;
3142 } else if (cmp_nocase(arg, "dcs") == 0) {
3143 return TT_dcs;
3144 } else if (cmp_nocase(arg, "none") == 0) {
3145 return TT_none;
3146 } else {
3147 return TT_invalid;
3148 }
3149}
3150/**
3151 * Checks to see if we're using legacy or modern shaders and based on the
3152 * result, it passes the vertex color calculations off to either the legacy or
3153 * modern vertex color functions.
3154 */
3155void MayaToEggConverter::
3156set_vertex_color(EggVertex &vert, MItMeshPolygon &pi, int vert_index, const MayaShader *shader, const LColor &color) {
3157 if (shader == nullptr || shader->_legacy_mode) {
3158 set_vertex_color_legacy(vert, pi, vert_index, shader, color);
3159 } else {
3160 set_vertex_color_modern(vert, pi, vert_index, shader, color);
3161 }
3162}
3163/**
3164 * Calls set_color on an EggVertex, determining the correct color values,
3165 * based on the shader, vert_color Maya's vertex & flat color(s). This is the
3166 * original implementation that works only on Lambert shaders/materials in
3167 * Maya.
3168 */
3169void MayaToEggConverter::
3170set_vertex_color_legacy(EggVertex &vert, MItMeshPolygon &pi, int vert_index, const MayaShader *shader, const LColor &color){
3171 if (pi.hasColor()) {
3172 MColor c;
3173 MStatus status = pi.getColor(c, vert_index);
3174 if (!status) {
3175 status.perror("MItMeshPolygon::getColor");
3176 } else {
3177 // I saw instances where the color components exceeded 1.0 so lets clamp
3178 // the values to 0 to 1
3179 c /= 1.0;
3180 // The vertex color is a color scale that modifies the polygon color,
3181 // not an override that replaces it.
3182 vert.set_color(LColor(c.r * color[0], c.g * color[1], c.b * color[2], c.a * color[3]));
3183
3184 if (mayaegg_cat.is_spam()) {
3185 mayaegg_cat.spam() << "maya_color = " << c.r << " " << c.g << " " << c.b << " " << c.a << endl;
3186 mayaegg_cat.spam() << "vert_color = " << vert.get_color() << endl;
3187 }
3188 }
3189 } else {
3190 vert.set_color(color);
3191 }
3192
3193}
3194/**
3195 * Calls set_color on an EggVertex, determining the correct color values,
3196 * based on the shader, vert_color Maya's vertex & flat color(s). This
3197 * implementation is designed to work specifically with Phong materials or
3198 * shaders.
3199 */
3200void MayaToEggConverter::
3201set_vertex_color_modern(EggVertex &vert, MItMeshPolygon &pi, int vert_index, const MayaShader *shader, const LColor &color) {
3202 // If there's an explicit vertex color, output it.
3203 if (pi.hasColor(vert_index)) {
3204 MColor c;
3205 MStatus status = pi.getColor(c, vert_index);
3206 if (status) {
3207 vert.set_color(LColor(c.r, c.g, c.b, c.a));
3208 return;
3209 }
3210 }
3211
3212 // If there's no explicit color, use flat color, or white on a textured
3213 // model.
3214 if (shader->_color_maps.empty()) {
3215 const LColord &c = shader->_flat_color;
3216 vert.set_color(LColor((PN_stdfloat)c[0], (PN_stdfloat)c[1], (PN_stdfloat)c[2], (PN_stdfloat)c[3]));
3217 } else {
3218 // there's no explicit color anywhere, must be textured (or blank)
3219 vert.set_color(LColor(1.0f, 1.0f, 1.0f, 1.0f));
3220 }
3221}
void add_data(double value)
Adds a single element to the table.
Definition eggAnimData.I:97
LColor get_color() const
Returns the color set on this particular attribute.
void set_fullpath(const Filename &fullpath)
Records the full pathname to the file, for the benefit of get_fullpath().
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
void ref_vertex(EggVertex *vert, double membership=1.0)
Adds the vertex to the set of those referenced by the group, at the indicated membership level.
Definition eggGroup.cxx:608
bool has_dcs_type() const
Returns true if the specified DCS type is not DC_none and not DC_unspecified.
Definition eggGroup.I:199
bool has_object_type(const std::string &object_type) const
Returns true if the indicated object type has been added to the group, or false otherwise.
Definition eggGroup.cxx:145
bool remove_object_type(const std::string &object_type)
Removes the first instance of the indicated object type from the group if it is present.
Definition eggGroup.cxx:161
std::pair< iterator, bool > insert(const MorphType &value)
This is similar to the insert() interface for sets, except it does not guarantee that the resulting l...
A single <Dxyz> or <Duv> or some such entry.
Definition eggMorph.h:30
A base class for things that may be directly added into the egg hierarchy.
Definition eggNode.h:36
const LMatrix4d & get_vertex_frame_inv() const
Returns the inverse of the matrix returned by get_vertex_frame().
Definition eggNode.I:135
const LMatrix4d & get_node_frame_inv() const
Returns the inverse of the matrix returned by get_node_frame().
Definition eggNode.I:149
A parametric NURBS curve.
void setup(int order, int num_knots)
Prepares a new curve definition with the indicated order and number of knots.
int get_num_cvs() const
Returns the total number of control vertices that *should* be defined for the curve.
set_knot
Resets the value of the indicated knot as indicated.
A parametric NURBS surface.
void setup(int u_order, int v_order, int num_u_knots, int num_v_knots)
Prepares a new surface definition with the indicated order and number of knots in each dimension.
int get_num_cvs() const
Returns the total number of control vertices that *should* be defined for the surface.
int get_u_index(int vertex_index) const
Returns the U index number of the given vertex within the EggPrimitive's linear list of vertices.
void set_u_knot(int k, double value)
Resets the value of the indicated knot as indicated.
int get_v_index(int vertex_index) const
Returns the V index number of the given vertex within the EggPrimitive's linear list of vertices.
void set_v_knot(int k, double value)
Resets the value of the indicated knot as indicated.
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
bool has_user_data() const
Returns true if a generic user data pointer has recently been set and not yet cleared,...
A single polygon.
Definition eggPolygon.h:24
bool calculate_normal(LNormald &result, CoordinateSystem cs=CS_default) const
Calculates the true polygon normal–the vector pointing out of the front of the polygon–based on the v...
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
set_bface_flag
Sets the backfacing flag of the polygon.
EggVertex * add_vertex(EggVertex *vertex)
Adds the indicated vertex to the end of the primitive's list of vertices, and returns it.
get_num_textures
Returns the number of textures applied to the primitive.
virtual void reverse_vertex_ordering()
Reverses the ordering of the vertices in this primitive, if appropriate, in order to change the direc...
void add_texture(EggTexture *texture)
Applies the indicated texture to the primitive.
Corresponding to an entry, this stores a single column of numbers, for instance for a morph target,...
void optimize()
Optimizes the data by collapsing a long table of duplicate values into a single value.
This corresponds to a.
Definition eggTable.h:27
void clear()
Removes all textures from the collection.
EggTexture * create_unique_texture(const EggTexture &copy, int eq)
Creates a new texture if there is not already one equivalent (according to eq, see EggTexture::is_equ...
Defines a texture map that may be applied to geometry.
Definition eggTexture.h:30
WrapMode determine_wrap_u() const
Determines the appropriate wrap in the U direction.
Definition eggTexture.I:107
set_uv_name
Specifies the named set of texture coordinates that this texture will use when it is applied to geome...
Definition eggTexture.h:341
WrapMode determine_wrap_v() const
Determines the appropriate wrap in the V direction.
Definition eggTexture.I:134
set_alpha_fullpath
Records the full pathname to the file, for the benefit of get_alpha_fullpath().
Definition eggTexture.h:348
set_alpha_filename
Specifies a separate file that will be loaded in with the 1- or 3-component texture and applied as th...
Definition eggTexture.h:347
const LMatrix4d & get_transform3d() const
Returns the overall transform as a 4x4 matrix.
void clear_transform()
Resets the transform to empty, identity.
void set_transform3d(const LMatrix4d &mat)
Sets the overall transform as a 4x4 matrix.
void add_matrix4(const LMatrix4d &mat)
Appends an arbitrary 4x4 matrix to the current transform.
void add_translate3d(const LVector3d &translate)
Appends a 3-d translation operation to the current transform.
void set_transform2d(const LMatrix3d &mat)
Sets the overall transform as a 3x3 matrix.
A collection of vertices.
iterator end() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
EggVertex * get_vertex(int index) const
Returns the vertex in the pool with the indicated index number, or NULL if no vertices have that inde...
iterator begin() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
EggVertex * add_vertex(EggVertex *vertex, int index=-1)
Adds the indicated vertex to the pool.
EggVertex * create_unique_vertex(const EggVertex &copy)
Creates a new vertex in the pool that is a copy of the indicated one and returns it.
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition eggVertex.h:39
int get_external_index() const
Returns the number set by set_external_index().
Definition eggVertex.I:300
void set_external_index(int external_index)
Sets a special index number that is associated with the EggVertex (but is not written to the egg file...
Definition eggVertex.I:292
void set_external_index2(int external_index2)
Similar to set_external_index(), but this is a different number which may be used for a different pur...
Definition eggVertex.I:310
void set_pos(double pos)
Sets the vertex position.
Definition eggVertex.I:42
int get_external_index2() const
Returns the number set by set_external_index2().
Definition eggVertex.I:319
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition eggVertex.I:131
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition eggVertex.I:193
This corresponds to an <Xfm$Anim_S$> entry, which is a collection of up to nine entries that specify...
Definition eggXfmSAnim.h:28
void optimize()
Optimizes the table by collapsing redundant sub-tables.
bool add_data(const LMatrix4d &mat)
Adds a new matrix to the table, by adding a new row to each of the subtables.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes,...
Definition filename.cxx:328
void set_fullpath(const std::string &s)
Replaces the entire filename: directory, basename, extension.
Definition filename.cxx:695
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
A handle to a Maya blend shape description.
void set_slider(PN_stdfloat value)
Moves the Maya slider associated with this blend shape to the indicated value.
PN_stdfloat get_slider() const
Returns the current position of the Maya slider associated with this blend shape.
This class contains extra user data which is piggybacked onto EggGroup objects for the purpose of the...
Describes a single instance of a node in the Maya scene graph, relating it to the corresponding egg s...
bool has_object_type(std::string object_type) const
Returns true if this node or any of its parent has_object_type of object_type.
const MDagPath & get_dag_path() const
Returns the dag path associated with this node.
MayaBlendDesc * get_blend_desc(int n) const
Returns the nth MayaBlendDesc object that affects the geometry in this node.
bool is_joint() const
Returns true if the node should be treated as a joint by the converter.
int get_num_blend_descs() const
Returns the number of unique MayaBlendDesc objects (and hence the number of morph sliders) that affec...
bool has_dag_path() const
Returns true if a Maya dag path has been associated with this node, false otherwise.
bool is_tagged() const
Returns true if the node has been tagged to be converted, false otherwise.
void reset_sliders()
Resets all of the sliders associated with all blend shapes down to 0.
EggGroup * get_egg_group(MayaNodeDesc *node_desc)
Returns the EggGroupNode corresponding to the group or joint for the indicated node.
void clear()
Resets the entire tree in preparation for repopulating with a new scene.
MayaNodeDesc * get_node(int n) const
Returns the nth node in the hierarchy, in an arbitrary ordering.
void tag_joint_all()
Tags the entire hierarchy for conversion.
bool tag_named(const GlobPattern &glob)
Tags nodes matching the indicated glob (and all of their children) for conversion.
MayaNodeDesc * build_node(const MDagPath &dag_path)
Returns a pointer to the node corresponding to the indicated dag_path object, creating it first if ne...
EggXfmSAnim * get_egg_anim(MayaNodeDesc *node_desc)
Returns the anim table corresponding to the joint for the indicated node.
bool tag_selected()
Tags the just the selected hierarchy for conversion, or the entire hierarchy if nothing is selected.
int get_num_nodes() const
Returns the total number of nodes in the hierarchy, not counting the root node.
void clear_egg(EggData *egg_data, EggGroupNode *egg_root, EggGroupNode *skeleton_node, EggGroupNode *morph_node)
Removes all of the references to generated egg structures from the tree, and prepares the tree for ge...
MayaBlendDesc * get_blend_desc(int n) const
Returns the nth MayaBlendDesc object discovered in the tree.
bool tag_joint_named(const GlobPattern &glob)
Tags nodes matching the indicated glob (and all of their children) for conversion.
EggSAnimData * get_egg_slider(MayaBlendDesc *blend_desc)
Returns the anim table corresponding to the slider for the indicated blend.
bool build_hierarchy()
Walks through the complete Maya hierarchy but does not tag any nodes for conversion.
bool untag_named(const GlobPattern &glob)
Un-tags nodes matching the indicated glob (and all of their children) for conversion.
int get_num_blend_descs() const
Returns the number of unique MayaBlendDesc objects (and hence the number of morph sliders) discovered...
void tag_all()
Tags the entire hierarchy for conversion.
This defines the various attributes that Maya may associate with the "color" channel for a particular...
LMatrix3d compute_texture_matrix() const
Returns a texture matrix corresponding to the texture transforms indicated by the shader.
bool has_projection() const
Returns true if the shader has a projection in effect.
std::string get_panda_uvset_name()
Maya's default uvset name is "map1".
LTexCoordd project_uv(const LPoint3d &pos, const LPoint3d &ref_point) const
If the shader has a projection (has_projection() returns true), this computes the appropriate UV corr...
Corresponds to a single "shader" in Maya.
Definition mayaShader.h:30
LColor get_rgba(size_t idx=0) const
Returns the overall color of the shader as a single-precision rgba value, where the alpha component r...
MayaShaderColorDef * get_color_def(size_t idx=0) const
This is part of the deprecated codepath.
void clear()
Frees all of the previously-defined MayaShader objects associated with this set.
void bind_uvsets(MObject mesh)
Causes all shaders in the set to use the given mesh as a file-to-uvset map.
MayaShader * find_shader_for_shading_engine(MObject engine, bool legacy_shader)
Returns the MayaShader object associated with the indicated "shading engine".
std::string find_uv_link(const std::string &match)
Returns the current mapping from file to uvset for the given file texture name.
MayaShader * find_shader_for_node(MObject node, bool legacy_shader)
Extracts the shader assigned to the indicated node.
This class supervises the construction of an EggData structure from a single Maya file,...
void set_from_selection(bool from_selection)
Sets the flag that indicates whether the currently selected Maya geometry will be converted.
bool convert_maya()
Fills up the egg_data structure according to the global maya model data.
void clear_ignore_sliders()
Empties the list of ignore_sliders added via add_ignore_slider().
bool open_api(bool revert_directory=true)
Attempts to open the Maya API if it was not already open, and returns true if successful,...
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
void clear_force_joints()
Empties the list of force_joints added via add_force_joint().
virtual DistanceUnit get_input_units()
This may be called after convert_file() has been called and returned true, indicating a successful co...
static TransformType string_transform_type(const std::string &arg)
Returns the TransformType value corresponding to the indicated string, or TT_invalid.
virtual std::string get_additional_extensions() const
Returns a space-separated list of extension, in addition to the one returned by get_extension(),...
void close_api()
Closes the Maya API, if it was previously opened.
void clear()
Frees all of the Maya pointers kept within this object, in preparation for loading a new scene or rel...
void clear_subsets()
Empties the list of subset nodes added via add_subset().
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
virtual bool convert_file(const Filename &filename)
Handles the reading of the input file and converting it to egg.
void add_ignore_slider(const GlobPattern &glob)
Adds a name pattern to the list of ignore_sliders.
virtual SomethingToEggConverter * make_copy()
Allocates and returns a new copy of the converter.
void clear_subroots()
Empties the list of subroot nodes added via add_subroot().
void add_subset(const GlobPattern &glob)
Adds a name pattern to the list of subset nodes.
bool force_joint(const std::string &name) const
Returns true if the indicated name is on the list of DAG nodes to treat as a joint,...
void add_force_joint(const GlobPattern &glob)
Adds a name pattern to the list of force_joints.
bool ignore_slider(const std::string &name) const
Returns true if the indicated name is on the list of sliders to ignore, false otherwise.
void add_subroot(const GlobPattern &glob)
Adds a name pattern to the list of subroot nodes.
void clear_excludes()
Empties the list of excluded nodes added via add_exclude().
void add_exclude(const GlobPattern &glob)
Adds a name pattern to the list of excluded nodes.
This is a base class for a family of converter classes that manage a conversion from some file type t...
bool had_error() const
Returns true if an error was detected during the conversion process (unless _allow_errors is true),...
AnimationConvert get_animation_convert() const
Returns how source animation will be converted into egg structures.
bool has_output_frame_rate() const
Returns true if the frame rate has been explicitly specified via set_output_frame_rate(),...
bool has_start_frame() const
Returns true if the starting frame has been explicitly specified via set_start_frame(),...
EggData * get_egg_data()
Returns the EggData structure.
double get_input_frame_rate() const
Returns the value set by a previous call to set_input_frame_rate().
bool has_neutral_frame() const
Returns true if the neutral frame has been explicitly specified via set_neutral_frame(),...
double get_frame_inc() const
Returns the value set by a previous call to set_frame_inc().
double get_output_frame_rate() const
Returns the value set by a previous call to set_output_frame_rate().
double get_end_frame() const
Returns the value set by a previous call to set_end_frame().
bool has_input_frame_rate() const
Returns true if the frame rate has been explicitly specified via set_input_frame_rate(),...
void clear_error()
Resets the error flag to the no-error state.
double get_start_frame() const
Returns the value set by a previous call to set_start_frame().
double get_neutral_frame() const
Returns the value set by a previous call to set_neutral_frame().
bool has_frame_inc() const
Returns true if the frame increment has been explicitly specified via set_frame_inc(),...
bool has_end_frame() const
Returns true if the ending frame has been explicitly specified via set_end_frame(),...
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 list.
Definition plist.h:35
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.
void init_libmayaegg()
Initializes the library.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
DistanceUnit
This enumerated type lists all the kinds of units we're likely to come across in model conversion pro...
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.
bool get_vec3d_attribute(MObject &node, const string &attribute_name, LVecBase3d &value)
Extracts the named three-component vector from the MObject.
bool get_bool_attribute(MObject &node, const string &attribute_name, bool &value)
Extracts the named boolean attribute from the MObject.
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.