diff --git a/panda/src/egg/eggTexture.cxx b/panda/src/egg/eggTexture.cxx index 9c0d65f87e..d033b58e38 100644 --- a/panda/src/egg/eggTexture.cxx +++ b/panda/src/egg/eggTexture.cxx @@ -322,6 +322,21 @@ sorts_less_than(const EggTexture &other, int eq) const { return false; } +//////////////////////////////////////////////////////////////////// +// Function: EggTexture::has_alpha_channel +// Access: Public +// Description: Given the number of color components (channels) in +// the image file as actually read from the disk, return +// true if this texture seems to have an alpha channel +// or not. This depends on the EggTexture's format as +// well as the number of channels. +//////////////////////////////////////////////////////////////////// +bool EggTexture:: +has_alpha_channel(int num_components) const { + return (num_components == 2 || num_components == 4 || + (num_components == 1 && _format == F_alpha)); +} + //////////////////////////////////////////////////////////////////// // Function: EggTexture::string_format // Access: Public diff --git a/panda/src/egg/eggTexture.h b/panda/src/egg/eggTexture.h index 894926a866..1729b2daea 100644 --- a/panda/src/egg/eggTexture.h +++ b/panda/src/egg/eggTexture.h @@ -44,6 +44,8 @@ public: bool is_equivalent_to(const EggTexture &other, int eq) const; bool sorts_less_than(const EggTexture &other, int eq) const; + bool has_alpha_channel(int num_components) const; + enum Format { F_unspecified, F_rgba, F_rgba12, F_rgba8, F_rgba4, F_rgba5, diff --git a/panda/src/egg2sg/eggLoader.cxx b/panda/src/egg2sg/eggLoader.cxx index 638fe3785a..3a6caf3624 100644 --- a/panda/src/egg2sg/eggLoader.cxx +++ b/panda/src/egg2sg/eggLoader.cxx @@ -871,8 +871,7 @@ setup_bucket(BuilderBucket &bucket, NamedNode *parent, Texture *tex = def._texture->get_texture(); nassertv(tex != (Texture *)NULL); int num_components = tex->_pbuffer->get_num_components(); - if (num_components == 2 || num_components == 4 || - (num_components == 1 && egg_tex->get_format() == EggTexture::F_alpha)) { + if (egg_tex->has_alpha_channel(num_components)) { implicit_alpha = true; } } diff --git a/panda/src/putil/globPattern.I b/panda/src/putil/globPattern.I index 6f791df2b2..43383bbcd5 100644 --- a/panda/src/putil/globPattern.I +++ b/panda/src/putil/globPattern.I @@ -13,6 +13,25 @@ INLINE GlobPattern:: GlobPattern(const string &pattern) : _pattern(pattern) { } +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE GlobPattern:: +GlobPattern(const GlobPattern ©) : _pattern(copy._pattern) { +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::Copy Assignment Operator +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void GlobPattern:: +operator = (const GlobPattern ©) { + _pattern = copy._pattern; +} + //////////////////////////////////////////////////////////////////// // Function: GlobPattern::set_pattern // Access: Public @@ -47,3 +66,12 @@ matches(const string &candidate) const { candidate.begin(), candidate.end()); } +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::output +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void GlobPattern:: +output(ostream &out) const { + out << _pattern; +} diff --git a/panda/src/putil/globPattern.h b/panda/src/putil/globPattern.h index b618e9bcef..725d010033 100644 --- a/panda/src/putil/globPattern.h +++ b/panda/src/putil/globPattern.h @@ -26,11 +26,15 @@ class EXPCL_PANDA GlobPattern { public: INLINE GlobPattern(const string &pattern = string()); + INLINE GlobPattern(const GlobPattern ©); + INLINE void operator = (const GlobPattern ©); INLINE void set_pattern(const string &pattern); INLINE const string &get_pattern() const; INLINE bool matches(const string &candidate) const; + + INLINE void output(ostream &out) const; private: bool matches_substr(string::const_iterator pi, @@ -45,6 +49,12 @@ private: string _pattern; }; +INLINE ostream &operator << (ostream &out, const GlobPattern &glob) { + glob.output(out); + return out; +} + + #include "globPattern.I" #endif diff --git a/pandatool/src/egg-palettize/Sources.pp b/pandatool/src/egg-palettize/Sources.pp index 8c4c254f28..bfbb12b989 100644 --- a/pandatool/src/egg-palettize/Sources.pp +++ b/pandatool/src/egg-palettize/Sources.pp @@ -1,4 +1,4 @@ -#begin bin_target +#begin noinst_bin_target #define TARGET egg-palettize #define LOCAL_LIBS \ eggbase progbase @@ -10,11 +10,14 @@ #define SOURCES \ attribFile.cxx attribFile.h config_egg_palettize.cxx \ eggPalettize.cxx eggPalettize.h \ - imageFile.cxx imageFile.h palette.cxx palette.h sourceEgg.cxx \ - sourceEgg.h string_utils.cxx string_utils.h pTexture.cxx pTexture.h \ - userAttribLine.cxx userAttribLine.h + imageFile.cxx imageFile.h palette.cxx palette.h paletteGroup.cxx \ + paletteGroup.h pTexture.cxx pTexture.h sourceEgg.cxx \ + sourceEgg.h string_utils.cxx string_utils.h \ + textureEggRef.cxx textureEggRef.h textureOmitReason.h \ + texturePacking.cxx \ + texturePacking.h userAttribLine.cxx userAttribLine.h #define INSTALL_HEADERS -#end bin_target +#end noinst_bin_target diff --git a/pandatool/src/egg-palettize/attribFile.cxx b/pandatool/src/egg-palettize/attribFile.cxx index 272affbdc0..4435e215a1 100644 --- a/pandatool/src/egg-palettize/attribFile.cxx +++ b/pandatool/src/egg-palettize/attribFile.cxx @@ -8,6 +8,8 @@ #include "eggPalettize.h" #include "string_utils.h" #include "pTexture.h" +#include "texturePacking.h" +#include "paletteGroup.h" #include "palette.h" #include "sourceEgg.h" @@ -30,7 +32,6 @@ AttribFile(const Filename &filename) { _optimal = false; _txa_needs_rewrite = false; - _palette_prefix = _name + "-palette."; _pal_xsize = 512; _pal_ysize = 512; _default_margin = 2; @@ -87,7 +88,7 @@ release_lock() { } bool AttribFile:: -read() { +read(bool force_redo_all) { bool okflag = true; okflag = read_txa(_txa_fstrm); @@ -102,7 +103,7 @@ read() { return false; } - okflag = read_pi(infile); + okflag = read_pi(infile, force_redo_all); } } @@ -156,8 +157,80 @@ update_params(EggPalettize *prog) { } } +//////////////////////////////////////////////////////////////////// +// Function: AttribFile::get_group +// Access: Public +// Description: Returns a pointer to the PaletteGroup object with the +// given name. If there is not yet any such +// PaletteGroup, creates one. +//////////////////////////////////////////////////////////////////// +PaletteGroup *AttribFile:: +get_group(const string &group_name) { + Groups::iterator gi; + gi = _groups.find(group_name); + if (gi != _groups.end()) { + return (*gi).second; + } + + PaletteGroup *new_group = new PaletteGroup(group_name); + _groups[group_name] = new_group; + return new_group; +} + +//////////////////////////////////////////////////////////////////// +// Function: AttribFile::get_default_group +// Access: Public +// Description: Returns the PaletteGroup that should be associated +// with any textures or egg files not explicitly placed +// in a different group. +//////////////////////////////////////////////////////////////////// +PaletteGroup *AttribFile:: +get_default_group() { + return get_group(_name); +} + +//////////////////////////////////////////////////////////////////// +// Function: AttribFile::get_egg_group_requests +// Access: Public +// Description: Checks all of the known egg filenames against the egg +// files named in the .txa file, looking to see which +// egg files as assigned to which groups. +//////////////////////////////////////////////////////////////////// void AttribFile:: -get_req_sizes() { +get_egg_group_requests() { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = (*ei).second; + UserLines::const_iterator ui; + + bool matched = false; + for (ui = _user_lines.begin(); + ui != _user_lines.end() && !matched; + ++ui) { + matched = (*ui)->get_group_request(egg); + } + + if (matched) { + egg->set_matched_anything(true); + } + } + + // Now go back and make sure that all textures are assigned to + // *something*. + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = (*ei).second; + egg->all_textures_assigned(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AttribFile::get_size_requests +// Access: Public +// Description: Determines the size/scaling requested for each +// texture by scanning the .txa file. +//////////////////////////////////////////////////////////////////// +void AttribFile:: +get_size_requests() { PTextures::iterator ti; for (ti = _textures.begin(); ti != _textures.end(); ++ti) { PTexture *tex = (*ti).second; @@ -170,7 +243,7 @@ get_req_sizes() { for (ui = _user_lines.begin(); ui != _user_lines.end() && !matched; ++ui) { - matched = (*ui)->match_texture(tex, margin); + matched = (*ui)->get_size_request(tex, margin); } if (matched) { @@ -185,11 +258,11 @@ get_req_sizes() { void AttribFile:: update_texture_flags() { // First, clear all the flags. - PTextures::iterator ti; - for (ti = _textures.begin(); ti != _textures.end(); ++ti) { - PTexture *tex = (*ti).second; - tex->set_unused(true); - tex->set_uses_alpha(false); + Packing::iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + TexturePacking *packing = (*pi); + packing->set_unused(true); + packing->set_uses_alpha(false); } // Then, for each egg file, mark all the textures it's known to be @@ -203,12 +276,12 @@ update_texture_flags() { // Now go back through and omit any unused textures. This is also a // fine time to mark the textures' original packing state, so we can // check later to see if they've been repacked elsewhere. - for (ti = _textures.begin(); ti != _textures.end(); ++ti) { - PTexture *tex = (*ti).second; - tex->record_orig_state(); + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + TexturePacking *packing = (*pi); + packing->record_orig_state(); - if (tex->unused()) { - tex->set_omit(PTexture::OR_unused); + if (packing->unused()) { + packing->set_omit(OR_unused); } } } @@ -218,31 +291,25 @@ update_texture_flags() { // algorithm can manage. void AttribFile:: repack_all_textures() { - // First, delete all the existing palettes. - Palettes::iterator pi; - for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { - // Remove the old palette file? - if (_aggressively_clean_mapdir && - !(*pi)->get_filename().empty()) { - if (access((*pi)->get_filename().c_str(), F_OK) == 0) { - nout << "Deleting " << (*pi)->get_filename() << "\n"; - unlink((*pi)->get_filename().c_str()); - } + // First, empty all the existing palette groups. + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + if (_aggressively_clean_mapdir) { + group->remove_palette_files(); } - - delete (*pi); + group->clear_palettes(); } - _palettes.clear(); // Reorder the textures in descending order by height and width for // optimal packing. - vector textures; + vector textures; get_eligible_textures(textures); // Now pack all the textures. This will create new palettes. - vector::iterator ti; + vector::iterator ti; for (ti = textures.begin(); ti != textures.end(); ++ti) { - pack_texture(*ti); + (*ti)->pack(); } _optimal = true; @@ -254,22 +321,20 @@ repack_all_textures() { // palette. void AttribFile:: repack_some_textures() { - bool empty_before = _palettes.empty(); + bool empty_before = _groups.empty(); bool any_added = false; // Reorder the textures in descending order by height and width for // optimal packing. - vector textures; + vector textures; get_eligible_textures(textures); // Now pack whatever textures are currently unpacked. - vector::iterator ti; + vector::iterator ti; for (ti = textures.begin(); ti != textures.end(); ++ti) { - PTexture *tex = (*ti); - if (!tex->is_packed()) { - if (pack_texture(tex)) { - any_added = true; - } + TexturePacking *packing = (*ti); + if (packing->pack()) { + any_added = true; } } @@ -278,17 +343,17 @@ repack_some_textures() { void AttribFile:: optimal_resize() { - Palettes::iterator pi; - for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { - (*pi)->optimal_resize(); + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + (*gi).second->optimal_resize(); } } void AttribFile:: finalize_palettes() { - Palettes::iterator pi; - for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { - (*pi)->finalize_palette(); + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + (*gi).second->finalize_palettes(); } } @@ -310,112 +375,41 @@ remove_unused_lines() { _user_lines.erase(write, _user_lines.end()); } -// Checks that each texture that wants packing has been packed, that -// no textures that don't need packing have been packed, and that all -// textures are packed at their correct sizes. Returns true if no -// changes need to be made, false otherwise. +//////////////////////////////////////////////////////////////////// +// Function: AttribFile::prepare_repack +// Access: Public +// Description: Checks if any texture needs to be repacked into a +// different location on the palette (for instance, +// because it has changed size). If so, unpacks it and +// returns true; otherwise, leaves it alone and returns +// false. +// +// If force_optimal is true, returns true if anything +// has changed at all that would result in a suboptimal +// palette. +//////////////////////////////////////////////////////////////////// bool AttribFile:: -check_packing(bool force_optimal) { - bool all_ok = true; +prepare_repack(bool force_optimal) { + bool needs_repack = false; - PTextures::iterator ti; - for (ti = _textures.begin(); ti != _textures.end(); ++ti) { - PTexture *texture = (*ti).second; - - if (texture->get_omit() == PTexture::OR_none) { - // Here's a texture that thinks it wants to be packed. Does it? - int xsize, ysize; - if (!texture->get_req(xsize, ysize)) { - // If we don't know the texture's size, we can't place it. - nout << "Warning! Can't determine size of " << texture->get_name() - << "\n"; - texture->set_omit(PTexture::OR_unknown); - - } else if ((xsize > _pal_xsize || ysize > _pal_ysize) || - (xsize == _pal_xsize && ysize == _pal_ysize)) { - // If the texture is too big for the palette (or exactly fills the - // palette), we can't place it. - texture->set_omit(PTexture::OR_size); - - } else { - // Ok, this texture really does want to be packed. Is it? - int px, py, m; - if (texture->get_packed_size(px, py, m)) { - // The texture is packed. Does it have the right size? - if (px != xsize || py != ysize) { - // Oops, we'll have to repack it. - unpack_texture(texture); - _optimal = false; - all_ok = false; - } - if (m != texture->get_margin()) { - // The margin has changed, although not the size. We - // won't have to repack it, but we do need to update it. - texture->set_changed(true); - } - } else { - // The texture isn't packed. Need to pack it. - all_ok = false; - } - } - } - - if (texture->get_omit() != PTexture::OR_none) { - // Here's a texture that doesn't want to be packed. Is it? - if (unpack_texture(texture)) { - // It was! Not any more. - _optimal = false; - all_ok = false; - } + Packing::iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + TexturePacking *packing = (*pi); + if (packing->prepare_repack(_optimal)) { + needs_repack = true; } } if (force_optimal && !_optimal) { // If the user wants to insist on an optimal packing, we'll have // to give it to him. - all_ok = false; + needs_repack = true; } - return all_ok; + return needs_repack; } -bool AttribFile:: -pack_texture(PTexture *texture) { - // Now try to place it in each of our existing palettes. - Palettes::iterator pi; - for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { - if ((*pi)->pack_texture(texture)) { - return true; - } - } - - // It didn't place anywhere; create a new palette for it. - Palette *palette = - new Palette(_palettes.size() + 1, _pal_xsize, _pal_ysize, 0, this); - if (!palette->pack_texture(texture)) { - // Hmm, it didn't fit on an empty palette. Must be too big. - texture->set_omit(PTexture::OR_size); - delete palette; - return false; - } - _palettes.push_back(palette); - - return true; -} - -bool AttribFile:: -unpack_texture(PTexture *texture) { - if (texture->is_packed()) { - bool unpacked = texture->get_palette()->unpack_texture(texture); - assert(unpacked); - return true; - } - - // It wasn't packed. - return false; -} - // Updates the timestamp on each egg file that will need to be // rebuilt, so that a future make process will pick it up. This is // only necessary to update egg files that may not have been included @@ -453,22 +447,23 @@ get_texture(const string &name) { } void AttribFile:: -get_eligible_textures(vector &textures) { +get_eligible_textures(vector &textures) { // First, copy the texture pointers into this map structure to sort // them in descending order by size. This is a 2-d map such that // each map[ysize][xsize] is a set of texture pointers. - typedef map > > TexBySize; + typedef map > > TexBySize; TexBySize tex_by_size; int num_textures = 0; - PTextures::iterator ti; - for (ti = _textures.begin(); ti != _textures.end(); ++ti) { - PTexture *texture = (*ti).second; + Packing::iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + TexturePacking *packing = (*pi); + PTexture *texture = packing->get_texture(); - if (texture->get_omit() == PTexture::OR_none) { + if (packing->get_omit() == OR_none) { int xsize, ysize; if (texture->get_req(xsize, ysize)) { - tex_by_size[-ysize][-xsize].insert(texture); + tex_by_size[-ysize][-xsize].insert(packing); num_textures++; } } @@ -481,9 +476,9 @@ get_eligible_textures(vector &textures) { TexBySize::const_iterator t1; for (t1 = tex_by_size.begin(); t1 != tex_by_size.end(); ++t1) { - map >::const_iterator t2; + map >::const_iterator t2; for (t2 = (*t1).second.begin(); t2 != (*t1).second.end(); ++t2) { - set::const_iterator t3; + set::const_iterator t3; for (t3 = (*t2).second.begin(); t3 != (*t2).second.end(); ++t3) { textures.push_back(*t3); } @@ -499,7 +494,7 @@ get_egg(Filename name) { return (*ei).second; } - SourceEgg *egg = new SourceEgg(); + SourceEgg *egg = new SourceEgg(this); egg->resolve_egg_filename(name); egg->set_egg_filename(name); _eggs[name] = egg; @@ -510,18 +505,10 @@ bool AttribFile:: generate_palette_images() { bool okflag = true; - Palettes::iterator pi; - for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { - Palette *palette = (*pi); - if (palette->new_palette()) { - // If the palette is a new palette, we'll have to generate a new - // image from scratch. - okflag = palette->generate_image() && okflag; - } else { - // Otherwise, we can probably get by with just updating - // whichever images, if any, have changed. - okflag = palette->refresh_image() && okflag; - } + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + okflag = group->generate_palette_images() && okflag; } return okflag; @@ -531,23 +518,24 @@ bool AttribFile:: transfer_unplaced_images(bool force_redo_all) { bool okflag = true; - PTextures::iterator ti; - for (ti = _textures.begin(); ti != _textures.end(); ++ti) { - PTexture *texture = (*ti).second; + Packing::iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + TexturePacking *packing = (*pi); + PTexture *texture = packing->get_texture(); - if (texture->get_omit() != PTexture::OR_none && - texture->get_omit() != PTexture::OR_unused) { + if (packing->get_omit() != OR_none && + packing->get_omit() != OR_unused) { // Here's a texture that needs to be moved to our mapdir. But // maybe it's already there and hasn't changed recently. - if (force_redo_all || texture->needs_refresh()) { + if (force_redo_all || packing->needs_refresh()) { // Nope, needs to be updated. okflag = texture->transfer() && okflag; } } else { - if (_aggressively_clean_mapdir) { - if (access(texture->get_filename().c_str(), F_OK) == 0) { + if (_aggressively_clean_mapdir && texture->is_unused()) { + if (texture->get_filename().exists()) { nout << "Deleting " << texture->get_filename() << "\n"; - unlink(texture->get_filename().c_str()); + texture->get_filename().unlink(); } } } @@ -560,6 +548,7 @@ transfer_unplaced_images(bool force_redo_all) { void AttribFile:: check_dup_textures(map &textures, map &dup_textures) const { + /* PTextures::const_iterator ti; for (ti = _textures.begin(); ti != _textures.end(); ++ti) { PTexture *texture = (*ti).second; @@ -623,14 +612,16 @@ check_dup_textures(map &textures, } } } + */ } void AttribFile:: collect_statistics(int &num_textures, int &num_placed, int &num_palettes, int &orig_size, int &resized_size, int &palette_size, int &unplaced_size) const { + /* num_textures = _textures.size(); - num_palettes = _palettes.size(); + num_palettes = 0; //_palettes.size(); num_placed = 0; orig_size = 0; resized_size = 0; @@ -641,10 +632,10 @@ collect_statistics(int &num_textures, int &num_placed, int &num_palettes, for (ti = _textures.begin(); ti != _textures.end(); ++ti) { PTexture *texture = (*ti).second; - int xsize, ysize; + int xsize, ysize, zsize; int rxsize, rysize; int rsize = 0; - if (texture->get_size(xsize, ysize) && + if (texture->get_size(xsize, ysize, zsize) && texture->get_last_req(rxsize, rysize)) { orig_size += xsize * ysize; resized_size += rxsize * rysize; @@ -662,11 +653,12 @@ collect_statistics(int &num_textures, int &num_placed, int &num_palettes, for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { Palette *palette = (*pi); if (palette->get_num_textures() > 1) { - int xsize, ysize; - palette->get_size(xsize, ysize); + int xsize, ysize, zsize; + palette->get_size(xsize, ysize, zsize); palette_size += xsize * ysize; } - } + } + */ } @@ -695,7 +687,7 @@ read_txa(istream &infile) { } bool AttribFile:: -read_pi(istream &infile) { +read_pi(istream &infile, bool force_redo_all) { string line; getline(infile, line); @@ -734,7 +726,7 @@ read_pi(istream &infile) { okflag = parse_pathname(words, infile, line, line_num); } else if (words[0] == "egg") { - okflag = parse_egg(words, infile, line, line_num); + okflag = parse_egg(words, infile, line, line_num, force_redo_all); } else if (words[0] == "palette") { okflag = parse_palette(words, infile, line, line_num); @@ -776,6 +768,8 @@ write_txa(ostream &out) const { bool AttribFile:: write_pi(ostream &out) const { + bool any_surprises = false; + out << "# This file was generated by egg-palettize. Edit it at your own peril.\n"; @@ -802,19 +796,21 @@ write_pi(ostream &out) const { Eggs::const_iterator ei; for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = (*ei).second; out << "\n"; - (*ei).second->write_pi(out); + egg->write_pi(out); + any_surprises = any_surprises || !egg->matched_anything(); } - Palettes::const_iterator pi; - for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { - out << "\n"; - (*pi)->write(out); + Groups::const_iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + (*gi).second->write(out); } out << "\n"; - for (ti = _textures.begin(); ti != _textures.end(); ++ti) { - (*ti).second->write_unplaced(out); + Packing::const_iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + (*pi)->write_unplaced(out); } // Sort textures in descending order by scale percent. @@ -826,8 +822,6 @@ write_pi(ostream &out) const { texture)); } - bool any_surprises = false; - out << "\ntextures\n"; SortPTextures::const_iterator sti; for (sti = sort_textures.begin(); sti != sort_textures.end(); ++sti) { @@ -837,8 +831,8 @@ write_pi(ostream &out) const { } if (any_surprises) { - // Some textures didn't match any commands; they're "surprise" - // textures. + // Some textures or egg files didn't match any commands; they're + // "surprises". out << "\nsurprises\n"; for (ti = _textures.begin(); ti != _textures.end(); ++ti) { PTexture *texture = (*ti).second; @@ -846,6 +840,13 @@ write_pi(ostream &out) const { out << " " << texture->get_name() << "\n"; } } + Eggs::const_iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + SourceEgg *egg = (*ei).second; + if (!egg->matched_anything()) { + out << " " << egg->get_egg_filename().get_basename() << "\n"; + } + } } if (!out) { @@ -873,14 +874,6 @@ parse_params(const vector &words, istream &infile, if (param == "map_directory") { _map_dirname = value; - - // These are all deprecated. - } else if (param == "converted_directory") { - } else if (param == "convert_extension") { - } else if (param == "convert_command") { - } else if (param == "palette_prefix") { - } else if (param == "map_prefix") { - } else if (param == "pal_xsize") { _pal_xsize = atoi(value.c_str()); } else if (param == "pal_ysize") { @@ -944,8 +937,9 @@ parse_texture(const vector &words, istream &infile, while (kw < (int)twords.size()) { if (kw + 3 <= (int)twords.size() && twords[kw] == "orig") { texture->set_size(atoi(twords[kw + 1].c_str()), - atoi(twords[kw + 2].c_str())); - kw += 3; + atoi(twords[kw + 2].c_str()), + atoi(twords[kw + 3].c_str())); + kw += 4; } else if (kw + 3 <= (int)twords.size() && twords[kw] == "new") { texture->set_last_req(atoi(twords[kw + 1].c_str()), @@ -1014,7 +1008,7 @@ parse_pathname(const vector &words, istream &infile, bool AttribFile:: parse_egg(const vector &words, istream &infile, - string &line, int &line_num) { + string &line, int &line_num, bool force_redo_all) { if (words.size() != 2) { nout << "Egg filename expected.\n"; return false; @@ -1035,10 +1029,20 @@ parse_egg(const vector &words, istream &infile, string name = twords[0]; bool repeats = false; bool alpha = false; + PaletteGroup *group = (PaletteGroup *)NULL; int kw = 1; while (kw < (int)twords.size()) { - if (twords[kw] == "repeats") { + if (twords[kw] == "in") { + kw++; + if (kw >= (int)twords.size()) { + nout << "Expected group name\n"; + return false; + } + group = get_group(twords[kw]); + kw++; + + } else if (twords[kw] == "repeats") { repeats = true; kw++; @@ -1052,7 +1056,14 @@ parse_egg(const vector &words, istream &infile, } } - egg->add_texture(get_texture(name), repeats, alpha); + PTexture *texture = get_texture(name); + TexturePacking *packing = (TexturePacking *)NULL; + + if (!force_redo_all) { + packing = texture->add_to_group(group); + } + + egg->add_texture(texture, packing, repeats, alpha); getline(infile, line); line = trim_right(line); @@ -1063,26 +1074,32 @@ parse_egg(const vector &words, istream &infile, } - bool AttribFile:: parse_palette(const vector &words, istream &infile, string &line, int &line_num) { - if (words.size() != 6) { - nout << "Palette filename, size, and number of components expected.\n"; + if (words.size() != 8) { + nout << "Palette filename, group, size, and number of components expected.\n"; return false; } string filename = words[1]; - if (!(words[2] == "size")) { + if (!(words[2] == "in")) { + nout << "Expected keyword 'in'\n"; + return false; + } + PaletteGroup *group = get_group(words[3]); + + if (!(words[4] == "size")) { nout << "Expected keyword 'size'\n"; return false; } - int xsize = atoi(words[3].c_str()); - int ysize = atoi(words[4].c_str()); - int components = atoi(words[5].c_str()); + int xsize = atoi(words[5].c_str()); + int ysize = atoi(words[6].c_str()); + int components = atoi(words[7].c_str()); - Palette *palette = new Palette(filename, xsize, ysize, components, this); - _palettes.push_back(palette); + Palette *palette = + new Palette(filename, group, xsize, ysize, components, this); + group->add_palette(palette); getline(infile, line); line = trim_right(line); @@ -1095,6 +1112,7 @@ parse_palette(const vector &words, istream &infile, } PTexture *texture = get_texture(twords[0]); + TexturePacking *packing = texture->add_to_group(group); if (!(twords[1] == "at")) { nout << "Expected keyword 'at'\n"; @@ -1115,8 +1133,8 @@ parse_palette(const vector &words, istream &infile, return false; } int margin = atoi(twords[8].c_str()); - - palette->place_texture_at(texture, left, top, xsize, ysize, margin); + + palette->place_texture_at(packing, left, top, xsize, ysize, margin); getline(infile, line); line = trim_right(line); @@ -1131,34 +1149,40 @@ parse_palette(const vector &words, istream &infile, bool AttribFile:: parse_unplaced(const vector &words, istream &infile, string &line, int &line_num) { - if (words.size() != 4) { + if (words.size() != 6) { nout << "Unplaced texture description expected.\n"; return false; } PTexture *texture = get_texture(words[1]); - if (!(words[2] == "because")) { + if (!(words[2] == "in")) { + nout << "Expected keyword 'in'\n"; + return false; + } + + PaletteGroup *group = get_group(words[3]); + TexturePacking *packing = texture->add_to_group(group); + + if (!(words[4] == "because")) { nout << "Expected keyword 'because'\n"; return false; } - if (words[3] == "size") { - texture->set_omit(PTexture::OR_size); - } else if (words[3] == "repeats") { - texture->set_omit(PTexture::OR_repeats); - } else if (words[3] == "omitted") { - texture->set_omit(PTexture::OR_omitted); - } else if (words[3] == "unused") { - texture->set_omit(PTexture::OR_unused); - } else if (words[3] == "unknown") { - texture->set_omit(PTexture::OR_unknown); - } else if (words[3] == "solitary") { - texture->set_omit(PTexture::OR_solitary); - } else if (words[3] == "cmdline") { - texture->set_omit(PTexture::OR_cmdline); + if (words[5] == "size") { + packing->set_omit(OR_size); + } else if (words[5] == "repeats") { + packing->set_omit(OR_repeats); + } else if (words[5] == "omitted") { + packing->set_omit(OR_omitted); + } else if (words[5] == "unused") { + packing->set_omit(OR_unused); + } else if (words[5] == "unknown") { + packing->set_omit(OR_unknown); + } else if (words[5] == "solitary") { + packing->set_omit(OR_solitary); } else { - nout << "Unknown keyword " << words[3] << "\n"; + nout << "Unknown keyword " << words[5] << "\n"; return false; } diff --git a/pandatool/src/egg-palettize/attribFile.h b/pandatool/src/egg-palettize/attribFile.h index c32fcc44fd..82c4653655 100644 --- a/pandatool/src/egg-palettize/attribFile.h +++ b/pandatool/src/egg-palettize/attribFile.h @@ -15,6 +15,8 @@ class UserAttribLine; class PTexture; +class TexturePacking; +class PaletteGroup; class Palette; class SourceEgg; class EggPalettize; @@ -32,12 +34,16 @@ public: bool grab_lock(); bool release_lock(); - bool read(); + bool read(bool force_redo_all); bool write(); void update_params(EggPalettize *prog); - void get_req_sizes(); + PaletteGroup *get_group(const string &group_name); + PaletteGroup *get_default_group(); + + void get_egg_group_requests(); + void get_size_requests(); void update_texture_flags(); void repack_all_textures(); @@ -45,15 +51,13 @@ public: void optimal_resize(); void finalize_palettes(); void remove_unused_lines(); - bool check_packing(bool force_optimal); - bool pack_texture(PTexture *texture); - bool unpack_texture(PTexture *texture); + bool prepare_repack(bool force_optimal); void touch_dirty_egg_files(bool force_redo_all, bool eggs_include_images); PTexture *get_texture(const string &name); - void get_eligible_textures(vector &textures); + void get_eligible_textures(vector &textures); SourceEgg *get_egg(Filename name); bool generate_palette_images(); @@ -74,16 +78,17 @@ private: typedef map Eggs; Eggs _eggs; - typedef vector Palettes; - Palettes _palettes; + typedef map Groups; + Groups _groups; typedef map PTextures; PTextures _textures; - string get_pi_filename(const string &txa_filename) const; + typedef vector Packing; + Packing _packing; bool read_txa(istream &outfile); - bool read_pi(istream &outfile); + bool read_pi(istream &outfile, bool force_redo_all); bool write_txa(ostream &outfile) const; bool write_pi(ostream &outfile) const; @@ -96,7 +101,7 @@ private: bool parse_pathname(const vector &words, istream &infile, string &line, int &line_num); bool parse_egg(const vector &words, istream &infile, - string &line, int &line_num); + string &line, int &line_num, bool force_redo_all); bool parse_palette(const vector &words, istream &infile, string &line, int &line_num); bool parse_unplaced(const vector &words, istream &infile, @@ -111,7 +116,6 @@ private: Filename _txa_filename; Filename _pi_filename; - public: // These parameter values come from the command line, or from the // .pi file if omitted from the command line. These are the @@ -119,7 +123,6 @@ public: // palettes, and thus should be stored in the .pi file for future // reference. Filename _map_dirname; - string _palette_prefix; int _pal_xsize, _pal_ysize; int _default_margin; bool _force_power_2; @@ -127,6 +130,8 @@ public: int _txa_fd; fstream _txa_fstrm; + + friend class PTexture; }; #endif diff --git a/pandatool/src/egg-palettize/eggPalettize.cxx b/pandatool/src/egg-palettize/eggPalettize.cxx index 8a2c190d7c..23bdcb5112 100644 --- a/pandatool/src/egg-palettize/eggPalettize.cxx +++ b/pandatool/src/egg-palettize/eggPalettize.cxx @@ -8,6 +8,7 @@ #include "pTexture.h" #include "string_utils.h" #include "sourceEgg.h" +#include "textureOmitReason.h" #include #include @@ -40,6 +41,11 @@ EggPalettize() : EggMultiFilter(true) { clear_runlines(); add_runline("[opts] attribfile.txa file.egg [file.egg ...]"); + // We always have EggMultiBase's -f on: force complete load. In + // fact, we use -f for our own purposes, below. + remove_option("f"); + _force_complete = true; + add_option ("s", "", 0, "Do not process anything, but report statistics on all palette " @@ -124,10 +130,6 @@ EggPalettize() : EggMultiFilter(true) { "which is an even power of 2. They will be scaled down to " "achieve this.", &EggPalettize::dispatch_none, &_got_force_power_2); - add_option - ("x", "", 0, - "Don't palettize anything, but do resize textures.", - &EggPalettize::dispatch_none, &_dont_palettize); add_option ("k", "", 0, "Kill lines from the attributes file that aren't used on any " @@ -227,14 +229,21 @@ read_egg(const Filename &filename) { // We should only call this function if we have exactly one .txa file. nassertr(_attrib_files.size() == 1, (EggData *)NULL); - SourceEgg *egg = _attrib_files[0]->get_egg(filename); - if (egg->read(filename)) { - return egg; + SourceEgg *data = _attrib_files[0]->get_egg(filename); + if (!data->read(filename)) { + // Failure reading. + delete data; + return (EggData *)NULL; } - // Failure reading. - delete egg; - return (EggData *)NULL; + if (_force_complete) { + if (!data->resolve_externals()) { + delete data; + return (EggData *)NULL; + } + } + + return data; } //////////////////////////////////////////////////////////////////// @@ -267,20 +276,48 @@ describe_input_file() { "applied). Finally, the keyword 'omit' may be included along with the " "size to specify that the texture should not be placed in a palette.\n\n" + "The attributes file may also assign certain egg files into various " + "named palette groups. The syntax is similar to the above:\n\n" + + " car-blue.egg : main\n" + " road.egg house.egg : main\n" + " plane.egg : phase2 main\n" + " car*.egg : phase2\n\n" + + "Any number of egg files may be named on one line, and the group of " + "egg files may be simultaneously assigned to one or more groups. Each " + "named group represents a semi-independent collection of textures; a " + "different set of palette images will be created for each group. Each " + "texture that is referenced by a given egg file will be palettized " + "in one of the groups assigned to the egg file. Also see the " + ":group command, below, which defines relationships between the " + "different groups.\n\n" + "There are some other special lines that may appear in this second, " "along with the resize commands. They begin with a colon to " "distinguish them from the resize commands. They are:\n\n" - ":palette xsize ysize\n\n" + " :palette xsize ysize\n\n" "This specifies the size of the palette file(s) to be created. It " "overrides the -s command-line option.\n\n" - ":margin msize\n\n" + " :margin msize\n\n" "This specifies the size of the default margin for all subsequent " "resize commands. This may appear several times in a given file.\n\n" + " :group groupname1 with groupname2 [groupname3 ...]\n\n" + + "This indicates that the palette group named by groupname1 should " + "be allowed to shared textures with those on groupname2 or groupname3, " + "etc. In other words, that whenever palette group groupname1 is in " + "texture memory, we can assume that palette groups groupname2 and " + "groupname3 will also be in memory. Textures that already exist on " + "groupname2 and other dependent groups will not be added to groupname1; " + "instead, egg files will reference the textures directly from the " + "other palettes.\n\n" + "Comments may appear freely throughout the file, and are set off by a " "hash mark (#).\n"; } @@ -314,6 +351,7 @@ format_space(int size_pixels, bool verbose) { //////////////////////////////////////////////////////////////////// void EggPalettize:: report_statistics() { + /* // Look for textures in common. map textures; map dup_textures; @@ -371,17 +409,17 @@ report_statistics() { int net_orig_size = 0; int net_resized_size = 0; int net_unplaced_size = 0; - typedef map > UnplacedReasons; + typedef map > UnplacedReasons; UnplacedReasons unplaced_reasons; map::iterator ti; for (ti = textures.begin(); ti != textures.end(); ++ti) { PTexture *texture = (*ti).second; - int xsize, ysize; + int xsize, ysize, zsize; int rxsize, rysize; int rsize = 0; - if (texture->get_size(xsize, ysize) && + if (texture->get_size(xsize, ysize, zsize) && texture->get_last_req(rxsize, rysize)) { net_orig_size += xsize * ysize; net_resized_size += rxsize * rysize; @@ -427,7 +465,7 @@ report_statistics() { for (uri = unplaced_reasons.begin(); uri != unplaced_reasons.end(); ++uri) { - PTexture::OmitReason reason = (*uri).first; + TextureOmitReason reason = (*uri).first; int count = (*uri).second.first; int size = (*uri).second.second; cout << count << " textures (" << format_space(size) @@ -471,6 +509,7 @@ report_statistics() { } cout << "\n"; + */ } //////////////////////////////////////////////////////////////////// @@ -503,12 +542,12 @@ run() { if (_statistics_only) { nout << "Reading " << af.get_name() << "\n"; - okflag = af.read(); + okflag = af.read(false); } else { nout << "Processing " << af.get_name() << "\n"; - if (!af.read()) { + if (!af.read(_force_redo_all)) { // Failing to read the attribute file is a fatal error. exit(1); } @@ -519,15 +558,17 @@ run() { Eggs::iterator ei; for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { SourceEgg *egg = DCAST(SourceEgg, *ei); - egg->get_textures(af, this); + egg->get_textures(this); } + + // Assign textures into the appropriate groups. + af.get_egg_group_requests(); // Apply the user's requested size changes onto the textures. - af.get_req_sizes(); + af.get_size_requests(); af.update_texture_flags(); - if (!af.check_packing(_force_optimal) || _force_redo_all) { - // We need some more palettization work here. + if (af.prepare_repack(_force_optimal) || _force_redo_all) { if (_force_redo_all || _force_optimal) { af.repack_all_textures(); } else { diff --git a/pandatool/src/egg-palettize/eggPalettize.h b/pandatool/src/egg-palettize/eggPalettize.h index 348c8a6077..6ca4d9e842 100644 --- a/pandatool/src/egg-palettize/eggPalettize.h +++ b/pandatool/src/egg-palettize/eggPalettize.h @@ -36,7 +36,7 @@ public: AttribFiles _attrib_files; // The following parameter values specifically relate to textures - // and palettes. These values are store in the .pi file for future + // and palettes. These values are stored in the .pi file for future // reference. Filename _map_dirname; bool _got_map_dirname; @@ -62,7 +62,6 @@ public: bool _optimal_resize; bool _touch_eggs; bool _eggs_include_images; - bool _dont_palettize; bool _remove_unused_lines; bool _describe_input_file; diff --git a/pandatool/src/egg-palettize/pTexture.cxx b/pandatool/src/egg-palettize/pTexture.cxx index 55c2bec096..30e2980e02 100644 --- a/pandatool/src/egg-palettize/pTexture.cxx +++ b/pandatool/src/egg-palettize/pTexture.cxx @@ -4,32 +4,46 @@ //////////////////////////////////////////////////////////////////// #include "pTexture.h" +#include "texturePacking.h" #include "palette.h" #include "attribFile.h" #include #include - +//////////////////////////////////////////////////////////////////// +// Function: PTexture::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// PTexture:: -PTexture(AttribFile *af, const Filename &name) : +PTexture(AttribFile *attrib_file, const Filename &name) : _name(name), - _attrib_file(af) + _attrib_file(attrib_file) { _got_filename = false; _file_exists = false; _texture_changed = false; - _unused = true; _matched_anything = false; _got_size = false; _got_req = false; _got_last_req = false; _margin = _attrib_file->_default_margin; + _omit = false; _read_header = false; - _omit = OR_none; - _uses_alpha = false; - _is_packed = false; - _palette = NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: PTexture::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PTexture:: +~PTexture() { + Packing::iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + delete (*pi).second; + } } Filename PTexture:: @@ -68,15 +82,16 @@ add_filename(const Filename &filename) { read_header(); } - int oxsize, oysize; + int oxsize, oysize, ozsize; bool got_other_size = - read_image_header(filename, oxsize, oysize); + read_image_header(filename, oxsize, oysize, ozsize); if (got_other_size) { if (!_got_size || oxsize * oysize > _xsize * _ysize) { _filename = filename; _xsize = oxsize; _ysize = oysize; + _zsize = ozsize; _got_size = true; _texture_changed = true; @@ -106,7 +121,7 @@ get_basename() const { } bool PTexture:: -get_size(int &xsize, int &ysize) { +get_size(int &xsize, int &ysize, int &zsize) { if (!_got_size) { read_header(); } @@ -117,16 +132,18 @@ get_size(int &xsize, int &ysize) { xsize = _xsize; ysize = _ysize; + zsize = _zsize; return true; } void PTexture:: -set_size(int xsize, int ysize) { +set_size(int xsize, int ysize, int zsize) { // If we've already read the file header, don't let anyone tell us // differently. if (!_read_header) { _xsize = xsize; _ysize = ysize; + _zsize = zsize; _got_size = true; } } @@ -134,7 +151,8 @@ set_size(int xsize, int ysize) { bool PTexture:: get_req(int &xsize, int &ysize) { if (!_got_req) { - return get_size(xsize, ysize); + int zsize; + return get_size(xsize, ysize, zsize); } xsize = _req_xsize; ysize = _req_ysize; @@ -144,7 +162,8 @@ get_req(int &xsize, int &ysize) { bool PTexture:: get_last_req(int &xsize, int &ysize) { if (!_got_last_req) { - return get_size(xsize, ysize); + int zsize; + return get_size(xsize, ysize, zsize); } xsize = _last_req_xsize; ysize = _last_req_ysize; @@ -187,12 +206,6 @@ void PTexture:: clear_req() { _got_req = false; _margin = _attrib_file->_default_margin; - - if (_omit != OR_cmdline) { - // If we previously omitted this texture from the command line, - // preserve that property. - _omit = OR_none; - } } double PTexture:: @@ -219,179 +232,127 @@ set_margin(int margin) { _margin = margin; } -PTexture::OmitReason PTexture:: -get_omit() const { - return _omit; -} - +//////////////////////////////////////////////////////////////////// +// Function: PTexture::user_omit +// Access: Public +// Description: Omits the texture from all palettes, based on a +// user-supplied "omit" parameter (presumably read from +// the .txa file). +//////////////////////////////////////////////////////////////////// void PTexture:: -set_omit(OmitReason omit) { - _omit = omit; +user_omit() { + _omit = true; + + // Also omit each of our already-packed textures. + Packing::iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + (*pi).second->set_omit(OR_omitted); + } } -bool PTexture:: -needs_refresh() { - if (!_texture_changed) { - // We consider the texture to be out-of-date if it's moved around - // in the palette. - _texture_changed = packing_changed(); +//////////////////////////////////////////////////////////////////// +// Function: PTexture::add_to_group +// Access: Public +// Description: Adds the texture to the indicated PaletteGroup, if it +// has not already been added. Returns the +// TexturePacking pointer that represents the addition. +//////////////////////////////////////////////////////////////////// +TexturePacking *PTexture:: +add_to_group(PaletteGroup *group) { + Packing::iterator pi; + pi = _packing.find(group); + if (pi != _packing.end()) { + return (*pi).second; } - if (!_texture_changed && _file_exists) { - // Compare the texture's timestamp to that of its palette (or - // resized copy). If it's newer, it's changed and must be - // replaced. - - Filename target_filename; - if (is_packed() && _omit == OR_none) { - // Compare to the palette file. - target_filename = _palette->get_filename(); - if (_palette->new_palette()) { - // It's a brand new palette; don't even bother comparing - // timestamps. - _texture_changed = true; - } - - } else { - // Compare to the resized file. - target_filename = get_filename(); - } - - if (!_texture_changed) { - _texture_changed = - (target_filename.compare_timestamps(_filename, true, false) < 0); - } - } - - return _texture_changed; + TexturePacking *packing = new TexturePacking(this, group); + _packing[group] = packing; + _attrib_file->_packing.push_back(packing); + return packing; } +//////////////////////////////////////////////////////////////////// +// Function: PTexture::check_group +// Access: Public +// Description: If the texture has already been added to the +// indicated PaletteGroup, returns the associated +// TexturePacking object. If it is not yet been added, +// returns NULL. +//////////////////////////////////////////////////////////////////// +TexturePacking *PTexture:: +check_group(PaletteGroup *group) const { + Packing::const_iterator pi; + pi = _packing.find(group); + if (pi != _packing.end()) { + return (*pi).second; + } + + return (TexturePacking *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: PTexture::set_changed +// Access: Public +// Description: Sets the state of the changed flag. If this is true, +// the texture is known to have changed in some way such +// that files that depend on it will need to be rebuilt. +//////////////////////////////////////////////////////////////////// void PTexture:: set_changed(bool changed) { _texture_changed = changed; } -bool PTexture:: -unused() const { - return _unused; -} - -void PTexture:: -set_unused(bool unused) { - _unused = unused; -} - +//////////////////////////////////////////////////////////////////// +// Function: PTexture::matched_anything +// Access: Public +// Description: Returns true if the texture matched at least one line +// in the .txa file, false otherwise. +//////////////////////////////////////////////////////////////////// bool PTexture:: matched_anything() const { return _matched_anything; } +//////////////////////////////////////////////////////////////////// +// Function: PTexture::set_matched_anything +// Access: Public +// Description: Sets the state of the matched_anything flag. See +// matched_anything(). +//////////////////////////////////////////////////////////////////// void PTexture:: set_matched_anything(bool matched_anything) { _matched_anything = matched_anything; } +//////////////////////////////////////////////////////////////////// +// Function: PTexture::is_unused +// Access: Public +// Description: Returns true if the texture is not used by any egg +// file in any palette group, false if it is used by at +// least one in at least one group. +//////////////////////////////////////////////////////////////////// bool PTexture:: -uses_alpha() const { - return _uses_alpha; -} - -void PTexture:: -set_uses_alpha(bool uses_alpha) { - _uses_alpha = uses_alpha; -} - -void PTexture:: -mark_pack_location(Palette *palette, int left, int top, - int xsize, int ysize, int margin) { - _is_packed = true; - _palette = palette; - _pleft = left; - _ptop = top; - _pxsize = xsize; - _pysize = ysize; - _pmargin = margin; -} - -void PTexture:: -mark_unpacked() { - _is_packed = false; -} - -bool PTexture:: -is_packed() const { - return _is_packed; -} - -// Returns the same thing as is_packed(), except it doesn't consider a -// texture that has been left alone on a palette to be packed. -bool PTexture:: -is_really_packed() const { - return _is_packed && _omit != OR_solitary; -} - -Palette *PTexture:: -get_palette() const { - return _is_packed ? _palette : (Palette *)NULL; -} - -bool PTexture:: -get_packed_location(int &left, int &top) const { - left = _pleft; - top = _ptop; - return _is_packed; -} - -bool PTexture:: -get_packed_size(int &xsize, int &ysize, int &margin) const { - xsize = _pxsize; - ysize = _pysize; - margin = _pmargin; - return _is_packed; -} - -void PTexture:: -record_orig_state() { - // Records the current packing state, storing it aside as the state - // at load time. Later, when the packing state may have changed, - // packing_changed() will return true if it has or false if it - // has not. - _orig_is_packed = _is_packed; - if (_is_packed) { - _orig_palette_name = _palette->get_filename(); - _opleft = _pleft; - _optop = _ptop; - _opxsize = _pxsize; - _opysize = _pysize; - _opmargin = _pmargin; +is_unused() const { + Packing::const_iterator pi; + for (pi = _packing.begin(); pi != _packing.end(); ++pi) { + if (!(*pi).second->unused()) { + return false; + } } + + return true; } -bool PTexture:: -packing_changed() const { - if (_orig_is_packed != _is_packed) { - return true; - } - if (_is_packed) { - return _orig_palette_name != _palette->get_filename() || - _opleft != _pleft || - _optop != _ptop || - _opxsize != _pxsize || - _opysize != _pysize || - _opmargin != _pmargin; - } - return false; -} void PTexture:: write_size(ostream &out) { - if (_omit != OR_unused) { + if (!is_unused()) { if (!_got_size) { read_header(); } out << " " << _name; if (_got_size) { - out << " orig " << _xsize << " " << _ysize; + out << " orig " << _xsize << " " << _ysize << " " << _zsize; } if (_got_req) { out << " new " << _req_xsize << " " << _req_ysize; @@ -402,7 +363,7 @@ write_size(ostream &out) { void PTexture:: write_pathname(ostream &out) const { - if (_got_filename && _omit != OR_unused) { + if (_got_filename && !is_unused()) { if (!_file_exists) { nout << "Warning: texture " << _filename << " does not exist.\n"; } @@ -423,40 +384,6 @@ write_pathname(ostream &out) const { } } -void PTexture:: -write_unplaced(ostream &out) const { - if (_omit != OR_none && _omit != OR_unused) { - out << "unplaced " << get_name() << " because "; - switch (_omit) { - case OR_size: - out << "size"; - break; - case OR_repeats: - out << "repeats"; - break; - case OR_omitted: - out << "omitted"; - break; - case OR_unused: - out << "unused"; - break; - case OR_unknown: - out << "unknown"; - break; - case OR_solitary: - out << "solitary"; - break; - case OR_cmdline: - out << "cmdline"; - break; - default: - nout << "Invalid type: " << (int)_omit << "\n"; - abort(); - } - out << "\n"; - } -} - bool PTexture:: transfer() { bool okflag = true; @@ -557,14 +484,15 @@ read_header() { if (_got_filename && _file_exists) { if (!_read_header) { _read_header = true; - _got_size = read_image_header(_filename, _xsize, _ysize); + _got_size = read_image_header(_filename, _xsize, _ysize, _zsize); } _read_header = true; } } bool PTexture:: -read_image_header(const Filename &filename, int &xsize, int &ysize) { +read_image_header(const Filename &filename, int &xsize, int &ysize, + int &zsize) { PNMImageHeader header; if (!header.read_header(filename)) { nout << "Warning: cannot read texture " << filename << "\n"; @@ -573,6 +501,7 @@ read_image_header(const Filename &filename, int &xsize, int &ysize) { xsize = header.get_x_size(); ysize = header.get_y_size(); + zsize = header.get_num_channels(); return true; } diff --git a/pandatool/src/egg-palettize/pTexture.h b/pandatool/src/egg-palettize/pTexture.h index 052ccb0214..59d4424820 100644 --- a/pandatool/src/egg-palettize/pTexture.h +++ b/pandatool/src/egg-palettize/pTexture.h @@ -11,24 +11,26 @@ #include "imageFile.h" #include +#include class Palette; class PNMImage; class AttribFile; +class PaletteGroup; +class TexturePacking; +class TextureEggRef; //////////////////////////////////////////////////////////////////// // Class : PTexture -// Description : +// Description : A single texture filename, as read from an egg file +// or from a .txa file. This may be considered for +// palettization on a number of different groups, but it +// must have the same size in each group. //////////////////////////////////////////////////////////////////// class PTexture : public ImageFile { public: - enum OmitReason { - OR_none, - OR_size, OR_repeats, OR_omitted, OR_unused, OR_unknown, - OR_cmdline, OR_solitary - }; - - PTexture(AttribFile *af, const Filename &name); + PTexture(AttribFile *attrib_file, const Filename &name); + ~PTexture(); Filename get_name() const; @@ -37,8 +39,8 @@ public: virtual Filename get_filename() const; virtual Filename get_basename() const; - bool get_size(int &xsize, int &ysize); - void set_size(int xsize, int ysize); + bool get_size(int &xsize, int &ysize, int &zsize); + void set_size(int xsize, int ysize, int zsize); bool get_req(int &xsize, int &ysize); bool get_last_req(int &xsize, int &ysize); @@ -51,44 +53,32 @@ public: int get_margin() const; void set_margin(int margin); - OmitReason get_omit() const; - void set_omit(OmitReason omit); + void user_omit(); + + TexturePacking *add_to_group(PaletteGroup *group); + TexturePacking *check_group(PaletteGroup *group) const; - bool needs_refresh(); void set_changed(bool changed); - bool unused() const; - void set_unused(bool unused); - bool matched_anything() const; void set_matched_anything(bool matched_anything); - - bool uses_alpha() const; - void set_uses_alpha(bool uses_alpha); - - void mark_pack_location(Palette *palette, int left, int top, - int xsize, int ysize, int margin); - void mark_unpacked(); - bool is_packed() const; - bool is_really_packed() const; - Palette *get_palette() const; - bool get_packed_location(int &left, int &top) const; - bool get_packed_size(int &xsize, int &ysize, int &margin) const; - void record_orig_state(); - bool packing_changed() const; + bool is_unused() const; void write_size(ostream &out); void write_pathname(ostream &out) const; - void write_unplaced(ostream &out) const; bool transfer(); PNMImage *read_image(); + typedef set Eggs; + Eggs _eggs; + private: void check_size(); void read_header(); - bool read_image_header(const Filename &filename, int &xsize, int &ysize); + bool read_image_header(const Filename &filename, + int &xsize, int &ysize, int &zsize); static int to_power_2(int value); Filename _name; @@ -100,31 +90,27 @@ private: Filename _filename; bool _file_exists; bool _texture_changed; - bool _unused; bool _matched_anything; - bool _uses_alpha; bool _got_size; int _xsize, _ysize; + int _zsize; bool _got_req; int _req_xsize, _req_ysize; bool _got_last_req; int _last_req_xsize, _last_req_ysize; int _margin; - OmitReason _omit; - - bool _is_packed; - Palette *_palette; - int _pleft, _ptop, _pxsize, _pysize, _pmargin; - - bool _orig_is_packed; - Filename _orig_palette_name; - int _opleft, _optop, _opxsize, _opysize, _opmargin; + bool _omit; bool _read_header; AttribFile *_attrib_file; + + typedef map Packing; + Packing _packing; + + friend class TexturePacking; }; diff --git a/pandatool/src/egg-palettize/palette.cxx b/pandatool/src/egg-palettize/palette.cxx index 4f4740ac61..3cab1f0d3d 100644 --- a/pandatool/src/egg-palettize/palette.cxx +++ b/pandatool/src/egg-palettize/palette.cxx @@ -4,7 +4,9 @@ //////////////////////////////////////////////////////////////////// #include "palette.h" +#include "paletteGroup.h" #include "pTexture.h" +#include "texturePacking.h" #include "attribFile.h" #include "string_utils.h" @@ -135,13 +137,14 @@ add_margins(PNMImage *source) const { Palette:: -Palette(const Filename &filename, int xsize, int ysize, int components, - AttribFile *af) : +Palette(const Filename &filename, PaletteGroup *group, + int xsize, int ysize, int components, AttribFile *attrib_file) : _filename(filename), + _group(group), _xsize(xsize), _ysize(ysize), _components(components), - _attrib_file(af) + _attrib_file(attrib_file) { _index = -1; _palette_changed = false; @@ -149,12 +152,14 @@ Palette(const Filename &filename, int xsize, int ysize, int components, } Palette:: -Palette(int index, int xsize, int ysize, int components, AttribFile *af) : +Palette(PaletteGroup *group, int index, + int xsize, int ysize, int components, AttribFile *attrib_file) : + _group(group), _index(index), _xsize(xsize), _ysize(ysize), _components(components), - _attrib_file(af) + _attrib_file(attrib_file) { _palette_changed = false; _new_palette = true; @@ -165,7 +170,7 @@ Palette:: // Unmark any textures we've had packed. TexPlace::iterator ti; for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { - (*ti)._texture->mark_unpacked(); + (*ti)._packing->mark_unpacked(); } } @@ -199,7 +204,7 @@ check_uses_alpha() const { // Returns true if any texture in the palette uses alpha. TexPlace::const_iterator ti; for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { - if ((*ti)._texture->uses_alpha()) { + if ((*ti)._packing->uses_alpha()) { return true; } } @@ -215,10 +220,11 @@ get_size(int &xsize, int &ysize) const { void Palette:: -place_texture_at(PTexture *texture, int left, int top, +place_texture_at(TexturePacking *packing, int left, int top, int xsize, int ysize, int margin) { + nassertv(_group != (PaletteGroup *)NULL); TexturePlacement tp; - tp._texture = texture; + tp._packing = packing; tp._left = left; tp._top = top; tp._xsize = xsize; @@ -226,11 +232,12 @@ place_texture_at(PTexture *texture, int left, int top, tp._margin = margin; _texplace.push_back(tp); - texture->mark_pack_location(this, left, top, xsize, ysize, margin); + packing->mark_pack_location(this, left, top, xsize, ysize, margin); } bool Palette:: -pack_texture(PTexture *texture) { +pack_texture(TexturePacking *packing) { + PTexture *texture = packing->get_texture(); int xsize, ysize; if (!texture->get_req(xsize, ysize)) { return false; @@ -238,7 +245,7 @@ pack_texture(PTexture *texture) { int left, top; if (find_home(left, top, xsize, ysize)) { - place_texture_at(texture, left, top, xsize, ysize, texture->get_margin()); + place_texture_at(packing, left, top, xsize, ysize, texture->get_margin()); _palette_changed = true; return true; } @@ -246,12 +253,12 @@ pack_texture(PTexture *texture) { } bool Palette:: -unpack_texture(PTexture *texture) { +unpack_texture(TexturePacking *packing) { TexPlace::iterator ti; for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { - if ((*ti)._texture == texture) { + if ((*ti)._packing == packing) { _texplace.erase(ti); - texture->mark_unpacked(); + packing->mark_unpacked(); return true; } } @@ -308,7 +315,7 @@ optimal_resize() { // these textures will need to be rebuilt. TexPlace::iterator ti; for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { - (*ti)._texture->set_changed(true); + (*ti)._packing->set_changed(true); } // And we'll have to mark the palette as a new image. @@ -323,7 +330,7 @@ finalize_palette() { // Generate a filename based on the index number. char index_str[128]; sprintf(index_str, "%03d", _index); - _basename = _attrib_file->_palette_prefix + index_str + ".rgb"; + _basename = _group->get_name() + "-palette." + index_str + ".rgb"; _filename = _basename; _filename.set_dirname(_attrib_file->_map_dirname); } else { @@ -334,7 +341,7 @@ finalize_palette() { if (_texplace.size() == 1) { // If we packed exactly one texture, never mind. - PTexture *texture = (*_texplace.begin())._texture; + TexturePacking *packing = (*_texplace.begin())._packing; // This is a little odd: we mark the texture as being omitted, but // we don't actually unpack it. That way it will still be @@ -342,18 +349,18 @@ finalize_palette() { // palettizations), but it will also be copied to the map // directory, and any egg files that reference it will use the // texture and not the palette. - texture->set_omit(PTexture::OR_solitary); + packing->set_omit(OR_solitary); } } void Palette:: write(ostream &out) const { - out << "palette " << _filename + out << "palette " << _filename << " in " << _group->get_name() << " size " << _xsize << " " << _ysize << " " << _components << "\n"; TexPlace::const_iterator ti; for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { - out << " " << (*ti)._texture->get_name() + out << " " << (*ti)._packing->get_texture()->get_name() << " at " << (*ti)._left << " " << (*ti)._top << " size " << (*ti)._xsize << " " << (*ti)._ysize << " margin " << (*ti)._margin @@ -371,7 +378,8 @@ generate_image() { TexPlace::const_iterator ti; for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { - PTexture *texture = (*ti)._texture; + TexturePacking *packing = (*ti)._packing; + PTexture *texture = packing->get_texture(); nout << " " << texture->get_name() << "\n"; okflag = copy_texture_image(palette, *ti) && okflag; } @@ -415,8 +423,9 @@ refresh_image() { TexPlace::const_iterator ti; for (ti = _texplace.begin(); ti != _texplace.end(); ++ti) { - PTexture *texture = (*ti)._texture; - if (texture->needs_refresh()) { + TexturePacking *packing = (*ti)._packing; + PTexture *texture = packing->get_texture(); + if (packing->needs_refresh()) { if (!any_changed) { nout << "Refreshing " << _filename << "\n"; any_changed = true; @@ -442,7 +451,7 @@ refresh_image() { Palette *Palette:: try_resize(int new_xsize, int new_ysize) const { Palette *np = - new Palette(_index, new_xsize, new_ysize, + new Palette(_group, _index, new_xsize, new_ysize, _components, _attrib_file); bool okflag = true; @@ -450,7 +459,7 @@ try_resize(int new_xsize, int new_ysize) const { for (ti = _texplace.begin(); ti != _texplace.end() && okflag; ++ti) { - okflag = np->pack_texture((*ti)._texture); + okflag = np->pack_texture((*ti)._packing); } if (okflag) { @@ -518,9 +527,10 @@ find_home(int &left, int &top, int xsize, int ysize) const { bool Palette:: copy_texture_image(PNMImage &palette, const TexturePlacement &tp) { bool okflag = true; - PNMImage *image = tp._texture->read_image(); + PTexture *texture = tp._packing->get_texture(); + PNMImage *image = texture->read_image(); if (image == NULL) { - nout << " *** Unable to read " << tp._texture->get_name() << "\n"; + nout << " *** Unable to read " << texture->get_name() << "\n"; okflag = false; // Create a solid red texture for images we can't read. diff --git a/pandatool/src/egg-palettize/palette.h b/pandatool/src/egg-palettize/palette.h index f8087963a6..725a763179 100644 --- a/pandatool/src/egg-palettize/palette.h +++ b/pandatool/src/egg-palettize/palette.h @@ -14,25 +14,33 @@ #include -class PTexture; +class PaletteGroup; +class TexturePacking; class PNMImage; class AttribFile; //////////////////////////////////////////////////////////////////// // Class : Palette -// Description : +// Description : A single palettized image file within a palette +// group. This represents one page of all the +// palettized textures within this group; there might be +// multiple Palette images within a single group, +// depending on the number and size of the palettized +// textures. //////////////////////////////////////////////////////////////////// class Palette : public ImageFile { public: - Palette(const Filename &filename, int xsize, int ysize, int components, - AttribFile *af); - Palette(int index, int xsize, int ysize, int components, - AttribFile *af); + Palette(const Filename &filename, PaletteGroup *group, + int xsize, int ysize, int components, AttribFile *attrib_file); + Palette(PaletteGroup *group, int index, + int xsize, int ysize, int components, AttribFile *attrib_file); ~Palette(); virtual Filename get_filename() const; virtual Filename get_basename() const; + PaletteGroup *get_group() const; + bool changed() const; bool new_palette() const; int get_num_textures() const; @@ -41,11 +49,11 @@ public: void get_size(int &xsize, int &ysize) const; - void place_texture_at(PTexture *texture, int left, int top, + void place_texture_at(TexturePacking *packing, int left, int top, int xsize, int ysize, int margin); - bool pack_texture(PTexture *texture); - bool unpack_texture(PTexture *texture); + bool pack_texture(TexturePacking *packing); + bool unpack_texture(TexturePacking *packing); void optimal_resize(); @@ -63,7 +71,7 @@ private: PNMImage *resize_image(PNMImage *source) const; PNMImage *add_margins(PNMImage *source) const; - PTexture *_texture; + TexturePacking *_packing; int _left, _top; int _xsize, _ysize, _margin; }; @@ -79,6 +87,7 @@ private: Filename _filename; Filename _basename; + PaletteGroup *_group; int _index; int _xsize, _ysize, _components; bool _palette_changed; diff --git a/pandatool/src/egg-palettize/paletteGroup.cxx b/pandatool/src/egg-palettize/paletteGroup.cxx new file mode 100644 index 0000000000..c39364650c --- /dev/null +++ b/pandatool/src/egg-palettize/paletteGroup.cxx @@ -0,0 +1,259 @@ +// Filename: paletteGroup.cxx +// Created by: drose (06Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include "paletteGroup.h" +#include "texturePacking.h" +#include "palette.h" +#include "attribFile.h" + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PaletteGroup:: +PaletteGroup(const string &name) : Namable(name) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PaletteGroup:: +~PaletteGroup() { + clear_palettes(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::get_num_parents +// Access: Public +// Description: Returns the number of dependent PaletteGroup this +// PaletteGroup can share its textures with. See +// get_parent(). +//////////////////////////////////////////////////////////////////// +int PaletteGroup:: +get_num_parents() const { + return _parents.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::get_parent +// Access: Public +// Description: Returns the nth dependent PaletteGroup this +// PaletteGroup can share its textures with. If a +// texture is added to this group that already appears +// one of these groups, the reference to the shared +// texture is used instead of adding a new texture. +//////////////////////////////////////////////////////////////////// +PaletteGroup *PaletteGroup:: +get_parent(int n) const { + nassertr(n >= 0 && n < (int)_parents.size(), (PaletteGroup *)NULL); + return _parents[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::add_parent +// Access: Public +// Description: Adds a new PaletteGroup to the set of PaletteGroups +// this one can share its textures with. See +// get_parent(). +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +add_parent(PaletteGroup *parent) { + _parents.push_back(parent); +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::pack_texture +// Access: Public +// Description: Adds the texture to some suitable palette image +// within the PaletteGroup. Returns true if +// successfully packed, false otherwise. +//////////////////////////////////////////////////////////////////// +bool PaletteGroup:: +pack_texture(TexturePacking *packing, AttribFile *attrib_file) { + // Now try to place it in each of our existing palettes. + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + if ((*pi)->pack_texture(packing)) { + return true; + } + } + + // It didn't place anywhere; create a new palette for it. + Palette *palette = + new Palette(this, _palettes.size() + 1, + attrib_file->_pal_xsize, attrib_file->_pal_ysize, + 0, attrib_file); + if (!palette->pack_texture(packing)) { + // Hmm, it didn't fit on an empty palette. Must be too big. + packing->set_omit(OR_size); + delete palette; + return false; + } + _palettes.push_back(palette); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::generate_palette_images +// Access: Public +// Description: After all of the textures has been placed, generates +// the actual image files for each palette image within +// the group. Returns true if successful, false on +// error. +//////////////////////////////////////////////////////////////////// +bool PaletteGroup:: +generate_palette_images() { + bool okflag = true; + + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + Palette *palette = (*pi); + if (palette->new_palette()) { + // If the palette is a new palette, we'll have to generate a new + // image from scratch. + okflag = palette->generate_image() && okflag; + } else { + // Otherwise, we can probably get by with just updating + // whichever images, if any, have changed. + okflag = palette->refresh_image() && okflag; + } + } + + return okflag; +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::optimal_resize +// Access: Public +// Description: Resizes each palette image within the group downward, +// if possible, to the smallest power-of-two size that +// holds all of its images. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +optimal_resize() { + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + (*pi)->optimal_resize(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::finalize_palettes +// Access: Public +// Description: Performs some finalization of the palette images, +// such as generating filenames. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +finalize_palettes() { + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + (*pi)->finalize_palette(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::add_palette +// Access: Public +// Description: Adds the indicated already-created Palette image to +// the group. This is mainly intended to be called from +// AttribFile when reading in a previously-built .pi +// file. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +add_palette(Palette *palette) { + _palettes.push_back(palette); +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::remove_palette_files +// Access: Public +// Description: Removes all the image files generated for the Palette +// images within the group. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +remove_palette_files() { + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + // Remove the old palette file? + Palette *palette = *pi; + if (!palette->get_filename().empty()) { + if (palette->get_filename().exists()) { + nout << "Deleting " << palette->get_filename() << "\n"; + palette->get_filename().unlink(); + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::clear_palettes +// Access: Public +// Description: Deletes all of the Palette objects within the group. +// This loses all information about where textures are +// packed. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +clear_palettes() { + Palettes::iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + delete *pi; + } + _palettes.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::complete_groups +// Access: Public, Static +// Description: Expands the set of PaletteGroups to include all +// parents and parents of parents. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +complete_groups(PaletteGroups &groups) { + // Make a copy so we can safely modify the original set as we + // traverse the copy. + PaletteGroups groups_copy = groups; + PaletteGroups::const_iterator gi; + for (gi = groups_copy.begin(); gi != groups_copy.end(); ++gi) { + (*gi)->add_ancestors(groups); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::add_ancestors +// Access: Public +// Description: Adds all the ancestors of this PaletteGroup to the +// indicated set. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +add_ancestors(PaletteGroups &groups) { + Parents::const_iterator pri; + for (pri = _parents.begin(); pri != _parents.end(); ++pri) { + PaletteGroup *parent = *pri; + bool inserted = groups.insert(parent).second; + if (inserted) { + parent->add_ancestors(groups); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PaletteGroup::write +// Access: Public +// Description: Writes out a .pi file description of the palette +// group and all of its nested Palette images. +//////////////////////////////////////////////////////////////////// +void PaletteGroup:: +write(ostream &out) const { + Palettes::const_iterator pi; + for (pi = _palettes.begin(); pi != _palettes.end(); ++pi) { + out << "\n"; + (*pi)->write(out); + } +} diff --git a/pandatool/src/egg-palettize/paletteGroup.h b/pandatool/src/egg-palettize/paletteGroup.h new file mode 100644 index 0000000000..1b05e864e4 --- /dev/null +++ b/pandatool/src/egg-palettize/paletteGroup.h @@ -0,0 +1,79 @@ +// Filename: paletteGroup.h +// Created by: drose (06Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef PALETTEGROUP_H +#define PALETTEGROUP_H + +#include + +#include + +#include +#include + +class Palette; +class TexturePacking; +class AttribFile; +class PaletteGroup; + +typedef set PaletteGroups; + +//////////////////////////////////////////////////////////////////// +// Class : PaletteGroup +// Description : A named collection of textures. This is a group of +// textures that are to be palettized together as a +// unit; all of these textures are expected to be loaded +// into texture memory at one time. The PaletteGroup +// consists of a number of discrete Palette images, as +// many as are necessary to represent all of the +// textures within the PaletteGroup that are to be +// palettized. +// +// A PaletteGroup is also associated with one or more +// other PaletteGroups, which are assumed to be +// available in texture memory whenever this +// PaletteGroup is. If a texture is to be placed on +// this PaletteGroup that already exists on one of the +// dependent PaletteGroups, it is not placed; instead, +// it is referenced directly from the other +// PaletteGroup. This allows an intelligent sharing of +// textures between palettes with a minimum of wasted +// space. +//////////////////////////////////////////////////////////////////// +class PaletteGroup : public Namable { +public: + PaletteGroup(const string &name); + ~PaletteGroup(); + + int get_num_parents() const; + PaletteGroup *get_parent(int n) const; + void add_parent(PaletteGroup *parent); + + bool pack_texture(TexturePacking *packing, AttribFile *attrib_file); + bool generate_palette_images(); + void optimal_resize(); + void finalize_palettes(); + + void add_palette(Palette *palette); + + void remove_palette_files(); + void clear_palettes(); + + static void complete_groups(PaletteGroups &groups); + void add_ancestors(PaletteGroups &groups); + + void write(ostream &out) const; + +private: + typedef vector Parents; + Parents _parents; + + typedef vector Palettes; + Palettes _palettes; +}; + + +#endif + diff --git a/pandatool/src/egg-palettize/sourceEgg.cxx b/pandatool/src/egg-palettize/sourceEgg.cxx index c3527dbcc1..c80678a145 100644 --- a/pandatool/src/egg-palettize/sourceEgg.cxx +++ b/pandatool/src/egg-palettize/sourceEgg.cxx @@ -5,6 +5,9 @@ #include "sourceEgg.h" #include "pTexture.h" +#include "textureEggRef.h" +#include "texturePacking.h" +#include "paletteGroup.h" #include "eggPalettize.h" #include "string_utils.h" #include "palette.h" @@ -14,30 +17,52 @@ #include #include #include +#include + +#include TypeHandle SourceEgg::_type_handle; -SourceEgg::TextureRef:: -TextureRef(PTexture *texture, bool repeats, bool alpha) : - _texture(texture), - _repeats(repeats), - _alpha(alpha) -{ - _eggtex = NULL; -} - +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// SourceEgg:: -SourceEgg() { +SourceEgg(AttribFile *attrib_file) : _attrib_file(attrib_file) { + _matched_anything = false; } -SourceEgg::TextureRef &SourceEgg:: -add_texture(PTexture *texture, bool repeats, bool alpha) { - _texrefs.push_back(TextureRef(texture, repeats, alpha)); - return _texrefs.back(); +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::add_texture +// Access: Public +// Description: Adds a new texture to the set of textures known by +// the egg file. +//////////////////////////////////////////////////////////////////// +TextureEggRef *SourceEgg:: +add_texture(PTexture *texture, TexturePacking *packing, + bool repeats, bool alpha) { + TextureEggRef *ref = + new TextureEggRef(this, texture, packing, repeats, alpha); + _texrefs.insert(ref); + texture->_eggs.insert(ref); + return ref; } +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::get_textures +// Access: Public +// Description: Examines the egg file for all of the texture +// references and assigns each texture to a suitable +// palette group. +//////////////////////////////////////////////////////////////////// void SourceEgg:: -get_textures(AttribFile &af, EggPalettize *prog) { +get_textures(EggPalettize *prog) { + TexRefs::iterator tri; + for (tri = _texrefs.begin(); tri != _texrefs.end(); ++tri) { + TextureEggRef *texref = (*tri); + texref->_texture->_eggs.erase(texref); + } _texrefs.clear(); EggTextureCollection tc; @@ -48,20 +73,9 @@ get_textures(AttribFile &af, EggPalettize *prog) { EggTexture *eggtex = (*ti); string name = eggtex->get_basename(); - PTexture *texture = af.get_texture(name); + PTexture *texture = _attrib_file->get_texture(name); texture->add_filename(*eggtex); - if (prog->_dont_palettize) { - // If the user specified -x, it means to omit all textures - // processed in this run, forever. - texture->set_omit(PTexture::OR_cmdline); - } else { - // Or until we next see it without -x. - if (texture->get_omit() == PTexture::OR_cmdline) { - texture->set_omit(PTexture::OR_none); - } - } - bool repeats = eggtex->get_wrap_mode() == EggTexture::WM_repeat || eggtex->get_wrap_u() == EggTexture::WM_repeat || @@ -72,7 +86,21 @@ get_textures(AttribFile &af, EggPalettize *prog) { eggtex->get_wrap_u() == EggTexture::WM_unspecified && eggtex->get_wrap_v() == EggTexture::WM_unspecified; - bool alpha = true; //eggtex->uses_alpha(); + bool alpha = false; + + EggAlphaMode::AlphaMode alpha_mode = eggtex->get_alpha_mode(); + if (alpha_mode == EggAlphaMode::AM_unspecified) { + int xsize, ysize, zsize; + if (texture->get_size(xsize, ysize, zsize)) { + alpha = eggtex->has_alpha_channel(zsize); + } + + } else if (alpha_mode == EggAlphaMode::AM_off) { + alpha = false; + + } else { + alpha = true; + } // Check the range of UV's actually used within the egg file. bool any_uvs = false; @@ -153,48 +181,94 @@ get_textures(AttribFile &af, EggPalettize *prog) { } } - TextureRef &texref = add_texture(texture, repeats, alpha); - texref._eggtex = eggtex; + TextureEggRef *texref = add_texture(texture, (TexturePacking *)NULL, + repeats, alpha); + texref->_eggtex = eggtex; } } -// Updates each PTexture with the flags stored in the various egg -// files. Also marks textures as used. +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::require_groups +// Access: Public +// Description: Ensures that each texture in the egg file is packed +// into at least one of the indicated groups. +//////////////////////////////////////////////////////////////////// +void SourceEgg:: +require_groups(PaletteGroup *preferred, const PaletteGroups &groups) { + TexRefs::iterator ti; + for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { + (*ti)->require_groups(preferred, groups); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::all_textures_assigned +// Access: Public +// Description: Ensures that each texture in the egg file is packed +// into at least one group, any group. +//////////////////////////////////////////////////////////////////// +void SourceEgg:: +all_textures_assigned() { + TexRefs::iterator ti; + for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { + TextureEggRef *texref = (*ti); + if (texref->_packing == (TexturePacking *)NULL) { + texref->_packing = + texref->_texture->add_to_group(_attrib_file->get_default_group()); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::mark_texture_flags +// Access: Public +// Description: Updates each PTexture with the flags stored in the +// various egg files. Also marks textures as used. +//////////////////////////////////////////////////////////////////// void SourceEgg:: mark_texture_flags() { TexRefs::iterator ti; for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { - PTexture *texture = (*ti)._texture; - texture->set_unused(false); - if ((*ti)._alpha) { - texture->set_uses_alpha(true); + TexturePacking *packing = (*ti)->_packing; + packing->set_unused(false); + if ((*ti)->_alpha) { + packing->set_uses_alpha(true); } - if ((*ti)._repeats) { - texture->set_omit(PTexture::OR_repeats); + if ((*ti)->_repeats) { + packing->set_omit(OR_repeats); } } } -// Updates the egg file to point to the new palettes. +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::update_trefs +// Access: Public +// Description: Updates the egg file to point to the new palettes. +//////////////////////////////////////////////////////////////////// void SourceEgg:: update_trefs() { TexRefs::iterator ti; for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { - PTexture *texture = (*ti)._texture; - EggTexture *eggtex = (*ti)._eggtex; + TexturePacking *packing = (*ti)->_packing; + PTexture *texture = packing->get_texture(); + EggTexture *eggtex = (*ti)->_eggtex; if (eggtex != NULL) { // Make the alpha mode explicit if it isn't already. - /* - if (eggtex->get_alpha_mode == EggTexture::AM_unspecified) { - eggtex->set_alpha = eggtex->UsesAlpha() ? - EggTexture::AM_on : EggTexture::AM_off; + if (eggtex->get_alpha_mode() == EggTexture::AM_unspecified) { + int xsize, ysize, zsize; + if (texture->get_size(xsize, ysize, zsize)) { + if (eggtex->has_alpha_channel(zsize)) { + eggtex->set_alpha_mode(EggAlphaMode::AM_on); + } else { + eggtex->set_alpha_mode(EggAlphaMode::AM_off); + } + } } - */ - if (!texture->is_packed() || - texture->get_omit() != PTexture::OR_none) { + if (!packing->is_packed() || + packing->get_omit() != OR_none) { // This texture wasn't palettized, so just rename the // reference to the new one. eggtex->set_fullpath(texture->get_filename()); @@ -202,7 +276,7 @@ update_trefs() { } else { // This texture was palettized, so redirect the tref to point // within the palette. - Palette *palette = texture->get_palette(); + Palette *palette = packing->get_palette(); eggtex->set_fullpath(palette->get_filename()); @@ -217,8 +291,8 @@ update_trefs() { // Build a matrix that will scale the UV's to their new place // on the palette. int left, top, xsize, ysize, margin; - texture->get_packed_location(left, top); - texture->get_packed_size(xsize, ysize, margin); + packing->get_packed_location(left, top); + packing->get_packed_size(xsize, ysize, margin); // Shrink the box to be within the margins. top += margin; @@ -254,9 +328,14 @@ update_trefs() { } } -// Returns true if any of the textures referenced by the egg file have -// been adjusted this pass, implying that the egg file will have to be -// re-run through egg-palettize, and/or re-pfb'ed. +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::needs_rebuild +// Access: Public +// Description: Returns true if any of the textures referenced by the +// egg file have been adjusted this pass, implying that +// the egg file will have to be re-run through +// egg-palettize, and/or re-pfb'ed. +//////////////////////////////////////////////////////////////////// bool SourceEgg:: needs_rebuild(bool force_redo_all, bool eggs_include_images) const { @@ -265,8 +344,8 @@ needs_rebuild(bool force_redo_all, for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { bool dirty = eggs_include_images ? - (*ti)._texture->needs_refresh() : - (*ti)._texture->packing_changed(); + (*ti)->_packing->needs_refresh() : + (*ti)->_packing->packing_changed(); if (force_redo_all || dirty) { return true; } @@ -276,22 +355,57 @@ needs_rebuild(bool force_redo_all, return false; } +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::matched_anything +// Access: Public +// Description: Returns true if the egg file matched at least one +// line in the .txa file, false otherwise. +//////////////////////////////////////////////////////////////////// +bool SourceEgg:: +matched_anything() const { + return _matched_anything; +} + +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::set_matched_anything +// Access: Public +// Description: Sets the state of the matched_anything flag. See +// matched_anything(). +//////////////////////////////////////////////////////////////////// +void SourceEgg:: +set_matched_anything(bool matched_anything) { + _matched_anything = matched_anything; +} + +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::write_pi +// Access: Public +// Description: Writes the entry for this egg file to the .pi file. +//////////////////////////////////////////////////////////////////// void SourceEgg:: write_pi(ostream &out) const { out << "egg " << get_egg_filename() << "\n"; TexRefs::const_iterator ti; for (ti = _texrefs.begin(); ti != _texrefs.end(); ++ti) { - out << " " << (*ti)._texture->get_name(); - if ((*ti)._repeats) { + out << " " << (*ti)->_packing->get_texture()->get_name() + << " in " << (*ti)->_packing->get_group()->get_name(); + if ((*ti)->_repeats) { out << " repeats"; } - if ((*ti)._alpha) { + if ((*ti)->_alpha) { out << " alpha"; } out << "\n"; } } +//////////////////////////////////////////////////////////////////// +// Function: SourceEgg::get_uv_range +// Access: Private +// Description: Checks the geometry in the egg file to see what range +// of UV's are requested for this particular texture +// reference. +//////////////////////////////////////////////////////////////////// void SourceEgg:: get_uv_range(EggGroupNode *group, EggTexture *tref, bool &any_uvs, TexCoordd &min_uv, TexCoordd &max_uv) { diff --git a/pandatool/src/egg-palettize/sourceEgg.h b/pandatool/src/egg-palettize/sourceEgg.h index 6f813ff89c..43ac9badd8 100644 --- a/pandatool/src/egg-palettize/sourceEgg.h +++ b/pandatool/src/egg-palettize/sourceEgg.h @@ -8,24 +8,28 @@ #include +#include "paletteGroup.h" + #include #include - class PTexture; +class TexturePacking; class AttribFile; class EggPalettize; class EggTexture; class EggGroup; +class TextureEggRef; class SourceEgg : public EggData { public: - class TextureRef; + SourceEgg(AttribFile *attrib_file); - SourceEgg(); - - TextureRef &add_texture(PTexture *texture, bool repeats, bool alpha); - void get_textures(AttribFile &af, EggPalettize *prog); + TextureEggRef *add_texture(PTexture *texture, TexturePacking *packing, + bool repeats, bool alpha); + void get_textures(EggPalettize *prog); + void require_groups(PaletteGroup *preferred, const PaletteGroups &groups); + void all_textures_assigned(); void mark_texture_flags(); void update_trefs(); @@ -33,26 +37,22 @@ public: bool needs_rebuild(bool force_redo_all, bool eggs_include_images) const; + bool matched_anything() const; + void set_matched_anything(bool matched_anything); + void write_pi(ostream &out) const; - class TextureRef { - public: - TextureRef(PTexture *texture, bool repeats, bool alpha); - - PTexture *_texture; - bool _repeats; - bool _alpha; - - EggTexture *_eggtex; - }; + typedef set TexRefs; + TexRefs _texrefs; private: void get_uv_range(EggGroupNode *group, EggTexture *tref, bool &any_uvs, TexCoordd &min_uv, TexCoordd &max_uv); - typedef vector TexRefs; - TexRefs _texrefs; + + AttribFile *_attrib_file; + bool _matched_anything; public: static TypeHandle get_class_type() { diff --git a/pandatool/src/egg-palettize/textureEggRef.cxx b/pandatool/src/egg-palettize/textureEggRef.cxx new file mode 100644 index 0000000000..77ac9f39f3 --- /dev/null +++ b/pandatool/src/egg-palettize/textureEggRef.cxx @@ -0,0 +1,69 @@ +// Filename: textureEggRef.cxx +// Created by: drose (08Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include "textureEggRef.h" +#include "texturePacking.h" +#include "pTexture.h" + +#include + +#include + +//////////////////////////////////////////////////////////////////// +// Function: TextureEggRef::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +TextureEggRef:: +TextureEggRef(SourceEgg *egg, PTexture *texture, TexturePacking *packing, + bool repeats, bool alpha) : + _egg(egg), + _texture(texture), + _packing(packing), + _repeats(repeats), + _alpha(alpha) +{ + _eggtex = NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: TextureEggRef::require_groups +// Access: Public +// Description: Indicates the set of PaletteGroups that this texture +// (as appearing on this egg file) wants to be listed +// on. If the texture is already listed on one of these +// groups, does nothing; otherwise, moves the texture. +//////////////////////////////////////////////////////////////////// +void TextureEggRef:: +require_groups(PaletteGroup *preferred, const PaletteGroups &groups) { + nassertv(!groups.empty()); + + if (_packing != (TexturePacking *)NULL) { + PaletteGroup *now_on = _packing->get_group(); + if (groups.count(now_on) != 0) { + // The group we're on is on the list; no problem. + return; + } + } + + // Has the texture already been packed into any of the groups? + PaletteGroups::const_iterator gi; + for (gi = groups.begin(); gi != groups.end(); ++gi) { + TexturePacking *packing = _texture->check_group(*gi); + if (packing != (TexturePacking *)NULL) { + // It has, use that group. + _packing = packing; + return; + } + } + + // We need to pack the texture into one of the requested groups. + + // For now, we arbitrarily pick the preferred group. Later, maybe + // we'll try to be smart about this and do some kind of graph + // minimization to choose the group the leads to the least redundant + // packing. + _packing = _texture->add_to_group(preferred); +} diff --git a/pandatool/src/egg-palettize/textureEggRef.h b/pandatool/src/egg-palettize/textureEggRef.h new file mode 100644 index 0000000000..1c13df8550 --- /dev/null +++ b/pandatool/src/egg-palettize/textureEggRef.h @@ -0,0 +1,43 @@ +// Filename: textureEggRef.h +// Created by: drose (08Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTUREEGGREF_H +#define TEXTUREEGGREF_H + +#include + +#include "paletteGroup.h" + +#include + +class SourceEgg; +class PTexture; +class TexturePacking; +class EggTexture; + +//////////////////////////////////////////////////////////////////// +// Class : TextureEggRef +// Description : This associates a texture with the egg files it is +// placed on, and also associated an egg file with the +// various textures it contains. +//////////////////////////////////////////////////////////////////// +class TextureEggRef { +public: + TextureEggRef(SourceEgg *egg, PTexture *texture, + TexturePacking *packing, + bool repeats, bool alpha); + + void require_groups(PaletteGroup *preferred, const PaletteGroups &groups); + + SourceEgg *_egg; + PTexture *_texture; + TexturePacking *_packing; + bool _repeats; + bool _alpha; + + EggTexture *_eggtex; +}; + +#endif diff --git a/pandatool/src/egg-palettize/textureOmitReason.h b/pandatool/src/egg-palettize/textureOmitReason.h new file mode 100644 index 0000000000..8e45f5955f --- /dev/null +++ b/pandatool/src/egg-palettize/textureOmitReason.h @@ -0,0 +1,26 @@ +// Filename: textureOmitReason.h +// Created by: drose (06Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTUREOMITREASON_H +#define TEXTUREOMITREASON_H + +#include + +//////////////////////////////////////////////////////////////////// +// Enum : TextureOmitReason +// Description : This enumerates the various reasons a texture might +// have been omitted from a palette. +//////////////////////////////////////////////////////////////////// +enum TextureOmitReason { + OR_none, // No reason: not omitted + OR_size, // Too big to put on a palette + OR_repeats, // The texture repeats and can't be palettized + OR_omitted, // Explicitly omitted by user in .txa file + OR_unused, // Not used by any egg files + OR_unknown, // The texture file is unknown, so can't determine its size + OR_solitary // Would be palettized, but no other textures are on the palette. +}; + +#endif diff --git a/pandatool/src/egg-palettize/texturePacking.cxx b/pandatool/src/egg-palettize/texturePacking.cxx new file mode 100644 index 0000000000..1c8e63ab28 --- /dev/null +++ b/pandatool/src/egg-palettize/texturePacking.cxx @@ -0,0 +1,463 @@ +// Filename: texturePacking.cxx +// Created by: drose (06Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include "texturePacking.h" +#include "paletteGroup.h" +#include "attribFile.h" +#include "palette.h" +#include "pTexture.h" + + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +TexturePacking:: +TexturePacking(PTexture *texture, PaletteGroup *group) : + _texture(texture), + _group(group) +{ + _attrib_file = _texture->_attrib_file; + _omit = _texture->_omit ? OR_omitted : OR_none; + _unused = true; + _uses_alpha = false; + _is_packed = false; + _palette = NULL; + _packing_changed = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +TexturePacking:: +~TexturePacking() { +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::get_texture +// Access: Public +// Description: Returns the texture this TexturePacking object refers +// to. +//////////////////////////////////////////////////////////////////// +PTexture *TexturePacking:: +get_texture() const { + return _texture; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::get_group +// Access: Public +// Description: Returns the palette group this TexturePacking object +// refers to. +//////////////////////////////////////////////////////////////////// +PaletteGroup *TexturePacking:: +get_group() const { + return _group; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::get_omit +// Access: Public +// Description: Returns the reason this texture was omitted from the +// particular palette group, if it was. +//////////////////////////////////////////////////////////////////// +TextureOmitReason TexturePacking:: +get_omit() const { + return _omit; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::set_omit +// Access: Public +// Description: Indicates the reason this texture was omitted from +// the particular palette group, if it was. +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +set_omit(TextureOmitReason omit) { + _omit = omit; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::unused +// Access: Public +// Description: Returns true if this particular texture/group +// combination seems to be unused by any egg files, +// false if at least one egg file uses it. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +unused() const { + return _unused; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::set_unused +// Access: Public +// Description: Sets the state of the unused flag. See unused(). +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +set_unused(bool unused) { + _unused = unused; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::uses_alpha +// Access: Public +// Description: Returns true if this texture seems to require alpha; +// that is, at least one egg file that references the +// texture specifies an alpha mode. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +uses_alpha() const { + return _uses_alpha; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::set_uses_alpha +// Access: Public +// Description: Sets the state of the uses_alpha flag. See +// uses_alpha(). +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +set_uses_alpha(bool uses_alpha) { + _uses_alpha = uses_alpha; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::pack +// Access: Public +// Description: Adds this texture to its appropriate palette group, +// if it has not already been packed. Returns true if +// anything has changed (e.g. it has been packed), false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +pack() { + if (!_is_packed) { + return _group->pack_texture(this, _attrib_file); + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::unpack +// Access: Public +// Description: Removes this texture from its palette image if it is +// on one. Returns true if anything has changed +// (e.g. it has been unpacked), false otherwise. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +unpack() { + if (_is_packed) { + return _palette->unpack_texture(this); + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::prepare_repack +// Access: Public +// Description: Checks if the texture needs to be repacked into a +// different location on the palette (for instance, +// because it has changed size). If so, unpacks it and +// returns true; otherwise, leaves it alone and returns +// false. +// +// If unpacking it will leave a hole or some similar +// nonsense, also sets optimal to false. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +prepare_repack(bool &optimal) { + bool needs_repack = false; + + if (get_omit() == OR_none) { + // Here's a texture that thinks it wants to be packed. Does it? + int xsize, ysize; + int pal_xsize = _attrib_file->_pal_xsize; + int pal_ysize = _attrib_file->_pal_ysize; + if (!_texture->get_req(xsize, ysize)) { + // If we don't know the texture's size, we can't place it. + nout << "Warning! Can't determine size of " << _texture->get_name() + << "\n"; + set_omit(OR_unknown); + + } else if ((xsize > pal_xsize || ysize > pal_ysize) || + (xsize == pal_xsize && ysize == pal_ysize)) { + // If the texture is too big for the palette (or exactly fills the + // palette), we can't place it. + set_omit(OR_size); + + } else { + // Ok, this texture really does want to be packed. Is it? + int px, py, m; + if (get_packed_size(px, py, m)) { + // The texture is packed. Does it have the right size? + if (px != xsize || py != ysize) { + // Oops, we'll have to repack it. + unpack(); + optimal = false; + needs_repack = true; + } + if (m != _texture->get_margin()) { + // The margin has changed, although not the size. We + // won't have to repack it, but we do need to update it. + _texture->set_changed(true); + } + } else { + // The texture isn't packed. Need to pack it. + needs_repack = true; + } + } + } + + if (get_omit() != OR_none) { + // Here's a texture that doesn't want to be packed. Is it? + if (unpack()) { + // It was! Not any more. + optimal = false; + } + } + + return needs_repack; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::mark_pack_location +// Access: Public +// Description: Records the location at which the texture has been +// packed. +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +mark_pack_location(Palette *palette, int left, int top, + int xsize, int ysize, int margin) { + _is_packed = true; + _palette = palette; + _pleft = left; + _ptop = top; + _pxsize = xsize; + _pysize = ysize; + _pmargin = margin; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::mark_unpacked +// Access: Public +// Description: Records that the texture has not been packed. +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +mark_unpacked() { + _is_packed = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::is_packed +// Access: Public +// Description: Returns true if the texture has been packed, false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +is_packed() const { + return _is_packed; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::is_really_packed +// Access: Public +// Description: Returns the same thing as is_packed(), except it +// doesn't consider a texture that has been left alone +// on a palette to be packed. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +is_really_packed() const { + return _is_packed && _omit != OR_solitary; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::get_palette +// Access: Public +// Description: Returns the particular palette image the texture has +// been packed into, or NULL if the texture is unpacked. +//////////////////////////////////////////////////////////////////// +Palette *TexturePacking:: +get_palette() const { + return _is_packed ? _palette : (Palette *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::get_packed_location +// Access: Public +// Description: Fills left and top with the upper-left corner of the +// rectangle in which the texture has been packed within +// its Palette image. Returns true if the texture has +// been packed, false otherwise. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +get_packed_location(int &left, int &top) const { + left = _pleft; + top = _ptop; + return _is_packed; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::get_packed_size +// Access: Public +// Description: Fills xsize, ysize, and margin with the size of the +// rectangle in which the texture has been packed within +// its Palette image. The margin is an interior margin. +// Returns true if the texture has been packed, false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +get_packed_size(int &xsize, int &ysize, int &margin) const { + xsize = _pxsize; + ysize = _pysize; + margin = _pmargin; + return _is_packed; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::record_orig_state +// Access: Public +// Description: Records the current packing state, storing it aside +// as the state at load time. Later, when the packing +// state may have changed, packing_changed() will return +// true if it has or false if it has not. +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +record_orig_state() { + _orig_is_packed = _is_packed; + if (_is_packed) { + _orig_palette_name = _palette->get_filename(); + _opleft = _pleft; + _optop = _ptop; + _opxsize = _pxsize; + _opysize = _pysize; + _opmargin = _pmargin; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::packing_changed +// Access: Public +// Description: Returns true if the packing has changed in any way +// since the last call to record_orig_state(), false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +packing_changed() const { + if (_orig_is_packed != _is_packed) { + return true; + } + if (_is_packed) { + return _orig_palette_name != _palette->get_filename() || + _opleft != _pleft || + _optop != _ptop || + _opxsize != _pxsize || + _opysize != _pysize || + _opmargin != _pmargin; + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::set_changed +// Access: Public +// Description: Sets the state of the changed flag. If this is true, +// the state of this texture on this group is known to +// have changed in some way such that files that depend +// on it will need to be rebuilt. +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +set_changed(bool changed) { + _packing_changed = changed; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::needs_refresh +// Access: Public +// Description: Returns true if the texture has changed in any +// significant way and the palette it's placed on needs +// to be regenerated. +//////////////////////////////////////////////////////////////////// +bool TexturePacking:: +needs_refresh() { + bool any_change = + _texture->_texture_changed || _packing_changed; + + if (!any_change) { + // We consider the texture to be out-of-date if it's moved around + // in the palette. + any_change = packing_changed(); + } + + if (!any_change && _texture->_file_exists) { + // Compare the texture's timestamp to that of its palette (or + // resized copy). If it's newer, it's changed and must be + // replaced. + + Filename target_filename; + if (is_packed() && _omit == OR_none) { + // Compare to the palette file. + target_filename = _palette->get_filename(); + if (_palette->new_palette()) { + // It's a brand new palette; don't even bother comparing + // timestamps. + any_change = true; + } + + } else { + // Compare to the resized file. + target_filename = _texture->get_filename(); + } + + if (!any_change) { + any_change = + (target_filename.compare_timestamps(_texture->_filename, true, false) < 0); + } + } + + return any_change; +} + +//////////////////////////////////////////////////////////////////// +// Function: TexturePacking::write_unplaced +// Access: Public +// Description: Writes an "unplaced" entry to the .pi file if this +// texture has not been placed on the group, describing +// the reason for the failure. +//////////////////////////////////////////////////////////////////// +void TexturePacking:: +write_unplaced(ostream &out) const { + if (_omit != OR_none && _omit != OR_unused) { + out << "unplaced " << _texture->get_name() + << " in " << _group->get_name() << " because "; + switch (_omit) { + case OR_size: + out << "size"; + break; + case OR_repeats: + out << "repeats"; + break; + case OR_omitted: + out << "omitted"; + break; + case OR_unused: + out << "unused"; + break; + case OR_unknown: + out << "unknown"; + break; + case OR_solitary: + out << "solitary"; + break; + default: + nout << "Invalid type: " << (int)_omit << "\n"; + abort(); + } + out << "\n"; + } +} diff --git a/pandatool/src/egg-palettize/texturePacking.h b/pandatool/src/egg-palettize/texturePacking.h new file mode 100644 index 0000000000..651507e7fe --- /dev/null +++ b/pandatool/src/egg-palettize/texturePacking.h @@ -0,0 +1,82 @@ +// Filename: texturePacking.h +// Created by: drose (06Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TEXTUREPACKING_H +#define TEXTUREPACKING_H + +#include + +#include "textureOmitReason.h" + +#include + +class PTexture; +class PaletteGroup; +class Palette; +class AttribFile; + +//////////////////////////////////////////////////////////////////// +// Class : TexturePacking +// Description : This structure defines how a particular texture is +// packed into a Palette image, or even whether it is +// packed at all. +//////////////////////////////////////////////////////////////////// +class TexturePacking { +public: + TexturePacking(PTexture *texture, PaletteGroup *group); + ~TexturePacking(); + + PTexture *get_texture() const; + PaletteGroup *get_group() const; + + TextureOmitReason get_omit() const; + void set_omit(TextureOmitReason omit); + + bool unused() const; + void set_unused(bool unused); + + bool uses_alpha() const; + void set_uses_alpha(bool uses_alpha); + + bool pack(); + bool unpack(); + bool prepare_repack(bool &optimal); + + void mark_pack_location(Palette *palette, int left, int top, + int xsize, int ysize, int margin); + void mark_unpacked(); + bool is_packed() const; + bool is_really_packed() const; + Palette *get_palette() const; + bool get_packed_location(int &left, int &top) const; + bool get_packed_size(int &xsize, int &ysize, int &margin) const; + void record_orig_state(); + bool packing_changed() const; + + void set_changed(bool changed); + bool needs_refresh(); + + void write_unplaced(ostream &out) const; + +private: + AttribFile *_attrib_file; + PTexture *_texture; + PaletteGroup *_group; + TextureOmitReason _omit; + bool _unused; + bool _uses_alpha; + + bool _is_packed; + Palette *_palette; + int _pleft, _ptop, _pxsize, _pysize, _pmargin; + + bool _orig_is_packed; + Filename _orig_palette_name; + int _opleft, _optop, _opxsize, _opysize, _opmargin; + + bool _packing_changed; +}; + +#endif diff --git a/pandatool/src/egg-palettize/userAttribLine.cxx b/pandatool/src/egg-palettize/userAttribLine.cxx index c645cf3e04..5063da1a26 100644 --- a/pandatool/src/egg-palettize/userAttribLine.cxx +++ b/pandatool/src/egg-palettize/userAttribLine.cxx @@ -6,16 +6,14 @@ #include "userAttribLine.h" #include "string_utils.h" #include "pTexture.h" +#include "paletteGroup.h" +#include "sourceEgg.h" #include "attribFile.h" +#include "textureEggRef.h" #include #include -#include - -UserAttribLine::TextureName:: -TextureName(const string &pattern) : _pattern(pattern) { -} UserAttribLine:: UserAttribLine(const string &cline, AttribFile *af) : _attrib_file(af) { @@ -91,6 +89,8 @@ was_used() const { void UserAttribLine:: write(ostream &out) const { + int i; + switch (_line_type) { case LT_invalid: out << "*** invalid line ***\n"; @@ -107,8 +107,16 @@ write(ostream &out) const { out << ":palette " << _xsize << " " << _ysize; break; + case LT_group_relate: + out << ":group " << _names[0] << " with"; + for (i = 1; i < (int)_names.size(); i++) { + out << " " << _names[i]; + } + out << "\n"; + break; + case LT_size: - list_textures(out) << " : " << _xsize << " " << _ysize; + list_patterns(out) << " : " << _xsize << " " << _ysize; if (_msize > 0) { out << " " << _msize; } @@ -118,7 +126,7 @@ write(ostream &out) const { break; case LT_scale: - list_textures(out) << " : " << _scale_pct << "%"; + list_patterns(out) << " : " << _scale_pct << "%"; if (_msize > 0) { out << " " << _msize; } @@ -128,12 +136,17 @@ write(ostream &out) const { break; case LT_name: - list_textures(out) << " :"; + list_patterns(out) << " :"; if (_omit) { out << " omit"; } break; + case LT_group_assign: + list_patterns(out) << " : "; + list_names(out); + break; + default: nout << "Unexpected type: " << (int)_line_type << "\n"; abort(); @@ -143,17 +156,36 @@ write(ostream &out) const { } bool UserAttribLine:: -match_texture(PTexture *texture, int &margin) { +get_size_request(PTexture *texture, int &margin) { + if (_line_type == LT_group_assign) { + // We don't care about group lines for the size check. + return false; + } + // See if the texture name matches any of the filename patterns on // this line. + string texture_name = texture->get_name(); + bool matched_any = false; - TextureNames::const_iterator tni; - for (tni = _texture_names.begin(); - tni != _texture_names.end() && !matched_any; - ++tni) { - if (fnmatch((*tni)._pattern.c_str(), texture->get_name().c_str(), 0) == 0) { + Patterns::const_iterator pi; + for (pi = _patterns.begin(); + pi != _patterns.end() && !matched_any; + ++pi) { + if ((*pi).matches(texture_name)) { matched_any = true; } + + // Also check if it matches any of the egg files this texture is + // on. + PTexture::Eggs::const_iterator ei; + for (ei = texture->_eggs.begin(); + ei != texture->_eggs.end() && !matched_any; + ++ei) { + string egg_name = (*ei)->_egg->get_egg_filename().get_basename(); + if ((*pi).matches(egg_name)) { + matched_any = true; + } + } } if (matched_any) { @@ -164,6 +196,7 @@ match_texture(PTexture *texture, int &margin) { case LT_invalid: case LT_comment: case LT_palette: + case LT_group_relate: return false; case LT_margin: @@ -174,7 +207,7 @@ match_texture(PTexture *texture, int &margin) { texture->reset_req(_xsize, _ysize); texture->set_margin(_msize < 0 ? margin : _msize); if (_omit) { - texture->set_omit(PTexture::OR_omitted); + texture->user_omit(); } return true; @@ -182,13 +215,13 @@ match_texture(PTexture *texture, int &margin) { texture->scale_req(_scale_pct); texture->set_margin(_msize < 0 ? margin : _msize); if (_omit) { - texture->set_omit(PTexture::OR_omitted); + texture->user_omit(); } return true; case LT_name: if (_omit) { - texture->set_omit(PTexture::OR_omitted); + texture->user_omit(); } return true; @@ -201,12 +234,73 @@ match_texture(PTexture *texture, int &margin) { return false; } +bool UserAttribLine:: +get_group_request(SourceEgg *egg) { + if (_line_type != LT_group_assign) { + // We're only looking for group lines now. + return false; + } + + // See if the egg filename matches any of the filename patterns on + // this line. + string egg_name = egg->get_egg_filename().get_basename(); + + bool matched_any = false; + Patterns::const_iterator pi; + for (pi = _patterns.begin(); + pi != _patterns.end() && !matched_any; + ++pi) { + if ((*pi).matches(egg_name)) { + matched_any = true; + } + } + + if (matched_any) { + _was_used = true; + + // It does! Record the list of required groups with all of the + // textures on this egg file. + nassertr(!_names.empty(), false); + PaletteGroups groups; + Names::const_iterator ni = _names.begin(); + PaletteGroup *group = _attrib_file->get_group(*ni); + groups.insert(group); + + // The first-named group is preferred for any textures not already + // on another group. + PaletteGroup *preferred = group; + + while (ni != _names.end()) { + group = _attrib_file->get_group(*ni); + groups.insert(group); + ++ni; + } + PaletteGroup::complete_groups(groups); + egg->require_groups(preferred, groups); + + return true; + } + + return false; +} + ostream &UserAttribLine:: -list_textures(ostream &out) const { - if (!_texture_names.empty()) { - out << _texture_names[0]._pattern; - for (int i = 1; i < (int)_texture_names.size(); i++) { - out << " " << _texture_names[i]._pattern; +list_patterns(ostream &out) const { + if (!_patterns.empty()) { + out << _patterns[0]; + for (int i = 1; i < (int)_patterns.size(); i++) { + out << " " << _patterns[i]; + } + } + return out; +} + +ostream &UserAttribLine:: +list_names(ostream &out) const { + if (!_names.empty()) { + out << _names[0]; + for (int i = 1; i < (int)_names.size(); i++) { + out << " " << _names[i]; } } return out; @@ -229,7 +323,7 @@ keyword_line(const string &line) { } else if (words[0] == ":palette") { _line_type = LT_palette; if (words.size() != 3) { - nout << "Expected xsize ysize of palette.\n"; + nout << "Expected :palette xsize ysize.\n"; return false; } _xsize = atoi(words[1].c_str()); @@ -237,6 +331,18 @@ keyword_line(const string &line) { _attrib_file->_pal_xsize = _xsize; _attrib_file->_pal_ysize = _ysize; + } else if (words[0] == ":group") { + _line_type = LT_group_relate; + if (words.size() < 4 || !(words[2] == "with")) { + nout << "Expected :group groupname with groupname [groupname ...].\n"; + return false; + } + PaletteGroup *group = _attrib_file->get_group(words[1]); + + for (int i = 3; i < (int)words.size(); i++) { + group->add_parent(_attrib_file->get_group(words[i])); + } + } else { nout << "Unknown keyword: " << words[0] << "\n"; return false; @@ -262,7 +368,7 @@ texture_line(const string &line) { vector::const_iterator ni; for (ni = names.begin(); ni != names.end(); ++ni) { - _texture_names.push_back(TextureName(*ni)); + _patterns.push_back(GlobPattern(*ni)); } if (!params.empty() && params[params.size() - 1] == "omit") { @@ -277,6 +383,14 @@ texture_line(const string &line) { return true; } + // Is it a group name? If it is, this is an assignment of a texture + // or egg file to one or more groups. + if (!params[0].empty() && isalpha(params[0][0])) { + _names = params; + _line_type = LT_group_assign; + return true; + } + // Is it a percentage? if (!params[0].empty() && params[0][params[0].size() - 1] == '%') { _line_type = LT_scale; @@ -335,7 +449,7 @@ old_style_line(const string &line) { _is_old_style = true; _line_type = LT_size; - _texture_names.push_back(TextureName(words[0])); + _patterns.push_back(GlobPattern(words[0])); _xsize = atoi(words[1].c_str()); _ysize = atoi(words[2].c_str()); if (words.size() > 3) { diff --git a/pandatool/src/egg-palettize/userAttribLine.h b/pandatool/src/egg-palettize/userAttribLine.h index 7eb658542d..1a71c6a1bf 100644 --- a/pandatool/src/egg-palettize/userAttribLine.h +++ b/pandatool/src/egg-palettize/userAttribLine.h @@ -8,16 +8,18 @@ #include +#include + #include class AttribFile; class PTexture; +class SourceEgg; //////////////////////////////////////////////////////////////////// // Class : UserAttribLine -// Description : A single entry in the user part (the beginning) of -// the attrib file, this defines how the user would like -// some particular texture to be scaled. +// Description : A single entry in the .txa file, this defines how the +// user would like some particular texture to be scaled. //////////////////////////////////////////////////////////////////// // @@ -27,10 +29,13 @@ class PTexture; // # Comment // :margin msize // :palette xsize ysize +// :group groupname with groupname [groupname ...] // texturename xsize ysize msize // texturename [texturename ...] : xsize ysize [msize] [omit] // texturename [texturename ...] : scale% [msize] [omit] // texturename [texturename ...] : [omit] +// texturename [texturename ...] : groupname [groupname ...] +// eggname [eggname ...] : groupname [groupname ...] // class UserAttribLine { @@ -44,28 +49,25 @@ public: void write(ostream &out) const; - bool match_texture(PTexture *texture, int &margin); + bool get_size_request(PTexture *texture, int &margin); + bool get_group_request(SourceEgg *egg); private: enum LineType { LT_invalid, LT_comment, - LT_margin, LT_palette, - LT_size, LT_scale, LT_name - }; - class TextureName { - public: - TextureName(const string &pattern); - TextureName(const TextureName ©) : - _pattern(copy._pattern) { } - - string _pattern; + LT_margin, LT_palette, LT_group_relate, + LT_size, LT_scale, LT_name, + LT_group_assign }; - typedef vector TextureNames; - TextureNames _texture_names; + typedef vector Patterns; + Patterns _patterns; + typedef vector Names; + Names _names; - ostream &list_textures(ostream &out) const; + ostream &list_patterns(ostream &out) const; + ostream &list_names(ostream &out) const; bool keyword_line(const string &line); bool texture_line(const string &line); bool old_style_line(const string &line);