Explorar o código

*** empty log message ***

David Rose %!s(int64=25) %!d(string=hai) anos
pai
achega
e2e9663c4c

+ 15 - 0
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

+ 2 - 0
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,

+ 1 - 2
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;
 	}
       }

+ 28 - 0
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 &copy) : _pattern(copy._pattern) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GlobPattern::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void GlobPattern::
+operator = (const GlobPattern &copy) {
+  _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;
+}

+ 10 - 0
panda/src/putil/globPattern.h

@@ -26,11 +26,15 @@
 class EXPCL_PANDA GlobPattern {
 public:
   INLINE GlobPattern(const string &pattern = string());
+  INLINE GlobPattern(const GlobPattern &copy);
+  INLINE void operator = (const GlobPattern &copy);
 
   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

+ 8 - 5
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
 

+ 254 - 230
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_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_req_sizes() {
+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<PTexture *> textures;
+  vector<TexturePacking *> textures;
   get_eligible_textures(textures);
   
   // Now pack all the textures.  This will create new palettes.
-  vector<PTexture *>::iterator ti;
+  vector<TexturePacking *>::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<PTexture *> textures;
+  vector<TexturePacking *> textures;
   get_eligible_textures(textures);
   
   // Now pack whatever textures are currently unpacked.
-  vector<PTexture *>::iterator ti;
+  vector<TexturePacking *>::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;
-
-  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;
-      }
+prepare_repack(bool force_optimal) {
+  bool needs_repack = 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<PTexture *> &textures) {
+get_eligible_textures(vector<TexturePacking *> &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<int, map<int, set<PTexture *> > > TexBySize;
+  typedef map<int, map<int, set<TexturePacking *> > > 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<PTexture *> &textures) {
 
   TexBySize::const_iterator t1;
   for (t1 = tex_by_size.begin(); t1 != tex_by_size.end(); ++t1) {
-    map<int, set<PTexture *> >::const_iterator t2;
+    map<int, set<TexturePacking *> >::const_iterator t2;
     for (t2 = (*t1).second.begin(); t2 != (*t1).second.end(); ++t2) {
-      set<PTexture *>::const_iterator t3;
+      set<TexturePacking *>::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<string, PTexture *> &textures,
 		   map<string, int> &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<string, PTexture *> &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<string> &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<string> &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<string> &words, istream &infile,
 
 bool AttribFile::
 parse_egg(const vector<string> &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<string> &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<string> &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<string> &words, istream &infile,
 }
   
 
-
 bool AttribFile::
 parse_palette(const vector<string> &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<string> &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<string> &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<string> &words, istream &infile,
 bool AttribFile::
 parse_unplaced(const vector<string> &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;
   }
 

+ 18 - 13
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<PTexture *> &textures);
+  void get_eligible_textures(vector<TexturePacking *> &textures);
   SourceEgg *get_egg(Filename name);
 
   bool generate_palette_images();
@@ -74,16 +78,17 @@ private:
   typedef map<string, SourceEgg *> Eggs;
   Eggs _eggs;
 
-  typedef vector<Palette *> Palettes;
-  Palettes _palettes;
+  typedef map<string, PaletteGroup *> Groups;
+  Groups _groups;
 
   typedef map<string, PTexture *> PTextures;
   PTextures _textures;
 
-  string get_pi_filename(const string &txa_filename) const;
+  typedef vector<TexturePacking *> 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<string> &words, istream &infile, 
 			 string &line, int &line_num);
   bool parse_egg(const vector<string> &words, istream &infile, 
-		    string &line, int &line_num);
+		    string &line, int &line_num, bool force_redo_all);
   bool parse_palette(const vector<string> &words, istream &infile, 
 			string &line, int &line_num);
   bool parse_unplaced(const vector<string> &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

+ 63 - 22
pandatool/src/egg-palettize/eggPalettize.cxx

@@ -8,6 +8,7 @@
 #include "pTexture.h"
 #include "string_utils.h"
 #include "sourceEgg.h"
+#include "textureOmitReason.h"
 
 #include <pnmImage.h>
 #include <stdio.h>
@@ -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<string, PTexture *> textures;
   map<string, int> 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<PTexture::OmitReason, pair<int, int> > UnplacedReasons;
+  typedef map<TextureOmitReason, pair<int, int> > UnplacedReasons;
   UnplacedReasons unplaced_reasons;
 
   map<string, PTexture *>::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 {

+ 1 - 2
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;

+ 122 - 193
pandatool/src/egg-palettize/pTexture.cxx

@@ -4,32 +4,46 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "pTexture.h"
+#include "texturePacking.h"
 #include "palette.h"
 #include "attribFile.h"
 
 #include <pnmImage.h>
 #include <pnmReader.h>
 
-
+////////////////////////////////////////////////////////////////////
+//     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();
-    }
+  TexturePacking *packing = new TexturePacking(this, group);
+  _packing[group] = packing;
+  _attrib_file->_packing.push_back(packing);
+  return packing;
+}
 
-    if (!_texture_changed) {
-      _texture_changed = 
-	(target_filename.compare_timestamps(_filename, true, false) < 0);
-    }
+////////////////////////////////////////////////////////////////////
+//     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 _texture_changed;
+  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;
+    }
   }
-}
 
-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;
+  return true;
 }
 
+
 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;
 }
 

+ 29 - 43
pandatool/src/egg-palettize/pTexture.h

@@ -11,24 +11,26 @@
 #include "imageFile.h"
 
 #include <set>
+#include <map>
 
 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();
 
