open_toontown_panda3d/panda/src/egg/eggData.cxx

415 lines
14 KiB
C++

// Filename: eggData.cxx
// Created by: drose (20Jan99)
//
////////////////////////////////////////////////////////////////////
//
// 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."
//
////////////////////////////////////////////////////////////////////
#include "eggData.h"
#include "eggCoordinateSystem.h"
#include "eggTextureCollection.h"
#include "eggMaterialCollection.h"
#include "eggComment.h"
#include "eggPoolUniquifier.h"
#include "config_egg.h"
#include "config_util.h"
#include "config_express.h"
#include "string_utils.h"
#include "dSearchPath.h"
#include "virtualFileSystem.h"
#include "mutexHolder.h"
#include "zStream.h"
extern int eggyyparse();
#include "parserDefs.h"
#include "lexerDefs.h"
TypeHandle EggData::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: EggData::resolve_egg_filename
// Access: Public, Static
// Description: Looks for the indicated filename, first along the
// indicated searchpath, and then along the egg_path and
// finally along the model_path. If found, updates the
// filename to the full path and returns true;
// otherwise, returns false.
////////////////////////////////////////////////////////////////////
bool EggData::
resolve_egg_filename(Filename &egg_filename, const DSearchPath &searchpath) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
if (egg_filename.is_fully_qualified() && vfs->exists(egg_filename)) {
return true;
}
vfs->resolve_filename(egg_filename, searchpath, "egg") ||
vfs->resolve_filename(egg_filename, egg_path, "egg") ||
vfs->resolve_filename(egg_filename, get_model_path(), "egg");
return vfs->exists(egg_filename);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::read
// Access: Public
// Description: Opens the indicated filename and reads the egg data
// contents from it. Returns true if the file was
// successfully opened and read, false if there were
// some errors, in which case the data may be partially
// read.
//
// error is the output stream to which to write error
// messages.
////////////////////////////////////////////////////////////////////
bool EggData::
read(Filename filename, string display_name) {
filename.set_text();
set_egg_filename(filename);
if (display_name.empty()) {
display_name = filename;
}
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
istream *file = vfs->open_read_file(filename, true);
if (file == (istream *)NULL) {
egg_cat.error() << "Unable to open " << display_name << "\n";
return false;
}
egg_cat.info()
<< "Reading " << display_name << "\n";
bool read_ok = read(*file);
vfs->close_read_file(file);
return read_ok;
}
////////////////////////////////////////////////////////////////////
// Function: EggData::read
// Access: Public
// Description: Parses the egg syntax contained in the indicated
// input stream. Returns true if the stream was a
// completely valid egg file, false if there were some
// errors, in which case the data may be partially read.
//
// Before you call this routine, you should probably
// call set_egg_filename() to set the name of the egg
// file we're processing, if at all possible. If there
// is no such filename, you may set it to the empty
// string.
////////////////////////////////////////////////////////////////////
bool EggData::
read(istream &in) {
// First, dispense with any children we had previously. We will
// replace them with the new data.
clear();
// Create a temporary EggData structure to read into. We initialize
// it with a copy of ourselves, so that it will get our _coordsys
// value, if the user set it.
PT(EggData) data = new EggData(*this);
int error_count;
{
MutexHolder holder(egg_lock);
egg_init_parser(in, get_egg_filename(), data, data);
eggyyparse();
egg_cleanup_parser();
error_count = egg_error_count();
}
data->post_read();
steal_children(*data);
(*this) = *data;
return (error_count == 0);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::merge
// Access: Public
// Description: Appends the other egg structure to the end of this
// one. The other egg structure is invalidated.
////////////////////////////////////////////////////////////////////
void EggData::
merge(EggData &other) {
if (get_coordinate_system() == CS_default) {
// If we haven't specified a coordinate system yet, we inherit the
// other one's.
set_coordinate_system(other.get_coordinate_system());
} else {
// Otherwise, the other one is forced into our coordinate system
// before we merge.
other.set_coordinate_system(get_coordinate_system());
}
steal_children(other);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::load_externals
// Access: Public
// Description: Loads up all the egg files referenced by <File>
// entries within the egg structure, and inserts their
// contents in place of the <File> entries. Searches
// for files in the searchpath, if not found directly,
// and writes error messages to the indicated output
// stream. Returns true if all externals were loaded
// successfully, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggData::
load_externals(const DSearchPath &searchpath) {
return
r_load_externals(searchpath, get_coordinate_system(), NULL);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::load_externals
// Access: Public
// Description: Loads up all the egg files referenced by <File>
// entries within the egg structure, and inserts their
// contents in place of the <File> entries. Searches
// for files in the searchpath, if not found directly,
// and writes error messages to the indicated output
// stream. Returns true if all externals were loaded
// successfully, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggData::
load_externals(const DSearchPath &searchpath, BamCacheRecord *record) {
return
r_load_externals(searchpath, get_coordinate_system(), record);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::collapse_equivalent_textures
// Access: Public
// Description: Removes duplicate references to the same texture
// image with the same properties. Considers two
// texture references with identical properties, but
// different tref names, to be equivalent, and collapses
// them, choosing one tref name to keep arbitrarily.
// Returns the number of textures removed.
////////////////////////////////////////////////////////////////////
int EggData::
collapse_equivalent_textures() {
EggTextureCollection textures;
textures.find_used_textures(this);
return
textures.collapse_equivalent_textures(~EggTexture::E_tref_name, this);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::collapse_equivalent_materials
// Access: Public
// Description: Removes duplicate references to the same material
// with the same properties. Considers two material
// references with identical properties, but different
// mref names, to be equivalent, and collapses them,
// choosing one mref name to keep arbitrarily. Returns
// the number of materials removed.
////////////////////////////////////////////////////////////////////
int EggData::
collapse_equivalent_materials() {
EggMaterialCollection materials;
materials.find_used_materials(this);
return
materials.collapse_equivalent_materials(~EggMaterial::E_mref_name, this);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::write_egg
// Access: Public
// Description: The main interface for writing complete egg files.
////////////////////////////////////////////////////////////////////
bool EggData::
write_egg(Filename filename) {
filename.unlink();
filename.set_text();
#ifdef HAVE_ZLIB
bool pz_file = false;
if (filename.get_extension() == "pz") {
// The filename ends in .pz, which means to automatically compress
// the egg file that we write.
pz_file = true;
filename.set_binary();
}
#endif // HAVE_ZLIB
pofstream file;
if (!filename.open_write(file)) {
egg_cat.error() << "Unable to open " << filename << " for writing.\n";
return false;
}
#ifdef HAVE_ZLIB
if (pz_file) {
OCompressStream compressor(&file, false);
return write_egg(compressor);
}
#endif // HAVE_ZLIB
return write_egg(file);
}
////////////////////////////////////////////////////////////////////
// Function: EggData::write_egg
// Access: Public
// Description: The main interface for writing complete egg files.
////////////////////////////////////////////////////////////////////
bool EggData::
write_egg(ostream &out) {
pre_write();
write(out, 0);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: EggData::set_coordinate_system
// Access: Public
// Description: Changes the coordinate system of the EggData. If the
// coordinate system was previously different, this may
// result in a conversion of the data.
////////////////////////////////////////////////////////////////////
void EggData::
set_coordinate_system(CoordinateSystem new_coordsys) {
if (new_coordsys == CS_default) {
new_coordsys = get_default_coordinate_system();
}
if (new_coordsys != _coordsys &&
(_coordsys != CS_default && _coordsys != CS_invalid)) {
// Time to convert the data.
LMatrix4d mat = LMatrix4d::convert_mat(_coordsys, new_coordsys);
LMatrix4d inv = LMatrix4d::convert_mat(new_coordsys, _coordsys);
r_transform(mat, inv, new_coordsys);
r_transform_vertices(mat);
// Now we have to update the under_flags to ensure that all the
// cached relative matrices are correct.
update_under(0);
}
_coordsys = new_coordsys;
}
////////////////////////////////////////////////////////////////////
// Function: EggData::write
// Access: Protected, Virtual
// Description: Writes the egg data out to the indicated output
// stream.
////////////////////////////////////////////////////////////////////
void EggData::
write(ostream &out, int indent_level) const {
PT(EggCoordinateSystem) ecs = new EggCoordinateSystem(_coordsys);
ecs->write(out, indent_level);
EggGroupNode::write(out, indent_level);
out << flush;
}
////////////////////////////////////////////////////////////////////
// Function: EggData::post_read
// Access: Private
// Description: Does whatever processing is appropriate after reading
// the data in from an egg file.
////////////////////////////////////////////////////////////////////
void EggData::
post_read() {
CoordinateSystem old_coordsys = _coordsys;
_coordsys = find_coordsys_entry();
if (_coordsys == CS_default) {
// If the egg file didn't contain a <CoordinateSystem> entry,
// assume it's Y-up, by convention.
_coordsys = CS_yup_right;
} else if (_coordsys == CS_invalid) {
egg_cat.warning()
<< "Contradictory <CoordinateSystem> entries encountered.\n";
_coordsys = CS_yup_right;
}
r_mark_coordsys(_coordsys);
if (old_coordsys != CS_default) {
// Now if we had a previous definition, enforce it. This might
// convert the data to the given coordinate system.
set_coordinate_system(old_coordsys);
}
// Fill this in before we automatically resolve pathnames.
_had_absolute_pathnames = has_absolute_pathnames();
if (get_auto_resolve_externals()) {
// Resolve filenames that are relative to the egg file.
DSearchPath dir;
dir.append_directory(get_egg_filename().get_dirname());
resolve_filenames(dir);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggData::pre_write
// Access: Private
// Description: Does whatever processing is appropriate just before
// writing the data out to an egg file. This includes
// verifying that vertex pool names are unique, etc.
////////////////////////////////////////////////////////////////////
void EggData::
pre_write() {
// Pull out all of the texture definitions in the file and massage
// them a bit.
EggTextureCollection textures;
textures.extract_textures(this);
// Remove any textures that aren't being used.
textures.remove_unused_textures(this);
// Collapse out any textures that are completely equivalent. For
// this purpose, we consider two textures with identical properties
// but different tref names to be different.
textures.collapse_equivalent_textures(~0, this);
// Make sure all of the textures have unique TRef names.
textures.uniquify_trefs();
textures.sort_by_tref();
// Do the same thing with the materials.
EggMaterialCollection materials;
materials.extract_materials(this);
materials.remove_unused_materials(this);
materials.collapse_equivalent_materials(~0, this);
materials.uniquify_mrefs();
materials.sort_by_mref();
// Now put them all back at the head of the file, after any initial
// comment records.
iterator ci = begin();
while (ci != end() && (*ci)->is_of_type(EggComment::get_class_type())) {
++ci;
}
textures.insert_textures(this, ci);
materials.insert_materials(this, ci);
// Also make sure that the vertex pools are uniquely named. This
// also checks textures and materials, which is kind of redundant
// since we just did that, but we don't mind.
EggPoolUniquifier pu;
pu.uniquify(this);
}