369 lines
11 KiB
C++
369 lines
11 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 eggData.cxx
|
|
* @author drose
|
|
* @date 1999-01-20
|
|
*/
|
|
|
|
#include "eggData.h"
|
|
#include "eggCoordinateSystem.h"
|
|
#include "eggTextureCollection.h"
|
|
#include "eggMaterialCollection.h"
|
|
#include "eggComment.h"
|
|
#include "eggPoolUniquifier.h"
|
|
#include "config_egg.h"
|
|
#include "config_putil.h"
|
|
#include "config_express.h"
|
|
#include "string_utils.h"
|
|
#include "dSearchPath.h"
|
|
#include "virtualFileSystem.h"
|
|
#include "lightMutexHolder.h"
|
|
#include "zStream.h"
|
|
|
|
extern int eggyyparse();
|
|
#include "parserDefs.h"
|
|
#include "lexerDefs.h"
|
|
|
|
TypeHandle EggData::_type_handle;
|
|
|
|
/**
|
|
* Looks for the indicated filename, first along the indicated searchpath, and
|
|
* then 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, get_model_path(), "egg");
|
|
|
|
return vfs->exists(egg_filename);
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
|
|
PT(VirtualFile) vfile = vfs->get_file(filename);
|
|
if (vfile == NULL) {
|
|
egg_cat.error() << "Could not find " << display_name << "\n";
|
|
return false;
|
|
}
|
|
set_egg_timestamp(vfile->get_timestamp());
|
|
|
|
istream *file = vfile->open_read_file(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);
|
|
vfile->close_read_file(file);
|
|
return read_ok;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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;
|
|
{
|
|
LightMutexHolder 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* The main interface for writing complete egg files.
|
|
*/
|
|
bool EggData::
|
|
write_egg(Filename filename) {
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
filename.set_text();
|
|
vfs->delete_file(filename);
|
|
ostream *file = vfs->open_write_file(filename, true, true);
|
|
if (file == (ostream *)NULL) {
|
|
egg_cat.error() << "Unable to open " << filename << " for writing.\n";
|
|
return false;
|
|
}
|
|
|
|
bool wrote_ok = write_egg(*file);
|
|
vfs->close_write_file(file);
|
|
return wrote_ok;
|
|
}
|
|
|
|
/**
|
|
* The main interface for writing complete egg files.
|
|
*/
|
|
bool EggData::
|
|
write_egg(ostream &out) {
|
|
pre_write();
|
|
|
|
if (egg_precision > 0) {
|
|
// Change the egg precision as requested.
|
|
streamsize orig_precision = out.precision();
|
|
out.precision((streamsize)egg_precision);
|
|
write(out, 0);
|
|
out.precision(orig_precision);
|
|
} else {
|
|
// Use the stream default precision.
|
|
write(out, 0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|