-  bool needs_refresh();
-  void set_changed(bool changed);
+  TexturePacking *add_to_group(PaletteGroup *group);
+  TexturePacking *check_group(PaletteGroup *group) const;
 
-  bool unused() const;
-  void set_unused(bool unused);
+  void set_changed(bool changed);
 
   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<TextureEggRef *> 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<PaletteGroup *, TexturePacking *> Packing;
+  Packing _packing;
+
+  friend class TexturePacking;
 };
 
 

+ 38 - 28
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.

+ 19 - 10
pandatool/src/egg-palettize/palette.h

@@ -14,25 +14,33 @@
 
 #include <vector>
 
-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;

+ 259 - 0
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);
+  }
+}

+ 79 - 0
pandatool/src/egg-palettize/paletteGroup.h

@@ -0,0 +1,79 @@
+// Filename: paletteGroup.h
+// Created by:  drose (06Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PALETTEGROUP_H
+#define PALETTEGROUP_H
+
+#include <pandatoolbase.h>
+
+#include <namable.h>
+
+#include <vector>
+#include <set>
+
+class Palette;
+class TexturePacking;
+class AttribFile;
+class PaletteGroup;
+
+typedef set<PaletteGroup *> 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<PaletteGroup *> Parents;
+  Parents _parents;
+
+  typedef vector<Palette *> Palettes;
+  Palettes _palettes;
+};
+
+
+#endif
+

+ 173 - 59
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 <eggNurbsSurface.h>
 #include <eggPrimitive.h>
 #include <eggTextureCollection.h>
+#include <eggAlphaMode.h>
 
-TypeHandle SourceEgg::_type_handle;
+#include <algorithm>
 
-SourceEgg::TextureRef::
-TextureRef(PTexture *texture, bool repeats, bool alpha) :
-  _texture(texture),
-  _repeats(repeats),
-  _alpha(alpha) 
-{
-  _eggtex = NULL;
-}
+TypeHandle SourceEgg::_type_handle;
 
+////////////////////////////////////////////////////////////////////
+//     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) {

+ 18 - 18
pandatool/src/egg-palettize/sourceEgg.h

@@ -8,24 +8,28 @@
 
 #include <pandatoolbase.h>
 
+#include "paletteGroup.h"
+
 #include <eggData.h>
 #include <luse.h>
 
-
 class PTexture;
+class TexturePacking;
 class AttribFile;
 class EggPalettize;
 class EggTexture;
 class EggGroup;
+class TextureEggRef;
     
 class SourceEgg : public EggData {
 public:
-  class TextureRef;
-
-  SourceEgg();
+  SourceEgg(AttribFile *attrib_file);
 
-  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;
 
-  void write_pi(ostream &out) const;
-
+  bool matched_anything() const;
+  void set_matched_anything(bool matched_anything);
 
-  class TextureRef {
-  public:
-    TextureRef(PTexture *texture, bool repeats, bool alpha);
+  void write_pi(ostream &out) const;
 
-    PTexture *_texture;
-    bool _repeats;
-    bool _alpha;
 
-    EggTexture *_eggtex;
-  };
+  typedef set<TextureEggRef *> TexRefs;
+  TexRefs _texrefs;
 
 private:  
   void get_uv_range(EggGroupNode *group, EggTexture *tref,
 		    bool &any_uvs, TexCoordd &min_uv, TexCoordd &max_uv);
 
-  typedef vector<TextureRef> TexRefs;
-  TexRefs _texrefs;
+
+  AttribFile *_attrib_file;
+  bool _matched_anything;
 
 public:
   static TypeHandle get_class_type() {

+ 69 - 0
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 <notify.h>
+
+#include <algorithm>
+
+////////////////////////////////////////////////////////////////////
+//     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);
+}

+ 43 - 0
pandatool/src/egg-palettize/textureEggRef.h

@@ -0,0 +1,43 @@
+// Filename: textureEggRef.h
+// Created by:  drose (08Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREEGGREF_H
+#define TEXTUREEGGREF_H
+
+#include <pandatoolbase.h>
+
+#include "paletteGroup.h"
+
+#include <vector>
+
+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

+ 26 - 0
pandatool/src/egg-palettize/textureOmitReason.h

@@ -0,0 +1,26 @@
+// Filename: textureOmitReason.h
+// Created by:  drose (06Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREOMITREASON_H
+#define TEXTUREOMITREASON_H
+
+#include <pandatoolbase.h>
+
+////////////////////////////////////////////////////////////////////
+// 	  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

+ 463 - 0
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";
+  }
+}

+ 82 - 0
pandatool/src/egg-palettize/texturePacking.h

@@ -0,0 +1,82 @@
+// Filename: texturePacking.h
+// Created by:  drose (06Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREPACKING_H
+#define TEXTUREPACKING_H
+
+#include <pandatoolbase.h>
+
+#include "textureOmitReason.h"
+
+#include <filename.h>
+
+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

+ 139 - 25
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 <notify.h>
 
 #include <ctype.h>
-#include <fnmatch.h>
-
-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<string>::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) {

+ 19 - 17
pandatool/src/egg-palettize/userAttribLine.h

@@ -8,16 +8,18 @@
 
 #include <pandatoolbase.h>
 
+#include <globPattern.h>
+
 #include <vector>
 
 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 &copy) : 
-      _pattern(copy._pattern) { }
-
-    string _pattern;
+    LT_margin, LT_palette, LT_group_relate,
+    LT_size, LT_scale, LT_name,
+    LT_group_assign
   };
 
-  typedef vector<TextureName> TextureNames;
-  TextureNames _texture_names;
+  typedef vector<GlobPattern> Patterns;
+  Patterns _patterns;
+  typedef vector<string> 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);