Panda3D
Loading...
Searching...
No Matches
fltRecord.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 fltRecord.cxx
10 * @author drose
11 * @date 2000-08-24
12 */
13
14#include "fltRecord.h"
15#include "fltRecordReader.h"
16#include "fltRecordWriter.h"
17#include "fltHeader.h"
18#include "fltGroup.h"
19#include "fltObject.h"
20#include "fltFace.h"
21#include "fltCurve.h"
22#include "fltMesh.h"
23#include "fltLocalVertexPool.h"
24#include "fltMeshPrimitive.h"
25#include "fltVertexList.h"
26#include "fltLOD.h"
28#include "fltInstanceRef.h"
31#include "fltVectorRecord.h"
32#include "config_flt.h"
33
34#include "dcast.h"
35#include "indent.h"
36#include "datagramIterator.h"
37
38#include <assert.h>
39
40TypeHandle FltRecord::_type_handle;
41
42/**
43 *
44 */
45FltRecord::
46FltRecord(FltHeader *header) :
47 _header(header)
48{
49}
50
51/**
52 *
53 */
54FltRecord::
55~FltRecord() {
56}
57
58/**
59 * Returns the number of child records of this record. This reflects the
60 * normal scene graph hierarchy.
61 */
63get_num_children() const {
64 return _children.size();
65}
66
67/**
68 * Returns the nth child of this record.
69 */
71get_child(int n) const {
72 nassertr(n >= 0 && n < (int)_children.size(), nullptr);
73 return _children[n];
74}
75
76/**
77 * Removes all children from this record.
78 */
81 _children.clear();
82}
83
84/**
85 * Adds a new child to the end of the list of children for this record.
86 */
88add_child(FltRecord *child) {
89 _children.push_back(child);
90}
91
92/**
93 * Returns the number of subface records of this record. Normally, subfaces
94 * will only be present on object records, although it is logically possible
95 * for them to appear anywhere.
96 */
98get_num_subfaces() const {
99 return _subfaces.size();
100}
101
102/**
103 * Returns the nth subface of this record.
104 */
106get_subface(int n) const {
107 nassertr(n >= 0 && n < (int)_subfaces.size(), nullptr);
108 return _subfaces[n];
109}
110
111/**
112 * Removes all subfaces from this record.
113 */
116 _subfaces.clear();
117}
118
119/**
120 * Adds a new subface to the end of the list of subfaces for this record.
121 */
123add_subface(FltRecord *subface) {
124 _subfaces.push_back(subface);
125}
126
127/**
128 * Returns the number of extension attribute records for this object. These
129 * are auxiliary nodes, presumably of type FO_extension, that have some local
130 * meaning to the object.
131 */
133get_num_extensions() const {
134 return _extensions.size();
135}
136
137/**
138 * Returns the nth extension of this record.
139 */
141get_extension(int n) const {
142 nassertr(n >= 0 && n < (int)_extensions.size(), nullptr);
143 return _extensions[n];
144}
145
146/**
147 * Removes all extensions from this record.
148 */
151 _extensions.clear();
152}
153
154/**
155 * Adds a new extension to the end of the list of extensions for this record.
156 * This should be a record of type FO_extension.
157 */
159add_extension(FltRecord *extension) {
160 _extensions.push_back(extension);
161}
162
163/**
164 * Returns the number of unsupported ancillary records of this record. These
165 * are ancillary records that appeared following this record in the flt file
166 * but that aren't directly understood by the flt loader--normally, an
167 * ancillary record is examined and decoded on the spot, and no pointer to it
168 * is kept.
169 */
171get_num_ancillary() const {
172 return _ancillary.size();
173}
174
175/**
176 * Returns the nth unsupported ancillary record of this record. See
177 * get_num_ancillary().
178 */
180get_ancillary(int n) const {
181 nassertr(n >= 0 && n < (int)_ancillary.size(), nullptr);
182 return _ancillary[n];
183}
184
185/**
186 * Removes all unsupported ancillary records from this record. See
187 * get_num_ancillary().
188 */
191 _ancillary.clear();
192}
193
194/**
195 * Adds a new unsupported ancillary record to the end of the list of ancillary
196 * records for this record. This record will be written to the flt file
197 * following this record, without attempting to understand what is in it.
198 *
199 * Normally, there is no reason to use this function; if the data stored in
200 * the FltRecord requires one or more ancillary record, the appropriate
201 * records will automatically be generated when the record is written. This
202 * function is only required to output a record whose type is not supported by
203 * the flt loader. But it would be better to extend the flt loader to know
204 * about this new kind of data record.
205 */
207add_ancillary(FltRecord *ancillary) {
208 _ancillary.push_back(ancillary);
209}
210
211/**
212 * Returns true if this record has a nonempty comment, false otherwise.
213 */
215has_comment() const {
216 return !_comment.empty();
217}
218
219/**
220 * Retrieves the comment for this record, or empty string if the record has no
221 * comment.
222 */
223const std::string &FltRecord::
224get_comment() const {
225 return _comment;
226}
227
228/**
229 * Removes the comment for this record.
230 */
233 _comment = "";
234}
235
236/**
237 * Changes the comment for this record.
238 */
240set_comment(const std::string &comment) {
241 _comment = comment;
242}
243
244/**
245 * Checks that the iterator has no bytes left, as it should at the end of a
246 * successfully read record. If there *are* remaining bytes, print a warning
247 * message but otherwise don't worry about it.
248 *
249 * If we are attempting to read a flt file whose version is newer than the
250 * newest this program understands, don't even print a warning message, since
251 * this is exactly the sort of thing we expect.
252 */
254check_remaining_size(const DatagramIterator &di, const std::string &name) const {
255 if (di.get_remaining_size() == 0) {
256 return;
257 }
258
259 if (_header->get_flt_version() <= _header->max_flt_version()) {
260 nout << "Warning! Ignoring extra " << di.get_remaining_size()
261 << " bytes at the end of a ";
262 if (name.empty()) {
263 nout << get_type();
264 } else {
265 nout << name;
266 }
267 nout << " record.\n";
268 }
269}
270
271/**
272 * Walks the hierarchy at this record and below and copies the
273 * _converted_filename record into the _orig_filename record, so the flt file
274 * will be written out with the converted filename instead of what was
275 * originally read in.
276 */
279 Records::const_iterator ci;
280 for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
281 (*ci)->apply_converted_filenames();
282 }
283 for (ci = _children.begin(); ci != _children.end(); ++ci) {
284 (*ci)->apply_converted_filenames();
285 }
286}
287
288/**
289 * Writes a quick one-line description of the record, but not its children.
290 * This is a human-readable description, primarily for debugging; to write a
291 * flt file, use FltHeader::write_flt().
292 */
294output(std::ostream &out) const {
295 out << get_type();
296}
297
298/**
299 * Writes a multiple-line description of the record and all of its children.
300 * This is a human-readable description, primarily for debugging; to write a
301 * flt file, use FltHeader::write_flt().
302 */
304write(std::ostream &out, int indent_level) const {
305 indent(out, indent_level) << *this;
306 write_children(out, indent_level);
307}
308
309/**
310 * Assuming the current write position has been left at the end of the last
311 * line of the record description, writes out the list of children.
312 */
313void FltRecord::
314write_children(std::ostream &out, int indent_level) const {
315 if (!_ancillary.empty()) {
316 out << " + " << _ancillary.size() << " ancillary";
317 }
318 if (!_extensions.empty()) {
319 out << " + " << _extensions.size() << " extensions";
320 }
321 if (!_subfaces.empty()) {
322 out << " [";
323 Records::const_iterator ci;
324 for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
325 out << " " << *(*ci);
326 }
327 out << " ]";
328 }
329 if (!_children.empty()) {
330 out << " {\n";
331 Records::const_iterator ci;
332 for (ci = _children.begin(); ci != _children.end(); ++ci) {
333 (*ci)->write(out, indent_level + 2);
334 }
335 indent(out, indent_level) << "}\n";
336 } else {
337 out << "\n";
338 }
339}
340
341 /*
342 virtual void write(ostream &out) const;
343 virtual void build_record(Datagram &datagram) const;
344 */
345
346/**
347 * Returns true if the indicated opcode corresponds to an ancillary record
348 * type, false otherwise. In general, this function is used to identify
349 * ancillary records that are not presently supported by the FltReader; these
350 * will be ignored. Normally, ancillary records will be detected and
351 * processed by extract_ancillary().
352 */
353bool FltRecord::
354is_ancillary(FltOpcode opcode) {
355 switch (opcode) {
356 case FO_comment:
357 case FO_long_id:
358 case FO_multitexture:
359 case FO_uv_list:
360 case FO_replicate:
361 case FO_road_zone:
362 case FO_transform_matrix:
363 case FO_rotate_about_edge:
364 case FO_translate:
365 case FO_scale:
366 case FO_rotate_about_point:
367 case FO_rotate_and_scale:
368 case FO_put:
369 case FO_general_matrix:
370 case FO_vector:
371 case FO_bounding_box:
372 case FO_bounding_sphere:
373 case FO_bounding_cylinder:
374 case FO_bv_center:
375 case FO_bv_orientation:
376 case FO_local_vertex_pool:
377 case FO_cat_data:
378
379 case FO_14_material_palette:
380 case FO_vertex_palette:
381 case FO_vertex_c:
382 case FO_vertex_cn:
383 case FO_vertex_cnu:
384 case FO_vertex_cu:
385 case FO_color_palette:
386 case FO_name_table:
387 case FO_15_material:
388 case FO_texture:
389 case FO_eyepoint_palette:
390 case FO_light_definition:
391 case FO_texture_map_palette:
392 return true;
393
394 case FO_header:
395 case FO_mesh:
396 case FO_mesh_primitive:
397 case FO_group:
398 case FO_object:
399 case FO_face:
400 case FO_light_point:
401 case FO_dof:
402 case FO_vertex_list:
403 case FO_morph_list:
404 case FO_bsp:
405 case FO_external_ref:
406 case FO_lod:
407 case FO_sound:
408 case FO_light_source:
409 case FO_road_segment:
410 case FO_road_construction:
411 case FO_road_path:
412 case FO_clip_region:
413 case FO_text:
414 case FO_switch:
415 case FO_cat:
416 case FO_extension:
417 case FO_curve:
418 return false;
419
420 case FO_push:
421 case FO_pop:
422 case FO_push_face:
423 case FO_pop_face:
424 case FO_push_attribute:
425 case FO_pop_attribute:
426 case FO_push_extension:
427 case FO_pop_extension:
428 case FO_instance:
429 case FO_instance_ref:
430 return false;
431
432 default:
433 nout << "Don't know whether " << opcode << " is ancillary.\n";
434 return false;
435 }
436}
437
438/**
439 * Creates a new FltRecord corresponding to the opcode. If the opcode is
440 * unknown, creates a FltUnsupportedRecord.
441 */
442FltRecord *FltRecord::
443create_new_record(FltOpcode opcode) const {
444 switch (opcode) {
445 case FO_group:
446 return new FltGroup(_header);
447
448 case FO_object:
449 return new FltObject(_header);
450
451 case FO_face:
452 return new FltFace(_header);
453
454 case FO_curve:
455 return new FltCurve(_header);
456
457 case FO_mesh:
458 return new FltMesh(_header);
459
460 case FO_local_vertex_pool:
461 return new FltLocalVertexPool(_header);
462
463 case FO_mesh_primitive:
464 return new FltMeshPrimitive(_header);
465
466 case FO_vertex_list:
467 return new FltVertexList(_header);
468
469 case FO_lod:
470 return new FltLOD(_header);
471
472 case FO_instance:
473 return new FltInstanceDefinition(_header);
474
475 case FO_instance_ref:
476 return new FltInstanceRef(_header);
477
478 case FO_external_ref:
479 return new FltExternalReference(_header);
480
481 case FO_vector:
482 return new FltVectorRecord(_header);
483
484 default:
485 nout << "Ignoring unsupported record " << opcode << "\n";
486 return new FltUnsupportedRecord(_header);
487 }
488}
489
490/**
491 * Extracts this record information from the current record presented in the
492 * reader, then advances the reader and continues to read any children, if
493 * present. On return, the reader is position on the next sibling record to
494 * this record.
495 *
496 * Returns FE_ok if successful, otherwise on error.
497 */
498FltError FltRecord::
499read_record_and_children(FltRecordReader &reader) {
500 if (!extract_record(reader)) {
501 nout << "Could not extract record for " << *this << "\n";
502 assert(!flt_error_abort);
503 return FE_invalid_record;
504 }
505 FltError result = reader.advance();
506 if (result == FE_end_of_file) {
507 return FE_ok;
508 } else if (result != FE_ok) {
509 return result;
510 }
511
512 while (true) {
513 if (extract_ancillary(reader)) {
514 // Ok, a known ancillary record. Fine.
515
516 } else if (reader.get_opcode() == FO_push) {
517 // A push begins a new list of children.
518 result = reader.advance();
519 if (result != FE_ok) {
520 return result;
521 }
522
523 while (reader.get_opcode() != FO_pop) {
524 PT(FltRecord) child = create_new_record(reader.get_opcode());
525 FltError result = child->read_record_and_children(reader);
526 if (result != FE_ok) {
527 return result;
528 }
529
530 if (child->is_of_type(FltInstanceDefinition::get_class_type())) {
531 // A special case for an instance definition. These shouldn't
532 // appear in the hierarchy, but should instead be added directly to
533 // the header.
534 _header->add_instance(DCAST(FltInstanceDefinition, child));
535
536 } else {
537 add_child(child);
538 }
539
540 if (reader.eof() || reader.error()) {
541 assert(!flt_error_abort);
542 return FE_end_of_file;
543 }
544 }
545
546 } else if (reader.get_opcode() == FO_push_face) {
547 // A push subface begins a new list of subfaces.
548 result = reader.advance();
549 if (result != FE_ok) {
550 return result;
551 }
552
553 while (reader.get_opcode() != FO_pop_face) {
554 PT(FltRecord) subface = create_new_record(reader.get_opcode());
555 FltError result = subface->read_record_and_children(reader);
556 if (result != FE_ok) {
557 return result;
558 }
559 add_subface(subface);
560 if (reader.eof() || reader.error()) {
561 assert(!flt_error_abort);
562 return FE_end_of_file;
563 }
564 }
565
566 } else if (reader.get_opcode() == FO_push_extension) {
567 // A push extension begins a new list of extensions.
568 result = reader.advance();
569 if (result != FE_ok) {
570 return result;
571 }
572
573 while (reader.get_opcode() != FO_pop_extension) {
574 PT(FltRecord) extension = create_new_record(reader.get_opcode());
575 FltError result = extension->read_record_and_children(reader);
576 if (result != FE_ok) {
577 return result;
578 }
579 add_extension(extension);
580 if (reader.eof() || reader.error()) {
581 assert(!flt_error_abort);
582 return FE_end_of_file;
583 }
584 }
585
586 } else if (is_ancillary(reader.get_opcode())) {
587 // An unsupported ancillary record. Skip it.
588 PT(FltRecord) ancillary = create_new_record(reader.get_opcode());
589 ancillary->extract_record(reader);
590 _ancillary.push_back(ancillary);
591
592 } else {
593 // None of the above: we're done.
594 return FE_ok;
595 }
596
597 // Skip to the next record. If that's the end, fine.
598 result = reader.advance(true);
599 if (reader.eof() || result != FE_ok) {
600 return result;
601 }
602 }
603}
604
605/**
606 * Fills in the information in this record based on the information given in
607 * the indicated datagram, whose opcode has already been read. Returns true
608 * on success, false if the datagram is invalid.
609 */
610bool FltRecord::
611extract_record(FltRecordReader &) {
612 return true;
613}
614
615/**
616 * Checks whether the given record, which follows this record sequentially in
617 * the file, is an ancillary record of this record. If it is, extracts the
618 * relevant information and returns true; otherwise, leaves it alone and
619 * returns false.
620 */
621bool FltRecord::
622extract_ancillary(FltRecordReader &reader) {
623 if (reader.get_opcode() == FO_comment) {
624 DatagramIterator &di = reader.get_iterator();
625 _comment = di.get_fixed_string(di.get_remaining_size());
626 return true;
627 }
628
629 return false;
630}
631
632/**
633 * Writes this record out to the flt file, along with all of its ancillary
634 * records and children records. Returns FE_ok on success, or something else
635 * on error.
636 */
637FltError FltRecord::
638write_record_and_children(FltRecordWriter &writer) const {
639 // First, write the record.
640 if (!build_record(writer)) {
641 assert(!flt_error_abort);
642 return FE_bad_data;
643 }
644
645 FltError result = writer.advance();
646 if (result != FE_ok) {
647 return result;
648 }
649
650 // Then the ancillary data.
651 result = write_ancillary(writer);
652 if (result != FE_ok) {
653 return result;
654 }
655 Records::const_iterator ci;
656 for (ci = _ancillary.begin(); ci != _ancillary.end(); ++ci) {
657 if (!(*ci)->build_record(writer)) {
658 assert(!flt_error_abort);
659 return FE_bad_data;
660 }
661 result = writer.advance();
662 if (result != FE_ok) {
663 return result;
664 }
665 }
666
667 // Any extensions?
668 if (!_extensions.empty()) {
669 result = writer.write_record(FO_push_face);
670 if (result != FE_ok) {
671 return result;
672 }
673
674 for (ci = _extensions.begin(); ci != _extensions.end(); ++ci) {
675 (*ci)->write_record_and_children(writer);
676 }
677
678 result = writer.write_record(FO_pop_face);
679 if (result != FE_ok) {
680 return result;
681 }
682 }
683
684 // Finally, write all the children.
685 if (!_children.empty()) {
686 result = writer.write_record(FO_push);
687 if (result != FE_ok) {
688 return result;
689 }
690
691 for (ci = _children.begin(); ci != _children.end(); ++ci) {
692 (*ci)->write_record_and_children(writer);
693 }
694
695 result = writer.write_record(FO_pop);
696 if (result != FE_ok) {
697 return result;
698 }
699 }
700
701 // We must write subfaces *after* the list of children, or Creator will
702 // crash trying to load the file.
703 if (!_subfaces.empty()) {
704 result = writer.write_record(FO_push_face);
705 if (result != FE_ok) {
706 return result;
707 }
708
709 for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
710 (*ci)->write_record_and_children(writer);
711 }
712
713 result = writer.write_record(FO_pop_face);
714 if (result != FE_ok) {
715 return result;
716 }
717 }
718
719 return FE_ok;
720}
721
722/**
723 * Fills up the current record on the FltRecordWriter with data for this
724 * record, but does not advance the writer. Returns true on success, false if
725 * there is some error.
726 */
727bool FltRecord::
728build_record(FltRecordWriter &) const {
729 return true;
730}
731
732/**
733 * Writes whatever ancillary records are required for this record. Returns
734 * FE_ok on success, or something else if there is some error.
735 */
736FltError FltRecord::
737write_ancillary(FltRecordWriter &writer) const {
738 if (!_comment.empty()) {
739 Datagram dc(_comment.data(), _comment.size());
740 FltError result = writer.write_record(FO_comment, dc);
741 if (result != FE_ok) {
742 return result;
743 }
744 }
745 return FE_ok;
746}
A class to retrieve the individual data elements previously stored in a Datagram.
std::string get_fixed_string(size_t size)
Extracts a fixed-length string.
size_t get_remaining_size() const
Return the bytes left in the datagram.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
A single curve, like a Bezier or B-Spline.
Definition fltCurve.h:27
An external reference to another flt file (possibly to a specific bead within the flt file).
A single face bead, e.g.
Definition fltFace.h:24
The main grouping bead of the flt file.
Definition fltGroup.h:24
This is the first bead in the file, the top of the bead hierarchy, and the primary interface to readi...
Definition fltHeader.h:44
static int max_flt_version()
Returns the latest flt version number that this codebase is known to support (times 100).
void add_instance(FltInstanceDefinition *instance)
Defines a new instance subtree.
int get_flt_version() const
Returns the version number of the flt file as reported in the header, times 100.
This special kind of record marks the top node of an instance subtree.
This bead appears in the hierarchy to refer to a FltInstanceDefinition node defined elsewhere.
A Level-of-Detail record.
Definition fltLOD.h:24
A local vertex pool, as might appear in the middle of the hierarchy, for instance for a mesh.
A single primitive of a mesh, like a triangle strip or fan.
A mesh of connected polygons and tristrips, etc., with a local vertex pool.
Definition fltMesh.h:27
The main objecting bead of the flt file.
Definition fltObject.h:24
This class turns an istream into a sequence of FltRecords by reading a sequence of Datagrams and extr...
FltOpcode get_opcode() const
Returns the opcode associated with the current record.
bool eof() const
Returns true if end-of-file has been reached without error.
bool error() const
Returns true if some error has been encountered while reading (for instance, a truncated file).
DatagramIterator & get_iterator()
Returns an iterator suitable for extracting data from the current record.
FltError advance(bool ok_eof=false)
Extracts the next record from the file.
This class writes a sequence of FltRecords to an ostream, handling opcode and size counts properly.
FltError advance()
Writes the current record to the flt file, and resets the current record to receive new data.
FltError write_record(FltOpcode opcode, const Datagram &datagram=Datagram())
A convenience function to quickly write a simple record that consists of an opcode and possibly a dat...
The base class for all kinds of records in a MultiGen OpenFlight file.
Definition fltRecord.h:36
FltRecord * get_subface(int n) const
Returns the nth subface of this record.
int get_num_extensions() const
Returns the number of extension attribute records for this object.
void clear_extensions()
Removes all extensions from this record.
FltRecord * get_extension(int n) const
Returns the nth extension of this record.
const std::string & get_comment() const
Retrieves the comment for this record, or empty string if the record has no comment.
void clear_subfaces()
Removes all subfaces from this record.
void add_child(FltRecord *child)
Adds a new child to the end of the list of children for this record.
Definition fltRecord.cxx:88
void clear_comment()
Removes the comment for this record.
FltRecord * get_child(int n) const
Returns the nth child of this record.
Definition fltRecord.cxx:71
void add_extension(FltRecord *extension)
Adds a new extension to the end of the list of extensions for this record.
int get_num_ancillary() const
Returns the number of unsupported ancillary records of this record.
int get_num_subfaces() const
Returns the number of subface records of this record.
Definition fltRecord.cxx:98
virtual void output(std::ostream &out) const
Writes a quick one-line description of the record, but not its children.
FltRecord * get_ancillary(int n) const
Returns the nth unsupported ancillary record of this record.
int get_num_children() const
Returns the number of child records of this record.
Definition fltRecord.cxx:63
void check_remaining_size(const DatagramIterator &di, const std::string &name=std::string()) const
Checks that the iterator has no bytes left, as it should at the end of a successfully read record.
virtual void apply_converted_filenames()
Walks the hierarchy at this record and below and copies the _converted_filename record into the _orig...
bool has_comment() const
Returns true if this record has a nonempty comment, false otherwise.
void add_ancillary(FltRecord *ancillary)
Adds a new unsupported ancillary record to the end of the list of ancillary records for this record.
void clear_children()
Removes all children from this record.
Definition fltRecord.cxx:80
void add_subface(FltRecord *subface)
Adds a new subface to the end of the list of subfaces for this record.
void set_comment(const std::string &comment)
Changes the comment for this record.
void clear_ancillary()
Removes all unsupported ancillary records from this record.
virtual void write(std::ostream &out, int indent_level=0) const
Writes a multiple-line description of the record and all of its children.
This is an ancillary record of the old (pre-15.4) face node.
A list of vertices, typically added as a child of a face bead.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
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.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.