Panda3D
Loading...
Searching...
No Matches
xFileToEggConverter.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 xFileToEggConverter.cxx
10 * @author drose
11 * @date 2001-06-21
12 */
13
14#include "xFileToEggConverter.h"
15#include "xFileMesh.h"
16#include "xFileMaterial.h"
17#include "xFileAnimationSet.h"
18#include "config_xfile.h"
19
20#include "eggData.h"
21#include "eggGroup.h"
22#include "eggTable.h"
23#include "eggXfmSAnim.h"
24#include "eggGroupUniquifier.h"
25#include "datagram.h"
28#include "dcast.h"
29
30using std::string;
31
32/**
33 *
34 */
35XFileToEggConverter::
36XFileToEggConverter() {
37 _make_char = false;
38 _frame_rate = 0.0;
39 _x_file = new XFile(true);
40 _dart_node = nullptr;
41}
42
43/**
44 *
45 */
46XFileToEggConverter::
47XFileToEggConverter(const XFileToEggConverter &copy) :
49 _make_char(copy._make_char)
50{
51 _x_file = new XFile(true);
52 _dart_node = nullptr;
53}
54
55/**
56 *
57 */
58XFileToEggConverter::
59~XFileToEggConverter() {
60 close();
61}
62
63/**
64 * Allocates and returns a new copy of the converter.
65 */
70
71
72/**
73 * Returns the English name of the file type this converter supports.
74 */
76get_name() const {
77 return "DirectX";
78}
79
80/**
81 * Returns the common extension of the file type this converter supports.
82 */
84get_extension() const {
85 return "x";
86}
87
88/**
89 * Returns true if this file type can transparently load compressed files
90 * (with a .pz extension), false otherwise.
91 */
93supports_compressed() const {
94 return true;
95}
96
97/**
98 * Handles the reading of the input file and converting it to egg. Returns
99 * true if successful, false otherwise.
100 *
101 * This is designed to be as generic as possible, generally in support of run-
102 * time loading. Command-line converters may choose to use convert_flt()
103 * instead, as it provides more control.
104 */
106convert_file(const Filename &filename) {
107 close();
108 clear_error();
109
110 if (!_x_file->read(filename)) {
111 nout << "Unable to open X file: " << filename << "\n";
112 return false;
113 }
114
115 if (_char_name.empty()) {
116 _char_name = filename.get_basename_wo_extension();
117 }
118
119 if (_egg_data->get_coordinate_system() == CS_default) {
120 _egg_data->set_coordinate_system(CS_yup_left);
121 }
122
123 if (!get_toplevel()) {
124 return false;
125 }
126
127 if (!create_polygons()) {
128 return false;
129 }
130
131 if (_make_char) {
132 // Now make sure that each joint has a unique name.
133 EggGroupUniquifier uniquifier(false);
134 uniquifier.uniquify(_dart_node);
135 }
136
137 if (!create_hierarchy()) {
138 return false;
139 }
140
141 if (_keep_model && !_keep_animation) {
142 strip_nodes(EggTable::get_class_type());
143 }
144
145 if (_keep_animation && !_keep_model) {
146 strip_nodes(EggGroup::get_class_type());
147 }
148
149 return !had_error();
150}
151
152/**
153 * Finalizes and closes the file previously opened via convert_file().
154 */
156close() {
157 _x_file->clear();
158
159 // Clean up all the other stuff.
160 Meshes::const_iterator mi;
161 for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) {
162 delete (*mi);
163 }
164 _meshes.clear();
165
166 AnimationSets::const_iterator asi;
167 for (asi = _animation_sets.begin(); asi != _animation_sets.end(); ++asi) {
168 delete (*asi);
169 }
170 _animation_sets.clear();
171
172 _joints.clear();
173}
174
175/**
176 * Removes all groups of the given type. This is used to implement the -anim
177 * and -model options.
178 */
181 pvector <EggNode *> garbage;
182 EggGroupNode::iterator i;
183 for (i=_egg_data->begin(); i!=_egg_data->end(); ++i) {
184 EggNode *node = (*i);
185 if (node->is_of_type(t)) {
186 garbage.push_back(node);
187 }
188 }
189 for (int n=0; n<(int)garbage.size(); n++) {
190 _egg_data->remove_child(garbage[n]);
191 }
192}
193
194/**
195 * Returns the root of the joint hierarchy, if _make_char is true, or NULL
196 * otherwise.
197 */
199get_dart_node() const {
200 return _dart_node;
201}
202
203/**
204 * Returns an EggTexture pointer whose properties match that of the the given
205 * EggTexture, except for the tref name.
206 */
209 return _textures.create_unique_texture(copy, ~EggTexture::E_tref_name);
210}
211
212/**
213 * Returns an EggMaterial pointer whose properties match that of the the given
214 * EggMaterial, except for the mref name.
215 */
218 return _materials.create_unique_material(copy, ~EggMaterial::E_mref_name);
219}
220
221/**
222 * This is called by set_animation_frame, for the purposes of building the
223 * frame data for the animation--it needs to know the original rest frame
224 * transform.
225 */
227find_joint(const string &joint_name) {
228 Joints::iterator ji;
229 ji = _joints.find(joint_name);
230 if (ji != _joints.end()) {
231 EggGroup *joint = (*ji).second;
232 if (joint == nullptr) {
233 // An invalid joint detected earlier.
234 return nullptr;
235 }
236
237 return joint;
238 }
239
240 // Joint name is unknown. Issue a warning, then insert NULL into the table
241 // so we don't get the same warning again with the next polygon.
242 if (_make_char) {
243 xfile_cat.warning()
244 << "Joint name " << joint_name << " in animation data is undefined.\n";
245 }
246 _joints[joint_name] = nullptr;
247
248 return nullptr;
249}
250
251/**
252 * Pulls off all of the top-level objects in the .x file and converts them,
253 * and their descendents, to the appropriate egg structures.
254 */
255bool XFileToEggConverter::
256get_toplevel() {
257 int num_objects = _x_file->get_num_objects();
258 int i;
259
260 _ticks_per_second = 4800; // X File default.
261
262 // First, make a pass through the toplevel objects and see if we have frames
263 // andor animation.
264 _any_frames = false;
265 _any_animation = false;
266 for (i = 0; i < num_objects; i++) {
267 XFileDataNode *child = _x_file->get_object(i);
268 if (child->is_standard_object("Frame")) {
269 _any_frames = true;
270 } else if (child->is_standard_object("AnimationSet")) {
271 _any_animation = true;
272 }
273 }
274
275 // If we have animation, assume we want to convert it as a character.
276 if (_any_animation) {
277 _make_char = true;
278 }
279
280 EggGroupNode *egg_parent = _egg_data;
281
282 // If we are converting an animatable model, make an extra node to represent
283 // the root of the hierarchy.
284 if (_make_char) {
285 _dart_node = new EggGroup(_char_name);
286 egg_parent->add_child(_dart_node);
287 _dart_node->set_dart_type(EggGroup::DT_default);
288 egg_parent = _dart_node;
289 }
290
291 // Now go back through and convert the objects.
292 for (i = 0; i < num_objects; i++) {
293 if (!convert_toplevel_object(_x_file->get_object(i), egg_parent)) {
294 return false;
295 }
296 }
297
298 return true;
299}
300
301/**
302 * Converts the indicated object, encountered outside of any Frames, to the
303 * appropriate egg structures.
304 */
305bool XFileToEggConverter::
306convert_toplevel_object(XFileDataNode *obj, EggGroupNode *egg_parent) {
307 if (obj->is_standard_object("Header")) {
308 // Quietly ignore headers.
309
310 } else if (obj->is_standard_object("Material")) {
311 // Quietly ignore toplevel materials. These will presumably be referenced
312 // below.
313
314 } else if (obj->is_standard_object("Frame")) {
315 if (!convert_frame(obj, egg_parent)) {
316 return false;
317 }
318
319 } else if (obj->is_standard_object("AnimationSet")) {
320 if (!convert_animation_set(obj)) {
321 return false;
322 }
323
324 } else if (obj->is_standard_object("AnimTicksPerSecond")) {
325 _ticks_per_second = (*obj)[0].i();
326
327 } else if (obj->is_standard_object("Mesh")) {
328 // If there are any Frames at all in the file, then assume a Mesh at the
329 // toplevel is just present to define a reference that will be included
330 // below--so we ignore it here. On the other hand, if the file has no
331 // Frames, then a Mesh at the toplevel must be actual geometry, so convert
332 // it now.
333 if (!_any_frames) {
334 if (!convert_mesh(obj, egg_parent)) {
335 return false;
336 }
337 }
338
339 } else {
340 if (xfile_cat.is_debug()) {
341 xfile_cat.debug()
342 << "Ignoring toplevel object of unknown type: "
343 << obj->get_template_name() << "\n";
344 }
345 }
346
347 return true;
348}
349
350/**
351 * Converts the indicated object to the appropriate egg structures.
352 */
353bool XFileToEggConverter::
354convert_object(XFileDataNode *obj, EggGroupNode *egg_parent) {
355 if (obj->is_standard_object("Header")) {
356 // Quietly ignore headers.
357
358 } else if (obj->is_standard_object("Frame")) {
359 if (!convert_frame(obj, egg_parent)) {
360 return false;
361 }
362
363 } else if (obj->is_standard_object("FrameTransformMatrix")) {
364 if (!convert_transform(obj, egg_parent)) {
365 return false;
366 }
367
368 } else if (obj->is_standard_object("Mesh")) {
369 if (!convert_mesh(obj, egg_parent)) {
370 return false;
371 }
372
373 } else {
374 if (xfile_cat.is_debug()) {
375 xfile_cat.debug()
376 << "Ignoring object of unknown type: "
377 << obj->get_template_name() << "\n";
378 }
379 }
380
381 return true;
382}
383
384/**
385 * Converts the indicated frame to the appropriate egg structures.
386 */
387bool XFileToEggConverter::
388convert_frame(XFileDataNode *obj, EggGroupNode *egg_parent) {
389
390 string name = obj->get_name();
391 EggGroup *group = new EggGroup(name);
392 egg_parent->add_child(group);
393
394 if (_make_char) {
395 group->set_group_type(EggGroup::GT_joint);
396 if (name.empty()) {
397 // Make up a name for this unnamed joint.
398 group->set_name("unnamed");
399
400 } else {
401 bool inserted = _joints.insert(Joints::value_type(name, group)).second;
402 if (!inserted) {
403 xfile_cat.warning()
404 << "Nonunique Frame name " << name
405 << " encountered; animation will be ambiguous.\n";
406 }
407 }
408 }
409
410 // Now walk through the children of the frame.
411 int num_objects = obj->get_num_objects();
412 for (int i = 0; i < num_objects; i++) {
413 if (!convert_object(obj->get_object(i), group)) {
414 return false;
415 }
416 }
417
418 return true;
419}
420
421/**
422 * Reads a transform matrix, a child of a given frame, and applies it to the
423 * node. Normally this can only be done if the node in question is an
424 * EggGroup, which should be the case if the transform was a child of a frame.
425 */
426bool XFileToEggConverter::
427convert_transform(XFileDataNode *obj, EggGroupNode *egg_parent) {
428 LMatrix4d mat = (*obj)["frameMatrix"]["matrix"].mat4();
429
430 if (egg_parent->is_of_type(EggGroup::get_class_type())) {
431 EggGroup *egg_group = DCAST(EggGroup, egg_parent);
432 egg_group->set_transform3d(mat);
433
434 } else {
435 xfile_cat.error()
436 << "Transform " << obj->get_name()
437 << " encountered without frame!\n";
438 }
439
440 return true;
441}
442
443/**
444 * Begins an AnimationSet. This is the root of one particular animation
445 * (table of frames per joint) to be applied to the model within this file.
446 */
447bool XFileToEggConverter::
448convert_animation_set(XFileDataNode *obj) {
449 XFileAnimationSet *animation_set = new XFileAnimationSet();
450 animation_set->set_name(obj->get_name());
451
452 _total_tick_deltas = 0;
453 _num_ticks = 0;
454
455 // Now walk through the children of the set; each one animates a different
456 // joint.
457 int num_objects = obj->get_num_objects();
458 for (int i = 0; i < num_objects; i++) {
459 if (!convert_animation_set_object(obj->get_object(i), *animation_set)) {
460 return false;
461 }
462 }
463
464 animation_set->_frame_rate = _frame_rate;
465 if (_num_ticks != 0 && _frame_rate == 0.0) {
466 // Compute the frame rate from the timing information.
467 double delta = (double)_total_tick_deltas / (double)_num_ticks;
468 if (delta != 0.0) {
469 animation_set->_frame_rate = (double)_ticks_per_second / delta;
470 }
471 }
472
473 _animation_sets.push_back(animation_set);
474
475 return true;
476}
477
478/**
479 * Converts the indicated object, a child of a AnimationSet.
480 */
481bool XFileToEggConverter::
482convert_animation_set_object(XFileDataNode *obj,
483 XFileAnimationSet &animation_set) {
484 if (obj->is_standard_object("Animation")) {
485 if (!convert_animation(obj, animation_set)) {
486 return false;
487 }
488
489 } else {
490 if (xfile_cat.is_debug()) {
491 xfile_cat.debug()
492 << "Ignoring animation set object of unknown type: "
493 << obj->get_template_name() << "\n";
494 }
495 }
496
497 return true;
498}
499
500/**
501 * Converts the indicated Animation template object.
502 */
503bool XFileToEggConverter::
504convert_animation(XFileDataNode *obj, XFileAnimationSet &animation_set) {
505 // Within an Animation template, we expect to find a reference to a frame,
506 // possibly an AnimationOptions object, and one or more AnimationKey
507 // objects.
508
509 // First, walk through the list of children, to find the one that is the
510 // frame reference. We need to know this up front so we know which table we
511 // should be building up.
512 string frame_name;
513 bool got_frame_name = false;
514
515 int num_objects = obj->get_num_objects();
516 int i;
517 for (i = 0; i < num_objects; i++) {
518 XFileDataNode *child = obj->get_object(i);
519 if (child->is_reference() && child->is_standard_object("Frame")) {
520 frame_name = child->get_name();
521 got_frame_name = true;
522 }
523 }
524
525 if (!got_frame_name) {
526 xfile_cat.error()
527 << "Animation " << obj->get_name()
528 << " includes no reference to a frame.\n";
529 return false;
530 }
531
532 FrameData &table = animation_set.create_frame_data(frame_name);
533
534 // Now go back again and get the actual data.
535 for (i = 0; i < num_objects; i++) {
536 if (!convert_animation_object(obj->get_object(i), frame_name, table)) {
537 return false;
538 }
539 }
540
541 return true;
542}
543
544/**
545 * Converts the indicated object, a child of a Animation.
546 */
547bool XFileToEggConverter::
548convert_animation_object(XFileDataNode *obj, const string &joint_name,
550 if (obj->is_standard_object("AnimationOptions")) {
551 // Quietly ignore AnimationOptions.
552
553 } else if (obj->is_standard_object("Frame")) {
554 // Quietly ignore frames, since we already got the frame name.
555
556 } else if (obj->is_standard_object("AnimationKey")) {
557 if (!convert_animation_key(obj, joint_name, table)) {
558 return false;
559 }
560
561 } else {
562 if (xfile_cat.is_debug()) {
563 xfile_cat.debug()
564 << "Ignoring animation object of unknown type: "
565 << obj->get_template_name() << "\n";
566 }
567 }
568
569 return true;
570}
571
572/**
573 * Converts the indicated AnimationKey template object.
574 */
575bool XFileToEggConverter::
576convert_animation_key(XFileDataNode *obj, const string &joint_name,
578 int key_type = (*obj)["keyType"].i();
579
580 const XFileDataObject &keys = (*obj)["keys"];
581
582 int last_time = 0;
583 for (int i = 0; i < keys.size(); i++) {
584 // The time value is problematic, since it allows x files to specify
585 // keyframes of arbitrary duration. Panda doesn't support this; all
586 // frames in Panda must be of a constant duration. Thus, we largely
587 // ignore the time value, but we take the average of all deltas as the
588 // duration. This will correctly handle .x files with uniform keyframes,
589 // at least.
590
591 int this_time = keys[i]["time"].i();
592 if (i != 0) {
593 int delta = this_time - last_time;
594 _total_tick_deltas += delta;
595 ++_num_ticks;
596 }
597 last_time = this_time;
598
599 const XFileDataObject &values = keys[i]["tfkeys"]["values"];
600 if (!set_animation_frame(joint_name, table, i, key_type, values)) {
601 return false;
602 }
603 }
604
605 return true;
606}
607
608/**
609 * Sets a single frame of the animation data.
610 */
611bool XFileToEggConverter::
612set_animation_frame(const string &joint_name,
613 XFileToEggConverter::FrameData &table, int frame,
614 int key_type, const XFileDataObject &values) {
615 if ((int)table._entries.size() <= frame) {
616 nassertr((int)table._entries.size() == frame, false);
617 table._entries.push_back(XFileAnimationSet::FrameEntry());
618 }
619
620 XFileAnimationSet::FrameEntry &frame_entry = table._entries[frame];
621
622 // Now modify the last row in the table.
623 switch (key_type) {
624 case 0:
625 // Key type 0: rotation. This appears to be a quaternion. Hope we get
626 // the coordinate system right.
627 if (values.size() != 4) {
628 xfile_cat.error()
629 << "Incorrect number of values in animation table: "
630 << values.size() << " for rotation data.\n";
631 return false;
632 }
633 frame_entry._rot.invert_from(LQuaterniond(values.vec4()));
634 table._flags |= XFileAnimationSet::FDF_rot;
635 break;
636
637 case 1:
638 if (values.size() != 3) {
639 xfile_cat.error()
640 << "Incorrect number of values in animation table: "
641 << values.size() << " for scale data.\n";
642 return false;
643 }
644 frame_entry._scale = values.vec3();
645 table._flags |= XFileAnimationSet::FDF_scale;
646 break;
647
648 case 2:
649 // Key type 2: position
650 if (values.size() != 3) {
651 xfile_cat.error()
652 << "Incorrect number of values in animation table: "
653 << values.size() << " for position data.\n";
654 return false;
655 }
656 frame_entry._trans = values.vec3();
657 table._flags |= XFileAnimationSet::FDF_trans;
658 break;
659
660 /*
661 case 3:
662 // Key type 3: ????
663 break;
664 */
665
666 case 4:
667 // Key type 4: full matrix
668 if (values.size() != 16) {
669 xfile_cat.error()
670 << "Incorrect number of values in animation table: "
671 << values.size() << " for matrix data.\n";
672 return false;
673 }
674 frame_entry._mat = values.mat4();
675 table._flags |= XFileAnimationSet::FDF_mat;
676 break;
677
678 default:
679 xfile_cat.error()
680 << "Unsupported key type " << key_type << " in animation table.\n";
681 return false;
682 }
683
684 return true;
685}
686
687/**
688 * Converts the indicated mesh to the appropriate egg structures.
689 */
690bool XFileToEggConverter::
691convert_mesh(XFileDataNode *obj, EggGroupNode *egg_parent) {
692 XFileMesh *mesh = new XFileMesh(_egg_data->get_coordinate_system());
693 mesh->set_name(obj->get_name());
694 mesh->set_egg_parent(egg_parent);
695
696 if (!mesh->fill_mesh(obj)) {
697 delete mesh;
698 return false;
699 }
700
701 _meshes.push_back(mesh);
702
703 return true;
704}
705
706/**
707 * Creates all the polygons associated with previously-saved meshes.
708 */
709bool XFileToEggConverter::
710create_polygons() {
711 bool okflag = true;
712
713 Meshes::const_iterator mi;
714 for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) {
715 if (!(*mi)->create_polygons(this)) {
716 okflag = false;
717 }
718 delete (*mi);
719 }
720 _meshes.clear();
721
722 return okflag;
723}
724
725/**
726 * Creates the animation table hierarchies for the previously-saved animation
727 * sets.
728 */
729bool XFileToEggConverter::
730create_hierarchy() {
731 bool okflag = true;
732
733 AnimationSets::const_iterator asi;
734 for (asi = _animation_sets.begin(); asi != _animation_sets.end(); ++asi) {
735 if (_make_char) {
736 if (!(*asi)->create_hierarchy(this)) {
737 okflag = false;
738 }
739 }
740 delete (*asi);
741 }
742 _animation_sets.clear();
743
744 return okflag;
745}
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.
This is a specialization of EggNameUniquifier to generate unique names for EggGroup nodes.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition eggGroup.h:34
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...
void uniquify(EggNode *node)
Begins the traversal from the indicated node.
A base class for things that may be directly added into the egg hierarchy.
Definition eggNode.h:36
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
void set_transform3d(const LMatrix4d &mat)
Sets the overall transform as a 4x4 matrix.
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
This is a base class for a family of converter classes that manage a conversion from some file type t...
bool had_error() const
Returns true if an error was detected during the conversion process (unless _allow_errors is true),...
void clear_error()
Resets the error flag to the no-error state.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
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 represents a tree of EggTables, corresponding to Animation entries in the X file.
FrameData & create_frame_data(const std::string &joint_name)
Returns a reference to a new FrameData table corresponding to the indicated joint.
This is an abstract base class for an XFileNode which is also an XFileDataObject.
const std::string & get_template_name() const
A convenience function to return the name of the template used to define this data object.
virtual bool is_standard_object(const std::string &template_name) const
Returns true if this node represents an instance of the standard template with the indicated name,...
The abstract base class for a number of different types of data elements that may be stored in the X ...
LMatrix4d mat4() const
Returns the object's representation as an LMatrix4d.
LVecBase4d vec4() const
Returns the object's representation as an LVecBase4d.
int i() const
Unambiguously returns the object's representation as an integer, or 0 if the object has no integer re...
int size() const
Returns the number of nested data objects within this object.
LVecBase3d vec3() const
Returns the object's representation as an LVecBase3d.
This is a collection of polygons; i.e.
Definition xFileMesh.h:45
bool fill_mesh(XFileDataNode *obj)
Fills the structure based on the raw data from the X file's Mesh object.
void set_egg_parent(EggGroupNode *egg_parent)
Specifies the egg node that will eventually be the parent of this mesh, when create_polygons() is lat...
int get_num_objects() const
Returns the list of child objects of this node.
Definition xFileNode.I:57
virtual bool is_reference() const
Returns true if this node represents an indirect reference to an object defined previously in the fil...
XFileDataNode * get_object(int n) const
Returns the nth child object of this node.
Definition xFileNode.I:67
virtual bool convert_file(const Filename &filename)
Handles the reading of the input file and converting it to egg.
virtual std::string get_extension() const
Returns the common extension of the file type this converter supports.
EggTexture * create_unique_texture(const EggTexture &copy)
Returns an EggTexture pointer whose properties match that of the the given EggTexture,...
EggGroup * get_dart_node() const
Returns the root of the joint hierarchy, if _make_char is true, or NULL otherwise.
virtual std::string get_name() const
Returns the English name of the file type this converter supports.
void close()
Finalizes and closes the file previously opened via convert_file().
EggMaterial * create_unique_material(const EggMaterial &copy)
Returns an EggMaterial pointer whose properties match that of the the given EggMaterial,...
virtual bool supports_compressed() const
Returns true if this file type can transparently load compressed files (with a .pz extension),...
virtual SomethingToEggConverter * make_copy()
Allocates and returns a new copy of the converter.
EggGroup * find_joint(const std::string &joint_name)
This is called by set_animation_frame, for the purposes of building the frame data for the animation–...
void strip_nodes(TypeHandle t)
Removes all groups of the given type.
This represents the complete contents of an X file (file.x) in memory.
Definition xFile.h:32
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.