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