Panda3D
eggSaver.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 eggSaver.cxx
10  * @author drose
11  * @date 2012-12-19
12  */
13 
14 #include "eggSaver.h"
15 
16 #include "pandaNode.h"
17 #include "workingNodePath.h"
18 #include "nodePath.h"
19 #include "billboardEffect.h"
20 #include "renderEffects.h"
21 #include "transformState.h"
22 #include "colorScaleAttrib.h"
23 #include "colorAttrib.h"
24 #include "materialAttrib.h"
25 #include "textureAttrib.h"
26 #include "cullBinAttrib.h"
27 #include "cullFaceAttrib.h"
28 #include "transparencyAttrib.h"
29 #include "depthTestAttrib.h"
30 #include "depthOffsetAttrib.h"
31 #include "depthWriteAttrib.h"
32 #include "lodNode.h"
33 #include "switchNode.h"
34 #include "sequenceNode.h"
35 #include "uvScrollNode.h"
36 #include "collisionNode.h"
37 #include "collisionPolygon.h"
38 #include "collisionPlane.h"
39 #include "collisionSphere.h"
40 #include "collisionBox.h"
41 #include "collisionInvSphere.h"
42 #include "collisionCapsule.h"
43 #include "textureStage.h"
44 #include "geomNode.h"
45 #include "geom.h"
46 #include "geomTriangles.h"
47 #include "geomPatches.h"
48 #include "geomPoints.h"
49 #include "geomLines.h"
50 #include "geomVertexReader.h"
51 #include "transformTable.h"
52 #include "modelNode.h"
53 #include "animBundleNode.h"
55 #include "characterJoint.h"
56 #include "character.h"
57 #include "string_utils.h"
58 #include "bamFile.h"
59 #include "bamCacheRecord.h"
60 #include "eggSAnimData.h"
61 #include "eggXfmAnimData.h"
62 #include "eggXfmSAnim.h"
63 #include "eggGroup.h"
64 #include "eggVertexPool.h"
65 #include "eggVertex.h"
66 #include "eggPrimitive.h"
67 #include "eggPolygon.h"
68 #include "eggPatch.h"
69 #include "eggPoint.h"
70 #include "eggLine.h"
71 #include "eggTexture.h"
72 #include "eggMaterial.h"
73 #include "eggRenderMode.h"
74 #include "eggTable.h"
75 #include "dcast.h"
76 
77 using std::pair;
78 using std::string;
79 
80 /**
81  *
82  */
83 EggSaver::
84 EggSaver(EggData *data) :
85  _data(data)
86 {
87  if (_data == nullptr) {
88  _data = new EggData;
89  }
90 }
91 
92 /**
93  * Adds the scene graph rooted at the indicated node to the accumulated egg
94  * data within this object. Call get_egg_data() to retrieve the result.
95  */
97 add_node(PandaNode *node) {
98  _vpool = new EggVertexPool(node->get_name());
99  _data->add_child(_vpool);
100 
101  NodePath root(node);
102  convert_node(WorkingNodePath(root), _data, false, nullptr);
103 
104  // Remove the vertex pool if it has no vertices.
105  if (_vpool->empty()) {
106  _data->remove_child(_vpool);
107  }
108  _vpool = nullptr;
109 }
110 
111 /**
112  * Adds the scene graph rooted at the indicated node (but without the node
113  * itself) to the accumulated egg data within this object. Call
114  * get_egg_data() to retrieve the result.
115  */
117 add_subgraph(PandaNode *root) {
118  _vpool = new EggVertexPool(root->get_name());
119  _data->add_child(_vpool);
120 
121  NodePath root_path(root);
122  recurse_nodes(root_path, _data, false, nullptr);
123 
124  // Remove the vertex pool if it has no vertices.
125  if (_vpool->empty()) {
126  _data->remove_child(_vpool);
127  }
128  _vpool = nullptr;
129 }
130 
131 /**
132  * Converts the indicated node to the corresponding Egg constructs, by first
133  * determining what kind of node it is.
134  */
135 void EggSaver::
136 convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
137  bool has_decal, CharacterJointMap *joint_map) {
138  PandaNode *node = node_path.node();
139  if (node->is_geom_node()) {
140  convert_geom_node(DCAST(GeomNode, node), node_path, egg_parent, has_decal, joint_map);
141 
142  } else if (node->is_of_type(LODNode::get_class_type())) {
143  convert_lod_node(DCAST(LODNode, node), node_path, egg_parent, has_decal, joint_map);
144 
145  } else if (node->is_of_type(SequenceNode::get_class_type())) {
146  convert_sequence_node(DCAST(SequenceNode, node), node_path, egg_parent, has_decal, joint_map);
147 
148  } else if (node->is_of_type(SwitchNode::get_class_type())) {
149  convert_switch_node(DCAST(SwitchNode, node), node_path, egg_parent, has_decal, joint_map);
150 
151  } else if (node->is_of_type(CollisionNode::get_class_type())) {
152  convert_collision_node(DCAST(CollisionNode, node), node_path, egg_parent, has_decal, joint_map);
153 
154  } else if (node->is_of_type(AnimBundleNode::get_class_type())) {
155  convert_anim_node(DCAST(AnimBundleNode, node), node_path, egg_parent, has_decal);
156 
157  } else if (node->is_of_type(Character::get_class_type())) {
158  convert_character_node(DCAST(Character, node), node_path, egg_parent, has_decal);
159 
160  } else {
161  // Just a generic node.
162  EggGroup *egg_group = new EggGroup(node->get_name());
163  egg_parent->add_child(egg_group);
164  apply_node_properties(egg_group, node);
165 
166  recurse_nodes(node_path, egg_group, has_decal, joint_map);
167  }
168 }
169 
170 /**
171  * Converts the indicated LODNode to the corresponding Egg constructs.
172  */
173 void EggSaver::
174 convert_lod_node(LODNode *node, const WorkingNodePath &node_path,
175  EggGroupNode *egg_parent, bool has_decal,
176  CharacterJointMap *joint_map) {
177  // An LOD node gets converted to an ordinary EggGroup, but we apply the
178  // appropriate switch conditions to each of our children.
179  EggGroup *egg_group = new EggGroup(node->get_name());
180  egg_parent->add_child(egg_group);
181  apply_node_properties(egg_group, node);
182 
183  int num_children = node->get_num_children();
184  int num_switches = node->get_num_switches();
185 
186  num_children = std::min(num_children, num_switches);
187 
188  for (int i = 0; i < num_children; i++) {
189  PandaNode *child = node->get_child(i);
190 
191  // Convert just this one node to an EggGroup.
192  PT(EggGroup) next_group = new EggGroup;
193  convert_node(WorkingNodePath(node_path, child), next_group, has_decal, joint_map);
194 
195  if (next_group->size() == 1) {
196  // If we have exactly one child, and that child is an EggGroup,
197  // collapse.
198  EggNode *child_node = *next_group->begin();
199  if (child_node->is_of_type(EggGroup::get_class_type())) {
200  PT(EggGroup) child = DCAST(EggGroup, child_node);
201  next_group->remove_child(child.p());
202  next_group = child;
203  }
204  }
205 
206  // Now set up the switching properties appropriately.
207  PN_stdfloat in = node->get_in(i);
208  PN_stdfloat out = node->get_out(i);
209  LPoint3 center = node->get_center();
210  EggSwitchConditionDistance dist(in, out, LCAST(double, center));
211  next_group->set_lod(dist);
212  egg_group->add_child(next_group.p());
213  }
214 }
215 
216 /**
217  * Converts the indicated SequenceNode to the corresponding Egg constructs.
218  */
219 void EggSaver::
220 convert_sequence_node(SequenceNode *node, const WorkingNodePath &node_path,
221  EggGroupNode *egg_parent, bool has_decal,
222  CharacterJointMap *joint_map) {
223  // A sequence node gets converted to an ordinary EggGroup, we only apply the
224  // appropriate switch attributes to turn it into a sequence
225  EggGroup *egg_group = new EggGroup(node->get_name());
226  egg_parent->add_child(egg_group);
227  apply_node_properties(egg_group, node);
228 
229  // turn it into a sequence with the right frame-rate
230  egg_group->set_switch_flag(true);
231  egg_group->set_switch_fps(node->get_frame_rate());
232 
233  int num_children = node->get_num_children();
234 
235  for (int i = 0; i < num_children; i++) {
236  PandaNode *child = node->get_child(i);
237 
238  // Convert just this one node to an EggGroup.
239  PT(EggGroup) next_group = new EggGroup;
240  convert_node(WorkingNodePath(node_path, child), next_group, has_decal, joint_map);
241 
242  egg_group->add_child(next_group.p());
243  }
244 
245 }
246 
247 /**
248  * Converts the indicated SwitchNode to the corresponding Egg constructs.
249  */
250 void EggSaver::
251 convert_switch_node(SwitchNode *node, const WorkingNodePath &node_path,
252  EggGroupNode *egg_parent, bool has_decal,
253  CharacterJointMap *joint_map) {
254  // A sequence node gets converted to an ordinary EggGroup, we only apply the
255  // appropriate switch attributes to turn it into a sequence
256  EggGroup *egg_group = new EggGroup(node->get_name());
257  egg_parent->add_child(egg_group);
258  apply_node_properties(egg_group, node);
259 
260  // turn it into a switch..
261  egg_group->set_switch_flag(true);
262 
263  int num_children = node->get_num_children();
264 
265  for (int i = 0; i < num_children; i++) {
266  PandaNode *child = node->get_child(i);
267 
268  // Convert just this one node to an EggGroup.
269  PT(EggGroup) next_group = new EggGroup;
270  convert_node(WorkingNodePath(node_path, child), next_group, has_decal, joint_map);
271 
272  egg_group->add_child(next_group.p());
273  }
274 }
275 
276 /**
277  * Converts the indicated AnimationGroupNodes to the corresponding Egg
278  * constructs.
279  */
280 EggGroupNode * EggSaver::convert_animGroup_node(AnimGroup *animGroup, double fps ) {
281  int num_children = animGroup->get_num_children();
282 
283  EggGroupNode *eggNode = nullptr;
284  if (animGroup->is_of_type(AnimBundle::get_class_type())) {
285  EggTable *eggTable = new EggTable(animGroup->get_name());
286  eggTable ->set_table_type(EggTable::TT_bundle);
287  eggNode = eggTable;
288  } else if (animGroup->is_of_type(AnimGroup::get_class_type())) {
289  EggTable *eggTable = new EggTable(animGroup->get_name());
290  eggTable ->set_table_type(EggTable::TT_table);
291  eggNode = eggTable;
292  }
293 
294  if (animGroup->is_of_type(AnimChannelMatrixXfmTable::get_class_type())) {
295  AnimChannelMatrixXfmTable *xmfTable = DCAST(AnimChannelMatrixXfmTable, animGroup);
296  EggXfmSAnim *egg_anim = new EggXfmSAnim("xform");
297  egg_anim->set_fps(fps);
298  for (int i = 0; i < num_matrix_components; i++) {
299  string componentName(1, matrix_component_letters[i]);
300  char table_id = matrix_component_letters[i];
301  CPTA_stdfloat table = xmfTable->get_table(table_id);
302 
303  if (xmfTable->has_table(table_id)) {
304  for (unsigned int j = 0; j < table.size(); j++) {
305  egg_anim->add_component_data(componentName, table[(int)j]);
306  }
307  }
308  }
309  eggNode->add_child(egg_anim);
310  }
311  for (int i = 0; i < num_children; i++) {
312  AnimGroup *animChild = animGroup->get_child(i);
313  EggGroupNode *eggChildNode = convert_animGroup_node(animChild, fps);
314  if (eggChildNode!=nullptr) {
315  nassertr(eggNode!=nullptr, nullptr);
316  eggNode->add_child(eggChildNode);
317  }
318  }
319  return eggNode;
320 }
321 
322 /**
323  * Converts the indicated AnimNode to the corresponding Egg constructs.
324  */
325 void EggSaver::
326 convert_anim_node(AnimBundleNode *node, const WorkingNodePath &node_path,
327  EggGroupNode *egg_parent, bool has_decal) {
328 
329  // A sequence node gets converted to an ordinary EggGroup, we only apply the
330  // appropriate switch attributes to turn it into a sequence
331  EggTable *eggTable = new EggTable();
332  // egg_parent->add_child(eggTable);
333  _data->add_child(eggTable);
334 
335  AnimBundle *animBundle = node->get_bundle();
336  // turn it into a switch.. egg_group->set_switch_flag(true);
337 
338  EggGroupNode *eggAnimation = convert_animGroup_node(animBundle, animBundle->get_base_frame_rate());
339  eggTable->add_child(eggAnimation);
340 }
341 
342 /**
343  * Converts the indicated Character Bundle to the corresponding Egg joints
344  * structure.
345  */
346 void EggSaver::
347 convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, CharacterJointMap *joint_map) {
348  convert_character_bundle(bundleNode, egg_parent, joint_map, nullptr);
349 }
350 
351 /**
352  * Converts the indicated Character Bundle to the corresponding Egg joints
353  * structure.
354  */
355 void EggSaver::
356 convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent,
357  CharacterJointMap *joint_map, const CharacterJoint *parent_joint) {
358  int num_children = bundleNode->get_num_children();
359 
360  const CharacterJoint *character_joint = nullptr;
361 
362  EggGroupNode *joint_group = egg_parent;
363  if (bundleNode->is_of_type(CharacterJoint::get_class_type())) {
364  character_joint = DCAST(CharacterJoint, bundleNode);
365 
366  EggGroup *joint = new EggGroup(bundleNode->get_name());
367  joint->set_group_type(EggGroup::GT_joint);
368 
369  // The default_value originally passed to the CharacterJoint is what is used
370  // for skinning. However, the _default_value can be changed after joint
371  // construction (such as via a <DefaultPose>), so we can't just pull the
372  // current _default_value.
373  //
374  // We have to instead work back from the _initial_net_transform_inverse,
375  // which is computed at construction time from the original default_value:
376  //
377  // _net_transform = default_value * parent_joint->_net_transform;
378  // _initial_net_transform_inverse = invert(_net_transform);
379  //
380  // So we should be able to reconstruct the original default_value like so:
381  //
382  // default_value = invert(_initial_net_transform_inverse)
383  // * parent_joint->_initial_net_transform_inverse;
384  //
385  LMatrix4d net_transform = invert(LCAST(double, character_joint->_initial_net_transform_inverse));
386  if (parent_joint != nullptr) {
387  if (parent_joint->_initial_net_transform_inverse != character_joint->_initial_net_transform_inverse) {
388  LMatrix4d parent_inverse = LCAST(double, parent_joint->_initial_net_transform_inverse);
389  joint->add_matrix4(net_transform * parent_inverse);
390  }
391  } else if (!net_transform.is_identity()) {
392  joint->add_matrix4(net_transform);
393  }
394 
395  // The joint's _default_value, if different, goes into a <DefaultPose>.
396  LMatrix4d default_pose = LCAST(double, character_joint->_default_value);
397  if (default_pose != joint->get_transform3d()) {
398  EggTransform transform;
399  transform.add_matrix4(LCAST(double, default_pose));
400  joint->set_default_pose(transform);
401  }
402 
403  joint_group = joint;
404  egg_parent->add_child(joint_group);
405  if (joint_map != nullptr) {
406  CharacterJointMap::iterator mi = joint_map->find(character_joint);
407  if (mi != joint_map->end()) {
408  pvector<pair<EggVertex*,PN_stdfloat> > &joint_vertices = (*mi).second;
409  pvector<pair<EggVertex*,PN_stdfloat> >::const_iterator vi;
410  for (vi = joint_vertices.begin(); vi != joint_vertices.end(); ++vi) {
411  joint->set_vertex_membership((*vi).first, (*vi).second);
412  }
413  }
414  }
415  }
416 
417  for (int i = 0; i < num_children ; i++) {
418  PartGroup *partGroup= bundleNode->get_child(i);
419  convert_character_bundle(partGroup, joint_group, joint_map, character_joint);
420  }
421 
422 }
423 
424 /**
425  * Converts the indicated Character to the corresponding Egg constructs.
426  */
427 void EggSaver::
428 convert_character_node(Character *node, const WorkingNodePath &node_path,
429  EggGroupNode *egg_parent, bool has_decal) {
430 
431  // A sequence node gets converted to an ordinary EggGroup, we only apply the
432  // appropriate switch attributes to turn it into a sequence.
433  // We have to use DT_structured since it is the only mode that preserves the
434  // node hierarchy, including LODNodes and CollisionNodes that may be under
435  // this Character node.
436  EggGroup *egg_group = new EggGroup(node->get_name());
437  egg_group->set_dart_type(EggGroup::DT_structured);
438  egg_parent->add_child(egg_group);
439  apply_node_properties(egg_group, node);
440 
441  CharacterJointMap joint_map;
442  recurse_nodes(node_path, egg_group, has_decal, &joint_map);
443 
444  // turn it into a switch.. egg_group->set_switch_flag(true);
445 
446  int num_bundles = node->get_num_bundles();
447  for (int i = 0; i < num_bundles; ++i) {
448  PartBundle *bundle = node->get_bundle(i);
449  convert_character_bundle(bundle, egg_group, &joint_map);
450  }
451 }
452 
453 
454 /**
455  * Converts the indicated CollisionNode to the corresponding Egg constructs.
456  */
457 void EggSaver::
458 convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path,
459  EggGroupNode *egg_parent, bool has_decal,
460  CharacterJointMap *joint_map) {
461  // A sequence node gets converted to an ordinary EggGroup, we only apply the
462  // appropriate switch attributes to turn it into a sequence
463  EggGroup *egg_group = new EggGroup(node->get_name());
464  egg_parent->add_child(egg_group);
465  apply_node_properties(egg_group, node, false);
466 
467  // Set the collision masks, if present.
468  CollideMask from_mask = node->get_from_collide_mask();
469  CollideMask into_mask = node->get_into_collide_mask();
470  if (from_mask != CollisionNode::get_default_collide_mask() ||
472  if (from_mask == into_mask) {
473  egg_group->set_collide_mask(into_mask);
474  } else {
475  egg_group->set_from_collide_mask(from_mask);
476  egg_group->set_into_collide_mask(into_mask);
477  }
478  }
479 
480  // turn it into a collision node
481  egg_group->set_collide_flags(EggGroup::CF_descend);
482 
483  NodePath np = node_path.get_node_path();
484  CPT(TransformState) net_transform = np.get_net_transform();
485  LMatrix4 net_mat = net_transform->get_mat();
486  LMatrix4 inv = LCAST(PN_stdfloat, egg_parent->get_vertex_frame_inv());
487  net_mat = net_mat * inv;
488 
489  int num_solids = node->get_num_solids();
490 
491  if (num_solids > 0) {
492  // create vertex pool for collisions
493  EggVertexPool *cvpool = new EggVertexPool("vpool-collision");
494  egg_group->add_child(cvpool);
495 
496  // traverse solids
497  for (int i = 0; i < num_solids; i++) {
498  CPT(CollisionSolid) child = node->get_solid(i);
499  int flags = EggGroup::CF_descend;
500 
501  if (!child->is_tangible()) {
502  flags |= EggGroup::CF_intangible;
503  }
504 
505  if (child->has_effective_normal() &&
506  child->get_effective_normal() == LVector3::up()) {
507  flags |= EggGroup::CF_level;
508  }
509 
510  if (child->is_of_type(CollisionPolygon::get_class_type())) {
511  egg_group->set_cs_type(EggGroup::CST_polyset);
512  egg_group->set_collide_flags(flags);
513 
514  EggPolygon *egg_poly = new EggPolygon;
515  egg_group->add_child(egg_poly);
516 
517  CPT(CollisionPolygon) poly = DCAST(CollisionPolygon, child);
518  int num_points = poly->get_num_points();
519  for (int j = 0; j < num_points; j++) {
520  EggVertex egg_vert;
521  egg_vert.set_pos(LCAST(double, poly->get_point(j) * net_mat));
522  egg_vert.set_normal(LCAST(double, poly->get_normal() * net_mat));
523 
524  EggVertex *new_egg_vert = cvpool->create_unique_vertex(egg_vert);
525  egg_poly->add_vertex(new_egg_vert);
526  }
527 
528  } else if (child->is_of_type(CollisionSphere::get_class_type())) {
529  CPT(CollisionSphere) sphere = DCAST(CollisionSphere, child);
530  LPoint3 center = sphere->get_center();
531  PN_stdfloat radius = sphere->get_radius();
532 
533  EggGroup *egg_sphere;
534  if (num_solids == 1) {
535  egg_sphere = egg_group;
536  } else {
537  egg_sphere = new EggGroup;
538  egg_group->add_child(egg_sphere);
539  }
540 
541  if (child->is_of_type(CollisionInvSphere::get_class_type())) {
542  egg_sphere->set_cs_type(EggGroup::CST_inv_sphere);
543  } else {
544  egg_sphere->set_cs_type(EggGroup::CST_sphere);
545  }
546  egg_sphere->set_collide_flags(flags);
547 
548  EggVertex ev1, ev2, ev3, ev4;
549  ev1.set_pos(LCAST(double, (center + LVector3(radius, 0, 0)) * net_mat));
550  ev2.set_pos(LCAST(double, (center + LVector3(0, radius, 0)) * net_mat));
551  ev3.set_pos(LCAST(double, (center + LVector3(-radius, 0, 0)) * net_mat));
552  ev4.set_pos(LCAST(double, (center + LVector3(0, -radius, 0)) * net_mat));
553 
554  EggPolygon *egg_poly = new EggPolygon;
555  egg_sphere->add_child(egg_poly);
556 
557  egg_poly->add_vertex(cvpool->create_unique_vertex(ev1));
558  egg_poly->add_vertex(cvpool->create_unique_vertex(ev2));
559  egg_poly->add_vertex(cvpool->create_unique_vertex(ev3));
560  egg_poly->add_vertex(cvpool->create_unique_vertex(ev4));
561 
562  } else if (child->is_of_type(CollisionPlane::get_class_type())) {
563  LPlane plane = DCAST(CollisionPlane, child)->get_plane();
564  LPoint3 origin = plane.get_point();
565  LVector3 normal = plane.get_normal();
566 
567  // Get an arbitrary vector on the plane by taking the cross product
568  // with any vector, as long as it is different.
569  LVector3 vec1;
570  if (std::fabs(normal[2]) > std::fabs(normal[1])) {
571  vec1 = normal.cross(LVector3(0, 1, 0));
572  } else {
573  vec1 = normal.cross(LVector3(0, 0, 1));
574  }
575 
576  // Find a second vector perpendicular to the two.
577  LVector3 vec2 = normal.cross(vec1);
578 
579  EggGroup *egg_plane;
580  if (num_solids == 1) {
581  egg_plane = egg_group;
582  } else {
583  egg_plane = new EggGroup;
584  egg_group->add_child(egg_plane);
585  }
586  egg_plane->set_cs_type(EggGroup::CST_plane);
587  egg_plane->set_collide_flags(flags);
588 
589  EggVertex ev0, ev1, ev2;
590  ev0.set_pos(LCAST(double, origin * net_mat));
591  ev1.set_pos(LCAST(double, (origin + vec1) * net_mat));
592  ev2.set_pos(LCAST(double, (origin + vec2) * net_mat));
593 
594  EggPolygon *egg_poly = new EggPolygon;
595  egg_plane->add_child(egg_poly);
596 
597  egg_poly->add_vertex(cvpool->create_unique_vertex(ev0));
598  egg_poly->add_vertex(cvpool->create_unique_vertex(ev1));
599  egg_poly->add_vertex(cvpool->create_unique_vertex(ev2));
600 
601  } else if (child->is_of_type(CollisionBox::get_class_type())) {
602  CPT(CollisionBox) box = DCAST(CollisionBox, child);
603  LPoint3 min_point = box->get_min();
604  LPoint3 max_point = box->get_max();
605 
606  EggGroup *egg_box;
607  if (num_solids == 1) {
608  egg_box = egg_group;
609  } else {
610  egg_box = new EggGroup;
611  egg_group->add_child(egg_box);
612  }
613  egg_box->set_cs_type(EggGroup::CST_box);
614  egg_box->set_collide_flags(flags);
615 
616  // Just add the min and max points.
617  EggVertex ev0, ev1;
618  ev0.set_pos(LCAST(double, min_point * net_mat));
619  ev1.set_pos(LCAST(double, max_point * net_mat));
620 
621  EggLine *egg_poly = new EggLine;
622  egg_box->add_child(egg_poly);
623 
624  egg_poly->add_vertex(cvpool->create_unique_vertex(ev0));
625  egg_poly->add_vertex(cvpool->create_unique_vertex(ev1));
626 
627  } else if (child->is_of_type(CollisionCapsule::get_class_type())) {
628  CPT(CollisionCapsule) capsule = DCAST(CollisionCapsule, child);
629  LPoint3 point_a = capsule->get_point_a();
630  LPoint3 point_b = capsule->get_point_b();
631  LPoint3 centroid = (point_a + point_b) * 0.5f;
632 
633  // Also get an arbitrary vector perpendicular to the capsule.
634  LVector3 axis = point_b - point_a;
635  LVector3 sideways;
636  if (std::fabs(axis[2]) > std::fabs(axis[1])) {
637  sideways = axis.cross(LVector3(0, 1, 0));
638  } else {
639  sideways = axis.cross(LVector3(0, 0, 1));
640  }
641  sideways.normalize();
642  sideways *= capsule->get_radius();
643  LVector3 extend = axis.normalized() * capsule->get_radius();
644 
645  EggGroup *egg_capsule;
646  if (num_solids == 1) {
647  egg_capsule = egg_group;
648  } else {
649  egg_capsule = new EggGroup;
650  egg_group->add_child(egg_capsule);
651  }
652  egg_capsule->set_cs_type(EggGroup::CST_tube);
653  egg_capsule->set_collide_flags(flags);
654 
655  // Add two points for the endcaps, and then two points around the
656  // centroid to indicate the radius.
657  EggVertex ev0, ev1, ev2, ev3;
658  ev0.set_pos(LCAST(double, (point_a - extend) * net_mat));
659  ev1.set_pos(LCAST(double, (centroid + sideways) * net_mat));
660  ev2.set_pos(LCAST(double, (point_b + extend) * net_mat));
661  ev3.set_pos(LCAST(double, (centroid - sideways) * net_mat));
662 
663  EggPolygon *egg_poly = new EggPolygon;
664  egg_capsule->add_child(egg_poly);
665 
666  egg_poly->add_vertex(cvpool->create_unique_vertex(ev0));
667  egg_poly->add_vertex(cvpool->create_unique_vertex(ev1));
668  egg_poly->add_vertex(cvpool->create_unique_vertex(ev2));
669  egg_poly->add_vertex(cvpool->create_unique_vertex(ev3));
670 
671  } else {
672  nout << "Encountered unknown collision solid type " << child->get_type() << "\n";
673  }
674  }
675  }
676 
677  // recurse over children - hm. do I need to do this?
678  recurse_nodes(node_path, egg_group, has_decal, joint_map);
679 }
680 
681 /**
682  * Converts a GeomNode to the corresponding egg structures.
683  */
684 void EggSaver::
685 convert_geom_node(GeomNode *node, const WorkingNodePath &node_path,
686  EggGroupNode *egg_parent, bool has_decal, CharacterJointMap *joint_map) {
687  PT(EggGroup) egg_group = new EggGroup(node->get_name());
688  bool fancy_attributes = apply_node_properties(egg_group, node);
689 
690  if (node->get_effects()->has_decal()) {
691  has_decal = true;
692  }
693 
694  if (has_decal) {
695  egg_group->set_decal_flag(true);
696  }
697 
698  if (fancy_attributes || has_decal || !node->get_name().empty()) {
699  // If we have any fancy attributes on the node, or if we're making decal
700  // geometry, we have to make a special node to hold the geometry (normally
701  // it would just appear within its parent).
702  egg_parent->add_child(egg_group.p());
703  egg_parent = egg_group;
704  }
705 
706  NodePath np = node_path.get_node_path();
707  CPT(RenderState) net_state = np.get_net_state();
708  CPT(TransformState) net_transform = np.get_net_transform();
709  LMatrix4 net_mat = net_transform->get_mat();
710  LMatrix4 inv = LCAST(PN_stdfloat, egg_parent->get_vertex_frame_inv());
711  net_mat = net_mat * inv;
712 
713  // Now get out all the various kinds of geometry.
714  int num_geoms = node->get_num_geoms();
715  for (int i = 0; i < num_geoms; ++i) {
716  CPT(RenderState) geom_state = node->get_geom_state(i);
717  CPT(RenderState) geom_net_state = net_state->compose(geom_state);
718 
719  // If there is only one Geom, and the node has no state, apply the state
720  // attributes from the Geom to the group instead, so that we don't end up
721  // duplicating it for a lot of primitives.
722  if (num_geoms == 1 && node->get_num_children() == 0 && egg_parent == egg_group &&
723  !geom_state->is_empty() && node->get_state()->is_empty()) {
724  apply_state_properties(egg_group, geom_state);
725  geom_state = RenderState::make_empty();
726  }
727 
728  const Geom *geom = node->get_geom(i);
729  int num_primitives = geom->get_num_primitives();
730  for (int j = 0; j < num_primitives; ++j) {
731  const GeomPrimitive *primitive = geom->get_primitive(j);
732  CPT(GeomPrimitive) simple = primitive->decompose();
733  CPT(GeomVertexData) vdata = geom->get_vertex_data();
734  // vdata = vdata->animate_vertices(true, Thread::get_current_thread());
735  convert_primitive(vdata, simple, geom_state, geom_net_state,
736  net_mat, egg_parent, joint_map);
737  }
738  }
739 
740  recurse_nodes(node_path, egg_parent, has_decal, joint_map);
741 }
742 
743 /**
744  *
745  */
746 void EggSaver::
747 convert_primitive(const GeomVertexData *vertex_data,
748  const GeomPrimitive *primitive,
749  const RenderState *geom_state, const RenderState *net_state,
750  const LMatrix4 &net_mat, EggGroupNode *egg_parent,
751  CharacterJointMap *joint_map) {
752  GeomVertexReader reader(vertex_data);
753  const GeomVertexFormat *format = vertex_data->get_format();
754 
755  // Make a zygote that will be duplicated for each primitive.
756  PT(EggPrimitive) egg_prim;
757  if (primitive->is_of_type(GeomTriangles::get_class_type())) {
758  egg_prim = new EggPolygon();
759  } else if (primitive->is_of_type(GeomPatches::get_class_type())) {
760  egg_prim = new EggPatch();
761  } else if (primitive->is_of_type(GeomPoints::get_class_type())) {
762  egg_prim = new EggPoint();
763  } else if (primitive->is_of_type(GeomLines::get_class_type())) {
764  egg_prim = new EggLine();
765  } else {
766  // Huh, an unknown geometry type.
767  return;
768  }
769 
770  // Apply render attributes.
771  apply_state_properties(egg_prim, geom_state);
772 
773  // Check for a color scale.
774  LVecBase4 color_scale(1.0f, 1.0f, 1.0f, 1.0f);
775  const ColorScaleAttrib *csa;
776  if (net_state->get_attrib(csa)) {
777  color_scale = csa->get_scale();
778  }
779 
780  // Check for a color override.
781  bool has_color_override = false;
782  bool has_color_off = false;
783  LColor color_override;
784  const ColorAttrib *ca;
785  if (net_state->get_attrib(ca)) {
786  if (ca->get_color_type() == ColorAttrib::T_flat) {
787  has_color_override = true;
788  color_override = ca->get_color();
789  color_override.set(color_override[0] * color_scale[0],
790  color_override[1] * color_scale[1],
791  color_override[2] * color_scale[2],
792  color_override[3] * color_scale[3]);
793 
794  } else if (ca->get_color_type() == ColorAttrib::T_off) {
795  has_color_off = true;
796  }
797  }
798 
799  // Check for a material.
800  EggMaterial *egg_mat = nullptr;
801  const MaterialAttrib *ma;
802  if (net_state->get_attrib(ma)) {
803  egg_mat = get_egg_material(ma->get_material());
804  if (egg_mat != nullptr) {
805  egg_prim->set_material(egg_mat);
806  }
807  }
808 
809  // Check for a texture.
810  const TextureAttrib *ta;
811  if (net_state->get_attrib(ta)) {
812  for (size_t i = 0; i < ta->get_num_on_stages(); ++i) {
813  TextureStage *tex_stage = ta->get_on_stage(i);
814 
815  EggTexture *egg_tex = get_egg_texture(ta->get_on_texture(tex_stage));
816  if (egg_tex != nullptr) {
817  switch (tex_stage->get_mode()) {
818  case TextureStage::M_modulate:
819  if (has_color_off == true && i == 0) {
820  egg_tex->set_env_type(EggTexture::ET_replace);
821  } else {
822  egg_tex->set_env_type(EggTexture::ET_modulate);
823  }
824  break;
825  case TextureStage::M_decal:
826  egg_tex->set_env_type(EggTexture::ET_decal);
827  break;
828  case TextureStage::M_blend:
829  egg_tex->set_env_type(EggTexture::ET_blend);
830  break;
831  case TextureStage::M_replace:
832  egg_tex->set_env_type(EggTexture::ET_replace);
833  break;
834  case TextureStage::M_add:
835  egg_tex->set_env_type(EggTexture::ET_add);
836  break;
837  case TextureStage::M_blend_color_scale:
838  egg_tex->set_env_type(EggTexture::ET_blend_color_scale);
839  break;
840  case TextureStage::M_modulate_glow:
841  egg_tex->set_env_type(EggTexture::ET_modulate_glow);
842  break;
843  case TextureStage::M_modulate_gloss:
844  egg_tex->set_env_type(EggTexture::ET_modulate_gloss);
845  break;
846  case TextureStage::M_normal:
847  egg_tex->set_env_type(EggTexture::ET_normal);
848  break;
849  case TextureStage::M_normal_height:
850  egg_tex->set_env_type(EggTexture::ET_normal_height);
851  break;
852  case TextureStage::M_glow:
853  egg_tex->set_env_type(EggTexture::ET_glow);
854  break;
855  case TextureStage::M_gloss:
856  egg_tex->set_env_type(EggTexture::ET_gloss);
857  break;
858  case TextureStage::M_height:
859  egg_tex->set_env_type(EggTexture::ET_height);
860  break;
861  case TextureStage::M_selector:
862  egg_tex->set_env_type(EggTexture::ET_selector);
863  break;
864  case TextureStage::M_normal_gloss:
865  egg_tex->set_env_type(EggTexture::ET_normal_gloss);
866  break;
867  case TextureStage::M_emission:
868  egg_tex->set_env_type(EggTexture::ET_emission);
869  break;
870  default:
871  break;
872  }
873 
874  const InternalName *name = tex_stage->get_texcoord_name();
875  if (name != nullptr && name != InternalName::get_texcoord()) {
876  egg_tex->set_uv_name(name->get_basename());
877  }
878 
879  egg_prim->add_texture(egg_tex);
880  }
881  }
882  }
883 
884  // Check the backface flag.
885  const CullFaceAttrib *cfa;
886  if (net_state->get_attrib(cfa)) {
887  if (cfa->get_effective_mode() == CullFaceAttrib::M_cull_none) {
888  egg_prim->set_bface_flag(true);
889  }
890  }
891 
892  // Check for line thickness and such.
893  const RenderModeAttrib *rma;
894  if (net_state->get_attrib(rma)) {
895  if (egg_prim->is_of_type(EggPoint::get_class_type())) {
896  EggPoint *egg_point = (EggPoint *)egg_prim.p();
897  egg_point->set_thick(rma->get_thickness());
898  egg_point->set_perspective(rma->get_perspective());
899 
900  } else if (egg_prim->is_of_type(EggLine::get_class_type())) {
901  EggLine *egg_line = (EggLine *)egg_prim.p();
902  egg_line->set_thick(rma->get_thickness());
903  }
904  }
905 
906  CPT(TransformBlendTable) transformBlendTable = vertex_data->get_transform_blend_table();
907 
908  int num_primitives = primitive->get_num_primitives();
909  int num_vertices = primitive->get_num_vertices_per_primitive();
910 
911  for (int i = 0; i < num_primitives; ++i) {
912  PT(EggPrimitive) egg_child = egg_prim->make_copy();
913  egg_parent->add_child(egg_child);
914 
915  for (int j = 0; j < num_vertices; j++) {
916  EggVertex egg_vert;
917 
918  // Get per-vertex properties.
919  reader.set_row(primitive->get_vertex(i * num_vertices + j));
920 
921  reader.set_column(InternalName::get_vertex());
922  LVertex vertex = reader.get_data3();
923  egg_vert.set_pos(LCAST(double, vertex * net_mat));
924 
925  if (vertex_data->has_column(InternalName::get_normal())) {
926  reader.set_column(InternalName::get_normal());
927  LNormal normal = reader.get_data3();
928  egg_vert.set_normal(LCAST(double, normal * net_mat));
929  }
930  if (has_color_override) {
931  egg_vert.set_color(color_override);
932 
933  } else if (!has_color_off) {
934  LColor color(1.0f, 1.0f, 1.0f, 1.0f);
935  if (vertex_data->has_column(InternalName::get_color())) {
936  reader.set_column(InternalName::get_color());
937  color = reader.get_data4();
938  }
939  egg_vert.set_color(LColor(color[0] * color_scale[0],
940  color[1] * color_scale[1],
941  color[2] * color_scale[2],
942  color[3] * color_scale[3]));
943  }
944 
945  for (size_t ti = 0; ti < format->get_num_texcoords(); ++ti) {
946  const InternalName *texcoord_name = format->get_texcoord(ti);
947  reader.set_column(texcoord_name);
948  LTexCoord uv = reader.get_data2();
949  if (texcoord_name == InternalName::get_texcoord()) {
950  egg_vert.set_uv(LCAST(double, uv));
951  } else {
952  egg_vert.set_uv(texcoord_name->get_basename(), LCAST(double, uv));
953  }
954  }
955 
956  EggVertex *new_egg_vert = _vpool->create_unique_vertex(egg_vert);
957 
958  if (vertex_data->has_column(InternalName::get_transform_blend()) &&
959  joint_map != nullptr && transformBlendTable != nullptr) {
960  reader.set_column(InternalName::get_transform_blend());
961  int idx = reader.get_data1i();
962  const TransformBlend &blend = transformBlendTable->get_blend(idx);
963  int num_weights = blend.get_num_transforms();
964  for (int k = 0; k < num_weights; ++k) {
965  PN_stdfloat weight = blend.get_weight(k);
966  if (weight!=0) {
967  const VertexTransform *vertex_transform = blend.get_transform(k);
968  if (vertex_transform->is_of_type(JointVertexTransform::get_class_type())) {
969  const JointVertexTransform *joint_vertex_transform = DCAST(const JointVertexTransform, vertex_transform);
970 
971  CharacterJointMap::iterator mi = joint_map->find(joint_vertex_transform->get_joint());
972  if (mi == joint_map->end()) {
973  mi = joint_map->insert(CharacterJointMap::value_type(joint_vertex_transform->get_joint(), pvector<pair<EggVertex*,PN_stdfloat> >())).first;
974  }
975  pvector<pair<EggVertex*,PN_stdfloat> > &joint_vertices = (*mi).second;
976  joint_vertices.push_back(pair<EggVertex*,PN_stdfloat>(new_egg_vert, weight));
977  }
978  }
979  }
980  }
981 
982  egg_child->add_vertex(new_egg_vert);
983  }
984  }
985 }
986 
987 /**
988  * Converts all the children of the indicated node.
989  */
990 void EggSaver::
991 recurse_nodes(const WorkingNodePath &node_path, EggGroupNode *egg_parent,
992  bool has_decal, CharacterJointMap *joint_map) {
993  PandaNode *node = node_path.node();
994  int num_children = node->get_num_children();
995 
996  for (int i = 0; i < num_children; i++) {
997  PandaNode *child = node->get_child(i);
998  convert_node(WorkingNodePath(node_path, child), egg_parent, has_decal, joint_map);
999  }
1000 }
1001 
1002 /**
1003  * Applies any special properties that might be stored on the node, like
1004  * billboarding. Returns true if any were applied, false otherwise.
1005  */
1006 bool EggSaver::
1007 apply_node_properties(EggGroup *egg_group, PandaNode *node, bool allow_backstage) {
1008  bool any_applied = false;
1009 
1010  if (node->is_overall_hidden() && allow_backstage) {
1011  // This node is hidden. We'll go ahead and convert it, but we'll put in
1012  // the "backstage" flag to mean it's not real geometry. unless the caller
1013  // wants to keep it (by setting allow_backstage to false)
1014  egg_group->add_object_type("backstage");
1015  }
1016 
1017  if (node->has_tags()) {
1018  if (apply_tags(egg_group, node)) {
1019  any_applied = true;
1020  }
1021  }
1022 
1023  if (node->is_of_type(ModelNode::get_class_type())) {
1024  ModelNode *model_node = DCAST(ModelNode, node);
1025  switch (model_node->get_preserve_transform()) {
1026  case ModelNode::PT_none:
1027  egg_group->set_model_flag(true);
1028  break;
1029 
1030  case ModelNode::PT_drop_node:
1031  break;
1032 
1033  case ModelNode::PT_net:
1034  egg_group->set_dcs_type(EggGroup::DC_net);
1035  break;
1036 
1037  case ModelNode::PT_local:
1038  egg_group->set_dcs_type(EggGroup::DC_local);
1039  break;
1040 
1041  case ModelNode::PT_no_touch:
1042  egg_group->set_dcs_type(EggGroup::DC_no_touch);
1043  break;
1044  }
1045  }
1046 
1047  if (node->is_of_type(UvScrollNode::get_class_type())) {
1048  const UvScrollNode *scroll_node = (const UvScrollNode *)node;
1049  egg_group->set_scroll_u(scroll_node->get_u_speed());
1050  egg_group->set_scroll_v(scroll_node->get_v_speed());
1051  egg_group->set_scroll_w(scroll_node->get_w_speed());
1052  egg_group->set_scroll_r(scroll_node->get_r_speed());
1053  }
1054 
1055  const RenderEffects *effects = node->get_effects();
1056  const RenderEffect *effect = effects->get_effect(BillboardEffect::get_class_type());
1057  if (effect != nullptr) {
1058  const BillboardEffect *bbe = DCAST(BillboardEffect, effect);
1059  if (bbe->get_axial_rotate()) {
1060  egg_group->set_billboard_type(EggGroup::BT_axis);
1061  any_applied = true;
1062 
1063  } else if (bbe->get_eye_relative()) {
1064  egg_group->set_billboard_type(EggGroup::BT_point_camera_relative);
1065  any_applied = true;
1066 
1067  } else {
1068  egg_group->set_billboard_type(EggGroup::BT_point_world_relative);
1069  any_applied = true;
1070  }
1071  }
1072 
1073  const TransformState *transform = node->get_transform();
1074  if (!transform->is_identity()) {
1075  if (transform->has_components()) {
1076  // If the transform can be represented componentwise, we prefer storing
1077  // it that way in the egg file.
1078  const LVecBase3 &scale = transform->get_scale();
1079  const LQuaternion &quat = transform->get_quat();
1080  const LVecBase3 &pos = transform->get_pos();
1081  if (!scale.almost_equal(LVecBase3(1.0f, 1.0f, 1.0f))) {
1082  egg_group->add_scale3d(LCAST(double, scale));
1083  }
1084  if (!quat.is_identity()) {
1085  egg_group->add_rotate3d(LCAST(double, quat));
1086  }
1087  if (!pos.almost_equal(LVecBase3::zero())) {
1088  egg_group->add_translate3d(LCAST(double, pos));
1089  }
1090 
1091  } else if (transform->has_mat()) {
1092  // Otherwise, we store the raw matrix.
1093  const LMatrix4 &mat = transform->get_mat();
1094  egg_group->set_transform3d(LCAST(double, mat));
1095  }
1096  any_applied = true;
1097  }
1098 
1099  const RenderState *state = node->get_state();
1100  if (apply_state_properties(egg_group, state)) {
1101  return true;
1102  }
1103 
1104  return any_applied;
1105 }
1106 
1107 /**
1108  * Applies any special render state settings on the primitive or group.
1109  * Returns true if any were applied, false otherwise.
1110  */
1111 bool EggSaver::
1112 apply_state_properties(EggRenderMode *egg_render_mode, const RenderState *state) {
1113  if (state->is_empty()) {
1114  return false;
1115  }
1116 
1117  bool any_applied = false;
1118 
1119  // Check the transparency mode.
1120  const TransparencyAttrib *tra;
1121  if (state->get_attrib(tra)) {
1122  EggRenderMode::AlphaMode tex_trans = EggRenderMode::AM_unspecified;
1123  switch (tra->get_mode()) {
1124  case TransparencyAttrib::M_none:
1125  tex_trans = EggRenderMode::AM_off;
1126  break;
1127  case TransparencyAttrib::M_alpha:
1128  tex_trans = EggRenderMode::AM_blend;
1129  break;
1130  case TransparencyAttrib::M_premultiplied_alpha:
1131  tex_trans = EggRenderMode::AM_premultiplied;
1132  break;
1133  case TransparencyAttrib::M_multisample:
1134  tex_trans = EggRenderMode::AM_ms;
1135  break;
1136  case TransparencyAttrib::M_multisample_mask:
1137  tex_trans = EggRenderMode::AM_ms_mask;
1138  break;
1139  case TransparencyAttrib::M_binary:
1140  tex_trans = EggRenderMode::AM_binary;
1141  break;
1142  case TransparencyAttrib::M_dual:
1143  tex_trans = EggRenderMode::AM_dual;
1144  break;
1145  default: // intentional fall-through
1146  break;
1147  }
1148  egg_render_mode->set_alpha_mode(tex_trans);
1149  }
1150 
1151  const DepthWriteAttrib *dwa;
1152  if (state->get_attrib(dwa)) {
1153  if (dwa->get_mode() != DepthWriteAttrib::M_off) {
1154  egg_render_mode->set_depth_write_mode(EggRenderMode::DWM_on);
1155 
1156  } else if (egg_render_mode->get_alpha_mode() == EggRenderMode::AM_blend) {
1157  // AM_blend_no_occlude is like AM_blend but also implies DWM_off.
1158  egg_render_mode->set_alpha_mode(EggRenderMode::AM_blend_no_occlude);
1159 
1160  } else {
1161  egg_render_mode->set_depth_write_mode(EggRenderMode::DWM_off);
1162  }
1163  any_applied = true;
1164  }
1165 
1166  const DepthTestAttrib *dta;
1167  if (state->get_attrib(dta)) {
1168  RenderAttrib::PandaCompareFunc mode = dta->get_mode();
1169  if (mode == DepthTestAttrib::M_none || mode == DepthTestAttrib::M_always) {
1170  egg_render_mode->set_depth_test_mode(EggRenderMode::DTM_off);
1171  } else {
1172  egg_render_mode->set_depth_test_mode(EggRenderMode::DTM_on);
1173  }
1174  any_applied = true;
1175  }
1176 
1177  const DepthOffsetAttrib *doa;
1178  if (state->get_attrib(doa)) {
1179  egg_render_mode->set_depth_offset(doa->get_offset());
1180  any_applied = true;
1181  }
1182 
1183  const CullBinAttrib *cba;
1184  if (state->get_attrib(cba)) {
1185  egg_render_mode->set_bin(cba->get_bin_name());
1186  egg_render_mode->set_draw_order(cba->get_draw_order());
1187  any_applied = true;
1188  }
1189 
1190  return any_applied;
1191 }
1192 
1193 /**
1194  * Applies string tags to the egg file. Returns true if any were applied,
1195  * false otherwise.
1196  */
1197 bool EggSaver::
1198 apply_tags(EggGroup *egg_group, PandaNode *node) {
1199  std::ostringstream strm;
1200  char delimiter = '\n';
1201  string delimiter_str(1, delimiter);
1202  node->list_tags(strm, delimiter_str);
1203 
1204  string data = strm.str();
1205  if (data.empty()) {
1206  return false;
1207  }
1208 
1209  bool any_applied = false;
1210 
1211  size_t p = 0;
1212  size_t q = data.find(delimiter);
1213  while (q != string::npos) {
1214  string tag = data.substr(p, q);
1215  if (apply_tag(egg_group, node, tag)) {
1216  any_applied = true;
1217  }
1218  p = q + 1;
1219  q = data.find(delimiter, p);
1220  }
1221 
1222  string tag = data.substr(p);
1223  if (apply_tag(egg_group, node, tag)) {
1224  any_applied = true;
1225  }
1226 
1227  return any_applied;
1228 }
1229 
1230 /**
1231  * Applies the named string tags to the egg file.
1232  */
1233 bool EggSaver::
1234 apply_tag(EggGroup *egg_group, PandaNode *node, const string &tag) {
1235  if (!node->has_tag(tag)) {
1236  return false;
1237  }
1238 
1239  string value = node->get_tag(tag);
1240  egg_group->set_tag(tag, value);
1241  return true;
1242 }
1243 
1244 /**
1245  * Returns an EggMaterial pointer that corresponds to the indicated Material.
1246  */
1247 EggMaterial *EggSaver::
1248 get_egg_material(Material *mat) {
1249  if (mat != nullptr) {
1250  EggMaterial temp(mat->get_name());
1251  if (mat->has_base_color()) {
1252  temp.set_base(mat->get_base_color());
1253  }
1254 
1255  if (mat->has_ambient()) {
1256  temp.set_amb(mat->get_ambient());
1257  }
1258 
1259  if (mat->has_diffuse()) {
1260  temp.set_diff(mat->get_diffuse());
1261  }
1262 
1263  if (mat->has_specular()) {
1264  temp.set_spec(mat->get_specular());
1265  }
1266 
1267  if (mat->has_emission()) {
1268  temp.set_emit(mat->get_emission());
1269  }
1270 
1271  if (mat->has_roughness()) {
1272  temp.set_roughness(mat->get_roughness());
1273  } else {
1274  temp.set_shininess(mat->get_shininess());
1275  }
1276 
1277  if (mat->has_metallic()) {
1278  temp.set_metallic(mat->get_metallic());
1279  }
1280 
1281  if (mat->has_refractive_index()) {
1282  temp.set_ior(mat->get_refractive_index());
1283  }
1284 
1285  temp.set_local(mat->get_local());
1286 
1287  return _materials.create_unique_material(temp, ~EggMaterial::E_mref_name);
1288  }
1289 
1290  return nullptr;
1291 }
1292 
1293 /**
1294  * Returns an EggTexture pointer that corresponds to the indicated Texture.
1295  */
1296 EggTexture *EggSaver::
1297 get_egg_texture(Texture *tex) {
1298  if (tex != nullptr) {
1299  if (tex->has_filename()) {
1300  Filename filename = tex->get_filename();
1301  EggTexture temp(filename.get_basename_wo_extension(), filename);
1302  if (tex->has_alpha_filename()) {
1303  Filename alpha = tex->get_alpha_filename();
1304  temp.set_alpha_filename(alpha);
1305  }
1306 
1307  switch (tex->get_minfilter()) {
1308  case SamplerState::FT_nearest:
1309  temp.set_minfilter(EggTexture::FT_nearest);
1310  break;
1311  case SamplerState::FT_linear:
1312  temp.set_minfilter(EggTexture::FT_linear);
1313  break;
1314  case SamplerState::FT_nearest_mipmap_nearest:
1315  temp.set_minfilter(EggTexture::FT_nearest_mipmap_nearest);
1316  break;
1317  case SamplerState::FT_linear_mipmap_nearest:
1318  temp.set_minfilter(EggTexture::FT_linear_mipmap_nearest);
1319  break;
1320  case SamplerState::FT_nearest_mipmap_linear:
1321  temp.set_minfilter(EggTexture::FT_nearest_mipmap_linear);
1322  break;
1323  case SamplerState::FT_linear_mipmap_linear:
1324  temp.set_minfilter(EggTexture::FT_linear_mipmap_linear);
1325  break;
1326 
1327  default:
1328  break;
1329  }
1330 
1331  switch (tex->get_magfilter()) {
1332  case SamplerState::FT_nearest:
1333  temp.set_magfilter(EggTexture::FT_nearest);
1334  break;
1335  case SamplerState::FT_linear:
1336  temp.set_magfilter(EggTexture::FT_linear);
1337  break;
1338 
1339  default:
1340  break;
1341  }
1342 
1343  switch (tex->get_wrap_u()) {
1344  case SamplerState::WM_clamp:
1345  temp.set_wrap_u(EggTexture::WM_clamp);
1346  break;
1347  case SamplerState::WM_repeat:
1348  temp.set_wrap_u(EggTexture::WM_repeat);
1349  break;
1350 
1351  default:
1352  // There are some new wrap options on Texture that aren't yet
1353  // supported in egg.
1354  break;
1355  }
1356 
1357  switch (tex->get_wrap_v()) {
1358  case SamplerState::WM_clamp:
1359  temp.set_wrap_v(EggTexture::WM_clamp);
1360  break;
1361  case SamplerState::WM_repeat:
1362  temp.set_wrap_v(EggTexture::WM_repeat);
1363  break;
1364 
1365  default:
1366  // There are some new wrap options on Texture that aren't yet
1367  // supported in egg.
1368  break;
1369  }
1370 
1371  switch (tex->get_format()) {
1372  case Texture::F_red:
1373  temp.set_format(EggTexture::F_red);
1374  break;
1375  case Texture::F_green:
1376  temp.set_format(EggTexture::F_green);
1377  break;
1378  case Texture::F_blue:
1379  temp.set_format(EggTexture::F_blue);
1380  break;
1381  case Texture::F_alpha:
1382  temp.set_format(EggTexture::F_alpha);
1383  break;
1384  case Texture::F_rgb:
1385  temp.set_format(EggTexture::F_rgb);
1386  break;
1387  case Texture::F_rgb5:
1388  temp.set_format(EggTexture::F_rgb5);
1389  break;
1390  case Texture::F_rgb8:
1391  temp.set_format(EggTexture::F_rgb8);
1392  break;
1393  case Texture::F_rgb12:
1394  temp.set_format(EggTexture::F_rgb12);
1395  break;
1396  case Texture::F_rgb332:
1397  temp.set_format(EggTexture::F_rgb332);
1398  break;
1399  case Texture::F_rgba:
1400  temp.set_format(EggTexture::F_rgba);
1401  break;
1402  case Texture::F_rgbm:
1403  temp.set_format(EggTexture::F_rgbm);
1404  break;
1405  case Texture::F_rgba4:
1406  temp.set_format(EggTexture::F_rgba4);
1407  break;
1408  case Texture::F_rgba5:
1409  temp.set_format(EggTexture::F_rgba5);
1410  break;
1411  case Texture::F_rgba8:
1412  temp.set_format(EggTexture::F_rgba8);
1413  break;
1414  case Texture::F_rgba12:
1415  temp.set_format(EggTexture::F_rgba12);
1416  break;
1417  case Texture::F_luminance:
1418  temp.set_format(EggTexture::F_luminance);
1419  break;
1420  case Texture::F_luminance_alpha:
1421  temp.set_format(EggTexture::F_luminance_alpha);
1422  break;
1423  case Texture::F_luminance_alphamask:
1424  temp.set_format(EggTexture::F_luminance_alphamask);
1425  break;
1426  case Texture::F_srgb:
1427  temp.set_format(EggTexture::F_srgb);
1428  break;
1429  case Texture::F_srgb_alpha:
1430  temp.set_format(EggTexture::F_srgb_alpha);
1431  break;
1432  default:
1433  break;
1434  }
1435 
1436  return _textures.create_unique_texture(temp, ~EggTexture::E_tref_name);
1437  }
1438  }
1439 
1440  return nullptr;
1441 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a node that contains a pointer to an AnimBundle.
This is the root of an AnimChannel hierarchy.
Definition: animBundle.h:29
get_base_frame_rate
Returns the ideal number of frames per second of the animation, when it is running at normal speed.
Definition: animBundle.h:41
An animation channel that issues a matrix each frame, read from a table such as might have been read ...
has_table
Returns true if the indicated subtable has been assigned.
get_table
Returns a pointer to the indicated subtable's data, if it exists, or NULL if it does not.
This is the base class for AnimChannel and AnimBundle.
Definition: animGroup.h:33
get_child
Returns the nth child of the group.
Definition: animGroup.h:45
get_num_children
Returns the number of child nodes of the group.
Definition: animGroup.h:45
Indicates that geometry at this node should automatically rotate to face the camera,...
bool get_eye_relative() const
Returns true if this billboard interprets the up vector relative to the camera, or false if it is rel...
bool get_axial_rotate() const
Returns true if this billboard rotates only around the axis of the up vector, or false if it rotates ...
This represents one joint of the character's animation, containing an animating transform matrix.
An animated character, with skeleton-morph animation and either soft- skinned or hard-skinned vertice...
Definition: character.h:38
A cuboid collision volume or object.
Definition: collisionBox.h:27
This implements a solid consisting of a cylinder with hemispherical endcaps, also known as a capsule ...
A node in the scene graph that can hold any number of CollisionSolids.
Definition: collisionNode.h:30
get_into_collide_mask
Returns the current "into" CollideMask.
Definition: collisionNode.h:61
get_from_collide_mask
Returns the current "from" CollideMask.
Definition: collisionNode.h:59
get_default_collide_mask
Returns the default into_collide_mask assigned to new CollisionNodes.
Definition: collisionNode.h:79
The abstract base class for all things that can collide with other things in the world,...
A spherical collision volume or object.
Indicates what color should be applied to renderable geometry.
Definition: colorAttrib.h:27
get_color
If the type is T_flat or T_off, this returns the color that will be applied to geometry.
Definition: colorAttrib.h:47
get_color_type
Returns the type of color specified by this ColorAttrib.
Definition: colorAttrib.h:46
Applies a scale to colors in the scene graph and on vertices.
get_scale
Returns the scale to be applied to colors.
Similar to PointerToArray, except that its contents may not be modified.
Assigns geometry to a particular bin by name.
Definition: cullBinAttrib.h:27
get_draw_order
Returns the draw order this attribute specifies.
Definition: cullBinAttrib.h:40
get_bin_name
Returns the name of the bin this attribute specifies.
Definition: cullBinAttrib.h:39
Indicates which faces should be culled based on their vertex ordering.
get_effective_mode
Returns the effective culling mode.
This is a special kind of attribute that instructs the graphics driver to apply an offset or bias to ...
get_offset
Returns the depth offset represented by this attrib.
Enables or disables writing to the depth buffer.
get_mode
Returns the depth write mode.
Enables or disables writing to the depth buffer.
get_mode
Returns the depth write mode.
This is the primary interface into all the egg data, and the root of the egg file structure.
Definition: eggData.h:37
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 set_vertex_membership(EggVertex *vert, double membership)
Explicitly sets the net membership of the indicated vertex in this group to the given value.
Definition: eggGroup.cxx:692
void set_tag(const std::string &key, const std::string &value)
Associates a user-defined value with a user-defined key which is stored on the node.
Definition: eggGroup.I:720
set_default_pose
Replaces the initial pose transform.
Definition: eggGroup.h:320
A line segment, or a series of connected line segments, defined by a <Line> entry.
Definition: eggLine.h:25
EggMaterial * create_unique_material(const EggMaterial &copy, int eq)
Creates a new material if there is not already one equivalent (according to eq, see EggMaterial::is_e...
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
A single "patch", a special primitive to be rendered only with a tessellation shader.
Definition: eggPatch.h:25
A single point, or a collection of points as defined by a single <PointLight> entry.
Definition: eggPoint.h:25
A single polygon.
Definition: eggPolygon.h:24
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
Definition: eggPrimitive.h:49
EggVertex * add_vertex(EggVertex *vertex)
Adds the indicated vertex to the end of the primitive's list of vertices, and returns it.
This class stores miscellaneous rendering properties that is associated with geometry,...
Definition: eggRenderMode.h:31
void set_draw_order(int order)
Sets the "draw-order" flag associated with this object.
void set_depth_write_mode(DepthWriteMode mode)
Specifies whether writes should be made to the depth buffer (assuming the rendering backend provides ...
Definition: eggRenderMode.I:27
void set_alpha_mode(AlphaMode mode)
Specifies precisely how the transparency for this geometry should be achieved, or if it should be use...
Definition: eggRenderMode.I:89
void set_depth_test_mode(DepthTestMode mode)
Specifies whether this geometry should be tested against the depth buffer when it is drawn (assuming ...
Definition: eggRenderMode.I:46
void set_bin(const std::string &bin)
Sets the "bin" string for this particular object.
AlphaMode get_alpha_mode() const
Returns the alpha mode that was set, or AM_unspecified if nothing was set.
Definition: eggRenderMode.I:98
void set_depth_offset(int bias)
Sets the "depth-offset" flag associated with this object.
void add_node(PandaNode *node)
Adds the scene graph rooted at the indicated node to the accumulated egg data within this object.
Definition: eggSaver.cxx:97
void add_subgraph(PandaNode *root)
Adds the scene graph rooted at the indicated node (but without the node itself) to the accumulated eg...
Definition: eggSaver.cxx:117
A SwitchCondition that switches the levels-of-detail based on distance from the camera's eyepoint.
This corresponds to a.
Definition: eggTable.h:27
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
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
This represents the <Transform> entry of a group or texture node: a list of component transform opera...
Definition: eggTransform.h:29
const LMatrix4d & get_transform3d() const
Returns the overall transform as a 4x4 matrix.
Definition: eggTransform.I:212
void add_scale3d(const LVecBase3d &scale)
Appends a possibly non-uniform scale to the current transform.
void set_transform3d(const LMatrix4d &mat)
Sets the overall transform as a 4x4 matrix.
Definition: eggTransform.I:188
void add_matrix4(const LMatrix4d &mat)
Appends an arbitrary 4x4 matrix to the current transform.
Definition: eggTransform.I:132
void add_translate3d(const LVector3d &translate)
Appends a 3-d translation operation to the current transform.
void add_rotate3d(double angle, const LVector3d &axis)
Appends a 3-d rotation about an arbitrary axis to the current transform.
A collection of vertices.
Definition: eggVertexPool.h:41
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
void set_pos(double pos)
Sets the vertex position.
Definition: eggVertex.I:42
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 add_component_data(const std::string &component_name, double value)
Adds a new row to the named component (one of matrix_component_letters) of the table.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
Definition: filename.I:386
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
get_num_geoms
Returns the number of geoms in the node.
Definition: geomNode.h:71
get_geom_state
Returns the RenderState associated with the nth geom of the node.
Definition: geomNode.h:75
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Definition: geomPrimitive.h:56
int get_num_primitives() const
Returns the number of individual primitives stored within this object.
get_vertex
Returns the ith vertex index in the table.
Definition: geomPrimitive.h:99
get_num_vertices_per_primitive
If the primitive type is a simple type in which all primitives have the same number of vertices,...
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
bool has_column(const InternalName *name) const
Returns true if the data has the named column, false otherwise.
get_format
Returns a pointer to the GeomVertexFormat structure that defines this data.
This class defines the physical layout of the vertex data stored within a Geom.
get_texcoord
Returns the name of the nth texcoord column.
get_num_texcoords
Returns the number of columns within the format that represent texture coordinates.
This object provides a high-level interface for quickly reading a sequence of numeric values from a v...
A container for geometry primitives.
Definition: geom.h:54
Encodes a string name in a hash table, mapping it to a pointer.
Definition: internalName.h:38
get_basename
Return the name represented by just this particular InternalName object, ignoring its parents names.
Definition: internalName.h:62
This is a specialization on VertexTransform that returns the transform necessary to move vertices as ...
const CharacterJoint * get_joint() const
Returns the joint for which this object returns the transform.
A Level-of-Detail node.
Definition: lodNode.h:28
get_out
Returns the "out" distance of the indicated switch range.
Definition: lodNode.h:61
get_in
Returns the "in" distance of the indicated switch range.
Definition: lodNode.h:59
get_center
Returns the center of the LOD.
Definition: lodNode.h:83
get_num_switches
Returns the number of switch ranges added to the LODNode.
Definition: lodNode.h:59
Indicates which, if any, material should be applied to geometry.
get_material
If the MaterialAttrib is not an 'off' MaterialAttrib, returns the material that is associated.
Defines the way an object appears in the presence of lighting.
Definition: material.h:43
This node is placed at key points within the scene graph to indicate the roots of "models": subtrees ...
Definition: modelNode.h:31
PreserveTransform get_preserve_transform() const
Returns the current setting of the preserve_transform flag.
Definition: modelNode.I:61
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
const LMatrix4 & get_mat() const
Returns the transform matrix that has been applied to the referenced node, or the identity matrix if ...
Definition: nodePath.I:776
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
virtual bool is_geom_node() const
A simple downcast check.
Definition: pandaNode.cxx:2062
is_overall_hidden
Returns true if the node has been hidden to all cameras by clearing its overall bit.
Definition: pandaNode.h:248
get_child
Returns the nth child node of this node.
Definition: pandaNode.h:124
void list_tags(std::ostream &out, const std::string &separator="\n") const
Writes a list of all the tag keys assigned to the node to the indicated stream.
Definition: pandaNode.cxx:1277
get_num_children
Returns the number of child nodes this node has.
Definition: pandaNode.h:124
This is the root of a MovingPart hierarchy.
Definition: partBundle.h:46
This is the base class for PartRoot and MovingPart.
Definition: partGroup.h:43
get_num_children
Returns the number of child nodes of the group.
Definition: partGroup.h:72
get_child
Returns the nth child of the group.
Definition: partGroup.h:72
This is the base class for a number of special render effects that may be set on scene graph nodes to...
Definition: renderEffect.h:48
This represents a unique collection of RenderEffect objects that correspond to a particular renderabl...
Definition: renderEffects.h:41
const RenderEffect * get_effect(size_t n) const
Returns the nth effect in the state.
Specifies how polygons are to be drawn.
get_perspective
Returns the perspective flag.
get_thickness
Returns the line width or point thickness.
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
bool is_empty() const
Returns true if the state is empty, false otherwise.
Definition: renderState.I:27
A node that automatically cycles through rendering each one of its children according to its frame ra...
Definition: sequenceNode.h:27
A node that renders only one of its children, according to the user's indication.
Definition: switchNode.h:25
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
Definition: textureAttrib.h:31
get_num_on_stages
Returns the number of stages that are turned on by the attribute.
Definition: textureAttrib.h:55
get_on_texture
Returns the texture associated with the indicated stage, or NULL if no texture is associated.
Definition: textureAttrib.h:69
get_on_stage
Returns the nth stage turned on by the attribute, sorted in render order.
Definition: textureAttrib.h:55
Defines the properties of a named stage of the multitexture pipeline.
Definition: textureStage.h:35
get_mode
Return the mode of this stage.
Definition: textureStage.h:198
get_texcoord_name
See set_texcoord_name.
Definition: textureStage.h:194
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
get_format
Returns the format of the texture, which represents both the semantic meaning of the texels and,...
Definition: texture.h:370
get_minfilter
Returns the filter mode of the texture for minification.
Definition: texture.h:391
has_filename
Returns true if the filename has been set and is available.
Definition: texture.h:320
get_filename
Returns the filename that has been set.
Definition: texture.h:320
get_alpha_filename
Returns the alpha_filename that has been set.
Definition: texture.h:326
get_magfilter
Returns the filter mode of the texture for magnification.
Definition: texture.h:397
has_alpha_filename
Returns true if the alpha_filename has been set and is available.
Definition: texture.h:326
get_wrap_u
Returns the wrap mode of the texture in the U direction.
Definition: texture.h:378
get_wrap_v
Returns the wrap mode of the texture in the V direction.
Definition: texture.h:382
This structure collects together the different combinations of transforms and blend amounts used by a...
This defines a single entry in a TransformBlendTable.
get_num_transforms
Returns the number of transforms stored in the blend object.
get_weight
Returns the weight associated with the indicated transform, or 0 if there is no entry for the transfo...
void get_blend(LMatrix4 &result, Thread *current_thread) const
Returns the current value of the blend, based on the current value of all of the nested transform obj...
get_transform
Returns the nth transform stored in the blend object.
Indicates a coordinate-system transform on vertices.
get_quat
Returns the rotation component of the transform as a quaternion.
bool has_mat() const
Returns true if the transform can be described as a matrix.
get_scale
Returns the scale component of the transform.
bool has_components() const
Returns true if the transform can be described by separate pos, hpr, and scale components.
get_mat
Returns the matrix that describes the transform.
bool is_identity() const
Returns true if the transform represents the identity matrix, false otherwise.
get_pos
Returns the pos component of the transform.
This controls the enabling of transparency.
get_mode
Returns the transparency mode.
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 node is placed at key points within the scene graph to animate uvs.
Definition: uvScrollNode.h:26
This is an abstract base class that holds a pointer to some transform, computed in some arbitrary way...
This is a class designed to support low-overhead traversals of the complete scene graph,...
PandaNode * node() const
Returns the node traversed to so far.
get_node_path
Constructs and returns an actual NodePath that represents the same path we have just traversed.
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.