Panda3D
Loading...
Searching...
No Matches
dcFile.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 dcFile.cxx
10 * @author drose
11 * @date 2000-10-05
12 */
13
14#include "dcFile.h"
15#include "dcClass.h"
16#include "dcSwitch.h"
17#include "dcParserDefs.h"
18#include "dcLexerDefs.h"
19#include "dcTypedef.h"
20#include "dcKeyword.h"
21#include "hashGenerator.h"
22
23#ifdef WITHIN_PANDA
24#include "filename.h"
25#include "config_express.h"
26#include "virtualFileSystem.h"
28#include "configVariableList.h"
29#endif
30
31using std::cerr;
32using std::string;
33
34
35/**
36 *
37 */
38DCFile::
39DCFile() {
40 _all_objects_valid = true;
41 _inherited_fields_stale = false;
42
43 setup_default_keywords();
44}
45
46/**
47 *
48 */
49DCFile::
50~DCFile() {
51 clear();
52}
53
54/**
55 * Removes all of the classes defined within the DCFile and prepares it for
56 * reading a new file.
57 */
59clear() {
60 Declarations::iterator di;
61 for (di = _declarations.begin(); di != _declarations.end(); ++di) {
62 delete (*di);
63 }
64 for (di = _things_to_delete.begin(); di != _things_to_delete.end(); ++di) {
65 delete (*di);
66 }
67
68 _classes.clear();
69 _imports.clear();
70 _things_by_name.clear();
71 _typedefs.clear();
72 _typedefs_by_name.clear();
73 _keywords.clear_keywords();
74 _declarations.clear();
75 _things_to_delete.clear();
76 setup_default_keywords();
77
78 _all_objects_valid = true;
79 _inherited_fields_stale = false;
80}
81
82#ifdef WITHIN_PANDA
83
84/**
85 * This special method reads all of the .dc files named by the "dc-file"
86 * config.prc variable, and loads them into the DCFile namespace.
87 */
88bool DCFile::
89read_all() {
90 static ConfigVariableList dc_files
91 ("dc-file", PRC_DESC("The list of dc files to load."));
92
93 if (dc_files.size() == 0) {
94 cerr << "No files specified via dc-file Config.prc variable!\n";
95 return false;
96 }
97
98 int size = dc_files.size();
99
100 // Load the DC files in opposite order, because we want to load the least-
101 // important (most fundamental) files first.
102 for (int i = size - 1; i >= 0; --i) {
103 string dc_file = ExecutionEnvironment::expand_string(dc_files[i]);
104 Filename filename = Filename::from_os_specific(dc_file);
105 if (!read(filename)) {
106 return false;
107 }
108 }
109
110 return true;
111}
112
113#endif // WITHIN_PANDA
114
115/**
116 * Opens and reads the indicated .dc file by name. The distributed classes
117 * defined in the file will be appended to the set of distributed classes
118 * already recorded, if any.
119 *
120 * Returns true if the file is successfully read, false if there was an error
121 * (in which case the file might have been partially read).
122 */
124read(Filename filename) {
125#ifdef WITHIN_PANDA
126 filename.set_text();
128 std::istream *in = vfs->open_read_file(filename, true);
129 if (in == nullptr) {
130 cerr << "Cannot open " << filename << " for reading.\n";
131 return false;
132 }
133 bool okflag = read(*in, filename);
134
135 // For some reason--compiler bug in gcc 3.2?--explicitly deleting the in
136 // pointer does not call the appropriate global delete function; instead
137 // apparently calling the system delete function. So we call the delete
138 // function by hand instead.
139 vfs->close_read_file(in);
140
141 return okflag;
142
143#else // WITHIN_PANDA
144
145 pifstream in;
146 in.open(filename.c_str());
147
148 if (!in) {
149 cerr << "Cannot open " << filename << " for reading.\n";
150 return false;
151 }
152
153 return read(in, filename);
154
155#endif // WITHIN_PANDA
156}
157
158/**
159 * Parses the already-opened input stream for distributed class descriptions.
160 * The filename parameter is optional and is only used when reporting errors.
161 *
162 * The distributed classes defined in the file will be appended to the set of
163 * distributed classes already recorded, if any.
164 *
165 * Returns true if the file is successfully read, false if there was an error
166 * (in which case the file might have been partially read).
167 */
169read(std::istream &in, const string &filename) {
170 cerr << "DCFile::read of " << filename << "\n";
171 dc_init_parser(in, filename, *this);
172 dcyyparse();
173 dc_cleanup_parser();
174
175 return (dc_error_count() == 0);
176}
177
178/**
179 * Opens the indicated filename for output and writes a parseable description
180 * of all the known distributed classes to the file.
181 *
182 * Returns true if the description is successfully written, false otherwise.
183 */
185write(Filename filename, bool brief) const {
186 pofstream out;
187
188#ifdef WITHIN_PANDA
189 filename.set_text();
190 filename.open_write(out);
191#else
192 out.open(filename.c_str());
193#endif
194
195 if (!out) {
196 cerr << "Can't open " << filename << " for output.\n";
197 return false;
198 }
199 return write(out, brief);
200}
201
202/**
203 * Writes a parseable description of all the known distributed classes to the
204 * stream.
205 *
206 * Returns true if the description is successfully written, false otherwise.
207 */
209write(std::ostream &out, bool brief) const {
210 if (!_imports.empty()) {
211 Imports::const_iterator ii;
212 for (ii = _imports.begin(); ii != _imports.end(); ++ii) {
213 const Import &import = (*ii);
214 if (import._symbols.empty()) {
215 out << "import " << import._module << "\n";
216 } else {
217 out << "from " << import._module << " import ";
218 ImportSymbols::const_iterator si = import._symbols.begin();
219 out << *si;
220 ++si;
221 while (si != import._symbols.end()) {
222 out << ", " << *si;
223 ++si;
224 }
225 out << "\n";
226 }
227 }
228 out << "\n";
229 }
230
231 Declarations::const_iterator di;
232 for (di = _declarations.begin(); di != _declarations.end(); ++di) {
233 (*di)->write(out, brief, 0);
234 out << "\n";
235 }
236
237 return !out.fail();
238}
239
240/**
241 * Returns the number of classes read from the .dc file(s).
242 */
244get_num_classes() const {
245 return _classes.size();
246}
247
248/**
249 * Returns the nth class read from the .dc file(s).
250 */
252get_class(int n) const {
253 nassertr(n >= 0 && n < (int)_classes.size(), nullptr);
254 return _classes[n];
255}
256
257/**
258 * Returns the class that has the indicated name, or NULL if there is no such
259 * class.
260 */
262get_class_by_name(const string &name) const {
263 ThingsByName::const_iterator ni;
264 ni = _things_by_name.find(name);
265 if (ni != _things_by_name.end()) {
266 return (*ni).second->as_class();
267 }
268
269 return nullptr;
270}
271
272/**
273 * Returns the switch that has the indicated name, or NULL if there is no such
274 * switch.
275 */
277get_switch_by_name(const string &name) const {
278 ThingsByName::const_iterator ni;
279 ni = _things_by_name.find(name);
280 if (ni != _things_by_name.end()) {
281 return (*ni).second->as_switch();
282 }
283
284 return nullptr;
285}
286
287/**
288 * Returns a pointer to the one DCField that has the indicated index number,
289 * of all the DCFields across all classes in the file.
290 *
291 * This method is only valid if dc-multiple-inheritance is set true in the
292 * Config.prc file. Without this setting, different DCFields may share the
293 * same index number, so this global lookup is not possible.
294 */
296get_field_by_index(int index_number) const {
297 nassertr(dc_multiple_inheritance, nullptr);
298
299 if (index_number >= 0 && index_number < (int)_fields_by_index.size()) {
300 return _fields_by_index[index_number];
301 }
302
303 return nullptr;
304}
305
306/**
307 * Returns the number of import lines read from the .dc file(s).
308 */
311 return _imports.size();
312}
313
314/**
315 * Returns the module named by the nth import line read from the .dc file(s).
316 */
318get_import_module(int n) const {
319 nassertr(n >= 0 && n < (int)_imports.size(), string());
320 return _imports[n]._module;
321}
322
323/**
324 * Returns the number of symbols explicitly imported by the nth import line.
325 * If this is 0, the line is "import modulename"; if it is more than 0, the
326 * line is "from modulename import symbol, symbol ... ".
327 */
329get_num_import_symbols(int n) const {
330 nassertr(n >= 0 && n < (int)_imports.size(), 0);
331 return _imports[n]._symbols.size();
332}
333
334/**
335 * Returns the ith symbol named by the nth import line read from the .dc
336 * file(s).
337 */
339get_import_symbol(int n, int i) const {
340 nassertr(n >= 0 && n < (int)_imports.size(), string());
341 nassertr(i >= 0 && i < (int)_imports[n]._symbols.size(), string());
342 return _imports[n]._symbols[i];
343}
344
345/**
346 * Returns the number of typedefs read from the .dc file(s).
347 */
349get_num_typedefs() const {
350 return _typedefs.size();
351}
352
353/**
354 * Returns the nth typedef read from the .dc file(s).
355 */
357get_typedef(int n) const {
358 nassertr(n >= 0 && n < (int)_typedefs.size(), nullptr);
359 return _typedefs[n];
360}
361
362/**
363 * Returns the typedef that has the indicated name, or NULL if there is no
364 * such typedef name.
365 */
367get_typedef_by_name(const string &name) const {
368 TypedefsByName::const_iterator ni;
369 ni = _typedefs_by_name.find(name);
370 if (ni != _typedefs_by_name.end()) {
371 return (*ni).second;
372 }
373
374 return nullptr;
375}
376
377/**
378 * Returns the number of keywords read from the .dc file(s).
379 */
381get_num_keywords() const {
382 return _keywords.get_num_keywords();
383}
384
385/**
386 * Returns the nth keyword read from the .dc file(s).
387 */
389get_keyword(int n) const {
390 return _keywords.get_keyword(n);
391}
392
393/**
394 * Returns the keyword that has the indicated name, or NULL if there is no
395 * such keyword name.
396 */
398get_keyword_by_name(const string &name) const {
399 const DCKeyword *keyword = _keywords.get_keyword_by_name(name);
400 if (keyword == nullptr) {
401 keyword = _default_keywords.get_keyword_by_name(name);
402 if (keyword != nullptr) {
403 // One of the historical default keywords was used, but wasn't defined.
404 // Define it implicitly right now.
405 ((DCFile *)this)->_keywords.add_keyword(keyword);
406 }
407 }
408
409 return keyword;
410}
411
412/**
413 * Returns a 32-bit hash index associated with this file. This number is
414 * guaranteed to be consistent if the contents of the file have not changed,
415 * and it is very likely to be different if the contents of the file do
416 * change.
417 */
418unsigned long DCFile::
419get_hash() const {
420 HashGenerator hashgen;
421 generate_hash(hashgen);
422 return hashgen.get_hash();
423}
424
425/**
426 * Accumulates the properties of this file into the hash.
427 */
429generate_hash(HashGenerator &hashgen) const {
430 if (dc_virtual_inheritance) {
431 // Just to make the hash number change in this case.
432 if (dc_sort_inheritance_by_file) {
433 hashgen.add_int(1);
434 } else {
435 hashgen.add_int(2);
436 }
437 }
438
439 hashgen.add_int(_classes.size());
440 Classes::const_iterator ci;
441 for (ci = _classes.begin(); ci != _classes.end(); ++ci) {
442 (*ci)->generate_hash(hashgen);
443 }
444}
445
446/**
447 * Adds the newly-allocated distributed class definition to the file. The
448 * DCFile becomes the owner of the pointer and will delete it when it
449 * destructs. Returns true if the class is successfully added, or false if
450 * there was a name conflict.
451 */
453add_class(DCClass *dclass) {
454 if (!dclass->get_name().empty()) {
455 bool inserted = _things_by_name.insert
456 (ThingsByName::value_type(dclass->get_name(), dclass)).second;
457
458 if (!inserted) {
459 return false;
460 }
461 }
462
463 if (!dclass->is_struct()) {
464 dclass->set_number(get_num_classes());
465 }
466 _classes.push_back(dclass);
467
468 if (dclass->is_bogus_class()) {
469 _all_objects_valid = false;
470 }
471
472 if (!dclass->is_bogus_class()) {
473 _declarations.push_back(dclass);
474 } else {
475 _things_to_delete.push_back(dclass);
476 }
477
478 return true;
479}
480
481/**
482 * Adds the newly-allocated switch definition to the file. The DCFile becomes
483 * the owner of the pointer and will delete it when it destructs. Returns
484 * true if the switch is successfully added, or false if there was a name
485 * conflict.
486 */
488add_switch(DCSwitch *dswitch) {
489 if (!dswitch->get_name().empty()) {
490 bool inserted = _things_by_name.insert
491 (ThingsByName::value_type(dswitch->get_name(), dswitch)).second;
492
493 if (!inserted) {
494 return false;
495 }
496 }
497
498 _declarations.push_back(dswitch);
499
500 return true;
501}
502
503/**
504 * Adds a new name to the list of names of Python modules that are to be
505 * imported by the client or AI to define the code that is associated with the
506 * class interfaces named within the .dc file.
507 */
509add_import_module(const string &import_module) {
510 Import import;
511 import._module = import_module;
512 _imports.push_back(import);
513}
514
515/**
516 * Adds a new name to the list of symbols that are to be explicitly imported
517 * from the most-recently added module, e.g. "from module_name import
518 * symbol". If the list of symbols is empty, the syntax is taken to be
519 * "import module_name".
520 */
522add_import_symbol(const string &import_symbol) {
523 nassertv(!_imports.empty());
524 _imports.back()._symbols.push_back(import_symbol);
525}
526
527/**
528 * Adds the newly-allocated distributed typedef definition to the file. The
529 * DCFile becomes the owner of the pointer and will delete it when it
530 * destructs. Returns true if the typedef is successfully added, or false if
531 * there was a name conflict.
532 */
534add_typedef(DCTypedef *dtypedef) {
535 bool inserted = _typedefs_by_name.insert
536 (TypedefsByName::value_type(dtypedef->get_name(), dtypedef)).second;
537
538 if (!inserted) {
539 return false;
540 }
541
542 dtypedef->set_number(get_num_typedefs());
543 _typedefs.push_back(dtypedef);
544
545 if (dtypedef->is_bogus_typedef()) {
546 _all_objects_valid = false;
547 }
548
549 if (!dtypedef->is_bogus_typedef() && !dtypedef->is_implicit_typedef()) {
550 _declarations.push_back(dtypedef);
551 } else {
552 _things_to_delete.push_back(dtypedef);
553 }
554
555 return true;
556}
557
558/**
559 * Adds the indicated keyword string to the list of keywords known to the
560 * DCFile. These keywords may then be added to DCFields. It is not an error
561 * to add a particular keyword more than once.
562 */
564add_keyword(const string &name) {
565 DCKeyword *keyword = new DCKeyword(name);
566 bool added = _keywords.add_keyword(keyword);
567
568 if (added) {
569 _declarations.push_back(keyword);
570 } else {
571 delete keyword;
572 }
573
574 return added;
575}
576
577/**
578 * Adds the indicated declaration to the list of declarations that are not
579 * reported with the file, but will be deleted when the DCFile object
580 * destructs. That is, transfers ownership of the indicated pointer to the
581 * DCFile.
582 */
585 _things_to_delete.push_back(decl);
586}
587
588/**
589 * Sets the next sequential available index number on the indicated field.
590 * This is only meant to be called
591 */
594 field->set_number((int)_fields_by_index.size());
595 _fields_by_index.push_back(field);
596}
597
598/**
599 * Adds an entry for each of the default keywords that are defined for every
600 * DCFile for legacy reasons.
601 */
602void DCFile::
603setup_default_keywords() {
604 struct KeywordDef {
605 const char *name;
606 int flag;
607 };
608 static KeywordDef default_keywords[] = {
609 { "required", 0x0001 },
610 { "broadcast", 0x0002 },
611 { "ownrecv", 0x0004 },
612 { "ram", 0x0008 },
613 { "db", 0x0010 },
614 { "clsend", 0x0020 },
615 { "clrecv", 0x0040 },
616 { "ownsend", 0x0080 },
617 { "airecv", 0x0100 },
618 { nullptr, 0 }
619 };
620
621 _default_keywords.clear_keywords();
622 for (int i = 0; default_keywords[i].name != nullptr; ++i) {
623 DCKeyword *keyword =
624 new DCKeyword(default_keywords[i].name,
625 default_keywords[i].flag);
626
627 _default_keywords.add_keyword(keyword);
628 _things_to_delete.push_back(keyword);
629 }
630}
631
632/**
633 * Reconstructs the inherited fields table of all classes.
634 */
635void DCFile::
636rebuild_inherited_fields() {
637 _inherited_fields_stale = false;
638
639 Classes::iterator ci;
640 for (ci = _classes.begin(); ci != _classes.end(); ++ci) {
641 (*ci)->clear_inherited_fields();
642 }
643 for (ci = _classes.begin(); ci != _classes.end(); ++ci) {
644 (*ci)->rebuild_inherited_fields();
645 }
646}
This class is similar to ConfigVariable, but it reports its value as a list of strings.
Defines a particular DistributedClass as read from an input .dc file.
Definition dcClass.h:44
bool is_struct() const
Returns true if the class has been identified with the "struct" keyword in the dc file,...
Definition dcClass.I:44
void set_number(int number)
Assigns the unique number to this class.
Definition dcClass.cxx:1326
bool is_bogus_class() const
Returns true if the class has been flagged as a bogus class.
Definition dcClass.I:55
const std::string & get_name() const
Returns the name of this class.
Definition dcClass.I:26
This is a common interface for a declaration in a DC file.
A single field of a Distributed Class, either atomic or molecular.
Definition dcField.h:37
void set_number(int number)
Assigns the unique number to this field.
Definition dcField.I:158
Represents the complete list of Distributed Class descriptions as read from a .dc file.
Definition dcFile.h:32
bool add_class(DCClass *dclass)
Adds the newly-allocated distributed class definition to the file.
Definition dcFile.cxx:453
bool add_switch(DCSwitch *dswitch)
Adds the newly-allocated switch definition to the file.
Definition dcFile.cxx:488
DCClass * get_class_by_name(const std::string &name) const
Returns the class that has the indicated name, or NULL if there is no such class.
Definition dcFile.cxx:262
unsigned long get_hash() const
Returns a 32-bit hash index associated with this file.
Definition dcFile.cxx:419
std::string get_import_module(int n) const
Returns the module named by the nth import line read from the .dc file(s).
Definition dcFile.cxx:318
int get_num_typedefs() const
Returns the number of typedefs read from the .dc file(s).
Definition dcFile.cxx:349
const DCKeyword * get_keyword(int n) const
Returns the nth keyword read from the .dc file(s).
Definition dcFile.cxx:389
void add_thing_to_delete(DCDeclaration *decl)
Adds the indicated declaration to the list of declarations that are not reported with the file,...
Definition dcFile.cxx:584
void set_new_index_number(DCField *field)
Sets the next sequential available index number on the indicated field.
Definition dcFile.cxx:593
DCTypedef * get_typedef(int n) const
Returns the nth typedef read from the .dc file(s).
Definition dcFile.cxx:357
DCTypedef * get_typedef_by_name(const std::string &name) const
Returns the typedef that has the indicated name, or NULL if there is no such typedef name.
Definition dcFile.cxx:367
DCField * get_field_by_index(int index_number) const
Returns a pointer to the one DCField that has the indicated index number, of all the DCFields across ...
Definition dcFile.cxx:296
int get_num_import_symbols(int n) const
Returns the number of symbols explicitly imported by the nth import line.
Definition dcFile.cxx:329
int get_num_classes() const
Returns the number of classes read from the .dc file(s).
Definition dcFile.cxx:244
int get_num_import_modules() const
Returns the number of import lines read from the .dc file(s).
Definition dcFile.cxx:310
void add_import_module(const std::string &import_module)
Adds a new name to the list of names of Python modules that are to be imported by the client or AI to...
Definition dcFile.cxx:509
const DCKeyword * get_keyword_by_name(const std::string &name) const
Returns the keyword that has the indicated name, or NULL if there is no such keyword name.
Definition dcFile.cxx:398
void add_import_symbol(const std::string &import_symbol)
Adds a new name to the list of symbols that are to be explicitly imported from the most-recently adde...
Definition dcFile.cxx:522
bool add_keyword(const std::string &name)
Adds the indicated keyword string to the list of keywords known to the DCFile.
Definition dcFile.cxx:564
bool write(Filename filename, bool brief) const
Opens the indicated filename for output and writes a parseable description of all the known distribut...
Definition dcFile.cxx:185
bool read(Filename filename)
Opens and reads the indicated .dc file by name.
Definition dcFile.cxx:124
DCClass * get_class(int n) const
Returns the nth class read from the .dc file(s).
Definition dcFile.cxx:252
int get_num_keywords() const
Returns the number of keywords read from the .dc file(s).
Definition dcFile.cxx:381
bool add_typedef(DCTypedef *dtypedef)
Adds the newly-allocated distributed typedef definition to the file.
Definition dcFile.cxx:534
DCSwitch * get_switch_by_name(const std::string &name) const
Returns the switch that has the indicated name, or NULL if there is no such switch.
Definition dcFile.cxx:277
void clear()
Removes all of the classes defined within the DCFile and prepares it for reading a new file.
Definition dcFile.cxx:59
std::string get_import_symbol(int n, int i) const
Returns the ith symbol named by the nth import line read from the .dc file(s).
Definition dcFile.cxx:339
void generate_hash(HashGenerator &hashgen) const
Accumulates the properties of this file into the hash.
Definition dcFile.cxx:429
void clear_keywords()
Removes all keywords from the field.
bool add_keyword(const DCKeyword *keyword)
Adds the indicated keyword to the list.
const DCKeyword * get_keyword(int n) const
Returns the nth keyword in the list.
const DCKeyword * get_keyword_by_name(const std::string &name) const
Returns the keyword in the list with the indicated name, or NULL if there is no keyword in the list w...
int get_num_keywords() const
Returns the number of keywords in the list.
This represents a single keyword declaration in the dc file.
Definition dcKeyword.h:28
This represents a switch statement, which can appear inside a class body and represents two or more a...
Definition dcSwitch.h:30
const std::string & get_name() const
Returns the name of this switch.
Definition dcSwitch.cxx:84
This represents a single typedef declaration in the dc file.
Definition dcTypedef.h:26
bool is_implicit_typedef() const
Returns true if the typedef has been flagged as an implicit typedef, meaning it was created for a DCC...
Definition dcTypedef.cxx:98
void set_number(int number)
Assigns the unique number to this typedef.
const std::string & get_name() const
Returns the name of this typedef.
Definition dcTypedef.cxx:68
bool is_bogus_typedef() const
Returns true if the typedef has been flagged as a bogus typedef.
Definition dcTypedef.cxx:89
static std::string expand_string(const std::string &str)
Reads the string, looking for environment variable names marked by a $.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
void set_text()
Indicates that the filename represents a text file.
Definition filename.I:424
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes,...
Definition filename.cxx:328
bool open_write(std::ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
This class generates an arbitrary hash number from a sequence of ints.
unsigned long get_hash() const
Returns the hash number generated.
void add_int(int num)
Adds another integer to the hash so far.
A hierarchy of directories and files that appears to be one continuous file system,...
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
std::istream * open_read_file(const Filename &filename, bool auto_unwrap) const
Convenience function; returns a newly allocated istream if the file exists and can be read,...
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
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.