Panda3D
maxToEggConverter.cxx
1 // Filename: maxToEggConverter.cxx
2 // Created by Corey Revilla and Ken Strickland (6/22/03)
3 // from mayaToEggConverter.cxx created by drose (10Nov99)
4 //
5 // Updated by Fei Wang, Carnegie Mellon University Entertainment
6 // Technology Center student, 29Jul2009: Fixed vertex color,
7 // animation hierarchy, texture swapping bugs; added collision choices to
8 // exporter.
9 //
10 // Updated by Andrew Gartner, Carnegie Mellon University Entertainment
11 // Technology Center. 27Apr2010: Collision is now done through User Defined Properties
12 // By default a plane without a standard material gets UV's as well
13 // as any object without a texture but with a standard material.
14 // Point objects are now supported as "locators" for a point in space
15 // within the egg.
16 ////////////////////////////////////////////////////////////////////
17 //
18 // PANDA 3D SOFTWARE
19 // Copyright (c) Carnegie Mellon University. All rights reserved.
20 //
21 // All use of this software is subject to the terms of the revised BSD
22 // license. You should have received a copy of this license along
23 // with this source code in a file named "LICENSE."
24 //
25 ////////////////////////////////////////////////////////////////////
26 
27 #include "maxEgg.h"
28 #include "config_util.h"
29 
30 ////////////////////////////////////////////////////////////////////
31 // Function: MaxToEggConverter::Constructor
32 // Access: Public
33 // Description:
34 ////////////////////////////////////////////////////////////////////
35 MaxToEggConverter::
36 MaxToEggConverter()
37 {
38  reset();
39 }
40 
41 ////////////////////////////////////////////////////////////////////
42 // Function: MaxToEggConverter::Destructor
43 // Access: Public, Virtual
44 // Description:
45 ////////////////////////////////////////////////////////////////////
46 MaxToEggConverter::
47 ~MaxToEggConverter()
48 {
49 }
50 
51 ////////////////////////////////////////////////////////////////////
52 // Function: MaxToEggConverter::reset
53 ////////////////////////////////////////////////////////////////////
54 void MaxToEggConverter::reset() {
55  _cur_tref = 0;
56  _current_frame = 0;
57  _textures.clear();
58  _egg_data = NULL;
59 }
60 
61 ////////////////////////////////////////////////////////////////////
62 // Function: MaxToEggConverter::convert
63 // Access: Public
64 // Description: Fills up the egg_data structure according to the
65 // global Max model data. Returns true if successful,
66 // false if there is an error. If from_selection is
67 // true, the converted geometry is based on that which
68 // is selected; otherwise, it is the entire Max scene.
69 ////////////////////////////////////////////////////////////////////
71 
72  _options = options;
73 
74  Filename fn;
75 #ifdef _UNICODE
76  fn = Filename::from_os_specific_w(_options->_file_name);
77 #else
78  fn = Filename::from_os_specific(_options->_file_name);
79 #endif
80 
81  _options->_path_replace->_path_directory = fn.get_dirname();
82 
83  _egg_data = new EggData;
84  if (_egg_data->get_coordinate_system() == CS_default) {
85  _egg_data->set_coordinate_system(CS_zup_right);
86  }
87 
88  // Figure out the animation parameters.
89 
90  // Get the start and end frames and the animation frame rate from Max
91 
92  Interval anim_range = _options->_max_interface->GetAnimRange();
93  int start_frame = anim_range.Start()/GetTicksPerFrame();
94  int end_frame = anim_range.End()/GetTicksPerFrame();
95 
96  if (!_options->_export_all_frames) {
97  if (_options->_start_frame < start_frame) _options->_start_frame = start_frame;
98  if (_options->_start_frame > end_frame) _options->_start_frame = end_frame;
99  if (_options->_end_frame < start_frame) _options->_end_frame = start_frame;
100  if (_options->_end_frame > end_frame) _options->_end_frame = end_frame;
101  if (_options->_end_frame < _options->_start_frame) _options->_end_frame = _options->_start_frame;
102  start_frame = _options->_start_frame;
103  end_frame = _options->_end_frame;
104  }
105 
106  int frame_inc = 1;
107  int output_frame_rate = GetFrameRate();
108 
109  bool all_ok = true;
110 
111  if (_options->_export_whole_scene) {
112  _tree._export_mesh = false;
113  all_ok = _tree.build_complete_hierarchy(_options->_max_interface->GetRootNode(), NULL, 0);
114  } else {
115  _tree._export_mesh = true;
116  all_ok = _tree.build_complete_hierarchy(_options->_max_interface->GetRootNode(), &_options->_node_list.front(), _options->_node_list.size());
117  }
118 
119  if (all_ok) {
120  switch (_options->_anim_type) {
121  case MaxEggOptions::AT_pose:
122  //pose: set to a specific frame, then get out the static geometry.
123  //sprintf(Logger::GetLogString(), "Extracting geometry from frame #%d.", start_frame);
124  //Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, Logger::GetLogString() );
125  //Logger::Log( MTEC, Logger::SAT_MEDIUM_LEVEL, "Converting static model." );
126  _current_frame = start_frame;
127  all_ok = convert_hierarchy(_egg_data);
128  break;
129 
130  case MaxEggOptions::AT_model:
131  // model: get out an animatable model with joints and vertex
132  // membership.
133  all_ok = convert_char_model();
134  break;
135 
136  case MaxEggOptions::AT_chan:
137  // chan: get out a series of animation tables.
138  all_ok = convert_char_chan(start_frame, end_frame, frame_inc,
139  output_frame_rate);
140  break;
141 
142  case MaxEggOptions::AT_both:
143  // both: Put a model and its animation into the same egg file.
144  _options->_anim_type = MaxEggOptions::AT_model;
145  if (!convert_char_model()) {
146  all_ok = false;
147  }
148  _options->_anim_type = MaxEggOptions::AT_chan;
149  if (!convert_char_chan(start_frame, end_frame, frame_inc,
150  output_frame_rate)) {
151  all_ok = false;
152  }
153  // Set the type back to AT_both
154  _options->_anim_type = MaxEggOptions::AT_both;
155  break;
156 
157  default:
158  all_ok = false;
159  };
160 
161  reparent_decals(_egg_data);
162  }
163 
164  if (all_ok) {
165  _egg_data->recompute_tangent_binormal_auto();
166  _egg_data->remove_unused_vertices(true);
167  }
168 
169  _options->_successful = all_ok;
170 
171  if (all_ok) {
172 #ifdef _UNICODE
173  Filename fn = Filename::from_os_specific_w(_options->_file_name);
174 #else
175  Filename fn = Filename::from_os_specific(_options->_file_name);
176 #endif
177  return _egg_data->write_egg(fn);
178  } else {
179  return false;
180  }
181 }
182 
183 ////////////////////////////////////////////////////////////////////
184 // Function: MaxToEggConverter::convert_char_model
185 // Access: Private
186 // Description: Converts the file as an animatable character
187 // model, with joints and vertex membership.
188 ////////////////////////////////////////////////////////////////////
189 bool MaxToEggConverter::
190 convert_char_model() {
191  std::string character_name = "character";
192  _current_frame = _options->_start_frame;
193 
194  EggGroup *char_node = new EggGroup(character_name);
195  _egg_data->add_child(char_node);
196  char_node->set_dart_type(EggGroup::DT_default);
197 
198  return convert_hierarchy(char_node);
199 }
200 
201 ////////////////////////////////////////////////////////////////////
202 // Function: MaxToEggConverter::convert_char_chan
203 // Access: Private
204 // Description: Converts the animation as a series of tables to apply
205 // to the character model, as retrieved earlier via
206 // AC_model.
207 ////////////////////////////////////////////////////////////////////
208 bool MaxToEggConverter::
209 convert_char_chan(double start_frame, double end_frame, double frame_inc,
210  double output_frame_rate) {
211  std::string character_name = "character";
212 
213  EggTable *root_table_node = new EggTable();
214  _egg_data->add_child(root_table_node);
215  EggTable *bundle_node = new EggTable(character_name);
216  bundle_node->set_table_type(EggTable::TT_bundle);
217  root_table_node->add_child(bundle_node);
218  EggTable *skeleton_node = new EggTable("<skeleton>");
219  bundle_node->add_child(skeleton_node);
220 
221  // Set the frame rate before we start asking for anim tables to be
222  // created.
223  _tree._fps = output_frame_rate / frame_inc;
224  _tree.clear_egg(_egg_data, NULL, skeleton_node);
225 
226  // Now we can get the animation data by walking through all of the
227  // frames, one at a time, and getting the joint angles at each
228  // frame.
229 
230  // This is just a temporary EggGroup to receive the transform for
231  // each joint each frame.
232  EggGroup* tgroup;
233 
234  int num_nodes = _tree.get_num_nodes();
235  int i;
236 
237  TimeValue frame = start_frame;
238  TimeValue frame_stop = end_frame;
239  while (frame <= frame_stop) {
240  _current_frame = frame;
241  for (i = 0; i < num_nodes; i++) {
242  // Find all joints in the hierarchy
243  MaxNodeDesc *node_desc = _tree.get_node(i);
244  if (node_desc->is_joint()) {
245  tgroup = new EggGroup();
246  INode *max_node = node_desc->get_max_node();
247 
248  if (node_desc->_parent && node_desc->_parent->is_joint()) {
249  // If this joint also has a joint as a parent, the parent's
250  // transformation has to be divided out of this joint's TM
251  get_joint_transform(max_node, node_desc->_parent->get_max_node(),
252  tgroup);
253  } else {
254  get_joint_transform(max_node, NULL, tgroup);
255  }
256 
257  EggXfmSAnim *anim = _tree.get_egg_anim(node_desc);
258  if (!anim->add_data(tgroup->get_transform3d())) {
259  // *** log an error
260  }
261  delete tgroup;
262  }
263  }
264 
265  frame += frame_inc;
266  }
267 
268  // Now optimize all of the tables we just filled up, for no real
269  // good reason, except that it makes the resulting egg file a little
270  // easier to read.
271  for (i = 0; i < num_nodes; i++) {
272  MaxNodeDesc *node_desc = _tree.get_node(i);
273  if (node_desc->is_joint()) {
274  _tree.get_egg_anim(node_desc)->optimize();
275  }
276  }
277 
278  return true;
279 }
280 
281 ////////////////////////////////////////////////////////////////////
282 // Function: MaxToEggConverter::convert_hierarchy
283 // Access: Private
284 // Description: Generates egg structures for each node in the Max
285 // hierarchy.
286 ////////////////////////////////////////////////////////////////////
287 bool MaxToEggConverter::
288 convert_hierarchy(EggGroupNode *egg_root) {
289  //int num_nodes = _tree.get_num_nodes();
290 
291  _tree.clear_egg(_egg_data, egg_root, NULL);
292  for (int i = 0; i < _tree.get_num_nodes(); i++) {
293  if (!process_model_node(_tree.get_node(i))) {
294  return false;
295  }
296  }
297 
298  return true;
299 }
300 
301 ////////////////////////////////////////////////////////////////////
302 // Function: MaxToEggConverter::process_model_node
303 // Access: Private
304 // Description: Converts the indicated Max node to the
305 // corresponding Egg structure. Returns true if
306 // successful, false if an error was encountered.
307 ////////////////////////////////////////////////////////////////////
308 bool MaxToEggConverter::
309 process_model_node(MaxNodeDesc *node_desc) {
310  if (!node_desc->has_max_node()) {
311  // If the node has no Max equivalent, never mind.
312  return true;
313  }
314 
315  // Skip all nodes that represent joints in the geometry, but aren't
316  // the actual joints themselves
317  if (node_desc->is_node_joint()) {
318  return true;
319  }
320 
321  TimeValue time = 0;
322  INode *max_node = node_desc->get_max_node();
323 
324  ObjectState state;
325  state = max_node->EvalWorldState(_current_frame * GetTicksPerFrame());
326 
327  if (node_desc->is_joint()) {
328  EggGroup *egg_group = _tree.get_egg_group(node_desc);
329  // Don't bother with joints unless we're getting an animatable
330  // model.
331  if (_options->_anim_type == MaxEggOptions::AT_model) {
332  get_joint_transform(max_node, egg_group);
333  }
334  } else {
335  if (state.obj) {
336  EggGroup *egg_group = NULL;
337  TriObject *myMaxTriObject;
338  Mesh max_mesh;
339  //Call the correct exporter based on what type of object this is.
340  switch( state.obj->SuperClassID() ){
341 
342  case GEOMOBJECT_CLASS_ID:
343  egg_group = _tree.get_egg_group(node_desc);
344  get_transform(max_node, egg_group);
345 
346  //Try converting this geometric object to a mesh we can use.
347  if (!state.obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0))) {
348  return false;
349  }
350  //Convert our state object to a TriObject.
351  myMaxTriObject = (TriObject *) state.obj->ConvertToType(time, Class_ID(TRIOBJ_CLASS_ID, 0 ));
352  // *** Want to figure this problem out
353  // If actual conversion was required, then we want to delete this
354  // new mesh later to avoid mem leaks. **BROKEN. doesnt delete
355 
356  //Now, get the mesh.
357  max_mesh = myMaxTriObject->GetMesh();
358  make_polyset(max_node, &max_mesh, egg_group);
359 
360  if (myMaxTriObject != state.obj)
361  delete myMaxTriObject;
362  break;
363 
364  case SHAPE_CLASS_ID:
365  if (state.obj->ClassID() == EDITABLE_SURF_CLASS_ID) {
366  NURBSSet getSet;
367  if (GetNURBSSet(state.obj, time, getSet, TRUE)) {
368  NURBSObject *nObj = getSet.GetNURBSObject(0);
369  if (nObj->GetType() == kNCVCurve) {
370  //It's a CV Curve, process it
371  egg_group = _tree.get_egg_group(node_desc);
372  get_transform(max_node, egg_group);
373  make_nurbs_curve(max_node, (NURBSCVCurve *)nObj,
374  time, egg_group);
375  }
376  }
377  }
378  break;
379 
380  case CAMERA_CLASS_ID:
381  break;
382 
383  case LIGHT_CLASS_ID:
384  break;
385 
386  case HELPER_CLASS_ID:
387  //we should export Point objects to give Max the equivalent of Maya locators
388  if (state.obj->ClassID() == Class_ID(POINTHELP_CLASS_ID, 0)) {
389 
390  egg_group = _tree.get_egg_group(node_desc);
391  get_transform(max_node, egg_group);
392 
393  } else {
394 
395  break;
396 
397  }
398 
399 
400 
401 
402  }
403  }
404  }
405 
406  return true;
407 }
408 
409 ////////////////////////////////////////////////////////////////////
410 // Function: MaxToEggConverter::get_transform
411 // Access: Private
412 // Description: Extracts the transform on the indicated Maya node,
413 // and applies it to the corresponding Egg node.
414 ////////////////////////////////////////////////////////////////////
415 void MaxToEggConverter::
416 get_transform(INode *max_node, EggGroup *egg_group) {
417  if (_options->_anim_type == MaxEggOptions::AT_model) {
418  // When we're getting an animated model, we only get transforms
419  // for joints.
420  return;
421  }
422 
423  if ( !egg_group ) {
424  return;
425  }
426 
427  // Gets the TM for this node, a matrix which encapsulates all transformations
428  // it takes to get to the current node, including parent transformations.
429  Matrix3 pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
430 
431  //This is the Panda-flava-flav-style matrix we'll be exporting to.
432  Point3 row0 = pivot.GetRow(0);
433  Point3 row1 = pivot.GetRow(1);
434  Point3 row2 = pivot.GetRow(2);
435  Point3 row3 = pivot.GetRow(3);
436 
437  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
438  row1.x, row1.y, row1.z, 0.0f,
439  row2.x, row2.y, row2.z, 0.0f,
440  row3.x, row3.y, row3.z, 1.0f );
441 
442  // Now here's the tricky part. I believe this command strips out the node
443  // "frame" which is the sum of all transformations enacted by the parent of
444  // this node. This should reduce to the transformation relative to this
445  // node's parent
446  m4d = m4d * egg_group->get_node_frame_inv();
447  if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
448  egg_group->add_matrix4(m4d);
449  }
450 }
451 
452 ////////////////////////////////////////////////////////////////////
453 // Function: MaxToEggConverter::get_object_transform
454 // Access: Private
455 // Description: Extracts the transform on the indicated Maya node,
456 // and applies it to the corresponding Egg node.
457 ////////////////////////////////////////////////////////////////////
458 LMatrix4d MaxToEggConverter::
459 get_object_transform(INode *max_node) {
460 
461  // Gets the TM for this node, a matrix which encapsulates all transformations
462  // it takes to get to the current node, including parent transformations.
463  Matrix3 pivot = max_node->GetObjectTM(_current_frame * GetTicksPerFrame());
464 
465  Point3 row0 = pivot.GetRow(0);
466  Point3 row1 = pivot.GetRow(1);
467  Point3 row2 = pivot.GetRow(2);
468  Point3 row3 = pivot.GetRow(3);
469 
470  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
471  row1.x, row1.y, row1.z, 0.0f,
472  row2.x, row2.y, row2.z, 0.0f,
473  row3.x, row3.y, row3.z, 1.0f );
474  return m4d;
475 }
476 
477 ////////////////////////////////////////////////////////////////////
478 // Function: MaxToEggConverter::get_joint_transform
479 // Access: Private
480 // Description: Extracts the transform on the indicated Maya node,
481 // as appropriate for a joint in an animated character,
482 // and applies it to the indicated node. This is
483 // different from get_transform() in that it does not
484 // respect the _transform_type flag, and it does not
485 // consider the relative transforms within the egg file.
486 ////////////////////////////////////////////////////////////////////
487 void MaxToEggConverter::
488 get_joint_transform(INode *max_node, EggGroup *egg_group) {
489 
490  if ( !egg_group ) {
491  return;
492  }
493 
494  // Gets the TM for this node, a matrix which encapsulates all transformations
495  // it takes to get to the current node, including parent transformations.
496  Matrix3 pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
497  Point3 row0 = pivot.GetRow(0);
498  Point3 row1 = pivot.GetRow(1);
499  Point3 row2 = pivot.GetRow(2);
500  Point3 row3 = pivot.GetRow(3);
501 
502  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
503  row1.x, row1.y, row1.z, 0.0f,
504  row2.x, row2.y, row2.z, 0.0f,
505  row3.x, row3.y, row3.z, 1.0f );
506 
507  // Now here's the tricky part. I believe this command strips out the node
508  // "frame" which is the sum of all transformations enacted by the parent of
509  // this node. This should reduce to the transformation relative to this
510  // node's parent
511  m4d = m4d * egg_group->get_node_frame_inv();
512  if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
513  egg_group->add_matrix4(m4d);
514  }
515 }
516 
517 ////////////////////////////////////////////////////////////////////
518 // Function: MaxToEggConverter::get_joint_transform
519 // Access: Private
520 // Description: Extracts the transform on the indicated Maya node,
521 // as appropriate for a joint in an animated character,
522 // and applies it to the indicated node. This is
523 // different from get_transform() in that it does not
524 // respect the _transform_type flag, and it does not
525 // consider the relative transforms within the egg file.
526 ////////////////////////////////////////////////////////////////////
527 void MaxToEggConverter::
528 get_joint_transform(INode *max_node, INode *parent_node, EggGroup *egg_group) {
529 
530  if ( !egg_group ) {
531  return;
532  }
533 
534  // Gets the TM for this node, a matrix which encapsulates all transformations
535  // it takes to get to the current node, including parent transformations.
536  Matrix3 pivot = max_node->GetNodeTM(_current_frame * GetTicksPerFrame());
537  Point3 row0 = pivot.GetRow(0);
538  Point3 row1 = pivot.GetRow(1);
539  Point3 row2 = pivot.GetRow(2);
540  Point3 row3 = pivot.GetRow(3);
541 
542  LMatrix4d m4d(row0.x, row0.y, row0.z, 0.0f,
543  row1.x, row1.y, row1.z, 0.0f,
544  row2.x, row2.y, row2.z, 0.0f,
545  row3.x, row3.y, row3.z, 1.0f );
546 
547  if (parent_node) {
548  Matrix3 parent_pivot = parent_node->GetNodeTM(_current_frame * GetTicksPerFrame());
549  // parent_pivot.Invert();
550  row0 = parent_pivot.GetRow(0);
551  row1 = parent_pivot.GetRow(1);
552  row2 = parent_pivot.GetRow(2);
553  row3 = parent_pivot.GetRow(3);
554 
555  LMatrix4d pi_m4d(row0.x, row0.y, row0.z, 0.0f,
556  row1.x, row1.y, row1.z, 0.0f,
557  row2.x, row2.y, row2.z, 0.0f,
558  row3.x, row3.y, row3.z, 1.0f );
559 
560  // Now here's the tricky part. I believe this command strips out the node
561  // "frame" which is the sum of all transformations enacted by the parent of
562  // this node. This should reduce to the transformation relative to this
563  // node's parent
564  pi_m4d.invert_in_place();
565  m4d = m4d * pi_m4d;
566  }
567  if (!m4d.almost_equal(LMatrix4d::ident_mat(), 0.0001)) {
568  egg_group->add_matrix4(m4d);
569  }
570 }
571 
572 ////////////////////////////////////////////////////////////////////
573 // Function: MaxToEggConverter::make_nurbs_curve
574 // Access: Private
575 // Description: Converts the indicated Maya NURBS curve (a standalone
576 // curve, not a trim curve) to a corresponding egg
577 // structure and attaches it to the indicated egg group.
578 ////////////////////////////////////////////////////////////////////
579 bool MaxToEggConverter::
580 make_nurbs_curve(INode *max_node, NURBSCVCurve *curve,
581  TimeValue time, EggGroup *egg_group)
582 {
583  int degree = curve->GetOrder();
584  int cvs = curve->GetNumCVs();
585  int knots = curve->GetNumKnots();
586  int i;
587 
588  if (knots != cvs + degree) {
589  return false;
590  }
591 
592 #ifdef _UNICODE
593  char mbname[1024];
594  mbname[1023] = 0;
595  wcstombs(mbname, max_node->GetName(), 1023);
596  string name(mbname);
597 #else
598  string name = max_node->GetName();
599 #endif
600 
601  string vpool_name = name + ".cvs";
602  EggVertexPool *vpool = new EggVertexPool(vpool_name);
603  egg_group->add_child(vpool);
604 
605  EggNurbsCurve *egg_curve = new EggNurbsCurve(name);
606  egg_group->add_child(egg_curve);
607  egg_curve->setup(degree, knots);
608 
609  for (i = 0; i < knots; i++)
610  egg_curve->set_knot(i, curve->GetKnot(i));
611 
612  LMatrix4d vertex_frame_inv = egg_group->get_vertex_frame_inv();
613 
614  for (i = 0; i < cvs; i++) {
615  NURBSControlVertex *cv = curve->GetCV(i);
616  if (!cv) {
617  char buf[1024];
618  sprintf(buf, "Error getting CV %d", i);
619  return false;
620  } else {
621  EggVertex vert;
622  LPoint4d p4d(0, 0, 0, 1.0);
623  cv->GetPosition(time, p4d[0], p4d[1], p4d[2]);
624  p4d = p4d * vertex_frame_inv;
625  vert.set_pos(p4d);
626  egg_curve->add_vertex(vpool->create_unique_vertex(vert));
627  }
628  }
629 
630  return true;
631 }
632 
633 ////////////////////////////////////////////////////////////////////
634 // Function: MaxToEggConverter::make_polyset
635 // Access: Private
636 // Description: Converts the indicated Maya polyset to a bunch of
637 // EggPolygons and parents them to the indicated egg
638 // group.
639 ////////////////////////////////////////////////////////////////////
640 void MaxToEggConverter::
641 make_polyset(INode *max_node, Mesh *mesh,
642  EggGroup *egg_group, Shader *default_shader) {
643 
644  mesh->buildNormals();
645 
646  if (mesh->getNumFaces() == 0) {
647  return;
648  }
649 
650  // One way to convert the mesh would be to first get out all the
651  // vertices in the mesh and add them into the vpool, then when we
652  // traverse the polygons we would only have to index them into the
653  // vpool according to their Maya vertex index.
654 
655  // Unfortunately, since Maya may store multiple normals and/or
656  // colors for each vertex according to which polygon it is in, that
657  // approach won't necessarily work. In egg, those split-property
658  // vertices have to become separate vertices. So instead of adding
659  // all the vertices up front, we'll start with an empty vpool, and
660  // add vertices to it on the fly.
661 
662 #ifdef _UNICODE
663  char mbname[1024];
664  mbname[1023] = 0;
665  wcstombs(mbname, max_node->GetName(), 1023);
666  string node_name(mbname);
667 #else
668  string node_name = max_node->GetName();
669 #endif
670 
671  string vpool_name = node_name + ".verts";
672  EggVertexPool *vpool = new EggVertexPool(vpool_name);
673  egg_group->add_child(vpool);
674 
675  // We will need to transform all vertices from world coordinate
676  // space into the vertex space appropriate to this node. Usually,
677  // this is the same thing as world coordinate space, and this matrix
678  // will be identity; but if the node is under an instance
679  // (particularly, for instance, a billboard) then the vertex space
680  // will be different from world space.
681  LMatrix4d vertex_frame = get_object_transform(max_node) *
682  egg_group->get_vertex_frame_inv();
683 
684 
685  for ( int iFace=0; iFace < mesh->getNumFaces(); iFace++ ) {
686  EggPolygon *egg_poly = new EggPolygon;
687  egg_group->add_child(egg_poly);
688 
689  egg_poly->set_bface_flag(_options->_double_sided);
690 
691  Face face = mesh->faces[iFace];
692 
693  const PandaMaterial &pmat = get_panda_material(max_node->GetMtl(), face.getMatID());
694 
695  // Get the vertices for the polygon.
696  for ( int iVertex=0; iVertex < 3; iVertex++ ) {
697  EggVertex vert;
698 
699  // Get the vertex position
700  Point3 vertex = mesh->getVert(face.v[iVertex]);
701  LPoint3d p3d(vertex.x, vertex.y, vertex.z);
702  p3d = p3d * vertex_frame;
703  vert.set_pos(p3d);
704 
705  // Get the vertex normal
706  Point3 normal = get_max_vertex_normal(mesh, iFace, iVertex);
707  LVector3d n3d(normal.x, normal.y, normal.z);
708  // *** Not quite sure if this transform should be applied, but it may
709  // explain why normals were weird previously
710  n3d = n3d * vertex_frame;
711  vert.set_normal(n3d);
712 
713  // Get the vertex color
714  if(mesh->vcFace) // if has vcFace, has used vertex color
715  {
716  VertColor vertexColor = get_max_vertex_color(mesh, iFace, iVertex);
717  LColor pVC(vertexColor.x, vertexColor.y, vertexColor.z, 1);
718  vert.set_color(pVC);
719  }
720  // Get the UVs for this vertex
721 
722  //first check if we returned nothing in the channels slot
723  //we need UV's even in this case
724  //because the user may not have put a material
725  //on the object at all
726  if (pmat._map_channels.size() == 0) {
727  //since the channel will always be one because there's
728  //no other textures then don't bother with the name
729  UVVert uvw = get_max_vertex_texcoord(mesh, iFace, iVertex, 1);
730  vert.set_uv( LTexCoordd(uvw.x, uvw.y));
731  }
732  //otherwise go through and generate the maps per channel
733  //this will also generate default UV's as long
734  //as the user applies a standard material to the object
735  for (int iChan=0; iChan<pmat._map_channels.size(); iChan++) {
736  int channel = pmat._map_channels[iChan];
737  ostringstream uvname;
738  uvname << "m" << channel;
739  UVVert uvw = get_max_vertex_texcoord(mesh, iFace, iVertex, channel);
740  // changes allow the first channel to be swapped
741  if(channel == 1)
742  vert.set_uv( LTexCoordd(uvw.x, uvw.y));
743  else
744  vert.set_uv( uvname.str(), LTexCoordd(uvw.x, uvw.y));
745 
746  }
747 
748  vert.set_external_index(face.v[iVertex]);
749 
750  egg_poly->add_vertex(vpool->create_unique_vertex(vert));
751  }
752 
753  //Max uses normals, not winding, to determine which way a
754  //polygon faces. Make sure the winding and that normal agree
755 
756  EggVertex *verts[3];
757  LPoint3d points[3];
758 
759  for (int i = 0; i < 3; i++) {
760  verts[i] = egg_poly->get_vertex(i);
761  points[i] = verts[i]->get_pos3();
762  }
763 
764  LVector3d realNorm = ((points[1] - points[0]).cross(points[2] - points[0]));
765  Point3 maxNormTemp = mesh->getFaceNormal(iFace);
766  LVector3d maxNorm = (LVector3d(maxNormTemp.x, maxNormTemp.y, maxNormTemp.z) *
767  vertex_frame);
768 
769  if (realNorm.dot(maxNorm) < 0.0) {
770  egg_poly->set_vertex(0, verts[2]);
771  egg_poly->set_vertex(2, verts[0]);
772  }
773 
774  for (int i=0; i<pmat._texture_list.size(); i++) {
775  egg_poly->add_texture(pmat._texture_list[i]);
776  }
777  egg_poly->set_color(pmat._color);
778 
779 
780  }
781 
782  // Now that we've added all the polygons (and created all the
783  // vertices), go back through the vertex pool and set up the
784  // appropriate joint membership for each of the vertices.
785 
786  if (_options->_anim_type == MaxEggOptions::AT_model) {
787  get_vertex_weights(max_node, vpool);
788  }
789 }
790 
791 UVVert MaxToEggConverter::get_max_vertex_texcoord(Mesh *mesh, int faceNo, int vertNo, int channel) {
792 
793  // extract the texture coordinate
794  UVVert uvVert(0,0,0);
795  if(mesh->mapSupport(channel)) {
796  TVFace *pTVFace = mesh->mapFaces(channel);
797  UVVert *pUVVert = mesh->mapVerts(channel);
798  uvVert = pUVVert[pTVFace[faceNo].t[vertNo]];
799  } else if(mesh->numTVerts > 0) {
800  uvVert = mesh->tVerts[mesh->tvFace[faceNo].t[vertNo]];
801  }
802  return uvVert;
803 }
804 
805 VertColor MaxToEggConverter::get_max_vertex_color(Mesh *mesh,int FaceNo,int VertexNo, int channel) {
806 
807  VertColor vc(0,0,0);
808  if(mesh->mapSupport(channel))
809  {
810  // We get the color from vcFace
811  TVFace& _vcface = mesh->vcFace[FaceNo];
812  //Get its index into the vertCol array
813  int VertexColorIndex = _vcface.t[VertexNo];
814  //Get its color
815  vc =mesh->vertCol[VertexColorIndex];
816  }
817  else
818  {
819  TVFace *pTVFace = mesh->mapFaces(channel);
820  vc = mesh->vertCol[pTVFace[FaceNo].t[VertexNo]];
821  }
822  return vc;
823 }
824 
825 VertColor MaxToEggConverter::get_max_vertex_color(Mesh *mesh,int FaceNo,int VertexNo)
826 {
827  VertColor vc(0,0,0);
828  // We get the color from vcFace
829  TVFace& _vcface = mesh->vcFace[FaceNo];
830  //Get its index into the vertCol array
831  int VertexColorIndex = _vcface.t[VertexNo];
832  //Get its color
833  vc =mesh->vertCol[VertexColorIndex];
834  return vc;
835 }
836 
837 Point3 MaxToEggConverter::get_max_vertex_normal(Mesh *mesh, int faceNo, int vertNo)
838 {
839  Face f = mesh->faces[faceNo];
840  DWORD smGroup = f.smGroup;
841  int vert = f.getVert(vertNo);
842  RVertex *rv = mesh->getRVertPtr(vert);
843 
844  int numNormals;
845  Point3 vertexNormal;
846 
847  // Is normal specified
848  // SPCIFIED is not currently used, but may be used in future versions.
849  if (rv->rFlags & SPECIFIED_NORMAL) {
850  vertexNormal = rv->rn.getNormal();
851  }
852  // If normal is not specified it's only available if the face belongs
853  // to a smoothing group
854  else if ((numNormals = rv->rFlags & NORCT_MASK) && smGroup) {
855  // If there is only one vertex is found in the rn member.
856  if (numNormals == 1) {
857  vertexNormal = rv->rn.getNormal();
858  }
859  else {
860  // If two or more vertices are there you need to step through them
861  // and find the vertex with the same smoothing group as the current face.
862  // You will find multiple normals in the ern member.
863  for (int i = 0; i < numNormals; i++) {
864  if (rv->ern[i].getSmGroup() & smGroup) {
865  vertexNormal = rv->ern[i].getNormal();
866  }
867  }
868  }
869  }
870  else {
871  // Get the normal from the Face if no smoothing groups are there
872  vertexNormal = mesh->getFaceNormal(faceNo);
873  }
874 
875  return vertexNormal;
876 }
877 
878 ////////////////////////////////////////////////////////////////////
879 // Function: MaxToEggConverter::get_vertex_weights
880 // Access: Private
881 // Description:
882 ////////////////////////////////////////////////////////////////////
883 void MaxToEggConverter::
884 get_vertex_weights(INode *max_node, EggVertexPool *vpool) {
885  //Try to get the weights out of a physique if one exists
886  Modifier *mod = FindSkinModifier(max_node, PHYSIQUE_CLASSID);
888 
889  if (mod) {
890  // create a physique export interface
891  IPhysiqueExport *pPhysiqueExport = (IPhysiqueExport *)mod->GetInterface(I_PHYINTERFACE);
892  if (pPhysiqueExport) {
893  // create a context export interface
894  IPhyContextExport *pContextExport =
895  (IPhyContextExport *)pPhysiqueExport->GetContextInterface(max_node);
896  if (pContextExport) {
897  // set the flags in the context export interface
898  pContextExport->ConvertToRigid(TRUE);
899  pContextExport->AllowBlending(TRUE);
900 
901  for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
902  EggVertex *vert = (*vi);
903  int max_vi = vert->get_external_index();
904 
905  // get the vertex export interface
906  IPhyVertexExport *pVertexExport =
907  (IPhyVertexExport *)pContextExport->GetVertexInterface(max_vi);
908  if (pVertexExport) {
909  int vertexType = pVertexExport->GetVertexType();
910 
911  // handle the specific vertex type
912  if(vertexType == RIGID_TYPE) {
913  // typecast to rigid vertex
914  IPhyRigidVertex *pTypeVertex = (IPhyRigidVertex *)pVertexExport;
915  INode *bone_node = pTypeVertex->GetNode();
916  MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node);
917  if (joint_node_desc){
918  EggGroup *joint = _tree.get_egg_group(joint_node_desc);
919  if (joint != (EggGroup *)NULL)
920  joint->ref_vertex(vert, 1.0f);
921  }
922  }
923  else if(vertexType == RIGID_BLENDED_TYPE) {
924  // typecast to blended vertex
925  IPhyBlendedRigidVertex *pTypeVertex = (IPhyBlendedRigidVertex *)pVertexExport;
926 
927  for (int ji = 0; ji < pTypeVertex->GetNumberNodes(); ++ji) {
928  PN_stdfloat weight = pTypeVertex->GetWeight(ji);
929  if (weight > 0.0f) {
930  INode *bone_node = pTypeVertex->GetNode(ji);
931  MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node);
932  if (joint_node_desc){
933  EggGroup *joint = _tree.get_egg_group(joint_node_desc);
934  if (joint != (EggGroup *)NULL)
935  joint->ref_vertex(vert, weight);
936  }
937  }
938  }
939  }
940  //Release the vertex interface
941  pContextExport->ReleaseVertexInterface(pVertexExport);
942  }
943  }
944  //Release the context interface
945  pPhysiqueExport->ReleaseContextInterface(pContextExport);
946  }
947  //Release the physique export interface
948  mod->ReleaseInterface(I_PHYINTERFACE, pPhysiqueExport);
949  }
950  }
951  else {
952  //No physique, try to find a skin
953  mod = FindSkinModifier(max_node, SKIN_CLASSID);
954  if (mod) {
955  ISkin *skin = (ISkin*)mod->GetInterface(I_SKIN);
956  if (skin) {
957  ISkinContextData *skinMC = skin->GetContextInterface(max_node);
958  if (skinMC) {
959  for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
960  EggVertex *vert = (*vi);
961  int max_vi = vert->get_external_index();
962 
963  for (int ji = 0; ji < skinMC->GetNumAssignedBones(max_vi); ++ji) {
964  PN_stdfloat weight = skinMC->GetBoneWeight(max_vi, ji);
965  if (weight > 0.0f) {
966  INode *bone_node = skin->GetBone(skinMC->GetAssignedBone(max_vi, ji));
967  MaxNodeDesc *joint_node_desc = _tree.find_joint(bone_node);
968  if (joint_node_desc){
969  EggGroup *joint = _tree.get_egg_group(joint_node_desc);
970  if (joint != (EggGroup *)NULL) {
971  joint->ref_vertex(vert, weight);
972  }
973  }
974  }
975  }
976  }
977  }
978  }
979  }
980  }
981 }
982 
983 
984 ////////////////////////////////////////////////////////////////////
985 // Function: MaxToEggConverter::get_material_textures
986 // Access: Private
987 // Description: Converts a Max material into a set of Panda textures
988 // and a primitive color.
989 ////////////////////////////////////////////////////////////////////
990 const MaxToEggConverter::PandaMaterial &MaxToEggConverter::
991 get_panda_material(Mtl *mtl, MtlID matID) {
992 
993  MaterialMap::iterator it = _material_map.find(mtl);
994  if (it != _material_map.end()) {
995  return (*it).second;
996  }
997 
998  PandaMaterial &pandaMat = _material_map[mtl];
999  pandaMat._color = LColor(1,1,1,1);
1000  pandaMat._any_diffuse = false;
1001  pandaMat._any_opacity = false;
1002  pandaMat._any_gloss = false;
1003  pandaMat._any_normal = false;
1004 
1005 
1006 
1007 
1008  // If it's a multi-material, dig down.
1009 
1010  while (( mtl != 0) && (mtl->ClassID() == Class_ID(MULTI_CLASS_ID, 0 ))) {
1011  if (matID < mtl->NumSubMtls()) {
1012  mtl = mtl->GetSubMtl(matID);
1013  } else {
1014  mtl = 0;
1015  }
1016  }
1017 
1018  // If it's a standard material, we're good.
1019 
1020  if ((mtl != 0) && (mtl->ClassID() == Class_ID(DMTL_CLASS_ID, 0 ))) {
1021  StdMat *maxMaterial = (StdMat*)mtl;
1022  analyze_diffuse_maps(pandaMat, maxMaterial->GetSubTexmap(ID_DI));
1023  analyze_opacity_maps(pandaMat, maxMaterial->GetSubTexmap(ID_OP));
1024  analyze_gloss_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SP));
1025  if (!pandaMat._any_gloss)
1026  analyze_gloss_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SS));
1027  if (!pandaMat._any_gloss)
1028  analyze_gloss_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SH));
1029  analyze_glow_maps(pandaMat, maxMaterial->GetSubTexmap(ID_SI));
1030  analyze_normal_maps(pandaMat, maxMaterial->GetSubTexmap(ID_BU));
1031  for (int i=0; i<pandaMat._texture_list.size(); i++) {
1032  EggTexture *src = pandaMat._texture_list[i];
1033  pandaMat._texture_list[i] =
1034  _textures.create_unique_texture(*src, ~EggTexture::E_tref_name);
1035  }
1036 
1037  // The existence of a texture on either color channel completely
1038  // replaces the corresponding flat color.
1039  if (!pandaMat._any_diffuse) {
1040  // Get the default diffuse color of the material without the texture map
1041  Point3 diffuseColor = Point3(maxMaterial->GetDiffuse(0));
1042  pandaMat._color[0] = diffuseColor.x;
1043  pandaMat._color[1] = diffuseColor.y;
1044  pandaMat._color[2] = diffuseColor.z;
1045  }
1046  if (!pandaMat._any_opacity) {
1047  pandaMat._color[3] = (maxMaterial->GetOpacity(_current_frame * GetTicksPerFrame()));
1048  }
1049  if (pandaMat._texture_list.size() < 1) {
1050  //if we don't have any maps whatsoever,
1051  //give the material a dummy channel
1052  //so that UV's get created
1053  pandaMat._map_channels.push_back(1);
1054  }
1055  return pandaMat;
1056  }
1057 
1058  // Otherwise, it's unrecognizable. Leave result blank.
1059  return pandaMat;
1060 }
1061 ////////////////////////////////////////////////////////////////////
1062 // Function: MayaShader::analyze_diffuse_maps
1063 // Access: Private
1064 // Description:
1065 ////////////////////////////////////////////////////////////////////
1066 void MaxToEggConverter::analyze_diffuse_maps(PandaMaterial &pandaMat, Texmap *mat) {
1067  if (mat == 0) return;
1068 
1069  if (mat->ClassID() == Class_ID(RGBMULT_CLASS_ID, 0)) {
1070  for (int i=0; i<mat->NumSubTexmaps(); i++) {
1071  analyze_diffuse_maps(pandaMat, mat->GetSubTexmap(i));
1072  }
1073  return;
1074  }
1075 
1076  if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
1077  pandaMat._any_diffuse = true;
1078  PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
1079 
1080  BitmapTex *diffuseTex = (BitmapTex *)mat;
1081 
1082  Filename fullpath, outpath;
1083 #ifdef _UNICODE
1084  Filename filename = Filename::from_os_specific_w(diffuseTex->GetMapName());
1085 #else
1086  Filename filename = Filename::from_os_specific(diffuseTex->GetMapName());
1087 #endif
1088  _options->_path_replace->full_convert_path(filename, get_model_path(),
1089  fullpath, outpath);
1090  tex->set_filename(outpath);
1091  tex->set_fullpath(fullpath);
1092 
1093  apply_texture_properties(*tex, diffuseTex->GetMapChannel());
1094  add_map_channel(pandaMat, diffuseTex->GetMapChannel());
1095 
1096  Bitmap *diffuseBitmap = diffuseTex->GetBitmap(0);
1097  if ( diffuseBitmap && diffuseBitmap->HasAlpha()) {
1098  tex->set_format(EggTexture::F_rgba);
1099  } else {
1100  tex->set_format(EggTexture::F_rgb);
1101  }
1102  tex->set_env_type(EggTexture::ET_modulate);
1103 
1104  pandaMat._texture_list.push_back(tex);
1105  }
1106 }
1107 
1108 ////////////////////////////////////////////////////////////////////
1109 // Function: MayaShader::analyze_opacity_maps
1110 // Access: Private
1111 // Description:
1112 ////////////////////////////////////////////////////////////////////
1113 void MaxToEggConverter::analyze_opacity_maps(PandaMaterial &pandaMat, Texmap *mat) {
1114  if (mat == 0) return;
1115 
1116  if (mat->ClassID() == Class_ID(RGBMULT_CLASS_ID, 0)) {
1117  for (int i=0; i<mat->NumSubTexmaps(); i++) {
1118  analyze_opacity_maps(pandaMat, mat->GetSubTexmap(i));
1119  }
1120  return;
1121  }
1122 
1123  if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
1124  pandaMat._any_opacity = true;
1125  BitmapTex *transTex = (BitmapTex *)mat;
1126 
1127  Filename fullpath, outpath;
1128 #ifdef _UNICODE
1129  Filename filename = Filename::from_os_specific_w(transTex->GetMapName());
1130 #else
1131  Filename filename = Filename::from_os_specific(transTex->GetMapName());
1132 #endif
1133  _options->_path_replace->full_convert_path(filename, get_model_path(),
1134  fullpath, outpath);
1135 
1136  // See if this opacity map already showed up.
1137  for (int i=0; i<pandaMat._texture_list.size(); i++) {
1138  EggTexture *tex = pandaMat._texture_list[i];
1139  if ((tex->get_env_type()==EggTexture::ET_modulate)&&(tex->get_fullpath() == fullpath)) {
1140  tex->set_format(EggTexture::F_rgba);
1141  return;
1142  }
1143  }
1144 
1145  // Try to find a diffuse map to pair this with as an alpha-texture.
1146  std::string uvname = get_uv_name(transTex->GetMapChannel());
1147  for (int i=0; i<pandaMat._texture_list.size(); i++) {
1148  EggTexture *tex = pandaMat._texture_list[i];
1149  if ((tex->get_env_type()==EggTexture::ET_modulate)&&
1150  (tex->get_format() == EggTexture::F_rgb)&&
1151  (tex->get_uv_name() == uvname)) {
1152  tex->set_format(EggTexture::F_rgba);
1153  tex->set_alpha_filename(outpath);
1154  tex->set_alpha_fullpath(fullpath);
1155  return;
1156  }
1157  }
1158 
1159  // Otherwise, just create it as an alpha-texture.
1160  PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
1161  tex->set_filename(outpath);
1162  tex->set_fullpath(fullpath);
1163 
1164  apply_texture_properties(*tex, transTex->GetMapChannel());
1165  add_map_channel(pandaMat, transTex->GetMapChannel());
1166  tex->set_format(EggTexture::F_alpha);
1167 
1168  pandaMat._texture_list.push_back(tex);
1169  }
1170 }
1171 
1172 ////////////////////////////////////////////////////////////////////
1173 // Function: MayaShader::analyze_glow_maps
1174 // Access: Private
1175 // Description:
1176 ////////////////////////////////////////////////////////////////////
1177 void MaxToEggConverter::analyze_glow_maps(PandaMaterial &pandaMat, Texmap *mat) {
1178  if (mat == 0) return;
1179 
1180  if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
1181  BitmapTex *gtex = (BitmapTex *)mat;
1182 
1183  Filename fullpath, outpath;
1184 #ifdef _UNICODE
1185  Filename filename = Filename::from_os_specific_w(gtex->GetMapName());
1186 #else
1187  Filename filename = Filename::from_os_specific(gtex->GetMapName());
1188 #endif
1189  _options->_path_replace->full_convert_path(filename, get_model_path(),
1190  fullpath, outpath);
1191 
1192  // Try to find a diffuse map to pair this with as an alpha-texture.
1193  std::string uvname = get_uv_name(gtex->GetMapChannel());
1194  for (int i=0; i<pandaMat._texture_list.size(); i++) {
1195  EggTexture *tex = pandaMat._texture_list[i];
1196  if ((tex->get_env_type()==EggTexture::ET_modulate)&&
1197  (tex->get_format() == EggTexture::F_rgb)&&
1198  (tex->get_uv_name() == uvname)) {
1199  tex->set_env_type(EggTexture::ET_modulate_glow);
1200  tex->set_format(EggTexture::F_rgba);
1201  tex->set_alpha_filename(outpath);
1202  tex->set_alpha_fullpath(fullpath);
1203  return;
1204  }
1205  }
1206 
1207  // Otherwise, just create it as a separate glow-texture.
1208  PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
1209  tex->set_env_type(EggTexture::ET_glow);
1210  tex->set_filename(outpath);
1211  tex->set_fullpath(fullpath);
1212  apply_texture_properties(*tex, gtex->GetMapChannel());
1213  add_map_channel(pandaMat, gtex->GetMapChannel());
1214  tex->set_format(EggTexture::F_alpha);
1215 
1216  pandaMat._texture_list.push_back(tex);
1217  }
1218 }
1219 
1220 ////////////////////////////////////////////////////////////////////
1221 // Function: MayaShader::analyze_gloss_maps
1222 // Access: Private
1223 // Description:
1224 ////////////////////////////////////////////////////////////////////
1225 void MaxToEggConverter::analyze_gloss_maps(PandaMaterial &pandaMat, Texmap *mat) {
1226  if (mat == 0) return;
1227 
1228  if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
1229  pandaMat._any_gloss = true;
1230  BitmapTex *gtex = (BitmapTex *)mat;
1231 
1232  Filename fullpath, outpath;
1233 #ifdef _UNICODE
1234  Filename filename = Filename::from_os_specific_w(gtex->GetMapName());
1235 #else
1236  Filename filename = Filename::from_os_specific(gtex->GetMapName());
1237 #endif
1238  _options->_path_replace->full_convert_path(filename, get_model_path(),
1239  fullpath, outpath);
1240 
1241  // Try to find a diffuse map to pair this with as an alpha-texture.
1242  std::string uvname = get_uv_name(gtex->GetMapChannel());
1243  for (int i=0; i<pandaMat._texture_list.size(); i++) {
1244  EggTexture *tex = pandaMat._texture_list[i];
1245  if ((tex->get_env_type()==EggTexture::ET_modulate)&&
1246  (tex->get_format() == EggTexture::F_rgb)&&
1247  (tex->get_uv_name() == uvname)) {
1248  tex->set_env_type(EggTexture::ET_modulate_gloss);
1249  tex->set_format(EggTexture::F_rgba);
1250  tex->set_alpha_filename(outpath);
1251  tex->set_alpha_fullpath(fullpath);
1252  return;
1253  }
1254  }
1255 
1256  // Otherwise, just create it as a separate gloss-texture.
1257  PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
1258  tex->set_env_type(EggTexture::ET_gloss);
1259  tex->set_filename(outpath);
1260  tex->set_fullpath(fullpath);
1261  apply_texture_properties(*tex, gtex->GetMapChannel());
1262  add_map_channel(pandaMat, gtex->GetMapChannel());
1263  tex->set_format(EggTexture::F_alpha);
1264 
1265  pandaMat._texture_list.push_back(tex);
1266  }
1267 }
1268 
1269 ////////////////////////////////////////////////////////////////////
1270 // Function: MayaShader::analyze_normal_maps
1271 // Access: Private
1272 // Description:
1273 ////////////////////////////////////////////////////////////////////
1274 void MaxToEggConverter::analyze_normal_maps(PandaMaterial &pandaMat, Texmap *mat) {
1275  if (mat == 0) return;
1276 
1277  if (mat->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) {
1278  pandaMat._any_normal = true;
1279  BitmapTex *ntex = (BitmapTex *)mat;
1280 
1281  Filename fullpath, outpath;
1282 #ifdef _UNICODE
1283  Filename filename = Filename::from_os_specific_w(ntex->GetMapName());
1284 #else
1285  Filename filename = Filename::from_os_specific(ntex->GetMapName());
1286 #endif
1287  _options->_path_replace->full_convert_path(filename, get_model_path(),
1288  fullpath, outpath);
1289 
1290  PT(EggTexture) tex = new EggTexture(generate_tex_name(), "");
1291  tex->set_env_type(EggTexture::ET_normal);
1292  tex->set_filename(outpath);
1293  tex->set_fullpath(fullpath);
1294  apply_texture_properties(*tex, ntex->GetMapChannel());
1295  add_map_channel(pandaMat, ntex->GetMapChannel());
1296  tex->set_format(EggTexture::F_rgb);
1297 
1298  pandaMat._texture_list.push_back(tex);
1299  }
1300 }
1301 
1302 ////////////////////////////////////////////////////////////////////
1303 // Function: MayaShader::add_map_channel
1304 // Access: Private
1305 // Description: Adds the specified map channel to the map channel
1306 // list, if it's not already there.
1307 ////////////////////////////////////////////////////////////////////
1308 void MaxToEggConverter::add_map_channel(PandaMaterial &pandaMat, int chan) {
1309  for (int i=0; i<pandaMat._map_channels.size(); i++) {
1310  if (pandaMat._map_channels[i] == chan) {
1311  return;
1312  }
1313  }
1314  pandaMat._map_channels.push_back(chan);
1315 }
1316 
1317 ////////////////////////////////////////////////////////////////////
1318 // Function: MayaShader::generate_tex_name
1319 // Access: Private
1320 // Description: Generates an arbitrary unused texture name.
1321 ////////////////////////////////////////////////////////////////////
1322 std::string MaxToEggConverter::generate_tex_name() {
1323  ostringstream name_strm;
1324  name_strm << "Tex" << ++_cur_tref;
1325  return name_strm.str();
1326 }
1327 
1328 ////////////////////////////////////////////////////////////////////
1329 // Function: MayaShader::get_uv_name
1330 // Access: Private
1331 // Description: Returns the UV-name of the nth map-channel.
1332 ////////////////////////////////////////////////////////////////////
1333 std::string MaxToEggConverter::get_uv_name(int channel) {
1334  ostringstream uvname;
1335  uvname << "m" << channel;
1336  return uvname.str();
1337 }
1338 
1339 ////////////////////////////////////////////////////////////////////
1340 // Function: MayaShader::apply_texture_properties
1341 // Access: Private
1342 // Description: Applies all the appropriate texture properties to the
1343 // EggTexture object, including wrap modes and texture
1344 // matrix.
1345 ////////////////////////////////////////////////////////////////////
1346 void MaxToEggConverter::
1347 apply_texture_properties(EggTexture &tex, int channel) {
1348 
1349  // we leave a channel 1 for texture swapping, so don't name it
1350  if(channel == 1)
1351  tex.set_uv_name("");
1352  else
1353  tex.set_uv_name(get_uv_name(channel));
1354 
1355  tex.set_minfilter(EggTexture::FT_linear_mipmap_linear);
1356  tex.set_magfilter(EggTexture::FT_linear);
1357 
1358  EggTexture::WrapMode wrap_u = EggTexture::WM_repeat;
1359  EggTexture::WrapMode wrap_v = EggTexture::WM_repeat;
1360 
1361  tex.set_wrap_u(wrap_u);
1362  tex.set_wrap_v(wrap_v);
1363 }
1364 
1365 
1366 ////////////////////////////////////////////////////////////////////
1367 // Function: MayaShader::reparent_decals
1368 // Access: Private
1369 // Description: Recursively walks the egg hierarchy, reparenting
1370 // "decal" type nodes below their corresponding
1371 // "decalbase" type nodes, and setting the flags.
1372 //
1373 // Returns true on success, false if some nodes were
1374 // incorrect.
1375 ////////////////////////////////////////////////////////////////////
1376 bool MaxToEggConverter::
1377 reparent_decals(EggGroupNode *egg_parent) {
1378  bool okflag = true;
1379 
1380  // First, walk through all children of this node, looking for the
1381  // one decal base, if any.
1382  EggGroup *decal_base = (EggGroup *)NULL;
1383  pvector<EggGroup *> decal_children;
1384 
1385  EggGroupNode::iterator ci;
1386  for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) {
1387  EggNode *child = (*ci);
1388  if (child->is_of_type(EggGroup::get_class_type())) {
1389  EggGroup *child_group = (EggGroup *) child;
1390  if (child_group->has_object_type("decalbase")) {
1391  if (decal_base != (EggNode *)NULL) {
1392  // error
1393  okflag = false;
1394  }
1395  child_group->remove_object_type("decalbase");
1396  decal_base = child_group;
1397 
1398  } else if (child_group->has_object_type("decal")) {
1399  child_group->remove_object_type("decal");
1400  decal_children.push_back(child_group);
1401  }
1402  }
1403  }
1404 
1405  if (decal_base == (EggGroup *)NULL) {
1406  if (!decal_children.empty()) {
1407  // warning
1408  }
1409 
1410  } else {
1411  if (decal_children.empty()) {
1412  // warning
1413 
1414  } else {
1415  // All the decal children get moved to be a child of decal base.
1416  // This usually will not affect the vertex positions, but it
1417  // could if the decal base has a transform and the decal child
1418  // is an instance node. So don't do that.
1420  for (di = decal_children.begin(); di != decal_children.end(); ++di) {
1421  EggGroup *child_group = (*di);
1422  decal_base->add_child(child_group);
1423  }
1424 
1425  // Also set the decal state on the base.
1426  decal_base->set_decal_flag(true);
1427  }
1428  }
1429 
1430  // Now recurse on each of the child nodes.
1431  for (ci = egg_parent->begin(); ci != egg_parent->end(); ++ci) {
1432  EggNode *child = (*ci);
1433  if (child->is_of_type(EggGroupNode::get_class_type())) {
1434  EggGroupNode *child_group = (EggGroupNode *) child;
1435  if (!reparent_decals(child_group)) {
1436  okflag = false;
1437  }
1438  }
1439  }
1440 
1441  return okflag;
1442 }
1443 
1444 Modifier* MaxToEggConverter::FindSkinModifier (INode* node, const Class_ID &type)
1445 {
1446  // Get object from node. Abort if no object.
1447  Object* pObj = node->GetObjectRef();
1448  if (!pObj) return NULL;
1449 
1450  // Is derived object ?
1451  while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID) {
1452  // Yes -> Cast.
1453  IDerivedObject* pDerObj = static_cast<IDerivedObject*>(pObj);
1454 
1455  // Iterate over all entries of the modifier stack.
1456  for (int stackId = 0; stackId < pDerObj->NumModifiers(); ++stackId) {
1457  // Get current modifier.
1458  Modifier* mod = pDerObj->GetModifier(stackId);
1459 
1460  // Is this what we are looking for?
1461  if (mod->ClassID() == type )
1462  return mod;
1463  }
1464 
1465  // continue with next derived object
1466  pObj = pDerObj->GetObjRef();
1467  }
1468 
1469  // Not found.
1470  return NULL;
1471 }
void set_fullpath(const Filename &fullpath)
Records the full pathname to the file, for the benefit of get_fullpath().
string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:424
void setup(int order, int num_knots)
Prepares a new curve definition with the indicated order and number of knots.
const LMatrix4d & get_transform3d() const
Returns the overall transform as a 4x4 matrix.
Definition: eggTransform.I:251
bool convert(MaxEggOptions *options)
Fills up the egg_data structure according to the global Max model data.
This is an iterator adaptor that converts any iterator that returns a pair (e.g.
EggGroup * get_egg_group(MaxNodeDesc *node_desc)
Returns the EggGroupNode corresponding to the group or joint for the indicated 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...
bool add_data(const LMatrix4d &mat)
Adds a new matrix to the table, by adding a new row to each of the subtables.
This is a 4-by-4 transform matrix.
Definition: lmatrix.h:4716
void add_texture(EggTexture *texture)
Applies the indicated texture to the primitive.
Definition: eggPrimitive.I:197
bool is_joint() const
Returns true if the node should be treated as a joint by the converter.
const LMatrix4d & get_node_frame_inv() const
Returns the inverse of the matrix returned by get_node_frame().
Definition: eggNode.I:183
void set_pos(double pos)
Sets the vertex position.
Definition: eggVertex.I:54
bool almost_equal(const LMatrix4d &other, double threshold) const
Returns true if two matrices are memberwise equal within a specified tolerance.
Definition: lmatrix.cxx:2058
bool has_object_type(const string &object_type) const
Returns true if the indicated object type has been added to the group, or false otherwise.
Definition: eggGroup.cxx:157
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:51
This is a four-component point in space.
Definition: lpoint4.h:457
int get_num_nodes() const
Returns the total number of nodes in the hierarchy, not counting the root node.
EggXfmSAnim * get_egg_anim(MaxNodeDesc *node_desc)
Returns the anim table corresponding to the joint for the indicated node.
Defines a texture map that may be applied to geometry.
Definition: eggTexture.h:33
void set_vertex(int index, EggVertex *vertex)
Replaces a particular vertex based on its index number in the list of vertices.
Definition: eggPrimitive.I:455
bool is_node_joint() const
Returns true if the node is the parent or ancestor of a joint.
void clear()
Removes all textures from the collection.
Definition: shader.h:50
bool build_complete_hierarchy(INode *root, ULONG *selection_list, int len)
Walks through the complete Max hierarchy and builds up the corresponding tree.
void set_alpha_fullpath(const Filename &fullpath)
Records the full pathname to the file, for the benefit of get_alpha_fullpath().
Definition: eggTexture.I:890
MaxNodeDesc * find_joint(INode *max_node)
The recursive implementation of build_node().
This is a two-component point in space.
Definition: lpoint2.h:424
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition: eggVertex.I:160
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:365
This is the primary interface into all the egg data, and the root of the egg file structure...
Definition: eggData.h:41
void set_knot(int k, double value)
Resets the value of the indicated knot as indicated.
Definition: eggNurbsCurve.I:75
iterator end() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
void set_coordinate_system(CoordinateSystem coordsys)
Changes the coordinate system of the EggData.
Definition: eggData.cxx:279
bool remove_object_type(const string &object_type)
Removes the first instance of the indicated object type from the group if it is present.
Definition: eggGroup.cxx:176
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:39
iterator begin() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
MaxNodeDesc * get_node(int n) const
Returns the nth node in the hierarchy, in an arbitrary ordering.
void set_bface_flag(bool flag)
Sets the backfacing flag of the polygon.
Definition: eggPrimitive.I:289
void set_alpha_filename(const Filename &filename)
Specifies a separate file that will be loaded in with the 1- or 3-component texture and applied as th...
Definition: eggTexture.I:820
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:36
const string & get_uv_name() const
Returns the texcoord name that has been specified for this texture, or the empty string if no texcoor...
Definition: eggTexture.I:705
void clear_egg(EggData *egg_data, EggGroupNode *egg_root, EggGroupNode *skeleton_node)
Removes all of the references to generated egg structures from the tree, and prepares the tree for ge...
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
static const LMatrix4d & ident_mat()
Returns an identity matrix.
Definition: lmatrix.h:5168
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal...
Definition: eggVertex.h:41
bool invert_in_place()
Inverts the current matrix.
Definition: lmatrix.h:6553
This corresponds to an <Xfm$Anim_S$> entry, which is a collection of up to nine <S$Anim> entries that...
Definition: eggXfmSAnim.h:33
void set_uv_name(const string &uv_name)
Specifies the named set of texture coordinates that this texture will use when it is applied to geome...
Definition: eggTexture.I:664
Describes a single instance of a node in the Max scene graph, relating it to the corresponding egg st...
Definition: maxNodeDesc.h:26
A parametric NURBS curve.
Definition: eggNurbsCurve.h:28
void optimize()
Optimizes the table by collapsing redundant sub-tables.
Definition: eggXfmSAnim.cxx:75
const Filename & get_fullpath() const
Returns the full pathname to the file, if it is known; otherwise, returns the same thing as get_filen...
A single polygon.
Definition: eggPolygon.h:26
This corresponds to a.
Definition: eggTable.h:31
This is the base class for all three-component vectors and points.
Definition: lvecBase4.h:111
This is a three-component vector distance (as opposed to a three-component point, which represents a ...
Definition: lvector3.h:760
INode * get_max_node() const
Returns the INode associated with this node.
This is a three-component point in space (as opposed to a three-component vector, which represents a ...
Definition: lpoint3.h:544
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:38
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:678
bool has_max_node() const
Returns true if a Max INode has been associated with this node, false otherwise.
static Filename from_os_specific_w(const wstring &os_specific, Type type=T_general)
The wide-string variant of from_os_specific().
Definition: filename.cxx:401
int get_external_index() const
Returns the number set by set_external_index().
Definition: eggVertex.I:376
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:63
void add_matrix4(const LMatrix4d &mat)
Appends an arbitrary 4x4 matrix to the current transform.
Definition: eggTransform.I:149
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition: eggVertex.I:239
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.
A collection of vertices.
Definition: eggVertexPool.h:46
EggVertex * add_vertex(EggVertex *vertex)
Adds the indicated vertex to the end of the primitive&#39;s list of vertices, and returns it...
EggVertex * get_vertex(int index) const
Returns a particular index based on its index number.
Definition: eggPrimitive.I:466
const LMatrix4d & get_vertex_frame_inv() const
Returns the inverse of the matrix returned by get_vertex_frame().
Definition: eggNode.I:167
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332