Panda3D
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"
21 #include "mayaEggGroupUserData.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"
33 #include "eggTextureCollection.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 
79 using std::endl;
80 using std::string;
81 
82 
83 /**
84  *
85  */
86 MayaToEggConverter::
87 MayaToEggConverter(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  */
112 MayaToEggConverter::
113 MayaToEggConverter(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  */
139 MayaToEggConverter::
140 ~MayaToEggConverter() {
141  close_api();
142 }
143 
144 /**
145  * Allocates and returns a new copy of the converter.
146  */
149  return new MayaToEggConverter(*this);
150 }
151 
152 /**
153  * Returns the English name of the file type this converter supports.
154  */
156 get_name() const {
157  return "Maya";
158 }
159 
160 /**
161  * Returns the common extension of the file type this converter supports.
162  */
164 get_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  */
185 convert_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  */
226 add_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  */
246 add_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  */
262 add_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  */
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  */
292 ignore_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  */
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  */
331 force_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  */
349 set_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  */
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  */
541 open_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  */
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  */
567 clear() {
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  */
580 bool MayaToEggConverter::
581 convert_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  */
617 bool MayaToEggConverter::
618 convert_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  */
642 bool MayaToEggConverter::
643 convert_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  */
740 bool MayaToEggConverter::
741 convert_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  */
779 bool MayaToEggConverter::
780 process_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  */
1093 void MayaToEggConverter::
1094 get_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  */
1189 void MayaToEggConverter::
1190 get_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  */
1256 void MayaToEggConverter::
1257 make_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  */
1566 EggNurbsCurve *MayaToEggConverter::
1567 make_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  */
1648 void MayaToEggConverter::
1649 make_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  */
1732 int MayaToEggConverter::
1733 round(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  */
1745 void MayaToEggConverter::
1746 make_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  */
2197 void MayaToEggConverter::
2198 make_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  */
2250 void MayaToEggConverter::
2251 make_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  */
2290 void MayaToEggConverter::
2291 make_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  */
2339 bool MayaToEggConverter::
2340 get_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  */
2473 bool MayaToEggConverter::
2474 get_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  */
2561 void MayaToEggConverter::
2562 set_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  */
2579 void MayaToEggConverter::
2580 set_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  */
2619 void MayaToEggConverter::
2620 set_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  */
2910 void MayaToEggConverter::
2911 apply_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  */
2931 void MayaToEggConverter::
2932 apply_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  */
2985 void MayaToEggConverter::
2986 apply_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  */
2997 void MayaToEggConverter::
2998 apply_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  */
3017 bool MayaToEggConverter::
3018 compare_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  */
3057 bool MayaToEggConverter::
3058 reparent_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  */
3136 MayaToEggConverter::TransformType MayaToEggConverter::
3137 string_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  */
3155 void MayaToEggConverter::
3156 set_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  */
3169 void MayaToEggConverter::
3170 set_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  */
3200 void MayaToEggConverter::
3201 set_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 set_fullpath(const Filename &fullpath)
Records the full pathname to the file, for the benefit of get_fullpath().
EggXfmSAnim * get_egg_anim(MayaNodeDesc *node_desc)
Returns the anim table corresponding to the joint for the indicated node.
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
Definition: eggPrimitive.h:47
void optimize()
Optimizes the data by collapsing a long table of duplicate values into a single value.
void setup(int order, int num_knots)
Prepares a new curve definition with the indicated order and number of knots.
void add_subset(const GlobPattern &glob)
Adds a name pattern to the list of subset nodes.
A handle to a Maya blend shape description.
Definition: mayaBlendDesc.h:40
const LMatrix4d & get_transform3d() const
Returns the overall transform as a 4x4 matrix.
Definition: eggTransform.I:212
void clear_transform()
Resets the transform to empty, identity.
Definition: eggTransform.I:114
This is an iterator adaptor that converts any iterator that returns a pair (e.g.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool open_api(bool revert_directory=true)
Attempts to open the Maya API if it was not already open, and returns true if successful,...
void tag_all()
Tags the entire hierarchy for conversion.
int get_num_nodes() const
Returns the total number of nodes in the hierarchy, not counting the root node.
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...
void add_texture(EggTexture *texture)
Applies the indicated texture to the primitive.
Definition: eggPrimitive.I:162
void set_transform2d(const LMatrix3d &mat)
Sets the overall transform as a 3x3 matrix.
Definition: eggTransform.I:165
void clear_subsets()
Empties the list of subset nodes added via add_subset().
void add_data(double value)
Adds a single element to the table.
Definition: eggAnimData.I:97
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
void bind_uvsets(MObject mesh)
Causes all shaders in the set to use the given mesh as a file-to-uvset map.
Definition: mayaShaders.cxx:99
void close_api()
Closes the Maya API, if it was previously opened.
const LMatrix4d & get_node_frame_inv() const
Returns the inverse of the matrix returned by get_node_frame().
Definition: eggNode.I:149
bool had_error() const
Returns true if an error was detected during the conversion process (unless _allow_errors is true),...
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
int get_external_index2() const
Returns the number set by set_external_index2().
Definition: eggVertex.I:319
EggVertex * add_vertex(EggVertex *vertex, int index=-1)
Adds the indicated vertex to the pool.
void set_pos(double pos)
Sets the vertex position.
Definition: eggVertex.I:42
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:46
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear()
Resets the entire tree in preparation for repopulating with a new scene.
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...
Defines a texture map that may be applied to geometry.
Definition: eggTexture.h:30
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...
set_alpha_fullpath
Records the full pathname to the file, for the benefit of get_alpha_fullpath().
Definition: eggTexture.h:344
bool has_frame_inc() const
Returns true if the frame increment has been explicitly specified via set_frame_inc(),...
bool has_start_frame() const
Returns true if the starting frame has been explicitly specified via set_start_frame(),...
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
Definition: filename.I:386
virtual DistanceUnit get_input_units()
This may be called after convert_file() has been called and returned true, indicating a successful co...
bool has_neutral_frame() const
Returns true if the neutral frame has been explicitly specified via set_neutral_frame(),...
void set_u_knot(int k, double value)
Resets the value of the indicated knot as indicated.
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 clear()
Removes all textures from the collection.
bool convert_maya()
Fills up the egg_data structure according to the global maya model data.
void init_libmayaegg()
Initializes the library.
EggData * get_egg_data()
Returns the EggData structure.
set_bface_flag
Sets the backfacing flag of the polygon.
Definition: eggPrimitive.h:116
std::string get_panda_uvset_name()
Maya's default uvset name is "map1".
bool has_projection() const
Returns true if the shader has a projection in effect.
Corresponding to an <S$Anim> entry, this stores a single column of numbers, for instance for a morph ...
Definition: eggSAnimData.h:25
LMatrix3d compute_texture_matrix() const
Returns a texture matrix corresponding to the texture transforms indicated by the shader.
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.
void clear()
Frees all of the Maya pointers kept within this object, in preparation for loading a new scene or rel...
virtual std::string get_additional_extensions() const
Returns a space-separated list of extension, in addition to the one returned by get_extension(),...
This is our own Panda specialization on the default STL list.
Definition: plist.h:35
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double get_neutral_frame() const
Returns the value set by a previous call to set_neutral_frame().
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition: eggVertex.I:131
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
EggGroup * get_egg_group(MayaNodeDesc *node_desc)
Returns the EggGroupNode corresponding to the group or joint for the indicated node.
A single <Dxyz> or <Duv> or some such entry.
Definition: eggMorph.h:30
LColor get_color() const
Returns the color set on this particular attribute.
Definition: eggAttributes.I:91
bool has_dag_path() const
Returns true if a Maya dag path has been associated with this node, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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 that affects the geometry in this node.
bool tag_named(const GlobPattern &glob)
Tags nodes matching the indicated glob (and all of their children) for conversion.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
iterator end() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
bool has_input_frame_rate() const
Returns true if the frame rate has been explicitly specified via set_input_frame_rate(),...
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...
Definition: eggMorphList.I:159
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
MayaShader * find_shader_for_node(MObject node, bool legacy_shader)
Extracts the shader assigned to the indicated node.
Definition: mayaShaders.cxx:51
void clear_force_joints()
Empties the list of force_joints added via add_force_joint().
bool has_user_data() const
Returns true if a generic user data pointer has recently been set and not yet cleared,...
Definition: eggObject.cxx:107
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
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...
DistanceUnit
This enumerated type lists all the kinds of units we're likely to come across in model conversion pro...
Definition: distanceUnit.h:23
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
iterator begin() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
MayaBlendDesc * get_blend_desc(int n) const
Returns the nth MayaBlendDesc object discovered in the tree.
MayaShaderColorDef * get_color_def(size_t idx=0) const
This is part of the deprecated codepath.
Definition: mayaShader.cxx:115
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:34
void set_v_knot(int k, double value)
Resets the value of the indicated knot as indicated.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_fullpath(const std::string &s)
Replaces the entire filename: directory, basename, extension.
Definition: filename.cxx:695
void clear_excludes()
Empties the list of excluded nodes added via add_exclude().
void reset_sliders()
Resets all of the sliders associated with all blend shapes down to 0.
WrapMode determine_wrap_v() const
Determines the appropriate wrap in the V direction.
Definition: eggTexture.I:134
bool get_bool_attribute(MObject &node, const string &attribute_name, bool &value)
Extracts the named boolean attribute from the MObject.
Definition: maya_funcs.cxx:157
int get_num_cvs() const
Returns the total number of control vertices that *should* be defined for the surface.
void clear_error()
Resets the error flag to the no-error state.
void set_transform3d(const LMatrix4d &mat)
Sets the overall transform as a 4x4 matrix.
Definition: eggTransform.I:188
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void reverse_vertex_ordering()
Reverses the ordering of the vertices in this primitive, if appropriate, in order to change the direc...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Describes a single instance of a node in the Maya scene graph, relating it to the corresponding egg s...
Definition: mayaNodeDesc.h:40
bool get_vec3d_attribute(MObject &node, const string &attribute_name, LVecBase3d &value)
Extracts the named three-component vector from the MObject.
Definition: maya_funcs.cxx:302
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
This defines the various attributes that Maya may associate with the "color" channel for a particular...
void clear_ignore_sliders()
Empties the list of ignore_sliders added via add_ignore_slider().
bool is_joint() const
Returns true if the node should be treated as a joint by the converter.
double get_input_frame_rate() const
Returns the value set by a previous call to set_input_frame_rate().
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...
Definition: mayaShader.cxx:131
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition: eggVertex.h:39
int get_num_blend_descs() const
Returns the number of unique MayaBlendDesc objects (and hence the number of morph sliders) discovered...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
AnimationConvert get_animation_convert() const
Returns how source animation will be converted into egg structures.
This corresponds to an <Xfm$Anim_S$> entry, which is a collection of up to nine <S$Anim> entries that...
Definition: eggXfmSAnim.h:28
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
MayaNodeDesc * get_node(int n) const
Returns the nth node in the hierarchy, in an arbitrary ordering.
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.
bool tag_joint_named(const GlobPattern &glob)
Tags nodes matching the indicated glob (and all of their children) for conversion.
Corresponds to a single "shader" in Maya.
Definition: mayaShader.h:30
MayaShader * find_shader_for_shading_engine(MObject engine, bool legacy_shader)
Returns the MayaShader object associated with the indicated "shading engine".
A parametric NURBS curve.
Definition: eggNurbsCurve.h:26
void optimize()
Optimizes the table by collapsing redundant sub-tables.
Definition: eggXfmSAnim.cxx:63
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool ignore_slider(const std::string &name) const
Returns true if the indicated name is on the list of sliders to ignore, false otherwise.
const MDagPath & get_dag_path() const
Returns the dag path associated with this node.
bool is_tagged() const
Returns true if the node has been tagged to be converted, false otherwise.
void clear_subroots()
Empties the list of subroot nodes added via add_subroot().
double get_frame_inc() const
Returns the value set by a previous call to set_frame_inc().
A single polygon.
Definition: eggPolygon.h:24
void add_translate3d(const LVector3d &translate)
Appends a 3-d translation operation to the current transform.
bool build_hierarchy()
Walks through the complete Maya hierarchy but does not tag any nodes for conversion.
set_uv_name
Specifies the named set of texture coordinates that this texture will use when it is applied to geome...
Definition: eggTexture.h:337
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class supervises the construction of an EggData structure from a single Maya file,...
get_num_textures
Returns the number of textures applied to the primitive.
Definition: eggPrimitive.h:100
set_knot
Resets the value of the indicated knot as indicated.
Definition: eggNurbsCurve.h:58
This corresponds to a.
Definition: eggTable.h:27
bool untag_named(const GlobPattern &glob)
Un-tags nodes matching the indicated glob (and all of their children) for conversion.
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.
double get_start_frame() const
Returns the value set by a previous call to set_start_frame().
std::string find_uv_link(const std::string &match)
Returns the current mapping from file to uvset for the given file texture name.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
WrapMode determine_wrap_u() const
Determines the appropriate wrap in the U direction.
Definition: eggTexture.I:107
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:35
bool has_end_frame() const
Returns true if the ending frame has been explicitly specified via set_end_frame(),...
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:343
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...
Definition: eggPolygon.cxx:57
void add_force_joint(const GlobPattern &glob)
Adds a name pattern to the list of force_joints.
A parametric NURBS surface.
bool has_dcs_type() const
Returns true if the specified DCS type is not DC_none and not DC_unspecified.
Definition: eggGroup.I:199
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
void set_slider(PN_stdfloat value)
Moves the Maya slider associated with this blend shape to the indicated value.
EggSAnimData * get_egg_slider(MayaBlendDesc *blend_desc)
Returns the anim table corresponding to the slider for the indicated blend.
PN_stdfloat get_slider() const
Returns the current position of the Maya slider associated with this blend shape.
double get_output_frame_rate() const
Returns the value set by a previous call to set_output_frame_rate().
void set_from_selection(bool from_selection)
Sets the flag that indicates whether the currently selected Maya geometry will be converted.
int get_external_index() const
Returns the number set by set_external_index().
Definition: eggVertex.I:300
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
void tag_joint_all()
Tags the entire hierarchy for conversion.
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
This is our own Panda specialization on the default STL set.
Definition: pset.h:49
void clear()
Frees all of the previously-defined MayaShader objects associated with this set.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_num_cvs() const
Returns the total number of control vertices that *should* be defined for the curve.
int get_num_blend_descs() const
Returns the number of unique MayaBlendDesc objects (and hence the number of morph sliders) that affec...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add_matrix4(const LMatrix4d &mat)
Appends an arbitrary 4x4 matrix to the current transform.
Definition: eggTransform.I:132
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition: eggVertex.I:193
This is a base class for a family of converter classes that manage a conversion from some file type t...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add_subroot(const GlobPattern &glob)
Adds a name pattern to the list of subroot nodes.
virtual SomethingToEggConverter * make_copy()
Allocates and returns a new copy of the converter.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A collection of vertices.
Definition: eggVertexPool.h:41
EggVertex * add_vertex(EggVertex *vertex)
Adds the indicated vertex to the end of the primitive's list of vertices, and returns it.
bool has_output_frame_rate() const
Returns true if the frame rate has been explicitly specified via set_output_frame_rate(),...
This class contains extra user data which is piggybacked onto EggGroup objects for the purpose of the...
virtual bool convert_file(const Filename &filename)
Handles the reading of the input file and converting it to egg.
static TransformType string_transform_type(const std::string &arg)
Returns the TransformType value corresponding to the indicated string, or TT_invalid.
void add_ignore_slider(const GlobPattern &glob)
Adds a name pattern to the list of ignore_sliders.
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
bool tag_selected()
Tags the just the selected hierarchy for conversion, or the entire hierarchy if nothing is selected.
This class can be used to test for string matches against standard Unix- shell filename globbing conv...
Definition: globPattern.h:32
const LMatrix4d & get_vertex_frame_inv() const
Returns the inverse of the matrix returned by get_vertex_frame().
Definition: eggNode.I:135
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double get_end_frame() const
Returns the value set by a previous call to set_end_frame().
void add_exclude(const GlobPattern &glob)
Adds a name pattern to the list of excluded nodes.