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 }
This is the primary interface into all the egg data, and the root of the egg file structure.
Definition: eggData.h:37
set_coordinate_system
Changes the coordinate system of the EggData.
Definition: eggData.h:73
void set_fullpath(const Filename &fullpath)
Records the full pathname to the file, for the benefit of get_fullpath().
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 base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:46
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:34
void ref_vertex(EggVertex *vert, double membership=1.0)
Adds the vertex to the set of those referenced by the group, at the indicated membership level.
Definition: eggGroup.cxx:608
bool has_object_type(const std::string &object_type) const
Returns true if the indicated object type has been added to the group, or false otherwise.
Definition: eggGroup.cxx:145
bool remove_object_type(const std::string &object_type)
Removes the first instance of the indicated object type from the group if it is present.
Definition: eggGroup.cxx:161
A base class for things that may be directly added into the egg hierarchy.
Definition: eggNode.h:36
const LMatrix4d & get_vertex_frame_inv() const
Returns the inverse of the matrix returned by get_vertex_frame().
Definition: eggNode.I:135
const LMatrix4d & get_node_frame_inv() const
Returns the inverse of the matrix returned by get_node_frame().
Definition: eggNode.I:149
A parametric NURBS curve.
Definition: eggNurbsCurve.h:26
void setup(int order, int num_knots)
Prepares a new curve definition with the indicated order and number of knots.
set_knot
Resets the value of the indicated knot as indicated.
Definition: eggNurbsCurve.h:58
A single polygon.
Definition: eggPolygon.h:24
set_bface_flag
Sets the backfacing flag of the polygon.
Definition: eggPrimitive.h:116
get_vertex
Returns a particular index based on its index number.
Definition: eggPrimitive.h:187
set_vertex
Replaces a particular vertex based on its index number in the list of vertices.
Definition: eggPrimitive.h:191
EggVertex * add_vertex(EggVertex *vertex)
Adds the indicated vertex to the end of the primitive's list of vertices, and returns it.
void add_texture(EggTexture *texture)
Applies the indicated texture to the primitive.
Definition: eggPrimitive.I:162
This corresponds to a.
Definition: eggTable.h:27
void clear()
Removes all textures from the collection.
EggTexture * create_unique_texture(const EggTexture &copy, int eq)
Creates a new texture if there is not already one equivalent (according to eq, see EggTexture::is_equ...
Defines a texture map that may be applied to geometry.
Definition: eggTexture.h:30
get_uv_name
Returns the texcoord name that has been specified for this texture, or the empty string if no texcoor...
Definition: eggTexture.h:341
set_uv_name
Specifies the named set of texture coordinates that this texture will use when it is applied to geome...
Definition: eggTexture.h:341
set_alpha_fullpath
Records the full pathname to the file, for the benefit of get_alpha_fullpath().
Definition: eggTexture.h:348
set_alpha_filename
Specifies a separate file that will be loaded in with the 1- or 3-component texture and applied as th...
Definition: eggTexture.h:347
const LMatrix4d & get_transform3d() const
Returns the overall transform as a 4x4 matrix.
Definition: eggTransform.I:212
void add_matrix4(const LMatrix4d &mat)
Appends an arbitrary 4x4 matrix to the current transform.
Definition: eggTransform.I:132
A collection of vertices.
Definition: eggVertexPool.h:41
iterator end() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
iterator begin() const
Returns an iterator that can be used to traverse through all the vertices in the pool.
EggVertex * create_unique_vertex(const EggVertex &copy)
Creates a new vertex in the pool that is a copy of the indicated one and returns it.
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition: eggVertex.h:39
int get_external_index() const
Returns the number set by set_external_index().
Definition: eggVertex.I:300
void set_external_index(int external_index)
Sets a special index number that is associated with the EggVertex (but is not written to the egg file...
Definition: eggVertex.I:292
void set_pos(double pos)
Sets the vertex position.
Definition: eggVertex.I:42
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition: eggVertex.I:131
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition: eggVertex.I:193
This corresponds to an <Xfm$Anim_S$> entry, which is a collection of up to nine <S$Anim> entries that...
Definition: eggXfmSAnim.h:28
void optimize()
Optimizes the table by collapsing redundant sub-tables.
Definition: eggXfmSAnim.cxx:63
bool add_data(const LMatrix4d &mat)
Adds a new matrix to the table, by adding a new row to each of the subtables.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
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
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
std::string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:358
Describes a single instance of a node in the Max scene graph, relating it to the corresponding egg st...
Definition: maxNodeDesc.h:22
bool is_joint() const
Returns true if the node should be treated as a joint by the converter.
bool is_node_joint() const
Returns true if the node is the parent or ancestor of a joint.
INode * get_max_node() const
Returns the INode associated with this node.
bool has_max_node() const
Returns true if a Max INode has been associated with this node, false otherwise.
Definition: maxNodeDesc.cxx:93
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
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.
EggGroup * get_egg_group(MaxNodeDesc *node_desc)
Returns the EggGroupNode corresponding to the group or joint for the indicated node.
MaxNodeDesc * find_joint(INode *max_node)
The recursive implementation of build_node().
MaxNodeDesc * get_node(int n) const
Returns the nth node in the hierarchy, in an arbitrary ordering.
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...
bool convert(MaxEggOptions *options)
Fills up the egg_data structure according to the global Max model data.
Definition: shader.h:49
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
This is an iterator adaptor that converts any iterator that returns a pair (e.g.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.