Panda3D
Loading...
Searching...
No Matches
eggToBam.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 eggToBam.cxx
10 * @author drose
11 * @date 2000-06-28
12 */
13
14#include "eggToBam.h"
15
16#include "config_putil.h"
17#include "bamFile.h"
18#include "load_egg_file.h"
19#include "config_egg2pg.h"
20#include "config_gobj.h"
21#include "config_chan.h"
22#include "pandaNode.h"
23#include "geomNode.h"
24#include "renderState.h"
25#include "textureAttrib.h"
26#include "dcast.h"
28#include "graphicsEngine.h"
29#include "graphicsBuffer.h"
31#include "load_prc_file.h"
32#include "windowProperties.h"
34
35/**
36 *
37 */
38EggToBam::
39EggToBam() :
40 EggToSomething("Bam", ".bam", true, false)
41{
42 set_program_brief("convert .egg files to .bam files");
43 set_program_description
44 ("This program reads Egg files and outputs Bam files, the binary format "
45 "suitable for direct loading of animation and models into Panda. Bam "
46 "files are tied to a particular version of Panda, so should not be "
47 "considered replacements for egg files, but they tend to be smaller and "
48 "load much faster than the equivalent egg files.");
49
50 // -f is always in effect for egg2bam. It doesn't make sense to provide it
51 // as an option to the user.
52 remove_option("f");
53
54 add_path_replace_options();
55 add_path_store_options();
56
57 add_option
58 ("flatten", "flag", 0,
59 "Specifies whether to flatten the egg hierarchy after it is loaded. "
60 "If flag is zero, the egg hierarchy will not be flattened, but will "
61 "instead be written to the bam file exactly as it is. If flag is "
62 "non-zero, the hierarchy will be flattened so that unnecessary nodes "
63 "(usually group nodes with only one child) are eliminated. The default "
64 "if this is not specified is taken from the egg-flatten Config.prc "
65 "variable.",
66 &EggToBam::dispatch_int, &_has_egg_flatten, &_egg_flatten);
67
68 add_option
69 ("combine-geoms", "flag", 0,
70 "Specifies whether to combine sibling GeomNodes into a common GeomNode "
71 "when possible. This flag is only respected if flatten, above, is also "
72 "enabled (or implicitly true from the Config.prc file). The default if "
73 "this is not specified is taken from the egg-combine-geoms Config.prc "
74 "variable.",
75 &EggToBam::dispatch_int, &_has_egg_combine_geoms, &_egg_combine_geoms);
76
77 add_option
78 ("suppress-hidden", "flag", 0,
79 "Specifies whether to suppress hidden geometry. If this is nonzero, "
80 "egg geometry tagged as \"hidden\" will be removed from the final "
81 "scene graph; otherwise, it will be preserved (but stashed). The "
82 "default is nonzero, to remove it.",
83 &EggToBam::dispatch_int, nullptr, &_egg_suppress_hidden);
84
85 add_option
86 ("ls", "", 0,
87 "Writes a scene graph listing to standard output after the egg "
88 "file has been loaded, showing the nodes that will be written out.",
89 &EggToBam::dispatch_none, &_ls);
90
91 add_option
92 ("C", "quality", 0,
93 "Specify the quality level for lossy channel compression. If this "
94 "is specified, the animation channels will be compressed at this "
95 "quality level, which is normally an integer value between 0 and 100, "
96 "inclusive, where higher numbers produce larger files with greater "
97 "quality. Generally, 95 is the highest useful quality level. Use "
98 "-NC (described below) to disable channel compression. If neither "
99 "option is specified, the default comes from the Config.prc file.",
100 &EggToBam::dispatch_int, &_has_compression_quality, &_compression_quality);
101
102 add_option
103 ("NC", "", 0,
104 "Turn off lossy compression of animation channels. Channels will be "
105 "written exactly as they are, losslessly.",
106 &EggToBam::dispatch_none, &_compression_off);
107
108 add_option
109 ("rawtex", "", 0,
110 "Record texture data directly in the bam file, instead of storing "
111 "a reference to the texture elsewhere on disk. The textures are "
112 "stored uncompressed, unless -ctex is also specified. "
113 "A particular texture that is encoded into "
114 "multiple different bam files in this way cannot be unified into "
115 "the same part of texture memory if the different bam files are loaded "
116 "together. That being said, this can sometimes be a convenient "
117 "way to ensure the bam file is completely self-contained.",
118 &EggToBam::dispatch_none, &_tex_rawdata);
119
120 add_option
121 ("txo", "", 0,
122 "Rather than writing texture data directly into the bam file, as in "
123 "-rawtex, create a texture object for each referenced texture. A "
124 "texture object is a kind of mini-bam file, with a .txo extension, "
125 "that contains all of the data needed to recreate a texture, including "
126 "its image contents, filter and wrap settings, and so on. 3-D textures "
127 "and cube maps can also be represented in a single .txo file. Texture "
128 "object files, like bam files, are tied to a particular version of "
129 "Panda.",
130 &EggToBam::dispatch_none, &_tex_txo);
131
132#ifdef HAVE_ZLIB
133 add_option
134 ("txopz", "", 0,
135 "In addition to writing texture object files as above, compress each "
136 "one using pzip to a .txo.pz file. In many cases, this will yield a "
137 "disk file size comparable to that achieved by png compression. This "
138 "is an on-disk compression only, and does not affect the amount of "
139 "RAM or texture memory consumed by the texture when it is loaded.",
140 &EggToBam::dispatch_none, &_tex_txopz);
141#endif // HAVE_ZLIB
142
143 add_option
144 ("ctex", "", 0,
145#ifdef HAVE_SQUISH
146 "Pre-compress the texture images using the libsquish library, when "
147 "using -rawtex or -txo. "
148#else
149 "Asks the graphics card to pre-compress the texture images when using "
150 "-rawtex or -txo. "
151#endif // HAVE_SQUISH
152#ifdef HAVE_ZLIB
153 "This is unrelated to the on-disk compression achieved "
154 "via -txopz (and it may be used in conjunction with that parameter). "
155#endif // HAVE_ZLIB
156 "This will result in a smaller RAM and texture memory footprint for "
157 "the texture images. The same "
158 "effect can be achieved at load time by setting compressed-textures in "
159 "your Config.prc file; but -ctex pre-compresses the "
160 "textures so that they do not need to be compressed at load time. "
161#ifndef HAVE_SQUISH
162 "Note that, since your Panda is not compiled with the libsquish "
163 "library, using -ctex will make .txo files that are only guaranteed "
164 "to load on the particular graphics card that was used to "
165 "generate them."
166#endif // HAVE_SQUISH
167 ,
168 &EggToBam::dispatch_none, &_tex_ctex);
169
170 add_option
171 ("mipmap", "", 0,
172 "Records the pre-generated mipmap levels in the texture object file "
173 "when using -rawtex or -txo, regardless of the texture filter mode. This "
174 "will increase the size of the texture object file by about 33%, but "
175 "it prevents the need to compute the mipmaps at runtime. The default "
176 "is to record mipmap levels only when the texture uses a mipmap "
177 "filter mode.",
178 &EggToBam::dispatch_none, &_tex_mipmap);
179
180 add_option
181 ("ctexq", "quality", 0,
182 "Specifies the compression quality to use when performing the "
183 "texture compression requested by -ctex. This may be one of "
184 "'default', 'fastest', 'normal', or 'best'. The default is 'best'. "
185 "Set it to 'default' to use whatever is specified by the Config.prc "
186 "file. This is a global setting only; individual texture quality "
187 "settings appearing within the egg file will override this.",
188 &EggToBam::dispatch_string, nullptr, &_ctex_quality);
189
190 add_option
191 ("load-display", "display name", 0,
192 "Specifies the particular display module to load to perform the texture "
193 "compression requested by -ctex. If this is omitted, the default is "
194 "taken from the Config.prc file."
195#ifdef HAVE_SQUISH
196 " Since your Panda has libsquish compiled in, this is not necessary; "
197 "Panda can compress textures without loading a display module."
198#endif // HAVE_SQUISH
199 ,
200 &EggToBam::dispatch_string, nullptr, &_load_display);
201
202 redescribe_option
203 ("cs",
204 "Specify the coordinate system of the resulting " + _format_name +
205 " file. This may be "
206 "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default "
207 "is z-up.");
208
209 _force_complete = true;
210 _egg_flatten = 0;
211 _egg_combine_geoms = 0;
212 _egg_suppress_hidden = 1;
213 _tex_txopz = false;
214 _ctex_quality = "best";
215}
216
217/**
218 *
219 */
220void EggToBam::
221run() {
222 if (_has_egg_flatten) {
223 // If the user specified some -flatten, we need to set the corresponding
224 // Config.prc variable.
225 egg_flatten = (_egg_flatten != 0);
226 }
227 if (_has_egg_combine_geoms) {
228 // Ditto with -combine_geoms.
229 egg_combine_geoms = (_egg_combine_geoms != 0);
230 }
231
232 // We always set egg_suppress_hidden.
233 egg_suppress_hidden = _egg_suppress_hidden;
234
235 if (_compression_off) {
236 // If the user specified -NC, turn off channel compression.
237 compress_channels = false;
238
239 } else if (_has_compression_quality) {
240 // Otherwise, if the user specified a compression quality with -C, use
241 // that quality level.
242 compress_channels = true;
243 compress_chan_quality = _compression_quality;
244 }
245
246 if (_ctex_quality != "default") {
247 // Override the user's config file with the command-line parameter for
248 // texture compression.
249 std::string prc = "texture-quality-level " + _ctex_quality;
250 load_prc_file_data("prc", prc);
251 }
252
253 if (!_got_coordinate_system) {
254 // If the user didn't specify otherwise, ensure the coordinate system is
255 // Z-up.
256 _data->set_coordinate_system(CS_zup_right);
257 }
258
259 PT(PandaNode) root = load_egg_data(_data);
260 if (root == nullptr) {
261 nout << "Unable to build scene graph from egg file.\n";
262 exit(1);
263 }
264
265 if (_tex_ctex) {
266#ifndef HAVE_SQUISH
267 if (!make_buffer()) {
268 nout << "Unable to initialize graphics context; cannot compress textures.\n";
269 exit(1);
270 }
271#endif // HAVE_SQUISH
272 }
273
274 if (_tex_txo || _tex_txopz || (_tex_ctex && _tex_rawdata)) {
275 collect_textures(root);
276 Textures::iterator ti;
277 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
278 Texture *tex = (*ti);
279 tex->get_ram_image();
280 bool want_mipmaps = (_tex_mipmap || tex->uses_mipmaps());
281 if (want_mipmaps) {
282 // Generate mipmap levels.
284 }
285
286 if (_tex_ctex) {
287#ifdef HAVE_SQUISH
288 if (!tex->compress_ram_image()) {
289 nout << " couldn't compress " << tex->get_name() << "\n";
290 }
291 tex->set_compression(Texture::CM_on);
292#else // HAVE_SQUISH
293 tex->set_keep_ram_image(true);
294 bool has_mipmap_levels = (tex->get_num_ram_mipmap_images() > 1);
295 if (!_engine->extract_texture_data(tex, _gsg)) {
296 nout << " couldn't compress " << tex->get_name() << "\n";
297 }
298 if (!has_mipmap_levels && !want_mipmaps) {
299 // Make sure we didn't accidentally introduce mipmap levels by
300 // rendezvousing through the graphics card.
302 }
303 tex->set_keep_ram_image(false);
304#endif // HAVE_SQUISH
305 }
306
307 if (_tex_txo || _tex_txopz) {
308 convert_txo(tex);
309 }
310 }
311 }
312
313 if (_ls) {
314 root->ls(nout, 0);
315 }
316
317 // This should be guaranteed because we pass false to the constructor,
318 // above.
319 nassertv(has_output_filename());
320
321 Filename filename = get_output_filename();
322 filename.make_dir();
323 nout << "Writing " << filename << "\n";
324 BamFile bam_file;
325 if (!bam_file.open_write(filename)) {
326 nout << "Error in writing.\n";
327 exit(1);
328 }
329
330 if (!bam_file.write_object(root)) {
331 nout << "Error in writing.\n";
332 exit(1);
333 }
334}
335
336/**
337 * Does something with the additional arguments on the command line (after all
338 * the -options have been parsed). Returns true if the arguments are good,
339 * false otherwise.
340 */
341bool EggToBam::
342handle_args(ProgramBase::Args &args) {
343 // If the user specified a path store option, we need to set the bam-
344 // texture-mode Config.prc variable directly to support this (otherwise the
345 // bam code will do what it wants to do anyway).
346 if (_tex_rawdata) {
347 bam_texture_mode = BamFile::BTM_rawdata;
348
349 } else if (_got_path_store) {
350 bam_texture_mode = BamFile::BTM_unchanged;
351
352 } else {
353 // Otherwise, the default path store is absolute; then the bam-texture-
354 // mode can do the appropriate thing to it.
355 _path_replace->_path_store = PS_absolute;
356 }
357
358 return EggToSomething::handle_args(args);
359}
360
361/**
362 * Recursively walks the scene graph, looking for Texture references.
363 */
364void EggToBam::
365collect_textures(PandaNode *node) {
366 collect_textures(node->get_state());
367 if (node->is_geom_node()) {
368 GeomNode *geom_node = DCAST(GeomNode, node);
369 int num_geoms = geom_node->get_num_geoms();
370 for (int i = 0; i < num_geoms; ++i) {
371 collect_textures(geom_node->get_geom_state(i));
372 }
373 }
374
375 PandaNode::Children children = node->get_children();
376 int num_children = children.get_num_children();
377 for (int i = 0; i < num_children; ++i) {
378 collect_textures(children.get_child(i));
379 }
380}
381
382/**
383 * Recursively walks the scene graph, looking for Texture references.
384 */
385void EggToBam::
386collect_textures(const RenderState *state) {
387 const TextureAttrib *tex_attrib = DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_type()));
388 if (tex_attrib != nullptr) {
389 int num_on_stages = tex_attrib->get_num_on_stages();
390 for (int i = 0; i < num_on_stages; ++i) {
391 _textures.insert(tex_attrib->get_on_texture(tex_attrib->get_on_stage(i)));
392 }
393 }
394}
395
396/**
397 * If the indicated Texture was not already loaded from a txo file, writes it
398 * to a txo file and updates the Texture object to reference the new file.
399 */
400void EggToBam::
401convert_txo(Texture *tex) {
402 if (!tex->get_loaded_from_txo()) {
403 Filename fullpath = tex->get_fullpath().get_filename_index(0);
404 if (_tex_txopz) {
405 fullpath.set_extension("txo.pz");
406 // We use this clumsy syntax so that the new extension appears to be two
407 // separate extensions, .txo followed by .pz, which is what
408 // Texture::write() expects to find.
409 fullpath = Filename(fullpath.get_fullpath());
410 } else {
411 fullpath.set_extension("txo");
412 }
413
414 if (tex->write(fullpath)) {
415 nout << " Writing " << fullpath;
416 if (tex->get_ram_image_compression() != Texture::CM_off) {
417 nout << " (compressed " << tex->get_ram_image_compression() << ")";
418 }
419 nout << "\n";
420 tex->set_loaded_from_txo();
421 tex->set_fullpath(fullpath);
423
424 Filename filename = tex->get_filename().get_filename_index(0);
425 if (_tex_txopz) {
426 filename.set_extension("txo.pz");
427 filename = Filename(filename.get_fullpath());
428 } else {
429 filename.set_extension("txo");
430 }
431
432 tex->set_filename(filename);
434 }
435 }
436}
437
438/**
439 * Creates a GraphicsBuffer for communicating with the graphics card.
440 */
441bool EggToBam::
442make_buffer() {
443 if (!_load_display.empty()) {
444 // Override the user's config file with the command-line parameter.
445 std::string prc = "load-display " + _load_display;
446 load_prc_file_data("prc", prc);
447 }
448
450 _pipe = selection->make_default_pipe();
451 if (_pipe == nullptr) {
452 nout << "Unable to create graphics pipe.\n";
453 return false;
454 }
455
456 _engine = new GraphicsEngine;
457
459
460 // Some graphics drivers can only create single-buffered offscreen buffers.
461 // So request that.
462 fbprops.set_back_buffers(0);
463
464 WindowProperties winprops;
465 winprops.set_size(1, 1);
466 winprops.set_origin(0, 0);
467 winprops.set_undecorated(true);
468 winprops.set_open(true);
469 winprops.set_z_order(WindowProperties::Z_bottom);
470
471 // We don't care how big the buffer is; we just need it to manifest the GSG.
472 _buffer = _engine->make_output(_pipe, "buffer", 0,
473 fbprops, winprops,
474 GraphicsPipe::BF_fb_props_optional);
475 _engine->open_windows();
476 if (_buffer == nullptr || !_buffer->is_valid()) {
477 nout << "Unable to create graphics window.\n";
478 return false;
479 }
480 _gsg = _buffer->get_gsg();
481
482 return true;
483}
484
485
486int main(int argc, char *argv[]) {
487 EggToBam prog;
488 prog.parse_command_line(argc, argv);
489 prog.run();
490 return 0;
491}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The principle public interface to reading and writing Bam disk files.
Definition bamFile.h:41
bool open_write(const Filename &bam_filename, bool report_errors=true)
Attempts to open the indicated file for writing.
Definition bamFile.cxx:190
bool write_object(const TypedWritable *object)
Writes the indicated object to the Bam file.
Definition bamFile.cxx:226
This is the general base class for a file-converter program that reads some model file format and gen...
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
bool make_dir() const
Creates all the directories in the path to the file specified in the filename, except for the basenam...
std::string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition filename.I:338
void set_extension(const std::string &s)
Replaces the file extension.
Definition filename.cxx:804
A container for the various kinds of properties we might ask to have on a graphics frameBuffer before...
static const FrameBufferProperties & get_default()
Returns a FrameBufferProperties structure with all of the default values filled in according to the u...
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 class is the main interface to controlling the render process.
void open_windows()
Fully opens (or closes) any windows that have recently been requested open or closed,...
bool extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg)
Asks the indicated GraphicsStateGuardian to retrieve the texture memory image of the indicated textur...
GraphicsOutput * make_output(GraphicsPipe *pipe, const std::string &name, int sort, const FrameBufferProperties &fb_prop, const WindowProperties &win_prop, int flags, GraphicsStateGuardian *gsg=nullptr, GraphicsOutput *host=nullptr)
Creates a new window (or buffer) and returns it.
get_gsg
Returns the GSG that is associated with this window.
bool is_valid() const
Returns true if the output is fully created and ready for rendering, false otherwise.
This maintains a list of GraphicsPipes by type that are available for creation.
static GraphicsPipeSelection * get_global_ptr()
Returns a pointer to the one global GraphicsPipeSelection object.
PandaNode * get_child(size_t n) const
Returns the nth child of the node.
Definition pandaNode.I:962
size_t get_num_children() const
Returns the number of children of the node.
Definition pandaNode.I:953
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
virtual bool is_geom_node() const
A simple downcast check.
get_children
Returns an object that can be used to walk through the list of children of the node.
Definition pandaNode.h:782
virtual void parse_command_line(int argc, char **argv)
Dispatches on each of the options on the command line, and passes the remaining parameters to handle_...
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition renderState.h:47
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.
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
set_compression
Requests that this particular Texture be compressed when it is loaded into texture memory.
Definition texture.h:414
void generate_ram_mipmap_images()
Automatically fills in the n mipmap levels of the Texture, based on the texture's source image.
Definition texture.I:1692
void clear_ram_mipmap_images()
Discards the current system-RAM image for all mipmap levels, except level 0 (the base image).
Definition texture.I:1675
bool write(const Filename &fullpath)
Writes the texture to the named filename.
Definition texture.I:299
get_filename
Returns the filename that has been set.
Definition texture.h:321
CPTA_uchar get_ram_image()
Returns the system-RAM image data associated with the texture.
Definition texture.I:1357
get_ram_image_compression
Returns the compression mode in which the ram image is already stored pre- compressed.
Definition texture.h:472
clear_alpha_fullpath
Removes the alpha fullpath, if it was previously set.
Definition texture.h:339
clear_alpha_filename
Removes the alpha filename, if it was previously set.
Definition texture.h:327
bool compress_ram_image(CompressionMode compression=CM_on, QualityLevel quality_level=QL_default, GraphicsStateGuardianBase *gsg=nullptr)
Attempts to compress the texture's RAM image internally, to a format supported by the indicated GSG.
Definition texture.I:1480
bool uses_mipmaps() const
Returns true if the minfilter settings on this texture indicate the use of mipmapping,...
Definition texture.I:1128
get_fullpath
Returns the fullpath that has been set.
Definition texture.h:333
set_loaded_from_txo
Sets the flag that indicates the texture has been loaded from a txo file.
Definition texture.h:587
set_fullpath
Sets the full pathname to the file that contains the image's contents, as found along the search path...
Definition texture.h:333
get_loaded_from_txo
Returns the flag that indicates the texture has been loaded from a txo file.
Definition texture.h:587
set_keep_ram_image
Sets the flag that indicates whether this Texture is eligible to have its main RAM copy of the textur...
Definition texture.h:473
set_filename
Sets the name of the file that contains the image's contents.
Definition texture.h:321
get_num_ram_mipmap_images
Returns the maximum number of mipmap level images available in system memory.
Definition texture.h:504
A container for the various kinds of properties we might ask to have on a graphics window before we o...
set_size
Specifies the requested size of the window, in pixels.
set_open
Specifies whether the window should be open.
set_undecorated
Specifies whether the window should be created with a visible title and border (false,...
set_z_order
Specifies the relative ordering of the window with respect to other windows.
set_origin
Specifies the origin on the screen (in pixels, relative to the top-left corner) at which the window s...
Filename get_output_filename() const
If has_output_filename() returns true, this is the filename that the user specified.
bool has_output_filename() const
Returns true if the user specified an output filename, false otherwise (e.g.
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.
EXPCL_PANDA_PUTIL ConfigPage * load_prc_file_data(const std::string &name, const std::string &data)
Another convenience function to load a prc file from an explicit string, which represents the content...
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.