2002-03-05 02:48:20 +00:00

637 lines
23 KiB
C++

// Filename: eggFile.cxx
// Created by: drose (29Nov00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license. You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include "eggFile.h"
#include "textureImage.h"
#include "paletteGroup.h"
#include "texturePlacement.h"
#include "textureReference.h"
#include "sourceTextureImage.h"
#include "palettizer.h"
#include "filenameUnifier.h"
#include <eggData.h>
#include <eggTextureCollection.h>
#include <datagram.h>
#include <datagramIterator.h>
#include <bamReader.h>
#include <bamWriter.h>
#include <executionEnvironment.h>
#include <dSearchPath.h>
TypeHandle EggFile::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: EggFile::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
EggFile::
EggFile() {
_data = (EggData *)NULL;
_default_group = (PaletteGroup *)NULL;
_is_surprise = true;
_is_stale = true;
_first_txa_match = false;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::from_command_line
// Access: Public
// Description: Accepts the information about the egg file as
// supplied from the command line.
////////////////////////////////////////////////////////////////////
void EggFile::
from_command_line(EggData *data,
const Filename &source_filename,
const Filename &dest_filename) {
_data = data;
// We save the current directory at the time the egg file appeared
// on the command line, so that we'll later be able to properly
// resolve external references (like textures) that might be
// relative to this directory.
_current_directory = ExecutionEnvironment::get_cwd();
_source_filename = source_filename;
_source_filename.make_absolute();
_dest_filename = dest_filename;
_dest_filename.make_absolute();
// We save the default PaletteGroup at this point, because the egg
// file inherits the default group that was in effect when it was
// specified on the command line.
_default_group = pal->get_default_group();
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::scan_textures
// Access: Public
// Description: Scans the egg file for texture references and updates
// the _textures list appropriately. This assumes the
// egg file was supplied on the command line and thus
// the _data member is available.
////////////////////////////////////////////////////////////////////
void EggFile::
scan_textures() {
nassertv(_data != (EggData *)NULL);
EggTextureCollection tc;
tc.find_used_textures(_data);
// Remove the old TextureReference objects.
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
delete (*ti);
}
_textures.clear();
EggTextureCollection::iterator eti;
for (eti = tc.begin(); eti != tc.end(); ++eti) {
EggTexture *egg_tex = (*eti);
TextureReference *ref = new TextureReference;
ref->from_egg(this, _data, egg_tex);
if (!ref->has_uvs()) {
// This texture isn't *really* referenced. (Usually this
// happens if the texture is only referenced by "backstage"
// geometry, which we don't care about.)
delete ref;
} else {
_textures.push_back(ref);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::get_textures
// Access: Public
// Description: Fills up the indicated set with the set of textures
// referenced by this egg file. It is the user's
// responsibility to ensure the set is empty before
// making this call; otherwise, the new textures will be
// appended to the existing set.
////////////////////////////////////////////////////////////////////
void EggFile::
get_textures(pset<TextureImage *> &result) const {
Textures::const_iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
result.insert((*ti)->get_texture());
}
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::pre_txa_file
// Access: Public
// Description: Does some processing prior to scanning the .txa file.
////////////////////////////////////////////////////////////////////
void EggFile::
pre_txa_file() {
_is_surprise = true;
_first_txa_match = true;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::match_txa_groups
// Access: Public
// Description: Adds the indicated set of groups, read from the .txa
// file, to the set of groups to which the egg file is
// assigned.
////////////////////////////////////////////////////////////////////
void EggFile::
match_txa_groups(const PaletteGroups &groups) {
if (_first_txa_match) {
// If this is the first line we matched in the .txa file, clear
// the set of groups we'd matched from before. We don't clear
// until we match a line in the .txa file, because if we don't
// match any lines we still want to remember what groups we used
// to be assigned to.
_explicitly_assigned_groups.clear();
_first_txa_match = false;
}
_explicitly_assigned_groups.make_union(_explicitly_assigned_groups, groups);
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::post_txa_file
// Access: Public
// Description: Once the egg file has been matched against all of the
// matching lines the .txa file, do whatever adjustment
// is necessary.
////////////////////////////////////////////////////////////////////
void EggFile::
post_txa_file() {
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::get_explicit_groups
// Access: Public
// Description: Returns the set of PaletteGroups that the egg file
// has been explicitly assigned to in the .txa file.
////////////////////////////////////////////////////////////////////
const PaletteGroups &EggFile::
get_explicit_groups() const {
return _explicitly_assigned_groups;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::get_default_group
// Access: Public
// Description: Returns the PaletteGroup that was specified as the
// default group on the command line at the time the egg
// file last appeared on the command line.
////////////////////////////////////////////////////////////////////
PaletteGroup *EggFile::
get_default_group() const {
return _default_group;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::get_complete_groups
// Access: Public
// Description: Returns the complete set of PaletteGroups that the
// egg file is assigned to. This is the set of all the
// groups it is explicitly assigned to, plus all the
// groups that these groups depend on.
////////////////////////////////////////////////////////////////////
const PaletteGroups &EggFile::
get_complete_groups() const {
return _complete_groups;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::clear_surprise
// Access: Public
// Description: Removes the 'surprise' flag; this file has been
// successfully matched against a line in the .txa file.
////////////////////////////////////////////////////////////////////
void EggFile::
clear_surprise() {
_is_surprise = false;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::is_surprise
// Access: Public
// Description: Returns true if this particular egg file is a
// 'surprise', i.e. it wasn't matched by a line in the
// .txa file that didn't include the keyword 'cont'.
////////////////////////////////////////////////////////////////////
bool EggFile::
is_surprise() const {
return _is_surprise;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::mark_stale
// Access: Public
// Description: Marks this particular egg file as stale, meaning that
// something has changed, such as the location of a
// texture within its palette, which causes the egg file
// to need to be regenerated.
////////////////////////////////////////////////////////////////////
void EggFile::
mark_stale() {
_is_stale = true;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::is_stale
// Access: Public
// Description: Returns true if the egg file needs to be updated,
// i.e. some palettizations have changed affecting it,
// or false otherwise.
////////////////////////////////////////////////////////////////////
bool EggFile::
is_stale() const {
return _is_stale;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::build_cross_links
// Access: Public
// Description: Calls TextureImage::note_egg_file() and
// SourceTextureImage::increment_egg_count() for each
// texture the egg file references, and
// PaletteGroup::increment_egg_count() for each palette
// group it wants. This sets up some of the back
// references to support determining an ideal texture
// assignment.
////////////////////////////////////////////////////////////////////
void EggFile::
build_cross_links() {
if (_explicitly_assigned_groups.empty()) {
// If the egg file has been assigned to no groups, we have to
// assign it to something.
_complete_groups.clear();
_complete_groups.insert(_default_group);
_complete_groups.make_complete(_complete_groups);
} else {
_complete_groups.make_complete(_explicitly_assigned_groups);
}
Textures::const_iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
TextureReference *reference = (*ti);
TextureImage *texture = reference->get_texture();
nassertv(texture != (TextureImage *)NULL);
texture->note_egg_file(this);
// Actually, this may count the same egg file multiple times for a
// particular SourceTextureImage, since a given texture may be
// reference multiples times within an egg file. No harm done,
// however.
reference->get_source()->increment_egg_count();
}
PaletteGroups::const_iterator gi;
for (gi = _complete_groups.begin();
gi != _complete_groups.end();
++gi) {
(*gi)->increment_egg_count();
}
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::choose_placements
// Access: Public
// Description: Once all the textures have been assigned to groups
// (but before they may actually be placed), chooses a
// suitable TexturePlacement for each texture that
// appears in the egg file. This will be necessary to
// do at some point before writing out the egg file
// anyway, and doing it before the textures are placed
// allows us to decide what the necessary UV range is
// for each to-be-placed texture.
////////////////////////////////////////////////////////////////////
void EggFile::
choose_placements() {
Textures::const_iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
TextureReference *reference = (*ti);
TextureImage *texture = reference->get_texture();
if (reference->get_placement() != (TexturePlacement *)NULL &&
texture->get_groups().count(reference->get_placement()->get_group()) != 0) {
// The egg file is already using a TexturePlacement that is
// suitable. Don't bother changing it.
} else {
// We need to select a new TexturePlacement.
PaletteGroups groups;
groups.make_intersection(get_complete_groups(), texture->get_groups());
// Now groups is the set of groups that the egg file requires,
// which also happen to include the texture. It better not be
// empty.
if (groups.empty()) {
nout << "Warning! Egg file " << get_name() << " and texture "
<< *reference << " do not have any groups in common.\n"
<< "Egg groups:\n";
get_complete_groups().write(nout, 2);
nout << "Texture groups:\n";
texture->get_groups().write(nout, 2);
} else {
// It doesn't really matter which group in the set we choose, so
// we arbitrarily choose the first one.
PaletteGroup *group = (*groups.begin());
// Now get the TexturePlacement object that corresponds to the
// placement of this texture into this group.
TexturePlacement *placement = texture->get_placement(group);
nassertv(placement != (TexturePlacement *)NULL);
reference->set_placement(placement);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::has_data
// Access: Public
// Description: Returns true if the EggData for this EggFile has ever
// been loaded.
////////////////////////////////////////////////////////////////////
bool EggFile::
has_data() const {
return (_data != (EggData *)NULL);
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::update_egg
// Access: Public
// Description: Once all textures have been placed appropriately,
// updates the egg file with all the information to
// reference the new textures.
////////////////////////////////////////////////////////////////////
void EggFile::
update_egg() {
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
TextureReference *reference = (*ti);
reference->update_egg();
}
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::remove_egg
// Access: Public
// Description: Removes this egg file from all things that reference
// it, in preparation for removing it from the database.
////////////////////////////////////////////////////////////////////
void EggFile::
remove_egg() {
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
TextureReference *reference = (*ti);
TexturePlacement *placement = reference->get_placement();
placement->remove_egg(reference);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::read_egg
// Access: Public
// Description: Reads in the egg file from its _source_filename. It
// is only valid to call this if it has not already been
// read in, e.g. from the command line. Returns true if
// successful, false if there is an error.
////////////////////////////////////////////////////////////////////
bool EggFile::
read_egg() {
nassertr(_data == (EggData *)NULL, false);
nassertr(!_source_filename.empty(), false);
if (!_source_filename.exists()) {
nout << _source_filename << " does not exist.\n";
return false;
}
EggData *data = new EggData;
if (!data->read(_source_filename)) {
// Failure reading.
delete data;
return false;
}
// We also want to search for filenames based on our current
// directory from which we originally loaded the egg file. This is
// important because it's possible the egg file referenced some
// textures or something relative to that directory.
DSearchPath dir;
dir.append_directory(_current_directory);
data->resolve_filenames(dir);
if (!data->resolve_externals()) {
// Failure reading an external.
delete data;
return false;
}
_data = data;
return true;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::write_egg
// Access: Public
// Description: Writes out the egg file to its _dest_filename.
// Returns true if successful, false if there is an
// error.
////////////////////////////////////////////////////////////////////
bool EggFile::
write_egg() {
nassertr(_data != (EggData *)NULL, false);
nassertr(!_dest_filename.empty(), false);
_dest_filename.make_dir();
nout << "Writing " << FilenameUnifier::make_user_filename(_dest_filename)
<< "\n";
if (!_data->write_egg(_dest_filename)) {
// Some error while writing. Most unusual.
_is_stale = true;
return false;
}
_is_stale = false;
return true;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::write_description
// Access: Public
// Description: Writes a one-line description of the egg file and its
// group assignments to the indicated output stream.
////////////////////////////////////////////////////////////////////
void EggFile::
write_description(ostream &out, int indent_level) const {
indent(out, indent_level) << get_name() << ": ";
if (_explicitly_assigned_groups.empty()) {
if (_default_group != (PaletteGroup *)NULL) {
out << _default_group->get_name();
}
} else {
out << _explicitly_assigned_groups;
}
if (is_stale()) {
out << " (needs update)";
}
out << "\n";
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::write_texture_refs
// Access: Public
// Description: Writes the list of texture references to the
// indicated output stream, one per line.
////////////////////////////////////////////////////////////////////
void EggFile::
write_texture_refs(ostream &out, int indent_level) const {
Textures::const_iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
TextureReference *reference = (*ti);
reference->write(out, indent_level);
}
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::register_with_read_factory
// Access: Public, Static
// Description: Registers the current object as something that can be
// read from a Bam file.
////////////////////////////////////////////////////////////////////
void EggFile::
register_with_read_factory() {
BamReader::get_factory()->
register_factory(get_class_type(), make_EggFile);
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::write_datagram
// Access: Public, Virtual
// Description: Fills the indicated datagram up with a binary
// representation of the current object, in preparation
// for writing to a Bam file.
////////////////////////////////////////////////////////////////////
void EggFile::
write_datagram(BamWriter *writer, Datagram &datagram) {
TypedWritable::write_datagram(writer, datagram);
datagram.add_string(get_name());
// We don't write out _data; that needs to be reread each session.
datagram.add_string(FilenameUnifier::make_bam_filename(_current_directory));
datagram.add_string(FilenameUnifier::make_bam_filename(_source_filename));
datagram.add_string(FilenameUnifier::make_bam_filename(_dest_filename));
datagram.add_uint32(_textures.size());
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
writer->write_pointer(datagram, (*ti));
}
_explicitly_assigned_groups.write_datagram(writer, datagram);
writer->write_pointer(datagram, _default_group);
// We don't write out _complete_groups; that is recomputed each
// session.
datagram.add_bool(_is_surprise);
datagram.add_bool(_is_stale);
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::complete_pointers
// Access: Public, Virtual
// Description: Called after the object is otherwise completely read
// from a Bam file, this function's job is to store the
// pointers that were retrieved from the Bam file for
// each pointer object written. The return value is the
// number of pointers processed from the list.
////////////////////////////////////////////////////////////////////
int EggFile::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
int index = TypedWritable::complete_pointers(p_list, manager);
int i;
_textures.reserve(_num_textures);
for (i = 0; i < _num_textures; i++) {
TextureReference *texture;
DCAST_INTO_R(texture, p_list[index], index);
_textures.push_back(texture);
index++;
}
if (p_list[index] != (TypedWritable *)NULL) {
DCAST_INTO_R(_default_group, p_list[index], index);
}
index++;
return index;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::make_EggFile
// Access: Protected
// Description: This method is called by the BamReader when an object
// of this type is encountered in a Bam file; it should
// allocate and return a new object with all the data
// read.
////////////////////////////////////////////////////////////////////
TypedWritable* EggFile::
make_EggFile(const FactoryParams &params) {
EggFile *me = new EggFile;
DatagramIterator scan;
BamReader *manager;
parse_params(params, scan, manager);
me->fillin(scan, manager);
return me;
}
////////////////////////////////////////////////////////////////////
// Function: EggFile::fillin
// Access: Protected
// Description: Reads the binary data from the given datagram
// iterator, which was written by a previous call to
// write_datagram().
////////////////////////////////////////////////////////////////////
void EggFile::
fillin(DatagramIterator &scan, BamReader *manager) {
TypedWritable::fillin(scan, manager);
set_name(scan.get_string());
_current_directory = FilenameUnifier::get_bam_filename(scan.get_string());
_source_filename = FilenameUnifier::get_bam_filename(scan.get_string());
_dest_filename = FilenameUnifier::get_bam_filename(scan.get_string());
_num_textures = scan.get_uint32();
manager->read_pointers(scan, this, _num_textures);
_explicitly_assigned_groups.fillin(scan, manager);
manager->read_pointer(scan, this); // _default_group
_is_surprise = scan.get_bool();
_is_stale = scan.get_bool();
}