Panda3D
Loading...
Searching...
No Matches
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
77using std::pair;
78using std::string;
79
80/**
81 *
82 */
83EggSaver::
84EggSaver(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 */
97add_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 */
117add_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 */
135void EggSaver::
136convert_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 */
173void EggSaver::
174convert_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 */
219void EggSaver::
220convert_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 */
250void EggSaver::
251convert_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 */
280EggGroupNode * 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 */
325void EggSaver::
326convert_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 */
346void EggSaver::
347convert_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 */
355void EggSaver::
356convert_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 */
427void EggSaver::
428convert_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 */
457void EggSaver::
458convert_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 */
684void EggSaver::
685convert_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 */
746void EggSaver::
747convert_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 */
990void EggSaver::
991recurse_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 */
1006bool EggSaver::
1007apply_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 */
1111bool EggSaver::
1112apply_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 */
1197bool EggSaver::
1198apply_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 */
1233bool EggSaver::
1234apply_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 */
1247EggMaterial *EggSaver::
1248get_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 */
1296EggTexture *EggSaver::
1297get_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.
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.
get_into_collide_mask
Returns the current "into" CollideMask.
get_from_collide_mask
Returns the current "from" CollideMask.
get_default_collide_mask
Returns the default into_collide_mask assigned to new CollisionNodes.
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.
get_draw_order
Returns the draw order this attribute specifies.
get_bin_name
Returns the name of the bin this attribute specifies.
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.
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,...
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,...
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 ...
void set_alpha_mode(AlphaMode mode)
Specifies precisely how the transparency for this geometry should be achieved, or if it should be use...
void set_depth_test_mode(DepthTestMode mode)
Specifies whether this geometry should be tested against the depth buffer when it is drawn (assuming ...
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.
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...
const LMatrix4d & get_transform3d() const
Returns the overall transform as a 4x4 matrix.
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.
void add_matrix4(const LMatrix4d &mat)
Appends an arbitrary 4x4 matrix to the current transform.
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.
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 entries that specify...
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:44
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...
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.
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.
get_basename
Return the name represented by just this particular InternalName object, ignoring its parents names.
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.
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.
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...
This represents a unique collection of RenderEffect objects that correspond to a particular renderabl...
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...
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...
get_num_on_stages
Returns the number of stages that are turned on by the attribute.
get_on_texture
Returns the texture associated with the indicated stage, or NULL if no texture is associated.
get_on_stage
Returns the nth stage turned on by the attribute, sorted in render order.
Defines the properties of a named stage of the multitexture pipeline.
get_mode
Return the mode of this stage.
get_texcoord_name
See set_texcoord_name.
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition texture.h:72
get_format
Returns the format of the texture, which represents both the semantic meaning of the texels and,...
Definition texture.h:371
get_minfilter
Returns the filter mode of the texture for minification.
Definition texture.h:392
has_filename
Returns true if the filename has been set and is available.
Definition texture.h:321
get_filename
Returns the filename that has been set.
Definition texture.h:321
get_alpha_filename
Returns the alpha_filename that has been set.
Definition texture.h:327
get_magfilter
Returns the filter mode of the texture for magnification.
Definition texture.h:398
has_alpha_filename
Returns true if the alpha_filename has been set and is available.
Definition texture.h:327
get_wrap_u
Returns the wrap mode of the texture in the U direction.
Definition texture.h:379
get_wrap_v
Returns the wrap mode of the texture in the V direction.
Definition texture.h:383
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.
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.