forked from Rowland/EG
747 lines
18 KiB
C++
747 lines
18 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file fltRecord.cxx
|
|
* @author drose
|
|
* @date 2000-08-24
|
|
*/
|
|
|
|
#include "fltRecord.h"
|
|
#include "fltRecordReader.h"
|
|
#include "fltRecordWriter.h"
|
|
#include "fltHeader.h"
|
|
#include "fltGroup.h"
|
|
#include "fltObject.h"
|
|
#include "fltFace.h"
|
|
#include "fltCurve.h"
|
|
#include "fltMesh.h"
|
|
#include "fltLocalVertexPool.h"
|
|
#include "fltMeshPrimitive.h"
|
|
#include "fltVertexList.h"
|
|
#include "fltLOD.h"
|
|
#include "fltInstanceDefinition.h"
|
|
#include "fltInstanceRef.h"
|
|
#include "fltUnsupportedRecord.h"
|
|
#include "fltExternalReference.h"
|
|
#include "fltVectorRecord.h"
|
|
#include "config_flt.h"
|
|
|
|
#include "dcast.h"
|
|
#include "indent.h"
|
|
#include "datagramIterator.h"
|
|
|
|
#include <assert.h>
|
|
|
|
TypeHandle FltRecord::_type_handle;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
FltRecord::
|
|
FltRecord(FltHeader *header) :
|
|
_header(header)
|
|
{
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
FltRecord::
|
|
~FltRecord() {
|
|
}
|
|
|
|
/**
|
|
* Returns the number of child records of this record. This reflects the
|
|
* normal scene graph hierarchy.
|
|
*/
|
|
int FltRecord::
|
|
get_num_children() const {
|
|
return _children.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the nth child of this record.
|
|
*/
|
|
FltRecord *FltRecord::
|
|
get_child(int n) const {
|
|
nassertr(n >= 0 && n < (int)_children.size(), nullptr);
|
|
return _children[n];
|
|
}
|
|
|
|
/**
|
|
* Removes all children from this record.
|
|
*/
|
|
void FltRecord::
|
|
clear_children() {
|
|
_children.clear();
|
|
}
|
|
|
|
/**
|
|
* Adds a new child to the end of the list of children for this record.
|
|
*/
|
|
void FltRecord::
|
|
add_child(FltRecord *child) {
|
|
_children.push_back(child);
|
|
}
|
|
|
|
/**
|
|
* Returns the number of subface records of this record. Normally, subfaces
|
|
* will only be present on object records, although it is logically possible
|
|
* for them to appear anywhere.
|
|
*/
|
|
int FltRecord::
|
|
get_num_subfaces() const {
|
|
return _subfaces.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the nth subface of this record.
|
|
*/
|
|
FltRecord *FltRecord::
|
|
get_subface(int n) const {
|
|
nassertr(n >= 0 && n < (int)_subfaces.size(), nullptr);
|
|
return _subfaces[n];
|
|
}
|
|
|
|
/**
|
|
* Removes all subfaces from this record.
|
|
*/
|
|
void FltRecord::
|
|
clear_subfaces() {
|
|
_subfaces.clear();
|
|
}
|
|
|
|
/**
|
|
* Adds a new subface to the end of the list of subfaces for this record.
|
|
*/
|
|
void FltRecord::
|
|
add_subface(FltRecord *subface) {
|
|
_subfaces.push_back(subface);
|
|
}
|
|
|
|
/**
|
|
* Returns the number of extension attribute records for this object. These
|
|
* are auxiliary nodes, presumably of type FO_extension, that have some local
|
|
* meaning to the object.
|
|
*/
|
|
int FltRecord::
|
|
get_num_extensions() const {
|
|
return _extensions.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the nth extension of this record.
|
|
*/
|
|
FltRecord *FltRecord::
|
|
get_extension(int n) const {
|
|
nassertr(n >= 0 && n < (int)_extensions.size(), nullptr);
|
|
return _extensions[n];
|
|
}
|
|
|
|
/**
|
|
* Removes all extensions from this record.
|
|
*/
|
|
void FltRecord::
|
|
clear_extensions() {
|
|
_extensions.clear();
|
|
}
|
|
|
|
/**
|
|
* Adds a new extension to the end of the list of extensions for this record.
|
|
* This should be a record of type FO_extension.
|
|
*/
|
|
void FltRecord::
|
|
add_extension(FltRecord *extension) {
|
|
_extensions.push_back(extension);
|
|
}
|
|
|
|
/**
|
|
* Returns the number of unsupported ancillary records of this record. These
|
|
* are ancillary records that appeared following this record in the flt file
|
|
* but that aren't directly understood by the flt loader--normally, an
|
|
* ancillary record is examined and decoded on the spot, and no pointer to it
|
|
* is kept.
|
|
*/
|
|
int FltRecord::
|
|
get_num_ancillary() const {
|
|
return _ancillary.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the nth unsupported ancillary record of this record. See
|
|
* get_num_ancillary().
|
|
*/
|
|
FltRecord *FltRecord::
|
|
get_ancillary(int n) const {
|
|
nassertr(n >= 0 && n < (int)_ancillary.size(), nullptr);
|
|
return _ancillary[n];
|
|
}
|
|
|
|
/**
|
|
* Removes all unsupported ancillary records from this record. See
|
|
* get_num_ancillary().
|
|
*/
|
|
void FltRecord::
|
|
clear_ancillary() {
|
|
_ancillary.clear();
|
|
}
|
|
|
|
/**
|
|
* Adds a new unsupported ancillary record to the end of the list of ancillary
|
|
* records for this record. This record will be written to the flt file
|
|
* following this record, without attempting to understand what is in it.
|
|
*
|
|
* Normally, there is no reason to use this function; if the data stored in
|
|
* the FltRecord requires one or more ancillary record, the appropriate
|
|
* records will automatically be generated when the record is written. This
|
|
* function is only required to output a record whose type is not supported by
|
|
* the flt loader. But it would be better to extend the flt loader to know
|
|
* about this new kind of data record.
|
|
*/
|
|
void FltRecord::
|
|
add_ancillary(FltRecord *ancillary) {
|
|
_ancillary.push_back(ancillary);
|
|
}
|
|
|
|
/**
|
|
* Returns true if this record has a nonempty comment, false otherwise.
|
|
*/
|
|
bool FltRecord::
|
|
has_comment() const {
|
|
return !_comment.empty();
|
|
}
|
|
|
|
/**
|
|
* Retrieves the comment for this record, or empty string if the record has no
|
|
* comment.
|
|
*/
|
|
const std::string &FltRecord::
|
|
get_comment() const {
|
|
return _comment;
|
|
}
|
|
|
|
/**
|
|
* Removes the comment for this record.
|
|
*/
|
|
void FltRecord::
|
|
clear_comment() {
|
|
_comment = "";
|
|
}
|
|
|
|
/**
|
|
* Changes the comment for this record.
|
|
*/
|
|
void FltRecord::
|
|
set_comment(const std::string &comment) {
|
|
_comment = comment;
|
|
}
|
|
|
|
/**
|
|
* Checks that the iterator has no bytes left, as it should at the end of a
|
|
* successfully read record. If there *are* remaining bytes, print a warning
|
|
* message but otherwise don't worry about it.
|
|
*
|
|
* If we are attempting to read a flt file whose version is newer than the
|
|
* newest this program understands, don't even print a warning message, since
|
|
* this is exactly the sort of thing we expect.
|
|
*/
|
|
void FltRecord::
|
|
check_remaining_size(const DatagramIterator &di, const std::string &name) const {
|
|
if (di.get_remaining_size() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (_header->get_flt_version() <= _header->max_flt_version()) {
|
|
nout << "Warning! Ignoring extra " << di.get_remaining_size()
|
|
<< " bytes at the end of a ";
|
|
if (name.empty()) {
|
|
nout << get_type();
|
|
} else {
|
|
nout << name;
|
|
}
|
|
nout << " record.\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Walks the hierarchy at this record and below and copies the
|
|
* _converted_filename record into the _orig_filename record, so the flt file
|
|
* will be written out with the converted filename instead of what was
|
|
* originally read in.
|
|
*/
|
|
void FltRecord::
|
|
apply_converted_filenames() {
|
|
Records::const_iterator ci;
|
|
for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
|
|
(*ci)->apply_converted_filenames();
|
|
}
|
|
for (ci = _children.begin(); ci != _children.end(); ++ci) {
|
|
(*ci)->apply_converted_filenames();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a quick one-line description of the record, but not its children.
|
|
* This is a human-readable description, primarily for debugging; to write a
|
|
* flt file, use FltHeader::write_flt().
|
|
*/
|
|
void FltRecord::
|
|
output(std::ostream &out) const {
|
|
out << get_type();
|
|
}
|
|
|
|
/**
|
|
* Writes a multiple-line description of the record and all of its children.
|
|
* This is a human-readable description, primarily for debugging; to write a
|
|
* flt file, use FltHeader::write_flt().
|
|
*/
|
|
void FltRecord::
|
|
write(std::ostream &out, int indent_level) const {
|
|
indent(out, indent_level) << *this;
|
|
write_children(out, indent_level);
|
|
}
|
|
|
|
/**
|
|
* Assuming the current write position has been left at the end of the last
|
|
* line of the record description, writes out the list of children.
|
|
*/
|
|
void FltRecord::
|
|
write_children(std::ostream &out, int indent_level) const {
|
|
if (!_ancillary.empty()) {
|
|
out << " + " << _ancillary.size() << " ancillary";
|
|
}
|
|
if (!_extensions.empty()) {
|
|
out << " + " << _extensions.size() << " extensions";
|
|
}
|
|
if (!_subfaces.empty()) {
|
|
out << " [";
|
|
Records::const_iterator ci;
|
|
for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
|
|
out << " " << *(*ci);
|
|
}
|
|
out << " ]";
|
|
}
|
|
if (!_children.empty()) {
|
|
out << " {\n";
|
|
Records::const_iterator ci;
|
|
for (ci = _children.begin(); ci != _children.end(); ++ci) {
|
|
(*ci)->write(out, indent_level + 2);
|
|
}
|
|
indent(out, indent_level) << "}\n";
|
|
} else {
|
|
out << "\n";
|
|
}
|
|
}
|
|
|
|
/*
|
|
virtual void write(ostream &out) const;
|
|
virtual void build_record(Datagram &datagram) const;
|
|
*/
|
|
|
|
/**
|
|
* Returns true if the indicated opcode corresponds to an ancillary record
|
|
* type, false otherwise. In general, this function is used to identify
|
|
* ancillary records that are not presently supported by the FltReader; these
|
|
* will be ignored. Normally, ancillary records will be detected and
|
|
* processed by extract_ancillary().
|
|
*/
|
|
bool FltRecord::
|
|
is_ancillary(FltOpcode opcode) {
|
|
switch (opcode) {
|
|
case FO_comment:
|
|
case FO_long_id:
|
|
case FO_multitexture:
|
|
case FO_uv_list:
|
|
case FO_replicate:
|
|
case FO_road_zone:
|
|
case FO_transform_matrix:
|
|
case FO_rotate_about_edge:
|
|
case FO_translate:
|
|
case FO_scale:
|
|
case FO_rotate_about_point:
|
|
case FO_rotate_and_scale:
|
|
case FO_put:
|
|
case FO_general_matrix:
|
|
case FO_vector:
|
|
case FO_bounding_box:
|
|
case FO_bounding_sphere:
|
|
case FO_bounding_cylinder:
|
|
case FO_bv_center:
|
|
case FO_bv_orientation:
|
|
case FO_local_vertex_pool:
|
|
case FO_cat_data:
|
|
|
|
case FO_14_material_palette:
|
|
case FO_vertex_palette:
|
|
case FO_vertex_c:
|
|
case FO_vertex_cn:
|
|
case FO_vertex_cnu:
|
|
case FO_vertex_cu:
|
|
case FO_color_palette:
|
|
case FO_name_table:
|
|
case FO_15_material:
|
|
case FO_texture:
|
|
case FO_eyepoint_palette:
|
|
case FO_light_definition:
|
|
case FO_texture_map_palette:
|
|
return true;
|
|
|
|
case FO_header:
|
|
case FO_mesh:
|
|
case FO_mesh_primitive:
|
|
case FO_group:
|
|
case FO_object:
|
|
case FO_face:
|
|
case FO_light_point:
|
|
case FO_dof:
|
|
case FO_vertex_list:
|
|
case FO_morph_list:
|
|
case FO_bsp:
|
|
case FO_external_ref:
|
|
case FO_lod:
|
|
case FO_sound:
|
|
case FO_light_source:
|
|
case FO_road_segment:
|
|
case FO_road_construction:
|
|
case FO_road_path:
|
|
case FO_clip_region:
|
|
case FO_text:
|
|
case FO_switch:
|
|
case FO_cat:
|
|
case FO_extension:
|
|
case FO_curve:
|
|
return false;
|
|
|
|
case FO_push:
|
|
case FO_pop:
|
|
case FO_push_face:
|
|
case FO_pop_face:
|
|
case FO_push_attribute:
|
|
case FO_pop_attribute:
|
|
case FO_push_extension:
|
|
case FO_pop_extension:
|
|
case FO_instance:
|
|
case FO_instance_ref:
|
|
return false;
|
|
|
|
default:
|
|
nout << "Don't know whether " << opcode << " is ancillary.\n";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new FltRecord corresponding to the opcode. If the opcode is
|
|
* unknown, creates a FltUnsupportedRecord.
|
|
*/
|
|
FltRecord *FltRecord::
|
|
create_new_record(FltOpcode opcode) const {
|
|
switch (opcode) {
|
|
case FO_group:
|
|
return new FltGroup(_header);
|
|
|
|
case FO_object:
|
|
return new FltObject(_header);
|
|
|
|
case FO_face:
|
|
return new FltFace(_header);
|
|
|
|
case FO_curve:
|
|
return new FltCurve(_header);
|
|
|
|
case FO_mesh:
|
|
return new FltMesh(_header);
|
|
|
|
case FO_local_vertex_pool:
|
|
return new FltLocalVertexPool(_header);
|
|
|
|
case FO_mesh_primitive:
|
|
return new FltMeshPrimitive(_header);
|
|
|
|
case FO_vertex_list:
|
|
return new FltVertexList(_header);
|
|
|
|
case FO_lod:
|
|
return new FltLOD(_header);
|
|
|
|
case FO_instance:
|
|
return new FltInstanceDefinition(_header);
|
|
|
|
case FO_instance_ref:
|
|
return new FltInstanceRef(_header);
|
|
|
|
case FO_external_ref:
|
|
return new FltExternalReference(_header);
|
|
|
|
case FO_vector:
|
|
return new FltVectorRecord(_header);
|
|
|
|
default:
|
|
nout << "Ignoring unsupported record " << opcode << "\n";
|
|
return new FltUnsupportedRecord(_header);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts this record information from the current record presented in the
|
|
* reader, then advances the reader and continues to read any children, if
|
|
* present. On return, the reader is position on the next sibling record to
|
|
* this record.
|
|
*
|
|
* Returns FE_ok if successful, otherwise on error.
|
|
*/
|
|
FltError FltRecord::
|
|
read_record_and_children(FltRecordReader &reader) {
|
|
if (!extract_record(reader)) {
|
|
nout << "Could not extract record for " << *this << "\n";
|
|
assert(!flt_error_abort);
|
|
return FE_invalid_record;
|
|
}
|
|
FltError result = reader.advance();
|
|
if (result == FE_end_of_file) {
|
|
return FE_ok;
|
|
} else if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
while (true) {
|
|
if (extract_ancillary(reader)) {
|
|
// Ok, a known ancillary record. Fine.
|
|
|
|
} else if (reader.get_opcode() == FO_push) {
|
|
// A push begins a new list of children.
|
|
result = reader.advance();
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
while (reader.get_opcode() != FO_pop) {
|
|
PT(FltRecord) child = create_new_record(reader.get_opcode());
|
|
FltError result = child->read_record_and_children(reader);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
if (child->is_of_type(FltInstanceDefinition::get_class_type())) {
|
|
// A special case for an instance definition. These shouldn't
|
|
// appear in the hierarchy, but should instead be added directly to
|
|
// the header.
|
|
_header->add_instance(DCAST(FltInstanceDefinition, child));
|
|
|
|
} else {
|
|
add_child(child);
|
|
}
|
|
|
|
if (reader.eof() || reader.error()) {
|
|
assert(!flt_error_abort);
|
|
return FE_end_of_file;
|
|
}
|
|
}
|
|
|
|
} else if (reader.get_opcode() == FO_push_face) {
|
|
// A push subface begins a new list of subfaces.
|
|
result = reader.advance();
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
while (reader.get_opcode() != FO_pop_face) {
|
|
PT(FltRecord) subface = create_new_record(reader.get_opcode());
|
|
FltError result = subface->read_record_and_children(reader);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
add_subface(subface);
|
|
if (reader.eof() || reader.error()) {
|
|
assert(!flt_error_abort);
|
|
return FE_end_of_file;
|
|
}
|
|
}
|
|
|
|
} else if (reader.get_opcode() == FO_push_extension) {
|
|
// A push extension begins a new list of extensions.
|
|
result = reader.advance();
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
while (reader.get_opcode() != FO_pop_extension) {
|
|
PT(FltRecord) extension = create_new_record(reader.get_opcode());
|
|
FltError result = extension->read_record_and_children(reader);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
add_extension(extension);
|
|
if (reader.eof() || reader.error()) {
|
|
assert(!flt_error_abort);
|
|
return FE_end_of_file;
|
|
}
|
|
}
|
|
|
|
} else if (is_ancillary(reader.get_opcode())) {
|
|
// An unsupported ancillary record. Skip it.
|
|
PT(FltRecord) ancillary = create_new_record(reader.get_opcode());
|
|
ancillary->extract_record(reader);
|
|
_ancillary.push_back(ancillary);
|
|
|
|
} else {
|
|
// None of the above: we're done.
|
|
return FE_ok;
|
|
}
|
|
|
|
// Skip to the next record. If that's the end, fine.
|
|
result = reader.advance(true);
|
|
if (reader.eof() || result != FE_ok) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fills in the information in this record based on the information given in
|
|
* the indicated datagram, whose opcode has already been read. Returns true
|
|
* on success, false if the datagram is invalid.
|
|
*/
|
|
bool FltRecord::
|
|
extract_record(FltRecordReader &) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the given record, which follows this record sequentially in
|
|
* the file, is an ancillary record of this record. If it is, extracts the
|
|
* relevant information and returns true; otherwise, leaves it alone and
|
|
* returns false.
|
|
*/
|
|
bool FltRecord::
|
|
extract_ancillary(FltRecordReader &reader) {
|
|
if (reader.get_opcode() == FO_comment) {
|
|
DatagramIterator &di = reader.get_iterator();
|
|
_comment = di.get_fixed_string(di.get_remaining_size());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Writes this record out to the flt file, along with all of its ancillary
|
|
* records and children records. Returns FE_ok on success, or something else
|
|
* on error.
|
|
*/
|
|
FltError FltRecord::
|
|
write_record_and_children(FltRecordWriter &writer) const {
|
|
// First, write the record.
|
|
if (!build_record(writer)) {
|
|
assert(!flt_error_abort);
|
|
return FE_bad_data;
|
|
}
|
|
|
|
FltError result = writer.advance();
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
// Then the ancillary data.
|
|
result = write_ancillary(writer);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
Records::const_iterator ci;
|
|
for (ci = _ancillary.begin(); ci != _ancillary.end(); ++ci) {
|
|
if (!(*ci)->build_record(writer)) {
|
|
assert(!flt_error_abort);
|
|
return FE_bad_data;
|
|
}
|
|
result = writer.advance();
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Any extensions?
|
|
if (!_extensions.empty()) {
|
|
result = writer.write_record(FO_push_face);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
for (ci = _extensions.begin(); ci != _extensions.end(); ++ci) {
|
|
(*ci)->write_record_and_children(writer);
|
|
}
|
|
|
|
result = writer.write_record(FO_pop_face);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Finally, write all the children.
|
|
if (!_children.empty()) {
|
|
result = writer.write_record(FO_push);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
for (ci = _children.begin(); ci != _children.end(); ++ci) {
|
|
(*ci)->write_record_and_children(writer);
|
|
}
|
|
|
|
result = writer.write_record(FO_pop);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// We must write subfaces *after* the list of children, or Creator will
|
|
// crash trying to load the file.
|
|
if (!_subfaces.empty()) {
|
|
result = writer.write_record(FO_push_face);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
|
|
for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) {
|
|
(*ci)->write_record_and_children(writer);
|
|
}
|
|
|
|
result = writer.write_record(FO_pop_face);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return FE_ok;
|
|
}
|
|
|
|
/**
|
|
* Fills up the current record on the FltRecordWriter with data for this
|
|
* record, but does not advance the writer. Returns true on success, false if
|
|
* there is some error.
|
|
*/
|
|
bool FltRecord::
|
|
build_record(FltRecordWriter &) const {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Writes whatever ancillary records are required for this record. Returns
|
|
* FE_ok on success, or something else if there is some error.
|
|
*/
|
|
FltError FltRecord::
|
|
write_ancillary(FltRecordWriter &writer) const {
|
|
if (!_comment.empty()) {
|
|
Datagram dc(_comment.data(), _comment.size());
|
|
FltError result = writer.write_record(FO_comment, dc);
|
|
if (result != FE_ok) {
|
|
return result;
|
|
}
|
|
}
|
|
return FE_ok;
|
|
}
|