Browse Source

*** empty log message ***

David Rose 25 years ago
parent
commit
caddc5b207
58 changed files with 7902 additions and 13 deletions
  1. 0 0
      pandatool/src/egg-palettize-old/Sources.pp
  2. 0 0
      pandatool/src/egg-palettize-old/attribFile.cxx
  3. 0 0
      pandatool/src/egg-palettize-old/attribFile.h
  4. 0 0
      pandatool/src/egg-palettize-old/config_egg_palettize.cxx
  5. 0 0
      pandatool/src/egg-palettize-old/eggPalettize.cxx
  6. 0 0
      pandatool/src/egg-palettize-old/eggPalettize.h
  7. 0 0
      pandatool/src/egg-palettize-old/pTexture.cxx
  8. 0 0
      pandatool/src/egg-palettize-old/pTexture.h
  9. 0 0
      pandatool/src/egg-palettize-old/palette.cxx
  10. 0 0
      pandatool/src/egg-palettize-old/palette.h
  11. 0 0
      pandatool/src/egg-palettize-old/paletteGroup.cxx
  12. 0 0
      pandatool/src/egg-palettize-old/paletteGroup.h
  13. 0 0
      pandatool/src/egg-palettize-old/sourceEgg.cxx
  14. 0 0
      pandatool/src/egg-palettize-old/sourceEgg.h
  15. 0 0
      pandatool/src/egg-palettize-old/string_utils.cxx
  16. 0 0
      pandatool/src/egg-palettize-old/string_utils.h
  17. 0 0
      pandatool/src/egg-palettize-old/textureEggRef.cxx
  18. 0 0
      pandatool/src/egg-palettize-old/textureEggRef.h
  19. 0 0
      pandatool/src/egg-palettize-old/textureOmitReason.h
  20. 0 0
      pandatool/src/egg-palettize-old/texturePacking.cxx
  21. 0 0
      pandatool/src/egg-palettize-old/texturePacking.h
  22. 0 0
      pandatool/src/egg-palettize-old/userAttribLine.cxx
  23. 0 0
      pandatool/src/egg-palettize-old/userAttribLine.h
  24. 13 0
      pandatool/src/egg-palettize/config_egg_palettize.h
  25. 330 0
      pandatool/src/egg-palettize/eggFile.cxx
  26. 103 0
      pandatool/src/egg-palettize/eggFile.h
  27. 357 0
      pandatool/src/egg-palettize/imageFile.cxx
  28. 86 0
      pandatool/src/egg-palettize/imageFile.h
  29. 34 0
      pandatool/src/egg-palettize/omitReason.cxx
  30. 44 0
      pandatool/src/egg-palettize/omitReason.h
  31. 319 0
      pandatool/src/egg-palettize/paletteGroups.cxx
  32. 101 0
      pandatool/src/egg-palettize/paletteGroups.h
  33. 549 0
      pandatool/src/egg-palettize/paletteImage.cxx
  34. 115 0
      pandatool/src/egg-palettize/paletteImage.h
  35. 311 0
      pandatool/src/egg-palettize/palettePage.cxx
  36. 92 0
      pandatool/src/egg-palettize/palettePage.h
  37. 499 0
      pandatool/src/egg-palettize/palettizer.cxx
  38. 128 0
      pandatool/src/egg-palettize/palettizer.h
  39. 215 0
      pandatool/src/egg-palettize/sourceTextureImage.cxx
  40. 75 0
      pandatool/src/egg-palettize/sourceTextureImage.h
  41. 726 0
      pandatool/src/egg-palettize/textureImage.cxx
  42. 139 0
      pandatool/src/egg-palettize/textureImage.h
  43. 911 0
      pandatool/src/egg-palettize/texturePlacement.cxx
  44. 132 0
      pandatool/src/egg-palettize/texturePlacement.h
  45. 145 0
      pandatool/src/egg-palettize/texturePosition.cxx
  46. 68 0
      pandatool/src/egg-palettize/texturePosition.h
  47. 605 0
      pandatool/src/egg-palettize/textureProperties.cxx
  48. 92 0
      pandatool/src/egg-palettize/textureProperties.h
  49. 687 0
      pandatool/src/egg-palettize/textureReference.cxx
  50. 121 0
      pandatool/src/egg-palettize/textureReference.h
  51. 39 0
      pandatool/src/egg-palettize/textureRequest.cxx
  52. 42 0
      pandatool/src/egg-palettize/textureRequest.h
  53. 202 0
      pandatool/src/egg-palettize/txaFile.cxx
  54. 43 0
      pandatool/src/egg-palettize/txaFile.h
  55. 467 0
      pandatool/src/egg-palettize/txaLine.cxx
  56. 86 0
      pandatool/src/egg-palettize/txaLine.h
  57. 25 13
      pandatool/src/eggbase/eggMultiFilter.cxx
  58. 1 0
      pandatool/src/eggbase/eggMultiFilter.h

+ 0 - 0
pandatool/src/egg-palettize/Sources.pp → pandatool/src/egg-palettize-old/Sources.pp


+ 0 - 0
pandatool/src/egg-palettize/attribFile.cxx → pandatool/src/egg-palettize-old/attribFile.cxx


+ 0 - 0
pandatool/src/egg-palettize/attribFile.h → pandatool/src/egg-palettize-old/attribFile.h


+ 0 - 0
pandatool/src/egg-palettize/config_egg_palettize.cxx → pandatool/src/egg-palettize-old/config_egg_palettize.cxx


+ 0 - 0
pandatool/src/egg-palettize/eggPalettize.cxx → pandatool/src/egg-palettize-old/eggPalettize.cxx


+ 0 - 0
pandatool/src/egg-palettize/eggPalettize.h → pandatool/src/egg-palettize-old/eggPalettize.h


+ 0 - 0
pandatool/src/egg-palettize/pTexture.cxx → pandatool/src/egg-palettize-old/pTexture.cxx


+ 0 - 0
pandatool/src/egg-palettize/pTexture.h → pandatool/src/egg-palettize-old/pTexture.h


+ 0 - 0
pandatool/src/egg-palettize/palette.cxx → pandatool/src/egg-palettize-old/palette.cxx


+ 0 - 0
pandatool/src/egg-palettize/palette.h → pandatool/src/egg-palettize-old/palette.h


+ 0 - 0
pandatool/src/egg-palettize/paletteGroup.cxx → pandatool/src/egg-palettize-old/paletteGroup.cxx


+ 0 - 0
pandatool/src/egg-palettize/paletteGroup.h → pandatool/src/egg-palettize-old/paletteGroup.h


+ 0 - 0
pandatool/src/egg-palettize/sourceEgg.cxx → pandatool/src/egg-palettize-old/sourceEgg.cxx


+ 0 - 0
pandatool/src/egg-palettize/sourceEgg.h → pandatool/src/egg-palettize-old/sourceEgg.h


+ 0 - 0
pandatool/src/egg-palettize/string_utils.cxx → pandatool/src/egg-palettize-old/string_utils.cxx


+ 0 - 0
pandatool/src/egg-palettize/string_utils.h → pandatool/src/egg-palettize-old/string_utils.h


+ 0 - 0
pandatool/src/egg-palettize/textureEggRef.cxx → pandatool/src/egg-palettize-old/textureEggRef.cxx


+ 0 - 0
pandatool/src/egg-palettize/textureEggRef.h → pandatool/src/egg-palettize-old/textureEggRef.h


+ 0 - 0
pandatool/src/egg-palettize/textureOmitReason.h → pandatool/src/egg-palettize-old/textureOmitReason.h


+ 0 - 0
pandatool/src/egg-palettize/texturePacking.cxx → pandatool/src/egg-palettize-old/texturePacking.cxx


+ 0 - 0
pandatool/src/egg-palettize/texturePacking.h → pandatool/src/egg-palettize-old/texturePacking.h


+ 0 - 0
pandatool/src/egg-palettize/userAttribLine.cxx → pandatool/src/egg-palettize-old/userAttribLine.cxx


+ 0 - 0
pandatool/src/egg-palettize/userAttribLine.h → pandatool/src/egg-palettize-old/userAttribLine.h


+ 13 - 0
pandatool/src/egg-palettize/config_egg_palettize.h

@@ -0,0 +1,13 @@
+// Filename: config_egg_palettize.h
+// Created by:  drose (01Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef CONFIG_EGG_PALETTIZE_H
+#define CONFIG_EGG_PALETTIZE_H
+
+#include <pandatoolbase.h>
+
+// No variables to declare here.
+
+#endif /* __CONFIG_UTIL_H__ */

+ 330 - 0
pandatool/src/egg-palettize/eggFile.cxx

@@ -0,0 +1,330 @@
+// Filename: eggFile.cxx
+// Created by:  drose (29Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "eggFile.h"
+#include "textureImage.h"
+#include "paletteGroup.h"
+#include "texturePlacement.h"
+#include "palettizer.h"
+
+#include <eggData.h>
+#include <eggTextureCollection.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+TypeHandle EggFile::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+EggFile::
+EggFile() {
+  _data = (EggData *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::from_command_line
+//       Access: Public
+//  Description: Accepts the information about the egg file as
+//               supplied from the command line.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+from_command_line(EggData *data,
+		  const Filename &source_filename, 
+		  const Filename &dest_filename) {
+  _data = data;
+  _source_filename = source_filename;
+  _dest_filename = dest_filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::scan_textures
+//       Access: Public
+//  Description: Scans the egg file for texture references and updates
+//               the _textures list appropriately.  This assumes the
+//               egg file was supplied on the command line and thus
+//               the _data member is available.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+scan_textures() {
+  nassertv(_data != (EggData *)NULL);
+
+  EggTextureCollection tc;
+  tc.find_used_textures(_data);
+
+  // Remove the old TextureReference objects.
+  Textures::iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    delete (*ti);
+  }
+  _textures.clear();
+
+  EggTextureCollection::iterator eti;
+  for (eti = tc.begin(); eti != tc.end(); ++eti) {
+    EggTexture *egg_tex = (*eti);
+
+    TextureReference *ref = new TextureReference;
+    ref->from_egg(_data, egg_tex);
+
+    _textures.push_back(ref);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::get_textures
+//       Access: Public
+//  Description: Fills up the indicated set with the set of textures
+//               referenced by this egg file.  It is the user's
+//               responsibility to ensure the set is empty before
+//               making this call; otherwise, the new textures will be
+//               appended to the existing set.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+get_textures(set<TextureImage *> &result) const {
+  Textures::const_iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    result.insert((*ti)->get_texture());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::post_txa_file
+//       Access: Public
+//  Description: Once the egg file has been matched against all of the
+//               matching lines the .txa file, do whatever adjustment
+//               is necessary.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+post_txa_file() {
+  if (_assigned_groups.empty()) {
+    // If the egg file has been assigned to no groups, we have to
+    // assign it to something.
+    _assigned_groups.insert(pal->get_default_group());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::get_groups
+//       Access: Public
+//  Description: Returns the set of PaletteGroups that the egg file
+//               has been explicitly assigned to in the .txa file.
+////////////////////////////////////////////////////////////////////
+const PaletteGroups &EggFile::
+get_groups() const {
+  return _assigned_groups;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::build_cross_links
+//       Access: Public
+//  Description: Calls TextureImage::note_egg_file() for each texture
+//               the egg file references, and
+//               PaletteGroup::increment_egg_count() for each palette
+//               group it wants.  This sets up some of the back
+//               references to support determining an ideal texture
+//               assignment.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+build_cross_links() {
+  Textures::const_iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    (*ti)->get_texture()->note_egg_file(this);
+  }
+
+  _assigned_groups.make_complete(_assigned_groups);
+
+  PaletteGroups::const_iterator gi;
+  for (gi = _assigned_groups.begin();
+       gi != _assigned_groups.end();
+       ++gi) {
+    (*gi)->increment_egg_count();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::choose_placements
+//       Access: Public
+//  Description: Once all the textures have been assigned to groups
+//               (but before they may actually be placed), chooses a
+//               suitable TexturePlacement for each texture that
+//               appears in the egg file.  This will be necessary to
+//               do at some point before writing out the egg file
+//               anyway, and doing it before the textures are placed
+//               allows us to decide what the necessary UV range is
+//               for each to-be-placed texture.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+choose_placements() {
+  Textures::const_iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    TextureReference *reference = (*ti);
+    TextureImage *texture = reference->get_texture();
+
+    if (reference->get_placement() != (TexturePlacement *)NULL &&
+	texture->get_groups().count(reference->get_placement()->get_group()) != 0) {
+      // The egg file is already using a TexturePlacement that is
+      // suitable.  Don't bother changing it.
+
+    } else {
+      // We need to select a new TexturePlacement.
+      PaletteGroups groups;
+      groups.make_intersection(get_groups(), texture->get_groups());
+      
+      // Now groups is the set of groups that the egg file requires,
+      // which also happen to include the texture.  It better not be
+      // empty.
+      nassertv(!groups.empty());
+      
+      // It doesn't really matter which group in the set we choose, so
+      // we arbitrarily choose the first one.
+      PaletteGroup *group = (*groups.begin());
+
+      // Now get the TexturePlacement object that corresponds to the
+      // placement of this texture into this group.
+      TexturePlacement *placement = texture->get_placement(group);
+      nassertv(placement != (TexturePlacement *)NULL);
+      
+      reference->set_placement(placement);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::update_egg
+//       Access: Public
+//  Description: Once all textures have been placed appropriately,
+//               updates the egg file with all the information to
+//               reference the new textures.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+update_egg() {
+  Textures::iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    TextureReference *reference = (*ti);
+    reference->update_egg();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::write_texture_refs
+//       Access: Public
+//  Description: Writes the list of texture references to the
+//               indicated output stream, one per line.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+write_texture_refs(ostream &out, int indent_level) const {
+  Textures::const_iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    TextureReference *reference = (*ti);
+    reference->write(out, indent_level);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_EggFile);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void EggFile::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  datagram.add_string(get_name());
+
+  // We don't write out _data; that needs to be reread each session.
+
+  datagram.add_string(_source_filename);
+  datagram.add_string(_dest_filename);
+
+  datagram.add_uint32(_textures.size());
+  Textures::iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    writer->write_pointer(datagram, (*ti));
+  }
+
+  _assigned_groups.write_datagram(writer, datagram);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int EggFile::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr((int)plist.size() >= _num_textures, 0);
+  int index = 0;
+
+  int i;
+  _textures.reserve(_num_textures);
+  for (i = 0; i < _num_textures; i++) {
+    TextureReference *texture;
+    DCAST_INTO_R(texture, plist[index], index);
+    _textures.push_back(texture);
+    index++;
+  }
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::make_EggFile
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* EggFile::
+make_EggFile(const FactoryParams &params) {
+  EggFile *me = new EggFile;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggFile::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void EggFile::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  set_name(scan.get_string());
+  _source_filename = scan.get_string();
+  _dest_filename = scan.get_string();
+
+  _num_textures = scan.get_uint32();
+  manager->read_pointers(scan, this, _num_textures);
+
+  _assigned_groups.fillin(scan, manager);
+}

+ 103 - 0
pandatool/src/egg-palettize/eggFile.h

@@ -0,0 +1,103 @@
+// Filename: eggFile.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef EGGFILE_H
+#define EGGFILE_H
+
+#include <pandatoolbase.h>
+
+#include "paletteGroups.h"
+#include "textureReference.h"
+
+#include <filename.h>
+#include <namable.h>
+#include <typedWriteable.h>
+
+#include <set>
+
+class SourceTextureImage;
+class EggData;
+class TextureImage;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : EggFile
+// Description : This represents a single egg file known to the
+//               palettizer.  It may reference a number of textures,
+//               and may also be assigned to a number of groups.  All
+//               of its textures will try to assign themselves to one
+//               of its groups.
+////////////////////////////////////////////////////////////////////
+class EggFile : public TypedWriteable, public Namable {
+public:
+  EggFile();
+
+  void from_command_line(EggData *data,
+			 const Filename &source_filename, 
+			 const Filename &dest_filename);
+
+  void scan_textures();
+  void get_textures(set<TextureImage *> &result) const;
+
+  void post_txa_file();
+
+  const PaletteGroups &get_groups() const;
+
+  void build_cross_links();
+  void choose_placements();
+
+  void update_egg();
+
+  void write_texture_refs(ostream &out, int indent_level = 0) const;
+
+private:
+  EggData *_data;
+  Filename _source_filename;
+  Filename _dest_filename;
+
+  typedef vector<TextureReference *> Textures;
+  Textures _textures;
+
+  PaletteGroups _assigned_groups;
+
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_EggFile(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  // This value is only filled in while reading from the bam file;
+  // don't use it otherwise.
+  int _num_textures;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    Namable::init_type();
+    register_type(_type_handle, "EggFile",
+		  TypedWriteable::get_class_type(),
+		  Namable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class TxaLine;
+};
+
+#endif
+

+ 357 - 0
pandatool/src/egg-palettize/imageFile.cxx

@@ -0,0 +1,357 @@
+// Filename: imageFile.cxx
+// Created by:  drose (29Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "imageFile.h"
+
+#include <pnmImage.h>
+#include <pnmFileType.h>
+#include <eggTexture.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+TypeHandle ImageFile::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+ImageFile::
+ImageFile() {
+  _size_known = false;
+  _x_size = 0;
+  _y_size = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::is_size_known
+//       Access: Public
+//  Description: Returns true if the size of the image file is known,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool ImageFile::
+is_size_known() const { 
+  return _size_known;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::get_x_size
+//       Access: Public
+//  Description: Returns the size of the image file in pixels in the X
+//               direction.  It is an error to call this unless
+//               is_size_known() returns true.
+////////////////////////////////////////////////////////////////////
+int ImageFile::
+get_x_size() const { 
+  nassertr(is_size_known(), 0);
+  return _x_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::get_y_size
+//       Access: Public
+//  Description: Returns the size of the image file in pixels in the Y
+//               direction.  It is an error to call this unless
+//               is_size_known() returns true.
+////////////////////////////////////////////////////////////////////
+int ImageFile::
+get_y_size() const { 
+  nassertr(is_size_known(), 0);
+  return _y_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::has_num_channels
+//       Access: Public
+//  Description: Returns true if the number of channels in the image
+//               is known, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool ImageFile::
+has_num_channels() const { 
+  return _properties.has_num_channels();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::get_num_channels
+//       Access: Public
+//  Description: Returns the number of channels of the image.  It is
+//               an error to call this unless has_num_channels()
+//               returns true.
+////////////////////////////////////////////////////////////////////
+int ImageFile::
+get_num_channels() const { 
+  return _properties.get_num_channels();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::get_properties
+//       Access: Public
+//  Description: Returns the grouping properties of the image.
+////////////////////////////////////////////////////////////////////
+const TextureProperties &ImageFile::
+get_properties() const {
+  return _properties;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::update_properties
+//       Access: Public
+//  Description: If the indicate TextureProperties structure is more
+//               specific than this one, updates this one.
+////////////////////////////////////////////////////////////////////
+void ImageFile::
+update_properties(const TextureProperties &properties) {
+  _properties.update_properties(properties);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::set_filename
+//       Access: Public
+//  Description: Sets the filename, and if applicable, the
+//               alpha_filename, from the indicated basename.  The
+//               extension appropriate to the image file type
+//               specified in _color_type (and _alpha_type) is
+//               automatically applied.
+////////////////////////////////////////////////////////////////////
+void ImageFile::
+set_filename(const string &basename) {
+  _filename = basename;
+
+  if (_properties._color_type != (PNMFileType *)NULL) {
+    _filename.set_extension
+      (_properties._color_type->get_suggested_extension());
+  }
+
+  if (_properties._alpha_type != (PNMFileType *)NULL) {
+    _alpha_filename = _filename.get_fullpath_wo_extension() + "_alpha";
+    _alpha_filename.set_extension
+      (_properties._alpha_type->get_suggested_extension());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::get_filename
+//       Access: Public
+//  Description: Returns the primary filename of the image file.
+////////////////////////////////////////////////////////////////////
+const Filename &ImageFile::
+get_filename() const {
+  return _filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::get_alpha_filename
+//       Access: Public
+//  Description: Returns the alpha filename of the image file.  This
+//               is the name of the file that contains the alpha
+//               channel, if it is stored in a separate file, or the
+//               empty string if it is not.
+////////////////////////////////////////////////////////////////////
+const Filename &ImageFile::
+get_alpha_filename() const {
+  return _alpha_filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::exists
+//       Access: Public
+//  Description: Returns true if the file or files named by the image
+//               file exist, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool ImageFile::
+exists() const {
+  if (!_filename.exists()) {
+    return false;
+  }
+  if (_properties._alpha_type != (PNMFileType *)NULL && 
+      _properties.uses_alpha()) {
+    if (_alpha_filename.exists()) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::read
+//       Access: Public
+//  Description: Reads in the image (or images, if the alpha_filename
+//               is separate) and stores it in the indicated PNMImage.
+//               Returns true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool ImageFile::
+read(PNMImage &image) const {
+  nassertr(!_filename.empty(), false);
+
+  image.set_type(_properties._color_type);
+  nout << "Reading " << _filename << "\n";
+  if (!image.read(_filename)) {
+    nout << "Unable to read.\n";
+    return false;
+  }
+
+  if (!_alpha_filename.empty()) {
+    // Read in a separate color image and an alpha channel image.
+    PNMImage alpha_image;
+    alpha_image.set_type(_properties._alpha_type);
+    nout << "Reading " << _alpha_filename << "\n";
+    if (!alpha_image.read(_alpha_filename)) {
+      nout << "Unable to read.\n";
+      return false;
+    }
+    if (image.get_x_size() != alpha_image.get_x_size() ||
+	image.get_y_size() != alpha_image.get_y_size()) {
+      return false;
+    }
+
+    image.add_alpha();
+    for (int y = 0; y < image.get_y_size(); y++) {
+      for (int x = 0; x < image.get_x_size(); x++) {
+	image.set_alpha(x, y, alpha_image.get_gray(x, y));
+      }
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::write
+//       Access: Public
+//  Description: Writes out the image in the indicated PNMImage to the
+//               _filename and/or _alpha_filename.  Returns true on
+//               success, false on failure.  Warning: this may change
+//               the PNMImage.  In particular, it may remove the alpha
+//               channel.
+////////////////////////////////////////////////////////////////////
+bool ImageFile::
+write(PNMImage &image) const {
+  nassertr(!_filename.empty(), false);
+
+  if (!image.has_alpha() || 
+      _properties._alpha_type == (PNMFileType *)NULL) {
+    if (!_alpha_filename.empty() && _alpha_filename.exists()) {
+      _alpha_filename.unlink();
+    }
+    nout << "Writing " << _filename << "\n";
+    if (!image.write(_filename, _properties._color_type)) {
+      nout << "Unable to write.\n";
+      return false;
+    }
+    return true;
+  }
+
+  // Write out a separate color image and an alpha channel image.
+  PNMImage alpha_image(image.get_x_size(), image.get_y_size(), 1,
+		       image.get_maxval());
+  for (int y = 0; y < image.get_y_size(); y++) {
+    for (int x = 0; x < image.get_x_size(); x++) {
+      alpha_image.set_gray_val(x, y, image.get_alpha_val(x, y));
+    }
+  }
+
+  image.remove_alpha();
+  nout << "Writing " << _filename << "\n";
+  if (!image.write(_filename, _properties._color_type)) {
+    nout << "Unable to write.\n";
+    return false;
+  }
+  nout << "Writing " << _alpha_filename << "\n";
+  if (!alpha_image.write(_alpha_filename, _properties._alpha_type)) {
+    nout << "Unable to write.\n";
+    return false;
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::unlink
+//       Access: Public
+//  Description: Deletes the image file or files.
+////////////////////////////////////////////////////////////////////
+void ImageFile::
+unlink() {
+  if (!_filename.empty() && _filename.exists()) {
+    nout << "Deleting " << _filename << "\n";
+    _filename.unlink();
+  }
+  if (!_alpha_filename.empty() && _alpha_filename.exists()) {
+    nout << "Deleting " << _alpha_filename << "\n";
+    _alpha_filename.unlink();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::update_egg_tex
+//       Access: Public
+//  Description: Sets the indicated EggTexture to refer to this file.
+////////////////////////////////////////////////////////////////////
+void ImageFile::
+update_egg_tex(EggTexture *egg_tex) const {
+  nassertv(egg_tex != (EggTexture *)NULL);
+
+  egg_tex->set_filename(_filename);
+
+  if (_properties._alpha_type != (PNMFileType *)NULL &&
+      !_alpha_filename.empty()) {
+    egg_tex->set_alpha_file(_alpha_filename);
+  } else {
+    egg_tex->clear_alpha_file();
+  }
+
+  _properties.update_egg_tex(egg_tex);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::output_filename
+//       Access: Public
+//  Description: Writes the filename (or pair of filenames) to the
+//               indicated output stream.
+////////////////////////////////////////////////////////////////////
+void ImageFile::
+output_filename(ostream &out) const {
+  out << _filename;
+  if (_properties._alpha_type != (PNMFileType *)NULL &&
+      !_alpha_filename.empty()) {
+    out << " " << _alpha_filename;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void ImageFile::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  _properties.write_datagram(writer, datagram);
+  datagram.add_string(_filename);
+  datagram.add_string(_alpha_filename);
+  datagram.add_bool(_size_known);
+  datagram.add_int32(_x_size);
+  datagram.add_int32(_y_size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageFile::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void ImageFile::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _properties.fillin(scan, manager);
+  _filename = scan.get_string();
+  _alpha_filename = scan.get_string();
+  _size_known = scan.get_bool();
+  _x_size = scan.get_int32();
+  _y_size = scan.get_int32();
+}

+ 86 - 0
pandatool/src/egg-palettize/imageFile.h

@@ -0,0 +1,86 @@
+// Filename: imageFile.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef IMAGEFILE_H
+#define IMAGEFILE_H
+
+#include <pandatoolbase.h>
+
+#include "textureProperties.h"
+
+#include <filename.h>
+#include <typedWriteable.h>
+
+class PNMImage;
+class EggTexture;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : ImageFile
+// Description : This is the base class of both TextureImage and
+//               PaletteImage.  It encapsulates all the information
+//               specific to an image file that can be assigned as a
+//               texture image to egg geometry.
+////////////////////////////////////////////////////////////////////
+class ImageFile : public TypedWriteable {
+public:
+  ImageFile();
+
+  bool is_size_known() const;
+  int get_x_size() const;
+  int get_y_size() const;
+  bool has_num_channels() const;
+  int get_num_channels() const;
+
+  const TextureProperties &get_properties() const;
+  void update_properties(const TextureProperties &properties);
+
+  void set_filename(const string &basename);
+  const Filename &get_filename() const;
+  const Filename &get_alpha_filename() const;
+  bool exists() const;
+
+  bool read(PNMImage &image) const;
+  bool write(PNMImage &image) const;
+  void unlink();
+
+  void update_egg_tex(EggTexture *egg_tex) const;
+
+  void output_filename(ostream &out) const;
+
+protected:
+  TextureProperties _properties;
+  Filename _filename;
+  Filename _alpha_filename;
+
+  bool _size_known;
+  int _x_size, _y_size;
+
+  // The TypedWriteable interface follows.
+public:
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+
+protected:
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    register_type(_type_handle, "ImageFile",
+		  TypedWriteable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+
+};
+
+#endif
+

+ 34 - 0
pandatool/src/egg-palettize/omitReason.cxx

@@ -0,0 +1,34 @@
+// Filename: omitReason.cxx
+// Created by:  drose (02Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "omitReason.h"
+
+ostream &
+operator << (ostream &out, OmitReason omit) {
+  switch (omit) {
+  case OR_none:
+    return out << "none";
+
+  case OR_working:
+    return out << "working";
+
+  case OR_omitted:
+    return out << "omitted";
+   
+  case OR_size:
+    return out << "size";
+
+  case OR_solitary:
+    return out << "solitary";
+
+  case OR_repeats:
+    return out << "repeats";
+
+  case OR_unknown:
+    return out << "unknown";
+  }
+
+  return out << "**invalid**(" << (int)omit << ")";
+}

+ 44 - 0
pandatool/src/egg-palettize/omitReason.h

@@ -0,0 +1,44 @@
+// Filename: omitReason.h
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef OMITREASON_H
+#define OMITREASON_H
+
+#include <pandatoolbase.h>
+
+////////////////////////////////////////////////////////////////////
+// 	  Enum : OmitReason
+// Description : This enumerates the reasons why a texture may not
+//               have been placed in a palette image.
+////////////////////////////////////////////////////////////////////
+enum OmitReason {
+  OR_none,
+  // Not omitted: the texture appears on a palette image.
+
+  OR_working,
+  // Still working on placing it.
+
+  OR_omitted,
+  // Explicitly omitted by the user via "omit" in .txa file.
+
+  OR_size,
+  // Too big to fit on a single palette image.
+
+  OR_solitary,
+  // It should be placed, but it's the only one on the palette image
+  // so far, so there's no point.
+
+  OR_repeats,
+  // The texture repeats.  Specifically, the UV's for the texture
+  // exceed the maximum rectangle allowed by repeat_threshold.
+
+  OR_unknown,
+  // The texture file cannot be read, so its size can't be determined.
+};
+
+ostream &operator << (ostream &out, OmitReason omit);
+
+#endif
+

+ 319 - 0
pandatool/src/egg-palettize/paletteGroups.cxx

@@ -0,0 +1,319 @@
+// Filename: paletteGroups.cxx
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "paletteGroups.h"
+#include "paletteGroup.h"
+
+#include <indent.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+TypeHandle PaletteGroups::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+PaletteGroups::
+PaletteGroups() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::insert
+//       Access: Public
+//  Description: Inserts a new group to the set, if it is not already
+//               there.
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+insert(PaletteGroup *group) {
+  _groups.insert(group);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::count
+//       Access: Public
+//  Description: Returns the number of times the given group appears
+//               in the set.  This is either 1 if it appears at all,
+//               or 0 if it does not appear.
+////////////////////////////////////////////////////////////////////
+PaletteGroups::size_type PaletteGroups::
+count(PaletteGroup *group) const {
+  return _groups.count(group);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::make_complete
+//       Access: Public
+//  Description: Completes the set with the transitive closure of all
+//               dependencies: for each PaletteGroup already in the
+//               set a, all of the groups that it depends on are added
+//               to the set, and so on.  The indicated set a may be
+//               the same as this set.
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+make_complete(const PaletteGroups &a) {
+  Groups result;
+
+  Groups::const_iterator gi;
+  for (gi = a._groups.begin(); gi != a._groups.end(); ++gi) {
+    r_make_complete(result, *gi);
+  }
+
+  _groups.swap(result);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::make_union
+//       Access: Public
+//  Description: Computes the union of PaletteGroups a and b, and
+//               stores the result in this object.  The result may be
+//               the same object as either a or b.
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+make_union(const PaletteGroups &a, const PaletteGroups &b) {
+  Groups u;
+  
+  Groups::const_iterator ai, bi;
+  ai = a._groups.begin();
+  bi = b._groups.begin();
+
+  while (ai != a._groups.end() && bi != b._groups.end()) {
+    if ((*ai) < (*bi)) {
+      u.insert(u.end(), *ai);
+      ++ai;
+
+    } else if ((*bi) < (*ai)) {
+      u.insert(u.end(), *bi);
+      ++bi;
+
+    } else { // (*ai) == (*bi)
+      u.insert(u.end(), *ai);
+      ++ai;
+      ++bi;
+    }
+  }
+
+  while (ai != a._groups.end()) {
+    u.insert(u.end(), *ai);
+    ++ai;
+  }
+
+  while (bi != b._groups.end()) {
+    u.insert(u.end(), *bi);
+    ++bi;
+  }
+
+  _groups.swap(u);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::make_intersection
+//       Access: Public
+//  Description: Computes the intersection of PaletteGroups a and b,
+//               and stores the result in this object.  The result may
+//               be the same object as either a or b.
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+make_intersection(const PaletteGroups &a, const PaletteGroups &b) {
+  Groups i;
+  
+  Groups::const_iterator ai, bi;
+  ai = a._groups.begin();
+  bi = b._groups.begin();
+
+  while (ai != a._groups.end() && bi != b._groups.end()) {
+    if ((*ai) < (*bi)) {
+      ++ai;
+
+    } else if ((*bi) < (*ai)) {
+      ++bi;
+
+    } else { // (*ai) == (*bi)
+      i.insert(i.end(), *ai);
+      ++ai;
+      ++bi;
+    }
+  }
+
+  _groups.swap(i);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::empty
+//       Access: Public
+//  Description: Returns true if the set is empty, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PaletteGroups::
+empty() const {
+  return _groups.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::size
+//       Access: Public
+//  Description: Returns the number of elements in the set.
+////////////////////////////////////////////////////////////////////
+PaletteGroups::size_type PaletteGroups::
+size() const {
+  return _groups.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::begin
+//       Access: Public
+//  Description: Returns an iterator suitable for traversing the set.
+////////////////////////////////////////////////////////////////////
+PaletteGroups::iterator PaletteGroups::
+begin() const {
+  return _groups.begin();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::end
+//       Access: Public
+//  Description: Returns an iterator suitable for traversing the set.
+////////////////////////////////////////////////////////////////////
+PaletteGroups::iterator PaletteGroups::
+end() const {
+  return _groups.end();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::output
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+output(ostream &out) const {
+  if (!_groups.empty()) {
+    Groups::const_iterator gi = _groups.begin();
+    out << (*gi)->get_name();
+    ++gi;
+    while (gi != _groups.end()) {
+      out << " " << (*gi)->get_name();
+      ++gi;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::write
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+write(ostream &out, int indent_level) const {
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    indent(out, indent_level) << (*gi)->get_name() << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::r_make_complete
+//       Access: Private
+//  Description: The recursive implementation of make_complete(), this
+//               adds the indicated group and all of its dependencies
+//               to the set.
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+r_make_complete(PaletteGroups::Groups &result, PaletteGroup *group) {
+  bool inserted = result.insert(group).second;
+
+  if (inserted) {
+    Groups::const_iterator gi;
+    for (gi = group->_dependent._groups.begin();
+	 gi != group->_dependent._groups.end();
+	 ++gi) {
+      r_make_complete(result, *gi);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_PaletteGroups);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  datagram.add_uint32(_groups.size());
+
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    writer->write_pointer(datagram, *gi);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int PaletteGroups::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr(_num_groups == (int)plist.size(), 0);
+  for (int i = 0; i < _num_groups; i++) {
+    PaletteGroup *group;
+    DCAST_INTO_R(group, plist[i], i);
+    _groups.insert(group);
+  }
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::make_PaletteGroups
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* PaletteGroups::
+make_PaletteGroups(const FactoryParams &params) {
+  PaletteGroups *me = new PaletteGroups;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroups::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void PaletteGroups::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _num_groups = scan.get_int32();
+  manager->read_pointers(scan, this, _num_groups);
+}

+ 101 - 0
pandatool/src/egg-palettize/paletteGroups.h

@@ -0,0 +1,101 @@
+// Filename: paletteGroups.h
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PALETTEGROUPS_H
+#define PALETTEGROUPS_H
+
+#include <pandatoolbase.h>
+
+#include <typedWriteable.h>
+
+#include <set>
+
+class PaletteGroup;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : PaletteGroups
+// Description : A set of PaletteGroups.  This presents an interface
+//               very like an STL set, with a few additional
+//               functions.
+////////////////////////////////////////////////////////////////////
+class PaletteGroups : public TypedWriteable {
+private:
+  typedef set<PaletteGroup *> Groups;
+
+public:
+  typedef Groups::const_pointer pointer;
+  typedef Groups::const_pointer const_pointer;
+  typedef Groups::const_reference reference;
+  typedef Groups::const_reference const_reference;
+  typedef Groups::const_iterator iterator;
+  typedef Groups::const_iterator const_iterator;
+  typedef Groups::const_reverse_iterator reverse_iterator;
+  typedef Groups::const_reverse_iterator const_reverse_iterator;
+  typedef Groups::size_type size_type;
+  typedef Groups::difference_type difference_type;
+
+  PaletteGroups();
+
+  void insert(PaletteGroup *group);
+  size_type count(PaletteGroup *group) const;
+  void make_complete(const PaletteGroups &a);
+  void make_union(const PaletteGroups &a, const PaletteGroups &b);
+  void make_intersection(const PaletteGroups &a, const PaletteGroups &b);
+
+  bool empty() const;
+  size_type size() const;
+  iterator begin() const;
+  iterator end() const;
+
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level = 0) const;
+
+private:
+  void r_make_complete(Groups &result, PaletteGroup *group);
+
+  Groups _groups;
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_PaletteGroups(const FactoryParams &params);
+
+public:
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  // This value is only filled in while reading from the bam file;
+  // don't use it otherwise.
+  int _num_groups;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    register_type(_type_handle, "PaletteGroups",
+		  TypedWriteable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const PaletteGroups &groups) {
+  groups.output(out);
+  return out;
+}
+
+#endif
+

+ 549 - 0
pandatool/src/egg-palettize/paletteImage.cxx

@@ -0,0 +1,549 @@
+// Filename: paletteImage.cxx
+// Created by:  drose (01Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "paletteImage.h"
+#include "palettePage.h"
+#include "paletteGroup.h"
+#include "texturePlacement.h"
+#include "palettizer.h"
+#include "textureImage.h"
+
+#include <indent.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+#include <algorithm>
+
+TypeHandle PaletteImage::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::ClearedRegion::Default Constructor
+//       Access: Public
+//  Description: The default constructor is only for the convenience
+//               of the bam reader.
+////////////////////////////////////////////////////////////////////
+PaletteImage::ClearedRegion::
+ClearedRegion() {
+  _x = 0;
+  _y = 0;
+  _x_size = 0;
+  _y_size = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::ClearedRegion::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PaletteImage::ClearedRegion::
+ClearedRegion(TexturePlacement *placement) {
+  _x = placement->get_placed_x();
+  _y = placement->get_placed_y();
+  _x_size = placement->get_placed_x_size();
+  _y_size = placement->get_placed_y_size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::ClearedRegion::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PaletteImage::ClearedRegion::
+ClearedRegion(const PaletteImage::ClearedRegion &copy) :
+  _x(copy._x),
+  _y(copy._y),
+  _x_size(copy._x_size),
+  _y_size(copy._y_size)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::ClearedRegion::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PaletteImage::ClearedRegion::
+operator = (const PaletteImage::ClearedRegion &copy) {
+  _x = copy._x;
+  _y = copy._y;
+  _x_size = copy._x_size;
+  _y_size = copy._y_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::ClearedRegion::clear
+//       Access: Public
+//  Description: Sets the appropriate region of the image to black.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::ClearedRegion::
+clear(PNMImage &image) {
+  for (int y = _y; y < _y + _y_size; y++) {
+    for (int x = _x; x < _x + _x_size; x++) {
+      image.set_xel_val(x, y, 0);
+    }
+  }
+  if (image.has_alpha()) {
+    for (int y = _y; y < _y + _y_size; y++) {
+      for (int x = _x; x < _x + _x_size; x++) {
+	image.set_alpha_val(x, y, 0);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::ClearedRegion::write_datagram
+//       Access: Public
+//  Description: Writes the contents of the ClearedRegion to the
+//               indicated datagram.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::ClearedRegion::
+write_datagram(Datagram &datagram) const {
+  datagram.add_int32(_x);
+  datagram.add_int32(_y);
+  datagram.add_int32(_x_size);
+  datagram.add_int32(_y_size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::ClearedRegion::write_datagram
+//       Access: Public
+//  Description: Extracts the contents of the ClearedRegion from the
+//               indicated datagram.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::ClearedRegion::
+fillin(DatagramIterator &scan) {
+  _x = scan.get_int32();
+  _y = scan.get_int32();
+  _x_size = scan.get_int32();
+  _y_size = scan.get_int32();
+}
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::Default Constructor
+//       Access: Private
+//  Description: The default constructor is only for the convenience
+//               of the Bam reader.
+////////////////////////////////////////////////////////////////////
+PaletteImage::
+PaletteImage() {
+  _page = (PalettePage *)NULL;
+  _index = 0;
+  _new_image = false;
+  _got_image = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+PaletteImage::
+PaletteImage(PalettePage *page, int index) :
+  _page(page),
+  _index(index)
+{
+  _properties = page->get_properties();
+  _size_known = true;
+  _x_size = pal->_pal_x_size;
+  _y_size = pal->_pal_y_size;
+  _new_image = true;
+  _got_image = false;
+
+  ostringstream name;
+  name << page->get_group()->get_name() << "_palette_" 
+       << page->get_name() << "_" << index + 1;
+
+  set_filename(name.str());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::get_page
+//       Access: Public
+//  Description: Returns the particular PalettePage this image is
+//               associated with.
+////////////////////////////////////////////////////////////////////
+PalettePage *PaletteImage::
+get_page() const {
+  return _page;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::is_empty
+//       Access: Public
+//  Description: Returns true if there are no textures, or only one
+//               texture, placed on the image.  In either case, the
+//               PaletteImage need not be generated.
+////////////////////////////////////////////////////////////////////
+bool PaletteImage::
+is_empty() const {
+  return _placements.size() < 2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::place
+//       Access: Public
+//  Description: Attempts to place the indicated texture on the image.
+//               Returns true if successful, or false if there was no
+//               available space.
+////////////////////////////////////////////////////////////////////
+bool PaletteImage::
+place(TexturePlacement *placement) {
+  int x, y;
+  if (find_hole(x, y, placement->get_x_size(), placement->get_y_size())) {
+    placement->place_at(this, x, y);
+    _placements.push_back(placement);
+    return true;
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::unplace
+//       Access: Public
+//  Description: Removes the texture from the image.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+unplace(TexturePlacement *placement) {
+  nassertv(placement->is_placed() && placement->get_image() == this);
+  
+  Placements::iterator pi;
+  pi = find(_placements.begin(), _placements.end(), placement);
+  if (pi != _placements.end()) {
+    _placements.erase(pi);
+  }
+  
+  _cleared_regions.push_back(ClearedRegion(placement));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::check_solitary
+//       Access: Public
+//  Description: To be called after all textures have been placed on
+//               the image, this checks to see if there is only one
+//               texture on the image.  If there is, it is flagged as
+//               'solitary' so that the egg files will not needlessly
+//               reference the palettized image.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+check_solitary() {
+  if (_placements.size() == 1) {
+    // How sad, only one.
+    TexturePlacement *placement = *_placements.begin();
+    nassertv(placement->get_omit_reason() == OR_none ||
+	     placement->get_omit_reason() == OR_solitary);
+    placement->omit_solitary();
+
+  } else {
+    Placements::const_iterator pi;
+    for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
+      TexturePlacement *placement = (*pi);
+      nassertv(placement->get_omit_reason() == OR_none ||
+	       placement->get_omit_reason() == OR_solitary);
+      placement->not_solitary();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::write_placements
+//       Access: Public
+//  Description: Writes a list of the textures that have been placed
+//               on this image to the indicated output stream, one per
+//               line.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+write_placements(ostream &out, int indent_level) const {
+  Placements::const_iterator pi;
+  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
+    TexturePlacement *placement = (*pi);
+    placement->write_placed(out, indent_level);
+  }
+}
+ 
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::update_image
+//       Access: Public
+//  Description: If the palette has changed since it was last written
+//               out, updates the image and writes out a new one.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+update_image() {
+  if (is_empty() && pal->_aggressively_clean_mapdir) {
+    // If the palette image is 'empty', ensure that it doesn't exist.
+    // No need to clutter up the map directory.
+    unlink();
+    _new_image = true;
+    return;
+  }
+
+  // Do we need to update?
+  bool needs_update =
+    _new_image || !exists() ||
+    !_cleared_regions.empty();
+
+  Placements::iterator pi;
+  for (pi = _placements.begin(); 
+       pi != _placements.end() && !needs_update; 
+       ++pi) {
+    TexturePlacement *placement = (*pi);
+    needs_update = !placement->is_filled();
+  }
+
+  if (!needs_update) {
+    // No sweat; nothing has changed.
+    return;
+  }
+
+  get_image();
+
+  // Set to black any parts of the image that we recently unplaced.
+  ClearedRegions::iterator ci;
+  for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) {
+    ClearedRegion &region = (*ci);
+    region.clear(_image);
+  }
+  _cleared_regions.clear();
+
+  // Now add the recent additions to the image.
+  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
+    TexturePlacement *placement = (*pi);
+    if (!placement->is_filled()) {
+      placement->fill_image(_image);
+    }
+  }
+
+  write(_image);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::find_hole
+//       Access: Private
+//  Description: Searches for a hole of at least x_size by y_size
+//               pixels somewhere within the PaletteImage.  If a
+//               suitable hole is found, sets x and y to the top left
+//               corner and returns true; otherwise, returns false.
+////////////////////////////////////////////////////////////////////
+bool PaletteImage::
+find_hole(int &x, int &y, int x_size, int y_size) const {
+  y = 0;
+  while (y + y_size <= _y_size) {
+    int next_y = _y_size;
+    // Scan along the row at 'y'.
+    x = 0;
+    while (x + x_size <= _x_size) {
+      int next_x = x;
+
+      // Consider the spot at x, y.
+      TexturePlacement *overlap = find_overlap(x, y, x_size, y_size);
+
+      if (overlap == (TexturePlacement *)NULL) {
+	// Hooray!
+	return true;
+      }
+
+      next_x = overlap->get_placed_x() + overlap->get_placed_x_size();
+      next_y = min(next_y, overlap->get_placed_y() + overlap->get_placed_y_size());
+      nassertr(next_x > x, false);
+      x = next_x;
+    }
+
+    nassertr(next_y > y, false);
+    y = next_y;
+  }
+
+  // Nope, wouldn't fit anywhere.
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::find_overlap
+//       Access: Private
+//  Description: If the rectangle whose top left corner is x, y and
+//               whose size is x_size, y_size describes an empty hole
+//               that does not overlap any placed images, returns
+//               NULL; otherwise, returns the first placed texture
+//               that the image does overlap.  It is assumed the
+//               rectangle lies completely within the boundaries of
+//               the image itself.
+////////////////////////////////////////////////////////////////////
+TexturePlacement *PaletteImage::
+find_overlap(int x, int y, int x_size, int y_size) const {
+  Placements::const_iterator pi;
+  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
+    TexturePlacement *placement = (*pi);
+    if (placement->is_placed() && 
+	placement->intersects(x, y, x_size, y_size)) {
+      return placement;
+    }
+  }
+
+  return (TexturePlacement *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::get_image
+//       Access: Public
+//  Description: Reads or generates the PNMImage that corresponds to
+//               the palette as it is known so far.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+get_image() {
+  if (_got_image) {
+    return;
+  }
+
+  if (!_new_image) {
+    if (read(_image)) {
+      _got_image = true;
+      return;
+    }
+  }
+
+  nout << "Generating new " << get_filename() << "\n";
+
+  // We won't be using this any more.
+  _cleared_regions.clear();
+
+  _image.clear(get_x_size(), get_y_size(), _properties.get_num_channels());
+  _new_image = false;
+  _got_image = true;
+
+  // Now fill up the image.
+  Placements::iterator pi;
+  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
+    TexturePlacement *placement = (*pi);
+    placement->fill_image(_image);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_PaletteImage);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  ImageFile::write_datagram(writer, datagram);
+
+  datagram.add_uint32(_cleared_regions.size());
+  ClearedRegions::const_iterator ci;
+  for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) {
+    (*ci).write_datagram(datagram);
+  }
+
+  datagram.add_uint32(_placements.size());
+  Placements::const_iterator pi;
+  for (pi = _placements.begin(); pi != _placements.end(); ++pi) {
+    writer->write_pointer(datagram, (*pi));
+  }
+
+  writer->write_pointer(datagram, _page);
+  datagram.add_uint32(_index);
+
+  // We don't write _new_image, _got_image, or _image.  These are all
+  // loaded per-session.
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int PaletteImage::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr((int)plist.size() >= _num_placements + 1, 0);
+  int index = 0;
+
+  int i;
+  _placements.reserve(_num_placements);
+  for (i = 0; i < _num_placements; i++) {
+    TexturePlacement *placement;
+    DCAST_INTO_R(placement, plist[index], index);
+    _placements.push_back(placement);
+    index++;
+  }
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_page, plist[index], index);
+  }
+  index++;
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::make_PaletteImage
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* PaletteImage::
+make_PaletteImage(const FactoryParams &params) {
+  PaletteImage *me = new PaletteImage;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  ImageFile::fillin(scan, manager);
+
+  int num_cleared_regions = scan.get_uint32();
+  _cleared_regions.reserve(num_cleared_regions);
+  for (int i = 0; i < num_cleared_regions; i++) {
+    _cleared_regions.push_back(ClearedRegion());
+    _cleared_regions.back().fillin(scan);
+  }
+
+  _num_placements = scan.get_uint32();
+  manager->read_pointers(scan, this, _num_placements);
+
+  manager->read_pointer(scan, this);  // _page
+
+  _index = scan.get_uint32();
+}

+ 115 - 0
pandatool/src/egg-palettize/paletteImage.h

@@ -0,0 +1,115 @@
+// Filename: paletteImage.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PALETTEIMAGE_H
+#define PALETTEIMAGE_H
+
+#include <pandatoolbase.h>
+
+#include "imageFile.h"
+
+#include <pnmImage.h>
+
+class PalettePage;
+class TexturePlacement;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : PaletteImage
+// Description : This is a single palette image, one of several within
+//               a PalettePage, which is in turn one of several pages
+//               within a PaletteGroup.  Each palette image is a
+//               collage of several different textures that were all
+//               assigned to the same PaletteGroup, and all share the
+//               same properties of the PalettePage.
+////////////////////////////////////////////////////////////////////
+class PaletteImage : public ImageFile {
+private:
+  PaletteImage();
+
+public:
+  PaletteImage(PalettePage *page, int index);
+
+  PalettePage *get_page() const;
+
+  bool is_empty() const;
+
+  bool place(TexturePlacement *placement);
+  void unplace(TexturePlacement *placement);
+  void check_solitary();
+
+  void write_placements(ostream &out, int indent_level = 0) const;
+  void update_image();
+
+private:
+  bool find_hole(int &x, int &y, int x_size, int y_size) const;
+  TexturePlacement *find_overlap(int x, int y, int x_size, int y_size) const;
+  void get_image();
+
+  // The ClearedRegion object keeps track of TexturePlacements that
+  // were recently removed and thus need to be set to black.
+  class ClearedRegion {
+  public:
+    ClearedRegion();
+    ClearedRegion(TexturePlacement *placement);
+    ClearedRegion(const ClearedRegion &copy);
+    void operator = (const ClearedRegion &copy);
+    void clear(PNMImage &image);
+
+    void write_datagram(Datagram &datagram) const; 
+    void fillin(DatagramIterator &scan);
+
+  private:
+    int _x, _y;
+    int _x_size, _y_size;
+  };
+
+  typedef vector<ClearedRegion> ClearedRegions;
+  ClearedRegions _cleared_regions;
+
+  typedef vector<TexturePlacement *> Placements;
+  Placements _placements;
+
+  PalettePage *_page;
+  int _index;
+
+  bool _new_image;
+  bool _got_image;
+  PNMImage _image;
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_PaletteImage(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  // This value is only filled in while reading from the bam file;
+  // don't use it otherwise.
+  int _num_placements;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ImageFile::init_type();
+    register_type(_type_handle, "PaletteImage",
+		  ImageFile::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif
+

+ 311 - 0
pandatool/src/egg-palettize/palettePage.cxx

@@ -0,0 +1,311 @@
+// Filename: palettePage.cxx
+// Created by:  drose (01Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "palettePage.h"
+#include "texturePlacement.h"
+#include "textureImage.h"
+#include "paletteImage.h"
+#include "paletteGroup.h"
+
+#include <indent.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+#include <algorithm>
+
+TypeHandle PalettePage::_type_handle;
+
+// This is an STL object to sort an array of TexturePlacement pointers
+// in order from biggest to smallest.
+class SortPlacementBySize {
+public:
+  bool operator ()(TexturePlacement *a, TexturePlacement *b) const {
+    if (a->get_y_size() < b->get_y_size()) {
+      return false;
+
+    } else if (b->get_y_size() < a->get_y_size()) {
+      return true;
+
+    } else if (a->get_x_size() < b->get_x_size()) {
+      return false;
+
+    } else if (b->get_x_size() < a->get_x_size()) {
+      return true;
+    }
+    return false;
+  }
+};
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::Default Constructor
+//       Access: Private
+//  Description: The default constructor is only for the convenience
+//               of the Bam reader.
+////////////////////////////////////////////////////////////////////
+PalettePage::
+PalettePage() {
+  _group = (PaletteGroup *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+PalettePage::
+PalettePage(PaletteGroup *group, const TextureProperties &properties) :
+  Namable(properties.get_string()),
+  _group(group),
+  _properties(properties)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::get_group
+//       Access: Public
+//  Description: Returns the group this particular PalettePage belongs
+//               to.
+////////////////////////////////////////////////////////////////////
+PaletteGroup *PalettePage::
+get_group() const {
+  return _group;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::get_properties
+//       Access: Public
+//  Description: Returns the texture grouping properties that all
+//               textures in this page share.
+////////////////////////////////////////////////////////////////////
+const TextureProperties &PalettePage::
+get_properties() const {
+  return _properties;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::assign
+//       Access: Public
+//  Description: Adds the indicated texture to the list of textures to
+//               consider placing on the page.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+assign(TexturePlacement *placement) {
+  _assigned.push_back(placement);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::place_all
+//       Access: Public
+//  Description: Assigns all the textures to their final home in a
+//               PaletteImage somewhere.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+place_all() {
+  // Sort the textures to be placed in order from biggest to smallest,
+  // as an aid to optimal packing.
+  sort(_assigned.begin(), _assigned.end(), SortPlacementBySize());
+
+  Assigned::const_iterator ai;
+  for (ai = _assigned.begin(); ai != _assigned.end(); ++ai) {
+    place(*ai);
+  }
+
+  _assigned.clear();
+
+  // Now, look for solitary images; these are left placed, but flagged
+  // with OR_solitary, so they won't go into egg references.  There's
+  // no real point in referencing these.
+  Images::iterator ii;
+  for (ii = _images.begin(); ii != _images.end(); ++ii) {
+    PaletteImage *image = (*ii);
+    image->check_solitary();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::place
+//       Access: Public
+//  Description: Assigns the particular TexturePlacement to a
+//               PaletteImage where it fits.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+place(TexturePlacement *placement) {
+  nassertv(placement->get_omit_reason() == OR_working);
+  
+  // First, try to place it in one of our existing PaletteImages.
+  Images::iterator ii;
+  for (ii = _images.begin(); ii != _images.end(); ++ii) {
+    PaletteImage *image = (*ii);
+    if (image->place(placement)) {
+      return;
+    }
+  }
+
+  // No good?  Then we need to create a new PaletteImage for it.
+  PaletteImage *image = new PaletteImage(this, _images.size());
+  _images.push_back(image);
+
+  bool placed = image->place(placement);
+
+  // This should have stuck.
+  nassertv(placed);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::unplace
+//       Access: Public
+//  Description: Removes the TexturePlacement from wherever it has
+//               been placed.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+unplace(TexturePlacement *placement) {
+  nassertv(placement->is_placed() && placement->get_page() == this);
+  placement->get_image()->unplace(placement);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::write_image_info
+//       Access: Public
+//  Description: Writes a list of the PaletteImages associated with
+//               this page, and all of their textures, to the
+//               indicated output stream.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+write_image_info(ostream &out, int indent_level) const {
+  Images::const_iterator ii;
+  for (ii = _images.begin(); ii != _images.end(); ++ii) {
+    PaletteImage *image = (*ii);
+    if (!image->is_empty()) {
+      indent(out, indent_level);
+      image->output_filename(out);
+      out << "\n";
+      image->write_placements(out, indent_level + 2);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::update_images
+//       Access: Public
+//  Description: Regenerates each PaletteImage on this page that needs
+//               it.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+update_images() {
+  Images::iterator ii;
+  for (ii = _images.begin(); ii != _images.end(); ++ii) {
+    PaletteImage *image = (*ii);
+    image->update_image();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_PalettePage);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  datagram.add_string(get_name());
+
+  writer->write_pointer(datagram, _group);
+  _properties.write_datagram(writer, datagram);
+
+  // We don't write out _assigned, since that's rebuilt each session.
+
+  datagram.add_uint32(_images.size());
+  Images::const_iterator ii;
+  for (ii = _images.begin(); ii != _images.end(); ++ii) {
+    writer->write_pointer(datagram, *ii);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int PalettePage::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr((int)plist.size() >= 1 + _num_images, 0);
+  int index = 0;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_group, plist[index], index);
+  }
+  index++;
+
+  int i;
+  _images.reserve(_num_images);
+  for (i = 0; i < _num_images; i++) {
+    PaletteImage *image;
+    DCAST_INTO_R(image, plist[index], index);
+    _images.push_back(image);
+    index++;
+  }
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::make_PalettePage
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* PalettePage::
+make_PalettePage(const FactoryParams &params) {
+  PalettePage *me = new PalettePage;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PalettePage::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void PalettePage::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  set_name(scan.get_string());
+
+  manager->read_pointer(scan, this);  // _group
+  _properties.fillin(scan, manager);
+
+  _num_images = scan.get_uint32();
+  manager->read_pointers(scan, this, _num_images);
+}

+ 92 - 0
pandatool/src/egg-palettize/palettePage.h

@@ -0,0 +1,92 @@
+// Filename: palettePage.h
+// Created by:  drose (01Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PALETTEPAGE_H
+#define PALETTEPAGE_H
+
+#include <pandatoolbase.h>
+
+#include "textureProperties.h"
+
+#include <namable.h>
+#include <typedWriteable.h>
+
+class PaletteGroup;
+class PaletteImage;
+class TexturePlacement;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : PalettePage
+// Description : This is a particular collection of textures, within a
+//               PaletteGroup, that all share the same
+//               TextureProperties.  The textures on the same page may
+//               therefore all be placed on the same set of
+//               PaletteImages together.
+////////////////////////////////////////////////////////////////////
+class PalettePage : public TypedWriteable, public Namable {
+private:
+  PalettePage();
+
+public:
+  PalettePage(PaletteGroup *group, const TextureProperties &properties);
+
+  PaletteGroup *get_group() const;
+  const TextureProperties &get_properties() const;
+
+  void assign(TexturePlacement *placement);
+  void place_all();
+  void place(TexturePlacement *placement);
+  void unplace(TexturePlacement *placement);
+
+  void write_image_info(ostream &out, int indent_level = 0) const;
+  void update_images();
+
+private:
+  PaletteGroup *_group;
+  TextureProperties _properties;
+
+  typedef vector<TexturePlacement *> Assigned;
+  Assigned _assigned;
+
+  typedef vector<PaletteImage *> Images;
+  Images _images;
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_PalettePage(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  // This value is only filled in while reading from the bam file;
+  // don't use it otherwise.
+  int _num_images;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    Namable::init_type();
+    register_type(_type_handle, "PalettePage",
+		  TypedWriteable::get_class_type(),
+		  Namable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif
+

+ 499 - 0
pandatool/src/egg-palettize/palettizer.cxx

@@ -0,0 +1,499 @@
+// Filename: palettizer.cxx
+// Created by:  drose (01Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "palettizer.h"
+#include "eggFile.h"
+#include "textureImage.h"
+#include "string_utils.h"
+#include "paletteGroup.h"
+
+#include <pnmImage.h>
+#include <pnmFileTypeRegistry.h>
+#include <pnmFileType.h>
+#include <eggData.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+Palettizer *pal = (Palettizer *)NULL;
+
+TypeHandle Palettizer::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+Palettizer::
+Palettizer() {
+  // This number is written out as the first number to the pi file, to
+  // indicate the version of egg-palettize that wrote it out.  This
+  // allows us to easily update egg-palettize to write out additional
+  // information to its pi file, without having it increment the bam
+  // version number for all bam and boo files anywhere in the world.
+  _pi_version = 0;
+
+  _margin = 2;
+  _repeat_threshold = 250.0;
+  _aggressively_clean_mapdir = false;
+  _force_power_2 = false;
+  _color_type = PNMFileTypeRegistry::get_ptr()->get_type_from_extension("rgb");
+  _alpha_type = (PNMFileType *)NULL;
+  _pal_x_size = _pal_y_size = 256;
+
+  _round_uvs = true;
+  _round_unit = 0.1;
+  _round_fuzz = 0.01;
+  _remap_uv = RU_poly;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::report_pi
+//       Access: Public
+//  Description: Output a verbose description of all the palettization
+//               information to standard output, for the user's
+//               perusal.
+////////////////////////////////////////////////////////////////////
+void Palettizer::
+report_pi() const {
+  cout 
+    << "\nparams\n"
+    << "  palettize size: " << _pal_x_size << " by " << _pal_y_size << "\n"
+    << "  margin: " << _margin << "\n"
+    << "  repeat threshold: " << _repeat_threshold << "%\n"
+    << "  force textures to power of 2: " << yesno(_force_power_2) << "\n"
+    << "  aggressively clean the map directory: "
+    << yesno(_aggressively_clean_mapdir) << "\n"
+    << "  round UV area: " << yesno(_round_uvs) << "\n";
+  if (_round_uvs) {
+    cout << "  round UV area to nearest " << _round_unit << " with fuzz "
+	 << _round_fuzz << "\n";
+  }
+  cout << "  remap UV's: ";
+  switch (_remap_uv) {
+  case RU_never:
+    cout << "never\n";
+    break;
+
+  case RU_group:
+    cout << "per group\n";
+    break;
+
+  case RU_poly:
+    cout << "per polygon\n";
+    break;
+  }
+
+  if (_color_type != (PNMFileType *)NULL) {
+    cout << "  generate image files of type: " 
+	 << _color_type->get_suggested_extension();
+    if (_alpha_type != (PNMFileType *)NULL) {
+      cout << "," << _alpha_type->get_suggested_extension();
+    }
+    cout << "\n";
+  }
+
+  cout << "\ntexture source pathnames and sizes\n";
+  Textures::const_iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    TextureImage *texture = (*ti).second;
+    cout << "  " << texture->get_name() << ":\n";
+    texture->write_source_pathnames(cout, 4);
+  }
+
+  cout << "\negg files and textures referenced\n";
+  EggFiles::const_iterator ei;
+  for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
+    EggFile *egg_file = (*ei).second;
+    cout << "  " << egg_file->get_name() << ": " 
+	 << egg_file->get_groups() << "\n";
+    egg_file->write_texture_refs(cout, 4);
+  }
+
+  cout << "\npalette groups\n";
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    PaletteGroup *group = (*gi).second;
+    cout << "  " << group->get_name() << ": "
+	 << group->get_groups() << "\n";
+    group->write_image_info(cout, 4);
+    cout << "\n";
+  }
+
+  cerr << "textures\n";
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    TextureImage *texture = (*ti).second;
+    texture->write_scale_info(cout, 2);
+  }
+
+  cout << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::run
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Palettizer::
+run(const TxaFile &txa_file) {
+  set<TextureImage *> command_line_textures;
+
+  // Start by scanning all the egg files we read up on the command
+  // line.
+  CommandLineEggs::const_iterator ei;
+  for (ei = _command_line_eggs.begin();
+       ei != _command_line_eggs.end();
+       ++ei) {
+    EggFile *egg_file = (*ei);
+
+    egg_file->scan_textures();
+    egg_file->get_textures(command_line_textures);
+
+    txa_file.match_egg(egg_file);
+
+    egg_file->post_txa_file();
+  }
+
+  // Now match each of the textures mentioned in those egg files
+  // against a line in the .txa file.
+  set<TextureImage *>::iterator ti;
+  for (ti = command_line_textures.begin(); 
+       ti != command_line_textures.end();
+       ++ti) {
+    TextureImage *texture = *ti;
+
+    texture->pre_txa_file();
+    txa_file.match_texture(texture);
+    texture->post_txa_file();
+  }
+
+  // Now that all of our data is read in, build in all the cross links
+  // and back pointers and stuff.
+  EggFiles::const_iterator efi;
+  for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
+    (*efi).second->build_cross_links();
+  }
+
+  // And now, assign each of the current set of textures to an
+  // appropriate group or groups.
+  for (ti = command_line_textures.begin(); 
+       ti != command_line_textures.end();
+       ++ti) {
+    TextureImage *texture = *ti;
+    texture->assign_groups();
+  }
+
+  // And then the egg files need to sign up for a particular
+  // TexturePlacement, so we can determine some more properties about
+  // how the textures are placed (for instance, how big the UV range
+  // is for a particular TexturePlacement).
+  for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
+    (*efi).second->choose_placements();
+  }
+
+  // Now that *that's* done, we need to make sure the various
+  // TexturePlacements require the right size for their textures.
+  for (ti = command_line_textures.begin(); 
+       ti != command_line_textures.end();
+       ++ti) {
+    TextureImage *texture = *ti;
+    texture->determine_placement_size();
+  }
+
+  // Now that each texture has been assigned to a suitable group,
+  // make sure the textures are placed on specific PaletteImages.
+  Groups::iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    PaletteGroup *group = (*gi).second;
+    group->place_all();
+  }
+
+  // Finally, generate all the images.
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    PaletteGroup *group = (*gi).second;
+    group->update_images();
+  }
+
+  // And now we can adjust all the egg files.
+  for (ei = _command_line_eggs.begin();
+       ei != _command_line_eggs.end();
+       ++ei) {
+    EggFile *egg_file = (*ei);
+    egg_file->update_egg();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::get_egg_file
+//       Access: Public
+//  Description: Returns the EggFile with the given name.  If there is
+//               no EggFile with the indicated name, creates one.
+//               This is the key name used to sort the egg files,
+//               which is typically the basename of the filename.
+////////////////////////////////////////////////////////////////////
+EggFile *Palettizer::
+get_egg_file(const string &name) {
+  EggFiles::iterator ei = _egg_files.find(name);
+  if (ei != _egg_files.end()) {
+    return (*ei).second;
+  }
+
+  EggFile *file = new EggFile;
+  file->set_name(name);
+  _egg_files.insert(EggFiles::value_type(name, file));
+  return file;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::get_palette_group
+//       Access: Public
+//  Description: Returns the PaletteGroup with the given name.  If
+//               there is no PaletteGroup with the indicated name,
+//               creates one.
+////////////////////////////////////////////////////////////////////
+PaletteGroup *Palettizer::
+get_palette_group(const string &name) {
+  Groups::iterator gi = _groups.find(name);
+  if (gi != _groups.end()) {
+    return (*gi).second;
+  }
+
+  PaletteGroup *group = new PaletteGroup;
+  group->set_name(name);
+  _groups.insert(Groups::value_type(name, group));
+  return group;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::test_palette_group
+//       Access: Public
+//  Description: Returns the PaletteGroup with the given name.  If
+//               there is no PaletteGroup with the indicated name,
+//               returns NULL.
+////////////////////////////////////////////////////////////////////
+PaletteGroup *Palettizer::
+test_palette_group(const string &name) const {
+  Groups::const_iterator gi = _groups.find(name);
+  if (gi != _groups.end()) {
+    return (*gi).second;
+  }
+
+  return (PaletteGroup *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::get_default_group
+//       Access: Public
+//  Description: Returns the default group to which an egg file should
+//               be assigned if it is not mentioned in the .txa file.
+////////////////////////////////////////////////////////////////////
+PaletteGroup *Palettizer::
+get_default_group() {
+  return get_palette_group(_default_groupname);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::get_texture
+//       Access: Public
+//  Description: Returns the TextureImage with the given name.  If
+//               there is no TextureImage with the indicated name,
+//               creates one.  This is the key name used to sort the
+//               textures, which is typically the basename of the
+//               primary filename.
+////////////////////////////////////////////////////////////////////
+TextureImage *Palettizer::
+get_texture(const string &name) {
+  Textures::iterator ti = _textures.find(name);
+  if (ti != _textures.end()) {
+    return (*ti).second;
+  }
+
+  TextureImage *image = new TextureImage;
+  image->set_name(name);
+  image->set_filename(name);
+  _textures.insert(Textures::value_type(name, image));
+  return image;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::yesno
+//       Access: Private, Static
+//  Description: A silly function to return "yes" or "no" based on a
+//               bool flag for nicely formatted output.
+////////////////////////////////////////////////////////////////////
+const char *Palettizer::
+yesno(bool flag) {
+  return flag ? "yes" : "no";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void Palettizer::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_Palettizer);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void Palettizer::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  datagram.add_int32(_pi_version);
+  datagram.add_string(_map_dirname);
+  datagram.add_string(_rel_dirname);
+  datagram.add_string(_default_groupname);
+  datagram.add_string(_default_groupdir);
+  datagram.add_int32(_pal_x_size);
+  datagram.add_int32(_pal_y_size);
+  datagram.add_int32(_margin);
+  datagram.add_float64(_repeat_threshold);
+  datagram.add_bool(_force_power_2);
+  datagram.add_bool(_aggressively_clean_mapdir);
+  datagram.add_bool(_round_uvs);
+  datagram.add_float64(_round_unit);
+  datagram.add_float64(_round_fuzz);
+  datagram.add_int32((int)_remap_uv);
+  writer->write_pointer(datagram, _color_type);
+  writer->write_pointer(datagram, _alpha_type); 
+
+  datagram.add_int32(_egg_files.size());
+  EggFiles::const_iterator ei;
+  for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
+    writer->write_pointer(datagram, (*ei).second);
+  }
+
+  // We don't write _command_line_eggs; that's specific to each
+  // session.
+
+  datagram.add_int32(_groups.size());
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    writer->write_pointer(datagram, (*gi).second);
+  }
+
+  datagram.add_int32(_textures.size());
+  Textures::const_iterator ti;
+  for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
+    writer->write_pointer(datagram, (*ti).second);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int Palettizer::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr((int)plist.size() >= 2 + _num_egg_files + _num_groups + _num_textures, 0);
+  int index = 0;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_color_type, plist[index], index);
+  }
+  index++;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_alpha_type, plist[index], index);
+  }
+  index++;
+
+  int i;
+  for (i = 0; i < _num_egg_files; i++) {
+    EggFile *egg_file;
+    DCAST_INTO_R(egg_file, plist[index], index);
+    _egg_files.insert(EggFiles::value_type(egg_file->get_name(), egg_file));
+    index++;
+  }
+
+  for (i = 0; i < _num_groups; i++) {
+    PaletteGroup *group;
+    DCAST_INTO_R(group, plist[index], index);
+    _groups.insert(Groups::value_type(group->get_name(), group));
+    index++;
+  }
+
+  for (i = 0; i < _num_textures; i++) {
+    TextureImage *texture;
+    DCAST_INTO_R(texture, plist[index], index);
+    _textures.insert(Textures::value_type(texture->get_name(), texture));
+    index++;
+  }
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::make_Palettizer
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* Palettizer::
+make_Palettizer(const FactoryParams &params) {
+  Palettizer *me = new Palettizer;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Palettizer::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void Palettizer::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _pi_version = scan.get_int32();
+  _map_dirname = scan.get_string();
+  _rel_dirname = scan.get_string();
+  _default_groupname = scan.get_string();
+  _default_groupdir = scan.get_string();
+  _pal_x_size = scan.get_int32();
+  _pal_y_size = scan.get_int32();
+  _margin = scan.get_int32();
+  _repeat_threshold = scan.get_float64();
+  _force_power_2 = scan.get_bool();
+  _aggressively_clean_mapdir = scan.get_bool();
+  _round_uvs = scan.get_bool();
+  _round_unit = scan.get_float64();
+  _round_fuzz = scan.get_float64();
+  _remap_uv = (RemapUV)scan.get_int32();
+  manager->read_pointer(scan, this);  // _color_type
+  manager->read_pointer(scan, this);  // _alpha_type
+
+  _num_egg_files = scan.get_int32();
+  manager->read_pointers(scan, this, _num_egg_files);
+
+  _num_groups = scan.get_int32();
+  manager->read_pointers(scan, this, _num_groups);
+
+  _num_textures = scan.get_int32();
+  manager->read_pointers(scan, this, _num_textures);
+}
+

+ 128 - 0
pandatool/src/egg-palettize/palettizer.h

@@ -0,0 +1,128 @@
+// Filename: palettizer.h
+// Created by:  drose (01Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef PALETTIZER_H
+#define PALETTIZER_H
+
+#include <pandatoolbase.h>
+
+#include "txaFile.h"
+
+#include <typedWriteable.h>
+
+#include <vector>
+#include <map>
+
+class PNMFileType;
+class EggFile;
+class PaletteGroup;
+class TextureImage;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : Palettizer
+// Description : This is the main engine behind egg-palettize.  It
+//               contains all of the program parameters, from the
+//               command line or saved from a previous session, and
+//               serves as the driving force in the actual palettizing
+//               process.
+////////////////////////////////////////////////////////////////////
+class Palettizer : public TypedWriteable {
+public:
+  Palettizer();
+
+  void report_pi() const;
+  void run(const TxaFile &txa_file);
+
+  EggFile *get_egg_file(const string &name);
+  PaletteGroup *get_palette_group(const string &name);
+  PaletteGroup *test_palette_group(const string &name) const;
+  PaletteGroup *get_default_group();
+  TextureImage *get_texture(const string &name);
+
+private:
+  static const char *yesno(bool flag);
+
+public:
+  int _pi_version;
+
+  enum RemapUV {
+    RU_never,
+    RU_group,
+    RU_poly
+  };
+
+  // The following parameter values specifically relate to textures
+  // and palettes.  These values are stored in the .pi file for future
+  // reference.
+  Filename _map_dirname;
+  Filename _rel_dirname;
+  string _default_groupname;
+  string _default_groupdir;
+  int _pal_x_size, _pal_y_size;
+  int _margin;
+  double _repeat_threshold;
+  bool _force_power_2;
+  bool _aggressively_clean_mapdir;
+  bool _round_uvs;
+  double _round_unit;
+  double _round_fuzz;
+  RemapUV _remap_uv;
+  PNMFileType *_color_type;
+  PNMFileType *_alpha_type;
+
+private:
+  typedef map<string, EggFile *> EggFiles;
+  EggFiles _egg_files;
+
+  typedef vector<EggFile *> CommandLineEggs;
+  CommandLineEggs _command_line_eggs;
+
+  typedef map<string, PaletteGroup *> Groups;
+  Groups _groups;
+
+  typedef map<string, TextureImage *> Textures;
+  Textures _textures;
+
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_Palettizer(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  // These values are only filled in while reading from the bam file;
+  // don't use them otherwise.
+  int _num_egg_files;
+  int _num_groups;
+  int _num_textures;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    register_type(_type_handle, "Palettizer",
+		  TypedWriteable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class EggPalettize;
+};
+
+extern Palettizer *pal;
+
+#endif

+ 215 - 0
pandatool/src/egg-palettize/sourceTextureImage.cxx

@@ -0,0 +1,215 @@
+// Filename: sourceTextureImage.cxx
+// Created by:  drose (29Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "sourceTextureImage.h"
+#include "textureImage.h"
+
+#include <pnmImageHeader.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+TypeHandle SourceTextureImage::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::Default Constructor
+//       Access: Private
+//  Description: The default constructor is only for the convenience
+//               of the Bam reader.
+////////////////////////////////////////////////////////////////////
+SourceTextureImage::
+SourceTextureImage() {
+  _texture = (TextureImage *)NULL;
+
+  _read_header = false;
+  _successfully_read_header = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SourceTextureImage::
+SourceTextureImage(TextureImage *texture, const Filename &filename,
+		   const Filename &alpha_filename) :
+  _texture(texture)
+{
+  _filename = filename;
+  _alpha_filename = alpha_filename;
+  _read_header = false;
+  _successfully_read_header = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::get_texture
+//       Access: Public
+//  Description: Returns the particular texture that this image is one
+//               of the sources for.
+////////////////////////////////////////////////////////////////////
+TextureImage *SourceTextureImage::
+get_texture() const {
+  return _texture;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::get_size
+//       Access: Public
+//  Description: Determines the size of the SourceTextureImage, if it
+//               is not already known.  Returns true if the size was
+//               successfully determined (or if was already known), or
+//               false if the size could not be determined (for
+//               instance, because the image file is missing).  After
+//               this call returns true, get_x_size() etc. may be
+//               safely called to return the size.
+////////////////////////////////////////////////////////////////////
+bool SourceTextureImage::
+get_size() {
+  if (!_size_known) {
+    return read_header();
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::read_header
+//       Access: Public
+//  Description: Reads the actual image header to determine the image
+//               properties, like its size.  Returns true if the image
+//               header is successfully read (or if has previously
+//               been successfully read this session), false
+//               otherwise.  After this call returns true,
+//               get_x_size() etc. may be safely called to return the
+//               newly determined size.
+////////////////////////////////////////////////////////////////////
+bool SourceTextureImage::
+read_header() {
+  if (_read_header) {
+    return _successfully_read_header;
+  }
+
+  _read_header = true;
+  _successfully_read_header = false;
+
+  PNMImageHeader header;
+  if (!header.read_header(_filename)) {
+    nout << "Warning: cannot read texture " << _filename << "\n";
+    return false;
+  }
+
+  _x_size = header.get_x_size();
+  _y_size = header.get_y_size();
+  _properties._got_num_channels = true;
+  _properties._num_channels = header.get_num_channels();
+
+  if (!_alpha_filename.empty()) {
+    // Assume if we have an alpha filename, that we have an additional
+    // alpha channel.
+    if (_properties._num_channels == 1 || _properties._num_channels == 3) {
+      _properties._num_channels++;
+    }
+  }
+
+  _size_known = true;
+  _successfully_read_header = true;
+
+  return true;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::output
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void SourceTextureImage::
+output(ostream &out) const {
+  out << _filename;
+  if (!_alpha_filename.empty()) {
+    out << " (" << _alpha_filename << ")";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void SourceTextureImage::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_SourceTextureImage);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void SourceTextureImage::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  ImageFile::write_datagram(writer, datagram);
+  writer->write_pointer(datagram, _texture);
+
+  // We don't store _read_header or _successfully_read_header in the
+  // Bam file; these are transitory and we need to reread the image
+  // header for each session (in case the image files change between
+  // sessions).
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int SourceTextureImage::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr(plist.size() >= 1, 0);
+
+  DCAST_INTO_R(_texture, plist[0], 0);
+  return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::make_SourceTextureImage
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* SourceTextureImage::
+make_SourceTextureImage(const FactoryParams &params) {
+  SourceTextureImage *me = new SourceTextureImage;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SourceTextureImage::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void SourceTextureImage::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  ImageFile::fillin(scan, manager);
+  manager->read_pointer(scan, this); // _texture
+}

+ 75 - 0
pandatool/src/egg-palettize/sourceTextureImage.h

@@ -0,0 +1,75 @@
+// Filename: sourceTextureImage.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SOURCETEXTUREIMAGE_H
+#define SOURCETEXTUREIMAGE_H
+
+#include <pandatoolbase.h>
+
+#include "imageFile.h"
+
+class TextureImage;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : SourceTextureImage
+// Description : This is a texture image reference as it appears in an
+//               egg file: the source image of the texture.
+////////////////////////////////////////////////////////////////////
+class SourceTextureImage : public ImageFile {
+private:
+  SourceTextureImage();
+
+public:
+  SourceTextureImage(TextureImage *texture, const Filename &filename,
+		     const Filename &alpha_filename);
+
+  TextureImage *get_texture() const;
+  
+  bool get_size();
+  bool read_header();
+
+  void output(ostream &out) const;
+
+private:
+  bool _read_header;
+  bool _successfully_read_header;
+  TextureImage *_texture;
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_SourceTextureImage(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ImageFile::init_type();
+    register_type(_type_handle, "SourceTextureImage",
+		  ImageFile::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &
+operator << (ostream &out, const SourceTextureImage &source) {
+  source.output(out);
+  return out;
+}
+
+#endif
+

+ 726 - 0
pandatool/src/egg-palettize/textureImage.cxx

@@ -0,0 +1,726 @@
+// Filename: textureImage.cxx
+// Created by:  drose (29Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "textureImage.h"
+#include "sourceTextureImage.h"
+#include "eggFile.h"
+#include "paletteGroup.h"
+#include "texturePlacement.h"
+
+#include <indent.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+TypeHandle TextureImage::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextureImage::
+TextureImage() {
+  _preferred_source = (SourceTextureImage *)NULL;
+  _read_source_image = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::note_egg_file
+//       Access: Public
+//  Description: Records that a particular egg file references this
+//               texture.  This is essential to know when deciding how
+//               to assign the TextureImage to the various
+//               PaletteGroups.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+note_egg_file(EggFile *egg_file) {
+  nassertv(!egg_file->get_groups().empty());
+  _egg_files.insert(egg_file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::assign_groups
+//       Access: Public
+//  Description: Assigns the texture to all of the PaletteGroups the
+//               various egg files that use it need.  Attempts to
+//               choose the minimum set of PaletteGroups that
+//               satisfies all of the egg files.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+assign_groups() {
+  nassertv(!_egg_files.empty());
+
+  PaletteGroups definitely_in;
+
+  // First, we need to eliminate from consideration all the egg files
+  // that are already taken care of by the user's explicit group
+  // assignments for this texture.
+  WorkingEggs needed_eggs;
+
+  if (_explicitly_assigned_groups.empty()) {
+    // If we have no explicit group assignments, we must consider all
+    // the egg files.
+    copy(_egg_files.begin(), _egg_files.end(), back_inserter(needed_eggs));
+  
+  } else {
+    // Otherwise, we only need to consider the egg files that don't
+    // have any groups in common with our explicit assignments.
+
+    EggFiles::const_iterator ei;
+    for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) {
+      PaletteGroups intersect;
+      intersect.make_intersection(_explicitly_assigned_groups, (*ei)->get_groups());
+      if (!intersect.empty()) {
+	// This egg file is satisfied by one of the texture's explicit
+	// assignments.
+
+	// We must use at least one of the explicitly-assigned groups
+	// that satisfied the egg file.  We don't need to use all of
+	// them, however, and we choose the first one arbitrarily.
+	definitely_in.insert(*intersect.begin());
+
+      } else {
+	// This egg file was not satisfied by any of the texture's
+	// explicit assignments.  Therefore, we'll need to choose some
+	// additional group to assign the texture to, to make the egg
+	// file happy.  Defer this a bit.
+	needed_eggs.push_back(*ei);
+      }
+    }
+  }
+
+  while (!needed_eggs.empty()) {
+    // We need to know the complete set of groups that we need to
+    // consider adding the texture to.  This is the union of all the egg
+    // files' requested groups.
+    PaletteGroups total;
+    WorkingEggs::const_iterator ei;
+    for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
+      total.make_union(total, (*ei)->get_groups());
+    }
+    
+    // Now, find the group that will satisfy the most egg files.  If two
+    // groups satisfy the same number of egg files, choose the one that
+    // has the fewest egg files sharing it.
+    nassertv(!total.empty());
+    PaletteGroups::iterator gi = total.begin();
+    PaletteGroup *best = (*gi);
+    int best_egg_count = compute_egg_count(best, needed_eggs);
+    ++gi;
+    while (gi != total.end()) {
+      PaletteGroup *group = (*gi);
+      int group_egg_count = compute_egg_count(group, needed_eggs);
+      if (group_egg_count > best_egg_count ||
+	  (group_egg_count == best_egg_count &&
+	   group->get_egg_count() < best->get_egg_count())) {
+	best = group;
+	best_egg_count = group_egg_count;
+      }
+      ++gi;
+    }
+    
+    // Okay, now we've picked the best group.  Eliminate all the eggs
+    // from consideration that are satisfied by this group, and repeat.
+    definitely_in.insert(best);
+    
+    WorkingEggs next_needed_eggs;
+    for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) {
+      if ((*ei)->get_groups().count(best) == 0) {
+	// This one wasn't eliminated.
+	next_needed_eggs.push_back(*ei);
+      }
+    }
+    needed_eggs.swap(next_needed_eggs);
+  }
+
+  // Finally, now that we've computed the set of groups we need to
+  // assign the texture to, we need to reconcile this with the set of
+  // groups we've assigned the texture to previously.
+  assign_to_groups(definitely_in);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::get_groups
+//       Access: Public
+//  Description: Once get_groups() has been called, this returns the
+//               actual set of groups the TextureImage has been
+//               assigned to.
+////////////////////////////////////////////////////////////////////
+const PaletteGroups &TextureImage::
+get_groups() const {
+  return _actual_assigned_groups;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::get_placement
+//       Access: Public
+//  Description: Gets the TexturePlacement object which represents the
+//               assignment of this texture to the indicated group.
+//               If the texture has not been assigned to the indicated
+//               group, returns NULL.
+////////////////////////////////////////////////////////////////////
+TexturePlacement *TextureImage::
+get_placement(PaletteGroup *group) const {
+  Placement::const_iterator pi;
+  pi = _placement.find(group);
+  if (pi == _placement.end()) {
+    return (TexturePlacement *)NULL;
+  }
+
+  return (*pi).second;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::force_replace
+//       Access: Public
+//  Description: Removes the texture from any PaletteImages it is
+//               assigned to, but does not remove it from the groups.
+//               It will be re-placed within each group when
+//               PaletteGroup::place_all() is called.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+force_replace() {
+  Placement::iterator pi;
+  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
+    (*pi).second->force_replace();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::pre_txa_file
+//       Access: Public
+//  Description: Updates any internal state prior to reading the .txa
+//               file.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+pre_txa_file() {
+  // Save our current properties, so we can note if they change.
+  _pre_txa_properties = _properties;
+
+  // Update our properties from the egg files that reference this
+  // texture.  It's possible the .txa file will update them further.
+  SourceTextureImage *source = get_preferred_source();
+  if (source != (SourceTextureImage *)NULL) {
+    _properties = source->get_properties();
+  }
+
+  _request.pre_txa_file();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::post_txa_file
+//       Access: Public
+//  Description: Once the .txa file has been read and the TextureImage
+//               matched against it, considers applying the requested
+//               size change.  Updates the TextureImage's size with
+//               the size the texture ought to be, if this can be
+//               determined.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+post_txa_file() {
+  // First, get the actual size of the texture.
+  SourceTextureImage *source = get_preferred_source();
+  if (source != (SourceTextureImage *)NULL) {
+    if (source->get_size()) {
+      _size_known = true;
+      _x_size = source->get_x_size();
+      _y_size = source->get_y_size();
+      _properties._got_num_channels = true;
+      _properties._num_channels = source->get_num_channels();
+    }
+  }
+
+  // Now update this with a particularly requested size.
+  if (_request._got_size) {
+    _size_known = true;
+    _x_size = _request._x_size;
+    _y_size = _request._y_size;
+  }
+    
+  if (_request._got_num_channels) {
+    _properties._got_num_channels = true;
+    _properties._num_channels = _request._num_channels;
+
+  } else {
+    // If we didn't request a particular number of channels, examine
+    // the image to determine if we can downgrade it, for instance
+    // from color to grayscale.
+    if (_properties._got_num_channels &&
+	(_properties._num_channels == 3 || _properties._num_channels == 4)) {
+      consider_grayscale();
+    }
+  }
+
+  if (_request._format != EggTexture::F_unspecified) {
+    _properties._format = _request._format;
+  }
+  if (_request._minfilter != EggTexture::FT_unspecified) {
+    _properties._minfilter = _request._minfilter;
+  }
+  if (_request._magfilter != EggTexture::FT_unspecified) {
+    _properties._magfilter = _request._magfilter;
+  }
+
+  // Finally, make sure our properties are fully defined.
+  _properties.fully_define();
+
+  // Now, if our properties have changed in all that from our previous
+  // session, we need to re-place ourself in all palette groups.
+  if (_properties != _pre_txa_properties) {
+    force_replace();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::determine_placement_size
+//       Access: Public
+//  Description: Calls determine_size() on each TexturePlacement for
+//               the texture, to ensure that each TexturePlacement is
+//               still requesting the best possible size for the
+//               texture.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+determine_placement_size() {
+  Placement::iterator pi;
+  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
+    TexturePlacement *placement = (*pi).second;
+    placement->determine_size();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::get_omit
+//       Access: Public
+//  Description: Returns true if the user specifically requested to
+//               omit this texture via the "omit" keyword in the .txa
+//               file, or false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TextureImage::
+get_omit() const {
+  return _request._omit;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::get_repeat_threshold
+//       Access: Public
+//  Description: Returns the suitable repeat threshold for this
+//               texture.  This is either the
+//               Palettizer::_repeat_threshold parameter, given
+//               globally via -r, or a particular value for this
+//               texture as supplied by the "repeat" keyword in the
+//               .txa file.
+////////////////////////////////////////////////////////////////////
+double TextureImage::
+get_repeat_threshold() const {
+  return _request._repeat_threshold;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::get_margin
+//       Access: Public
+//  Description: Returns the suitable repeat threshold for this
+//               texture.  This is either the Palettizer::_margin
+//               parameter, or a particular value for this texture as
+//               supplied by the "margin" keyword in the .txa file.
+////////////////////////////////////////////////////////////////////
+int TextureImage::
+get_margin() const {
+  return _request._margin;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::get_source
+//       Access: Public
+//  Description: Returns the SourceTextureImage corresponding to the
+//               given filename(s).  If the given filename has never
+//               been used as a SourceTexture for this particular
+//               texture, creates a new SourceTextureImage and returns
+//               that.
+////////////////////////////////////////////////////////////////////
+SourceTextureImage *TextureImage::
+get_source(const Filename &filename, const Filename &alpha_filename) {
+  string key = filename.get_fullpath() + ":" + alpha_filename.get_fullpath();
+  Sources::iterator si;
+  si = _sources.find(key);
+  if (si != _sources.end()) {
+    return (*si).second;
+  }
+
+  SourceTextureImage *source = 
+    new SourceTextureImage(this, filename, alpha_filename);
+  _sources.insert(Sources::value_type(key, source));
+
+  // Clear out the preferred source image to force us to rederive this
+  // next time someone asks.
+  _preferred_source = (SourceTextureImage *)NULL;
+  _read_source_image = false;
+
+  return source;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::get_preferred_source
+//       Access: Public
+//  Description: Determines the preferred source image for examining
+//               size and reading pixels, etc.  This is the largest
+//               and most recent of all the available source images.
+////////////////////////////////////////////////////////////////////
+SourceTextureImage *TextureImage::
+get_preferred_source() {
+  if (_preferred_source != (SourceTextureImage *)NULL) {
+    return _preferred_source;
+  }
+
+  // Now examine all of the various source images available to us and
+  // pick the most suitable.
+
+  // **** For now, we arbitrarily pick the first one.
+  if (!_sources.empty()) {
+    _preferred_source = (*_sources.begin()).second;
+  }
+
+  return _preferred_source;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::read_source_image
+//       Access: Public
+//  Description: Reads in the original image, if it has not already
+//               been read, and returns it.
+////////////////////////////////////////////////////////////////////
+const PNMImage &TextureImage::
+read_source_image() {
+  if (!_read_source_image) {
+    SourceTextureImage *source = get_preferred_source();
+    if (source != (SourceTextureImage *)NULL) {
+      source->read(_source_image);
+    }
+    _read_source_image = true;
+  }
+
+  return _source_image;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::write_source_pathnames
+//       Access: Public
+//  Description: Writes the list of source pathnames that might
+//               contribute to this texture to the indicated output
+//               stream, one per line.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+write_source_pathnames(ostream &out, int indent_level) const {
+  Sources::const_iterator si;
+  for (si = _sources.begin(); si != _sources.end(); ++si) {
+    SourceTextureImage *source = (*si).second;
+
+    indent(out, indent_level);
+    source->output_filename(out);
+    if (!source->is_size_known()) {
+      out << " (unknown size)";
+
+    } else {
+      out << " " << source->get_x_size() << " " 
+	  << source->get_y_size();
+
+      if (source->get_properties().has_num_channels()) {
+	out << " " << source->get_properties().get_num_channels();
+      }
+    }
+    out << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::write_scale_info
+//       Access: Public
+//  Description: Writes the list of source pathnames that might
+//               contribute to this texture to the indicated output
+//               stream, one per line.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+write_scale_info(ostream &out, int indent_level) {
+  SourceTextureImage *source = get_preferred_source();
+  indent(out, indent_level) << get_name() << " orig ";
+
+  if (source == (SourceTextureImage *)NULL ||
+      !source->is_size_known()) {
+    out << "unknown";
+  } else {
+    out << source->get_x_size() << " " << source->get_y_size()
+	<< " " << source->get_num_channels();
+  }
+
+  out << " new " << get_x_size() << " " << get_y_size()
+      << " " << get_num_channels();
+
+  if (source != (SourceTextureImage *)NULL &&
+      source->is_size_known()) {
+    double scale = 
+      100.0 * (((double)get_x_size() / (double)source->get_x_size()) +
+	       ((double)get_y_size() / (double)source->get_y_size())) / 2.0;
+    out << " " << scale << "%";
+  }
+  out << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::compute_egg_count
+//       Access: Private
+//  Description: Counts the number of egg files in the indicated set
+//               that will be satisfied if a texture is assigned to
+//               the indicated group.
+////////////////////////////////////////////////////////////////////
+int TextureImage::
+compute_egg_count(PaletteGroup *group, 
+		  const TextureImage::WorkingEggs &egg_files) {
+  int count = 0;
+
+  WorkingEggs::const_iterator ei;
+  for (ei = egg_files.begin(); ei != egg_files.end(); ++ei) {
+    if ((*ei)->get_groups().count(group) != 0) {
+      count++;
+    }
+  }
+
+  return count;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::assign_to_groups
+//       Access: Private
+//  Description: Assigns the texture to the indicated set of groups.
+//               If the texture was previously assigned to any of
+//               these groups, keeps the same TexturePlacement object
+//               for the assignment; at the same time, deletes any
+//               TexturePlacement objects that represent groups we are
+//               no longer assigned to.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+assign_to_groups(const PaletteGroups &groups) {
+  PaletteGroups::const_iterator gi;
+  Placement::const_iterator pi;
+
+  Placement new_placement;
+
+  gi = groups.begin();
+  pi = _placement.begin();
+
+  while (gi != groups.end() && pi != _placement.end()) {
+    PaletteGroup *a = (*gi);
+    PaletteGroup *b = (*pi).first;
+
+    if (a < b) {
+      // Here's a group we're now assigned to that we weren't assigned
+      // to previously.
+      TexturePlacement *place = a->prepare(this);
+      new_placement.insert
+	(new_placement.end(), Placement::value_type(a, place));
+      ++gi;
+
+    } else if (b < a) {
+      // Here's a group we're no longer assigned to.
+      TexturePlacement *place = (*pi).second;
+      delete place;
+      ++pi;
+
+    } else { // b == a
+      // Here's a group we're still assigned to.
+      TexturePlacement *place = (*pi).second;
+      new_placement.insert
+	(new_placement.end(), Placement::value_type(a, place));
+      ++gi;
+      ++pi;
+    }
+  }
+
+  while (gi != groups.end()) {
+    // Here's a group we're now assigned to that we weren't assigned
+    // to previously.
+    PaletteGroup *a = (*gi);
+    TexturePlacement *place = a->prepare(this);
+    new_placement.insert
+      (new_placement.end(), Placement::value_type(a, place));
+    ++gi;
+  }
+
+  while (pi != _placement.end()) {
+    // Here's a group we're no longer assigned to.
+    TexturePlacement *place = (*pi).second;
+    delete place;
+    ++pi;
+  }
+
+  _placement.swap(new_placement);
+  _actual_assigned_groups = groups;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::consider_grayscale
+//       Access: Private
+//  Description: Examines the actual contents of the image to
+//               determine if it should maybe be considered a
+//               grayscale image (even though it has separate rgb
+//               components).
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+consider_grayscale() {
+  const PNMImage &source = read_source_image();
+  if (!source.is_valid()) {
+    return;
+  }
+
+  for (int y = 0; y < source.get_y_size(); y++) {
+    for (int x = 0; x < source.get_x_size(); x++) {
+      const xel &v = source.get_xel_val(x, y);
+      if (PPM_GETR(v) != PPM_GETG(v) || PPM_GETR(v) != PPM_GETB(v)) {
+	// Here's a colored pixel.  We can't go grayscale.
+	return;
+      }
+    }
+  }
+
+  // All pixels in the image were grayscale!
+  _properties._num_channels -= 2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_TextureImage);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  ImageFile::write_datagram(writer, datagram);
+  datagram.add_string(get_name());
+
+  // We don't write out _request; this is re-read from the .txa file
+  // each time.
+
+  // We don't write out _pre_txa_properties; this is transitional.
+
+  // We don't write out _preferred_source; this is redetermined each
+  // session.
+
+  // We don't write out _explicitly_assigned_groups; this is re-read
+  // from the .txa file each time.
+
+  _actual_assigned_groups.write_datagram(writer, datagram);
+
+  // We don't write out _egg_files; this is redetermined each session.
+
+  datagram.add_uint32(_placement.size());
+  Placement::const_iterator pi;
+  for (pi = _placement.begin(); pi != _placement.end(); ++pi) {
+    writer->write_pointer(datagram, (*pi).first);
+    writer->write_pointer(datagram, (*pi).second);
+  }
+
+  datagram.add_uint32(_sources.size());
+  Sources::const_iterator si;
+  for (si = _sources.begin(); si != _sources.end(); ++si) {
+    writer->write_pointer(datagram, (*si).second);
+  }
+
+  // We don't write out _read_source_image or _source_image; this must
+  // be reread each session.
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int TextureImage::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr((int)plist.size() >= _num_placement * 2 + _num_sources, 0);
+  int index = 0;
+
+  int i;
+  for (i = 0; i < _num_placement; i++) {
+    PaletteGroup *group;
+    TexturePlacement *placement;
+    DCAST_INTO_R(group, plist[index], index);
+    index++;
+    DCAST_INTO_R(placement, plist[index], index);
+    index++;
+    _placement.insert(Placement::value_type(group, placement));
+  }
+
+  for (i = 0; i < _num_sources; i++) {
+    SourceTextureImage *source;
+    DCAST_INTO_R(source, plist[index], index);
+    string key = source->get_filename().get_fullpath() + ":" + 
+      source->get_alpha_filename().get_fullpath();
+
+    _sources.insert(Sources::value_type(key, source));
+    index++;
+  }
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::make_TextureImage
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* TextureImage::
+make_TextureImage(const FactoryParams &params) {
+  TextureImage *me = new TextureImage;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureImage::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void TextureImage::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  ImageFile::fillin(scan, manager);
+  set_name(scan.get_string());
+
+  _actual_assigned_groups.fillin(scan, manager);
+
+  _num_placement = scan.get_uint32();
+  manager->read_pointers(scan, this, _num_placement * 2);
+
+  _num_sources = scan.get_uint32();
+  manager->read_pointers(scan, this, _num_sources);
+}

+ 139 - 0
pandatool/src/egg-palettize/textureImage.h

@@ -0,0 +1,139 @@
+// Filename: textureImage.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREIMAGE_H
+#define TEXTUREIMAGE_H
+
+#include <pandatoolbase.h>
+
+#include "imageFile.h"
+#include "paletteGroups.h"
+#include "textureRequest.h"
+
+#include <namable.h>
+#include <filename.h>
+#include <pnmImage.h>
+
+#include <map>
+#include <set>
+
+class SourceTextureImage;
+class TexturePlacement;
+class EggFile;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TextureImage
+// Description : This represents a single source texture that is
+//               referenced by one or more egg files.  It may be
+//               assigned to multiple PaletteGroups, and thus placed
+//               on multiple PaletteImages (up to one per
+//               PaletteGroup).
+//
+//               Since a TextureImage may be referenced by multiple
+//               egg files that are each assigned to a different set
+//               of groups, it tries to maximize sharing between egg
+//               files and minimize the number of different
+//               PaletteGroups it is assigned to.
+////////////////////////////////////////////////////////////////////
+class TextureImage : public ImageFile, public Namable {
+public:
+  TextureImage();
+
+  void note_egg_file(EggFile *egg_file);
+  void assign_groups();
+
+  const PaletteGroups &get_groups() const;
+  TexturePlacement *get_placement(PaletteGroup *group) const;
+  void force_replace();
+
+  void pre_txa_file();
+  void post_txa_file();
+  void determine_placement_size();
+
+  bool get_omit() const;
+  double get_repeat_threshold() const;
+  int get_margin() const;
+
+
+  SourceTextureImage *get_source(const Filename &filename, 
+				 const Filename &alpha_filename);
+
+  SourceTextureImage *get_preferred_source();
+  bool get_size();
+
+  const PNMImage &read_source_image();
+
+  void write_source_pathnames(ostream &out, int indent_level = 0) const;
+  void write_scale_info(ostream &out, int indent_level = 0);
+
+private:
+  typedef set<EggFile *> EggFiles;
+  typedef vector<EggFile *> WorkingEggs;
+
+  static int compute_egg_count(PaletteGroup *group, 
+			       const WorkingEggs &egg_files);
+
+  void assign_to_groups(const PaletteGroups &groups);
+  void consider_grayscale();
+
+  TextureRequest _request;
+  TextureProperties _pre_txa_properties;
+  SourceTextureImage *_preferred_source;
+
+  PaletteGroups _explicitly_assigned_groups;
+  PaletteGroups _actual_assigned_groups;
+
+  EggFiles _egg_files;
+
+  typedef map<PaletteGroup *, TexturePlacement *> Placement;
+  Placement _placement;
+
+  typedef map<string, SourceTextureImage *> Sources;
+  Sources _sources;
+
+  bool _read_source_image;
+  PNMImage _source_image;
+
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_TextureImage(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  // These values are only filled in while reading from the bam file;
+  // don't use them otherwise.
+  int _num_placement;
+  int _num_sources;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ImageFile::init_type();
+    Namable::init_type();
+    register_type(_type_handle, "TextureImage",
+		  ImageFile::get_class_type(),
+		  Namable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class TxaLine;
+};
+
+#endif
+

+ 911 - 0
pandatool/src/egg-palettize/texturePlacement.cxx

@@ -0,0 +1,911 @@
+// Filename: texturePlacement.cxx
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "texturePlacement.h"
+#include "textureReference.h"
+#include "textureImage.h"
+#include "paletteGroup.h"
+#include "paletteImage.h"
+#include "palettizer.h"
+
+#include <indent.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+#include <pnmImage.h>
+
+TypeHandle TexturePlacement::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::Default Constructor
+//       Access: Private
+//  Description: The default constructor is only for the convenience
+//               of the Bam reader.
+////////////////////////////////////////////////////////////////////
+TexturePlacement::
+TexturePlacement() {
+  _texture = (TextureImage *)NULL;
+  _group = (PaletteGroup *)NULL;
+  _image = (PaletteImage *)NULL;
+  _has_uvs = false;
+  _size_known = false;
+  _is_filled = true;
+  _omit_reason = OR_none;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TexturePlacement::
+TexturePlacement(TextureImage *texture, PaletteGroup *group) :
+  _texture(texture),
+  _group(group)
+{
+  _omit_reason = OR_working;
+
+  if (!texture->is_size_known()) {
+    // If we were never able to figure out what size the texture
+    // actually is, then we can't place the texture on a palette.
+    _omit_reason = OR_unknown;
+  }
+
+  _image = (PaletteImage *)NULL;
+  _has_uvs = false;
+  _size_known = false;
+  _is_filled = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TexturePlacement::
+~TexturePlacement() {
+  // Make sure we tell all our egg references they're not using us any
+  // more.
+  References::iterator ri;
+  References copy_references = _references;
+  for (ri = copy_references.begin(); ri != copy_references.end(); ++ri) {
+    TextureReference *reference = (*ri);
+    nassertv(reference->get_placement() == this);
+    reference->clear_placement();
+  }
+
+  // And also our group, etc.
+  _group->unplace(this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_texture
+//       Access: Public
+//  Description: Returns the texture that this placement represents.
+////////////////////////////////////////////////////////////////////
+TextureImage *TexturePlacement::
+get_texture() const {
+  return _texture;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_properties
+//       Access: Public
+//  Description: Returns the grouping properties of the image.
+////////////////////////////////////////////////////////////////////
+const TextureProperties &TexturePlacement::
+get_properties() const {
+  return _texture->get_properties();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_group
+//       Access: Public
+//  Description: Returns the group that this placement represents.
+////////////////////////////////////////////////////////////////////
+PaletteGroup *TexturePlacement::
+get_group() const {
+  return _group;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::add_egg
+//       Access: Public
+//  Description: Records the fact that a particular egg file is using
+//               this particular TexturePlacement.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+add_egg(TextureReference *reference) {
+  _has_uvs = false;
+  _size_known = false;
+  _references.insert(reference);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::remove_egg
+//       Access: Public
+//  Description: Notes that a particular egg file is no longer using
+//               this particular TexturePlacement.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+remove_egg(TextureReference *reference) {
+  _has_uvs = false;
+  _size_known = false;
+  _references.erase(reference);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::determine_size
+//       Access: Public
+//  Description: Attempts to determine the appropriate size of the
+//               texture for the given placement.  This is based on
+//               the UV range of the egg files that reference the
+//               texture.  Returns true on success, or false if the
+//               texture size cannot be determined (e.g. the texture
+//               file is unknown).
+//
+//               After this returns true, get_x_size() and
+//               get_y_size() can be safely called.
+////////////////////////////////////////////////////////////////////
+bool TexturePlacement::
+determine_size() {
+  if (!_texture->is_size_known()) {
+    // Too bad.
+    force_replace();
+    _omit_reason = OR_unknown;
+    return false;
+  }
+
+  if (_omit_reason == OR_solitary) {
+    // If the texture was previously 'omitted' for being solitary, we
+    // give it a second chance now.
+    _omit_reason = OR_none;
+  }
+
+  // Determine the actual minmax of the UV's in use, as well as
+  // whether we should wrap or clamp.
+  _has_uvs = false;
+  _position._wrap_u = EggTexture::WM_clamp;
+  _position._wrap_v = EggTexture::WM_clamp;
+
+  TexCoordd max_uv, min_uv;
+
+  References::iterator ri;
+  for (ri = _references.begin(); ri != _references.end(); ++ri) {
+    TextureReference *reference = (*ri);
+    if (reference->has_uvs()) {
+      const TexCoordd &n = reference->get_min_uv();
+      const TexCoordd &x = reference->get_max_uv();
+    
+      if (_has_uvs) {
+	min_uv.set(min(min_uv[0], n[0]), min(min_uv[1], n[1]));
+	max_uv.set(max(max_uv[0], x[0]), max(max_uv[1], x[1]));
+      } else {
+	min_uv = n;
+	max_uv = x;
+	_has_uvs = true;
+      }
+    }
+
+    // If any reference repeats the texture, the texture repeats in
+    // the palette.
+    if (reference->get_wrap_u() == EggTexture::WM_repeat) {
+      _position._wrap_u = EggTexture::WM_repeat;
+    }
+    if (reference->get_wrap_v() == EggTexture::WM_repeat) {
+      _position._wrap_v = EggTexture::WM_repeat;
+    }
+  }
+
+  nassertr(_has_uvs, false);
+  TexCoordd rounded_min_uv = min_uv;
+  TexCoordd rounded_max_uv = max_uv;
+
+  // If so requested, round the minmax out to the next _round_unit.
+  // This cuts down on unnecessary resizing of textures within the
+  // palettes as the egg references change in trivial amounts.
+  if (pal->_round_uvs) {
+    rounded_max_uv[0] = 
+      ceil((rounded_max_uv[0] - pal->_round_fuzz) / pal->_round_unit) *
+      pal->_round_unit;
+    rounded_max_uv[1] = 
+      ceil((rounded_max_uv[1] - pal->_round_fuzz) / pal->_round_unit) *
+      pal->_round_unit;
+
+    rounded_min_uv[0] = 
+      floor((rounded_min_uv[0] + pal->_round_fuzz) / pal->_round_unit) *
+      pal->_round_unit;
+    rounded_min_uv[1] = 
+      floor((rounded_min_uv[1] + pal->_round_fuzz) / pal->_round_unit) *
+      pal->_round_unit;
+  }
+
+  // Now determine the size in pixels we require based on the UV's
+  // that actually reference this texture.
+  compute_size_from_uvs(rounded_min_uv, rounded_max_uv);
+
+  // Now, can it be placed?
+  if (_texture->get_omit()) {
+    // Not if the user says it can't.
+    force_replace();
+    _omit_reason = OR_omitted;
+
+  } else if (get_uv_area() > _texture->get_repeat_threshold() / 100.0) {
+    // If the texture repeats too many times, we can't place it.
+    force_replace();
+    _omit_reason = OR_repeats;
+
+  } else if ((_position._x_size > pal->_pal_x_size || 
+	      _position._y_size > pal->_pal_y_size) ||
+	     (_position._x_size == pal->_pal_x_size && 
+	      _position._y_size == pal->_pal_y_size)) {
+    // If the texture exceeds the size of an empty palette image in
+    // either dimension, or if it exactly equals the size of an empty
+    // palette image in both dimensions, we can't place it because
+    // it's too big.
+    force_replace();
+    _omit_reason = OR_size;
+
+  } else if (_omit_reason == OR_omitted ||
+	     _omit_reason == OR_size ||
+	     _omit_reason == OR_repeats ||
+	     _omit_reason == OR_unknown) {
+    // On the other hand, if the texture was previously omitted
+    // explicitly, or because of its size or repeat count, now it
+    // seems to fit.
+    force_replace();
+    _omit_reason = OR_working;
+
+  } else if (is_placed()) {
+    // It *can* be placed.  If it was already placed previously, can
+    // we leave it where it is?
+
+    if (_position._x_size != _placed._x_size || 
+	_position._y_size != _placed._y_size ||
+	_position._min_uv[0] < _placed._min_uv[0] ||
+	_position._min_uv[1] < _placed._min_uv[1] ||
+	_position._max_uv[0] > _placed._max_uv[0] ||
+	_position._max_uv[1] > _placed._max_uv[1]) {
+      // If the texture was previously placed but is now the wrong
+      // size, or if the area we need to cover is different, we need
+      // to re-place it.
+      
+      // However, we make a special exception: if it would have fit
+      // without rounding up the UV's, then screw rounding it up and
+      // just leave it alone.
+      if (pal->_round_uvs) {
+	compute_size_from_uvs(min_uv, max_uv);
+	if (_position._x_size <= _placed._x_size &&
+	    _position._y_size <= _placed._y_size &&
+	    _position._min_uv[0] >= _placed._min_uv[0] &&
+	    _position._min_uv[1] >= _placed._min_uv[1] &&
+	    _position._max_uv[0] <= _placed._max_uv[0] &&
+	    _position._max_uv[1] <= _placed._max_uv[1]) {
+	  // No problem!  It fits here, so leave well enough alone.
+
+	} else {
+	  // That's not good enough either, so go back to rounding.
+	  compute_size_from_uvs(rounded_min_uv, rounded_max_uv);
+	  force_replace();
+	}
+      } else {
+	force_replace();
+      }
+    }
+
+    if (_position._wrap_u != _placed._wrap_u ||
+	_position._wrap_v != _placed._wrap_v) {
+      // The wrap mode properties have changed slightly.  We may or
+      // may not need to re-place it, but we will need to update it.
+      _is_filled = false;
+      _placed._wrap_u = _position._wrap_u;
+      _placed._wrap_v = _position._wrap_v;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_omit_reason
+//       Access: Public
+//  Description: Returns the reason the texture has been omitted from
+//               a palette image, or OR_none if it has not.
+////////////////////////////////////////////////////////////////////
+OmitReason TexturePlacement::
+get_omit_reason() const {
+  return _omit_reason;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_x_size
+//       Access: Public
+//  Description: Returns the size in the X dimension, in pixels, of
+//               the texture image as it must appear in the palette.
+//               This accounts for any growing or shrinking of the
+//               texture due to the UV coordinate range.
+////////////////////////////////////////////////////////////////////
+int TexturePlacement::
+get_x_size() const {
+  nassertr(_size_known, 0);
+  return _position._x_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_y_size
+//       Access: Public
+//  Description: Returns the size in the Y dimension, in pixels, of
+//               the texture image as it must appear in the palette.
+//               This accounts for any growing or shrinking of the
+//               texture due to the UV coordinate range.
+////////////////////////////////////////////////////////////////////
+int TexturePlacement::
+get_y_size() const {
+  nassertr(_size_known, 0);
+  return _position._y_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_uv_area
+//       Access: Public
+//  Description: Returns the total area of the rectangle occupied by
+//               the UV minmax box, in UV coordinates.  1.0 is the
+//               entire texture; values greater than 1 imply the
+//               texture repeats.
+////////////////////////////////////////////////////////////////////
+double TexturePlacement::
+get_uv_area() const {
+  nassertr(_has_uvs, 0.0);
+
+  TexCoordd range = _position._max_uv - _position._min_uv;
+  return range[0] * range[1];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::is_placed
+//       Access: Public
+//  Description: Returns true if the texture has been placed on a
+//               palette image, false otherwise.  This will generally
+//               be true if get_omit_reason() returns OR_none or
+//               OR_solitary and false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TexturePlacement::
+is_placed() const {
+  return _image != (PaletteImage *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_image
+//       Access: Public
+//  Description: Returns the particular PaletteImage on which the
+//               texture has been placed.
+////////////////////////////////////////////////////////////////////
+PaletteImage *TexturePlacement::
+get_image() const {
+  nassertr(is_placed(), (PaletteImage *)NULL);
+  return _image;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_page
+//       Access: Public
+//  Description: Returns the particular PalettePage on which the
+//               texture has been placed.
+////////////////////////////////////////////////////////////////////
+PalettePage *TexturePlacement::
+get_page() const {
+  nassertr(is_placed(), (PalettePage *)NULL);
+  return _image->get_page();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_placed_x
+//       Access: Public
+//  Description: Returns the X pixel at which the texture has been
+//               placed within its PaletteImage.  It is an error to
+//               call this unless is_placed() returns true.
+////////////////////////////////////////////////////////////////////
+int TexturePlacement::
+get_placed_x() const {
+  nassertr(is_placed(), 0);
+  return _placed._x;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_placed_y
+//       Access: Public
+//  Description: Returns the Y pixel at which the texture has been
+//               placed within its PaletteImage.  It is an error to
+//               call this unless is_placed() returns true.
+////////////////////////////////////////////////////////////////////
+int TexturePlacement::
+get_placed_y() const {
+  nassertr(is_placed(), 0);
+  return _placed._y;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_placed_x_size
+//       Access: Public
+//  Description: Returns the size in the X dimension, in pixels, of
+//               the texture image as it has been placed within the
+//               palette.
+////////////////////////////////////////////////////////////////////
+int TexturePlacement::
+get_placed_x_size() const {
+  nassertr(is_placed(), 0);
+  return _placed._x_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_placed_y_size
+//       Access: Public
+//  Description: Returns the size in the Y dimension, in pixels, of
+//               the texture image as it has been placed within the
+//               palette.
+////////////////////////////////////////////////////////////////////
+int TexturePlacement::
+get_placed_y_size() const {
+  nassertr(is_placed(), 0);
+  return _placed._y_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::get_placed_uv_area
+//       Access: Public
+//  Description: Returns the total area of the rectangle occupied by
+//               the UV minmax box, as it has been placed.  See also
+//               get_uv_area().
+////////////////////////////////////////////////////////////////////
+double TexturePlacement::
+get_placed_uv_area() const {
+  nassertr(_has_uvs, 0.0);
+
+  TexCoordd range = _placed._max_uv - _placed._min_uv;
+  return range[0] * range[1];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::place_at
+//       Access: Public
+//  Description: Assigns the texture to a particular position within
+//               the indicated PaletteImage.  It is an error to call
+//               this if the texture has already been placed
+//               elsewhere.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+place_at(PaletteImage *image, int x, int y) {
+  nassertv(!is_placed());
+  nassertv(_size_known);
+
+  _image = image;
+  _is_filled = false;
+  _position._x = x;
+  _position._y = y;
+  _placed = _position;
+  _omit_reason = OR_none;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::force_replace
+//       Access: Public
+//  Description: Removes the texture from its particular PaletteImage,
+//               but does not remove it from the PaletteGroup.  It
+//               will be re-placed when the PaletteGroup::place_all()
+//               is called.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+force_replace() {
+  if (_image != (PaletteImage *)NULL) {
+    _image->unplace(this);
+    _image = (PaletteImage *)NULL;
+    _omit_reason = OR_working;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::omit_solitary
+//       Access: Public
+//  Description: Sets the omit reason (returned by get_omit()) to
+//               OR_solitary, indicating that the palettized version
+//               of the texture should not be used because it is the
+//               only texture on a PaletteImage.  However, the texture
+//               is still considered placed, and is_placed() will
+//               return true.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+omit_solitary() {
+  nassertv(is_placed());
+  _omit_reason = OR_solitary;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::not_solitary
+//       Access: Public
+//  Description: Indicates that the texture, formerly indicated as
+//               solitary, is now no longer.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+not_solitary() {
+  nassertv(is_placed());
+  _omit_reason = OR_none;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::intersects
+//       Access: Public
+//  Description: Returns true if the particular position this texture
+//               has been assigned to overlaps the rectangle whose
+//               top left corner is at x, y and whose size is given by
+//               x_size, y_size, or false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TexturePlacement::
+intersects(int x, int y, int x_size, int y_size) {
+  nassertr(is_placed(), false);
+
+  int hright = x + x_size;
+  int hbot = y + y_size;
+  
+  int mright = _placed._x + _placed._x_size;
+  int mbot = _placed._y + _placed._y_size;
+  
+  return !(x >= mright || hright <= _placed._x || 
+	   y >= mbot || hbot <= _placed._y);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::compute_tex_matrix
+//       Access: Public
+//  Description: Stores in the indicated matrix the appropriate
+//               texture matrix transform for the new placement of the
+//               texture.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+compute_tex_matrix(LMatrix3d &transform) {
+  nassertv(is_placed());
+
+  int x_size = _placed._x_size - _placed._margin * 2;
+  int y_size = _placed._y_size - _placed._margin * 2;
+
+  TexCoordd range = _placed._max_uv - _placed._min_uv;
+
+  int x_origin = (int)floor(_placed._min_uv[0] * x_size / range[0] + 0.5);
+  int y_origin = (int)floor(_placed._min_uv[1] * y_size / range[1] + 0.5);
+
+  x_size = (int)floor(x_size / range[0] + 0.5);
+  y_size = (int)floor(y_size / range[1] + 0.5);
+
+  int x = _placed._x + _placed._margin - x_origin;
+  int y = -_placed._y + _placed._margin - y_origin;
+
+  int pal_x_size = _image->get_x_size();
+  int pal_y_size = _image->get_y_size();
+
+  LVecBase2d t((double)x / (double)pal_x_size,
+	       (double)(pal_y_size - 1 - 
+			(_placed._y_size - 1 - y)) / (double)pal_y_size);
+	
+  LVecBase2d s((double)x_size / (double)pal_x_size, 
+	       (double)y_size / (double)pal_y_size);
+  
+  transform.set
+    (s[0],  0.0,  0.0,
+      0.0, s[1],  0.0,
+     t[0], t[1],  1.0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::write_placed
+//       Access: Public
+//  Description: Writes the placement position information on a line
+//               by itself.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+write_placed(ostream &out, int indent_level) {
+  indent(out, indent_level) 
+    << get_texture()->get_name();
+
+  if (is_placed()) {
+    out << " at "
+	<< get_placed_x() << " " << get_placed_y() << " to "
+	<< get_placed_x() + get_placed_x_size() << " "
+	<< get_placed_y() + get_placed_y_size() << " (used "
+	<< floor(get_placed_uv_area() * 10000.0 + 0.5) / 100.0
+	<< "%)\n";
+  } else {
+    out << " not yet placed.\n";
+  }
+};
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::is_filled
+//       Access: Public
+//  Description: Returns true if the texture has been filled
+//               (i.e. fill_image() has been called) since it was
+//               placed.
+////////////////////////////////////////////////////////////////////
+bool TexturePlacement::
+is_filled() const {
+  return _is_filled;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::fill_image
+//       Access: Public
+//  Description: Fills in the rectangle of the palette image
+//               represented by the texture placement with the image
+//               pixels.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+fill_image(PNMImage &image) {
+  nassertv(is_placed());
+
+  _is_filled = true;
+
+  // First, we need to determine the size to shrink the original image
+  // to.  This is almost, but not exactly, the same as the
+  // texture->size.  (It might differ a little because of the
+  // margins).
+  
+  int x_size = _placed._x_size - _placed._margin * 2;
+  int y_size = _placed._y_size - _placed._margin * 2;
+
+  // Actually, it might differ a great deal because of the UV range.
+  TexCoordd range = _placed._max_uv - _placed._min_uv;
+
+  int x_origin = (int)floor(_placed._min_uv[0] * x_size / range[0] + 0.5);
+  int y_origin = (int)floor(_placed._min_uv[1] * y_size / range[1] + 0.5);
+
+  x_size = (int)floor(x_size / range[0] + 0.5);
+  y_size = (int)floor(y_size / range[1] + 0.5);
+
+  // Now we get a PNMImage that represents the source texture at that
+  // size.
+  const PNMImage &source_full = _texture->read_source_image();
+  if (!source_full.is_valid()) {
+    flag_error_image(image);
+    return;
+  }
+
+  PNMImage source(x_size, y_size, source_full.get_num_channels(),
+		  source_full.get_maxval());
+  source.quick_filter_from(source_full);
+
+  bool alpha = image.has_alpha();
+  bool source_alpha = source.has_alpha();
+
+  // Now copy the pixels.  If we exceed the bounds from 0 .. size, we
+  // either repeat or clamp based on the texture reference.
+  for (int y = 0; y < _placed._y_size; y++) {
+    int sy = 
+      y_size - 1 -
+      ((_placed._y_size - 1 - y) - _placed._margin + y_origin);
+
+    if (_placed._wrap_v == EggTexture::WM_clamp) {
+      // Clamp at [0, y_size).
+      sy = max(min(sy, y_size - 1), 0);
+
+    } else {
+      // Wrap: sign-independent modulo.
+      sy = (sy < 0) ? y_size - 1 - ((-sy - 1) % y_size) : sy % y_size;
+    }
+
+    for (int x = 0; x < _placed._x_size; x++) {
+      int sx = x - _placed._margin + x_origin;
+
+      if (_placed._wrap_u == EggTexture::WM_clamp) {
+	// Clamp at [0, x_size).
+	sx = max(min(sx, x_size - 1), 0);
+	
+      } else {
+	// Wrap: sign-independent modulo.
+	sx = (sx < 0) ? x_size - 1 - ((-sx - 1) % x_size) : sx % x_size;
+      }
+
+      image.set_xel(x + _placed._x, y + _placed._y, source.get_xel(sx, sy));
+      if (alpha) {
+	if (source_alpha) {
+	  image.set_alpha(x + _placed._x, y + _placed._y,
+			  source.get_alpha(sx, sy));
+	} else {
+	  image.set_alpha(x + _placed._x, y + _placed._y, 1.0);
+	}
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::flag_error_image
+//       Access: Public
+//  Description: Sets the rectangle of the palette image
+//               represented by the texture placement to red, to
+//               represent a missing texture.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+flag_error_image(PNMImage &image) {
+  nassertv(is_placed());
+  for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) {
+    for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) {
+      image.set_xel_val(x, y, 1, 0, 0);
+    }
+  }
+  if (image.has_alpha()) {
+    for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) {
+      for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) {
+	image.set_alpha_val(x, y, 1);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::compute_size_from_uvs
+//       Access: Private
+//  Description: A support function for determine_size(), this
+//               computes the appropriate size of the texture in
+//               pixels based on the UV coverage (as well as on the
+//               size of the source texture).
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+compute_size_from_uvs(const TexCoordd &min_uv, const TexCoordd &max_uv) {
+  _position._min_uv = min_uv;
+  _position._max_uv = max_uv;
+
+  TexCoordd range = _position._max_uv - _position._min_uv;
+
+  _position._x_size = (int)floor(_texture->get_x_size() * range[0] + 0.5);
+  _position._y_size = (int)floor(_texture->get_y_size() * range[1] + 0.5);
+
+  // We arbitrarily require at least four pixels in each dimension.
+  // Fewer than this may be asking for trouble.
+  _position._x_size = max(_position._x_size, 4);
+  _position._y_size = max(_position._y_size, 4);
+
+  _position._margin = _texture->get_margin();
+
+  // Normally, we have interior margins, but if the image size is too
+  // small--i.e. the margin size is too great a percentage of the
+  // image size--we'll make them exterior margins so as not to overly
+  // degrade the quality of the image.
+  if ((double)_position._margin / (double)_position._x_size > 0.10) {
+    _position._x_size += _position._margin * 2;
+  }
+  if ((double)_position._margin / (double)_position._y_size > 0.10) {
+    _position._y_size += _position._margin * 2;
+  }
+
+  _size_known = true;
+}
+
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_TexturePlacement);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  writer->write_pointer(datagram, _texture);
+  writer->write_pointer(datagram, _group);
+  writer->write_pointer(datagram, _image);
+  
+  datagram.add_bool(_has_uvs);
+  datagram.add_bool(_size_known);
+  _position.write_datagram(writer, datagram);
+
+  datagram.add_bool(_is_filled);
+  _placed.write_datagram(writer, datagram);
+  datagram.add_int32((int)_omit_reason);
+
+  datagram.add_int32(_references.size());
+  References::const_iterator ri;
+  for (ri = _references.begin(); ri != _references.end(); ++ri) {
+    writer->write_pointer(datagram, (*ri));
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int TexturePlacement::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr((int)plist.size() >= 3 + _num_references, 0);
+  int index = 0;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_texture, plist[index], index);
+  }
+  index++;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_group, plist[index], index);
+  }
+  index++;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_image, plist[index], index);
+  }
+  index++;
+
+  int i;
+  for (i = 0; i < _num_references; i++) {
+    TextureReference *reference;
+    DCAST_INTO_R(reference, plist[index], index);
+    _references.insert(reference);
+    index++;
+  }
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::make_TexturePlacement
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* TexturePlacement::
+make_TexturePlacement(const FactoryParams &params) {
+  TexturePlacement *me = new TexturePlacement;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  manager->read_pointer(scan, this);  // _texture
+  manager->read_pointer(scan, this);  // _group
+  manager->read_pointer(scan, this);  // _image
+
+  _has_uvs = scan.get_bool();
+  _size_known = scan.get_bool();
+  _position.fillin(scan, manager);
+
+  _is_filled = scan.get_bool();
+  _placed.fillin(scan, manager);
+  _omit_reason = (OmitReason)scan.get_int32();
+
+  _num_references = scan.get_int32();
+  manager->read_pointers(scan, this, _num_references);
+}

+ 132 - 0
pandatool/src/egg-palettize/texturePlacement.h

@@ -0,0 +1,132 @@
+// Filename: texturePlacement.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREPLACEMENT_H
+#define TEXTUREPLACEMENT_H
+
+#include <pandatoolbase.h>
+
+#include "omitReason.h"
+#include "texturePosition.h"
+
+#include <typedWriteable.h>
+#include <luse.h>
+
+#include <set>
+
+
+class TextureImage;
+class PaletteGroup;
+class PaletteImage;
+class PalettePage;
+class TextureProperties;
+class TextureReference;
+class PNMImage;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TexturePlacement
+// Description : This corresponds to a particular assignment of a
+//               TextureImage with a PaletteGroup, and specifically
+//               describes which PaletteImage (if any), and where on
+//               the PaletteImage, the TextureImage has been assigned
+//               to.
+////////////////////////////////////////////////////////////////////
+class TexturePlacement : public TypedWriteable {
+private:
+  TexturePlacement();
+
+public:
+  TexturePlacement(TextureImage *texture, PaletteGroup *group);
+  ~TexturePlacement();
+
+  TextureImage *get_texture() const;
+  const TextureProperties &get_properties() const;
+  PaletteGroup *get_group() const;
+
+  void add_egg(TextureReference *reference);
+  void remove_egg(TextureReference *reference);
+
+  bool determine_size();
+  OmitReason get_omit_reason() const;
+  int get_x_size() const;
+  int get_y_size() const;
+  double get_uv_area() const;
+
+  bool is_placed() const;
+  PaletteImage *get_image() const;
+  PalettePage *get_page() const;
+  int get_placed_x() const;
+  int get_placed_y() const;
+  int get_placed_x_size() const;
+  int get_placed_y_size() const;
+  double get_placed_uv_area() const;
+
+  void place_at(PaletteImage *image, int x, int y);
+  void force_replace();
+  void omit_solitary();
+  void not_solitary();
+  bool intersects(int x, int y, int x_size, int y_size);
+
+  void compute_tex_matrix(LMatrix3d &transform);
+
+  void write_placed(ostream &out, int indent_level = 0);
+
+  bool is_filled() const;
+  void fill_image(PNMImage &image);
+  void flag_error_image(PNMImage &image);
+
+private:
+  void compute_size_from_uvs(const TexCoordd &min_uv, const TexCoordd &max_uv);
+
+  TextureImage *_texture;
+  PaletteGroup *_group;
+  PaletteImage *_image;
+
+  bool _has_uvs;
+  bool _size_known;
+  TexturePosition _position;
+
+  bool _is_filled;
+  TexturePosition _placed;
+  OmitReason _omit_reason;
+
+  typedef set<TextureReference *> References;
+  References _references;
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_TexturePlacement(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  // This value is only filled in while reading from the bam file;
+  // don't use it otherwise.
+  int _num_references;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    register_type(_type_handle, "TexturePlacement",
+		  TypedWriteable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif
+

+ 145 - 0
pandatool/src/egg-palettize/texturePosition.cxx

@@ -0,0 +1,145 @@
+// Filename: texturePosition.cxx
+// Created by:  drose (04Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "texturePosition.h"
+
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+TypeHandle TexturePosition::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePosition::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TexturePosition::
+TexturePosition() {
+  _margin = 0;
+  _x = 0;
+  _y = 0;
+  _x_size = 0;
+  _y_size = 0;
+  _min_uv.set(0.0, 0.0);
+  _max_uv.set(0.0, 0.0);
+  _wrap_u = EggTexture::WM_unspecified;
+  _wrap_v = EggTexture::WM_unspecified;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePosition::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TexturePosition::
+TexturePosition(const TexturePosition &copy) :
+  _margin(copy._margin),
+  _x(copy._x),
+  _y(copy._y),
+  _x_size(copy._x_size),
+  _y_size(copy._y_size),
+  _min_uv(copy._min_uv),
+  _max_uv(copy._max_uv),
+  _wrap_u(copy._wrap_u),
+  _wrap_v(copy._wrap_v)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePosition::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TexturePosition::
+operator = (const TexturePosition &copy) {
+  _margin = copy._margin;
+  _x = copy._x;
+  _y = copy._y;
+  _x_size = copy._x_size;
+  _y_size = copy._y_size;
+  _min_uv = copy._min_uv;
+  _max_uv = copy._max_uv;
+  _wrap_u = copy._wrap_u;
+  _wrap_v = copy._wrap_v;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePosition::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void TexturePosition::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_TexturePosition);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePosition::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void TexturePosition::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  datagram.add_int32(_margin);
+  datagram.add_int32(_x);
+  datagram.add_int32(_y);
+  datagram.add_int32(_x_size);
+  datagram.add_int32(_y_size);
+  datagram.add_float64(_min_uv[0]);
+  datagram.add_float64(_min_uv[1]);
+  datagram.add_float64(_max_uv[0]);
+  datagram.add_float64(_max_uv[1]);
+  datagram.add_int32((int)_wrap_u);
+  datagram.add_int32((int)_wrap_v);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePosition::make_TexturePosition
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* TexturePosition::
+make_TexturePosition(const FactoryParams &params) {
+  TexturePosition *me = new TexturePosition;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePosition::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void TexturePosition::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _margin = scan.get_int32();
+  _x = scan.get_int32();
+  _y = scan.get_int32();
+  _x_size = scan.get_int32();
+  _y_size = scan.get_int32();
+  _min_uv[0] = scan.get_float64();
+  _min_uv[1] = scan.get_float64();
+  _max_uv[0] = scan.get_float64();
+  _max_uv[1] = scan.get_float64();
+  _wrap_u = (EggTexture::WrapMode)scan.get_int32();
+  _wrap_v = (EggTexture::WrapMode)scan.get_int32();
+}

+ 68 - 0
pandatool/src/egg-palettize/texturePosition.h

@@ -0,0 +1,68 @@
+// Filename: texturePosition.h
+// Created by:  drose (04Dec00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREPOSITION_H
+#define TEXTUREPOSITION_H
+
+#include <pandatoolbase.h>
+
+#include <typedWriteable.h>
+#include <luse.h>
+#include <eggTexture.h>
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TexturePosition
+// Description : This represents a particular position of a texture
+//               within a PaletteImage.  There is only one of these
+//               per TexturePlacement, but it exists as a separate
+//               structure so the TexturePlacement can easily consider
+//               repositioning the texture.
+////////////////////////////////////////////////////////////////////
+class TexturePosition : public TypedWriteable {
+public:
+  TexturePosition();
+  TexturePosition(const TexturePosition &copy);
+  void operator = (const TexturePosition &copy);
+
+  int _margin;
+  int _x, _y;
+  int _x_size, _y_size;
+
+  TexCoordd _min_uv;
+  TexCoordd _max_uv;
+
+  EggTexture::WrapMode _wrap_u;
+  EggTexture::WrapMode _wrap_v;
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+
+protected:
+  static TypedWriteable *make_TexturePosition(const FactoryParams &params);
+
+public:
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    register_type(_type_handle, "TexturePosition",
+		  TypedWriteable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif
+

+ 605 - 0
pandatool/src/egg-palettize/textureProperties.cxx

@@ -0,0 +1,605 @@
+// Filename: textureProperties.cxx
+// Created by:  drose (29Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "textureProperties.h"
+#include "palettizer.h"
+
+#include <pnmFileType.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+TypeHandle TextureProperties::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextureProperties::
+TextureProperties() {
+  _got_num_channels = false;
+  _num_channels = 0;
+  _format = EggTexture::F_unspecified;
+  _minfilter = EggTexture::FT_unspecified;
+  _magfilter = EggTexture::FT_unspecified;
+  _color_type = (PNMFileType *)NULL;
+  _alpha_type = (PNMFileType *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextureProperties::
+TextureProperties(const TextureProperties &copy) :
+  _got_num_channels(copy._got_num_channels),
+  _num_channels(copy._num_channels),
+  _format(copy._format),
+  _minfilter(copy._minfilter),
+  _magfilter(copy._magfilter),
+  _color_type(copy._color_type),
+  _alpha_type(copy._alpha_type)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TextureProperties::
+operator = (const TextureProperties &copy) {
+  _got_num_channels = copy._got_num_channels;
+  _num_channels = copy._num_channels;
+  _format = copy._format;
+  _minfilter = copy._minfilter;
+  _magfilter = copy._magfilter;
+  _color_type = copy._color_type;
+  _alpha_type = copy._alpha_type;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::has_num_channels
+//       Access: Public
+//  Description: Returns true if the number of channels is known.
+////////////////////////////////////////////////////////////////////
+bool TextureProperties::
+has_num_channels() const {
+  return _got_num_channels;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::get_num_channels
+//       Access: Public
+//  Description: Returns the number of channels (1 through 4)
+//               associated with the image.  It is an error to call
+//               this unless has_num_channels() returns true.
+////////////////////////////////////////////////////////////////////
+int TextureProperties::
+get_num_channels() const {
+  nassertr(_got_num_channels, 0);
+  return _num_channels;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::uses_alpha
+//       Access: Public
+//  Description: Returns true if the texture uses an alpha channel,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TextureProperties::
+uses_alpha() const {
+  return (_num_channels == 2 || _num_channels == 4 ||
+	  _format == EggTexture::F_alpha);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::get_string
+//       Access: Public
+//  Description: Returns a string corresponding to the
+//               TextureProperties object.  Each unique set of
+//               TextureProperties will generate a unique string.
+//               This is used to generate unique palette image
+//               filenames.
+////////////////////////////////////////////////////////////////////
+string TextureProperties::
+get_string() const {
+  string result;
+
+  if (_got_num_channels) {
+    ostringstream num;
+    num << _num_channels;
+    result += num.str();
+  }
+  result += get_format_string(_format);
+  result += get_filter_string(_minfilter);
+  result += get_filter_string(_magfilter);
+  result += get_type_string(_color_type, _alpha_type);
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::update_properties
+//       Access: Public
+//  Description: If the indicate TextureProperties structure is more
+//               specific than this one, updates this one.
+////////////////////////////////////////////////////////////////////
+void TextureProperties::
+update_properties(const TextureProperties &other) {
+  if (!_got_num_channels) {
+    _got_num_channels = other._got_num_channels;
+    _num_channels = other._num_channels;
+  }
+  _format = union_format(_format, other._format);
+  _minfilter = union_filter(_minfilter, other._minfilter);
+  _magfilter = union_filter(_magfilter, other._magfilter);
+
+  if (_color_type == (PNMFileType *)NULL) {
+    _color_type = other._color_type;
+    _alpha_type = other._alpha_type;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::fully_define
+//       Access: Public
+//  Description: If any properties remain unspecified, specify them
+//               now.  Also reconcile conflicting information.
+////////////////////////////////////////////////////////////////////
+void TextureProperties::
+fully_define() {
+  if (!_got_num_channels) {
+    switch (_format) {
+    case EggTexture::F_rgba:
+    case EggTexture::F_rgba12:
+    case EggTexture::F_rgba8:
+    case EggTexture::F_rgba4:
+    case EggTexture::F_rgba5:
+      _num_channels = 4;
+      break;
+    
+    case EggTexture::F_unspecified: 
+    case EggTexture::F_rgb:
+    case EggTexture::F_rgb12:
+    case EggTexture::F_rgb8:
+    case EggTexture::F_rgb5:
+    case EggTexture::F_rgb332:
+      _num_channels = 3;
+      break;
+
+    case EggTexture::F_luminance_alpha:
+      _num_channels = 2;
+      break;
+
+    case EggTexture::F_red:
+    case EggTexture::F_green:
+    case EggTexture::F_blue:
+    case EggTexture::F_alpha:
+    case EggTexture::F_luminance:
+      _num_channels = 1;
+      break;
+    }
+    _got_num_channels = true;
+  }
+
+  // Make sure the format reflects the number of channels.
+  switch (_num_channels) {
+  case 1:
+    switch (_format) {
+    case EggTexture::F_red:
+    case EggTexture::F_green:
+    case EggTexture::F_blue:
+    case EggTexture::F_alpha:
+    case EggTexture::F_luminance:
+      break;
+
+    default:
+      _format = EggTexture::F_luminance;
+    }
+    break;
+
+  case 2:
+    switch (_format) {
+    case EggTexture::F_luminance_alpha:
+      break;
+
+    default:
+      _format = EggTexture::F_luminance_alpha;
+    }
+    break;
+
+  case 3:
+    switch (_format) {
+    case EggTexture::F_rgb:
+    case EggTexture::F_rgb12:
+    case EggTexture::F_rgb8:
+    case EggTexture::F_rgb5:
+    case EggTexture::F_rgb332:
+      break;
+
+    case EggTexture::F_rgba8:  
+      _format = EggTexture::F_rgb8;
+      break;
+
+    case EggTexture::F_rgba5:
+    case EggTexture::F_rgba4:
+      _format = EggTexture::F_rgb5;
+      break;
+
+    default:
+      _format = EggTexture::F_rgb;
+    }
+    break;
+
+  case 4:
+    switch (_format) {
+    case EggTexture::F_rgba:
+    case EggTexture::F_rgba12:
+    case EggTexture::F_rgba8:
+    case EggTexture::F_rgba4:
+    case EggTexture::F_rgba5:
+      break;
+
+    default:
+      _format = EggTexture::F_rgba;
+    }
+  }
+
+  switch (_minfilter) {
+  case EggTexture::FT_unspecified:
+    _minfilter = EggTexture::FT_linear;
+    break;
+
+  default:
+    break;
+  }
+
+  switch (_magfilter) {
+  case EggTexture::FT_unspecified:
+  case EggTexture::FT_nearest_mipmap_nearest:
+  case EggTexture::FT_linear_mipmap_nearest:
+  case EggTexture::FT_nearest_mipmap_linear:
+  case EggTexture::FT_linear_mipmap_linear:
+    _magfilter = EggTexture::FT_linear;
+    break;
+
+  default:
+    break;
+  }
+
+  if (_color_type == (PNMFileType *)NULL) {
+    _color_type = pal->_color_type;
+    _alpha_type = pal->_alpha_type;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::update_egg_tex
+//       Access: Public
+//  Description: Adjusts the texture properties of the indicated egg
+//               reference to match these properties.
+////////////////////////////////////////////////////////////////////
+void TextureProperties::
+update_egg_tex(EggTexture *egg_tex) const {
+  egg_tex->set_format(_format);
+  egg_tex->set_minfilter(_minfilter);
+  egg_tex->set_magfilter(_minfilter);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::Ordering Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool TextureProperties::
+operator < (const TextureProperties &other) const {
+  if (_format != other._format) {
+    return (int)_format < (int)other._format;
+  }
+  if (_minfilter != other._minfilter) {
+    return (int)_minfilter < (int)other._minfilter;
+  }
+  if (_magfilter != other._magfilter) {
+    return (int)_magfilter < (int)other._magfilter;
+  }
+  if (_color_type != other._color_type) {
+    return _color_type < other._color_type;
+  }
+  if (_color_type != (PNMFileType *)NULL) {
+    if (_alpha_type != other._alpha_type) {
+      return _alpha_type < other._alpha_type;
+    }
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::Equality Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool TextureProperties::
+operator == (const TextureProperties &other) const {
+  return (_format == other._format &&
+	  _minfilter == other._minfilter &&
+	  _magfilter == other._magfilter &&
+	  _color_type == other._color_type &&
+	  (_color_type == (PNMFileType *)NULL ||
+	   _alpha_type == other._alpha_type));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::Nonequality Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool TextureProperties::
+operator != (const TextureProperties &other) const {
+  return !operator == (other);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::get_format_string
+//       Access: Private, Static
+//  Description: Returns a short string representing the given
+//               EggTexture format.
+////////////////////////////////////////////////////////////////////
+string TextureProperties::
+get_format_string(EggTexture::Format format) {
+  switch (format) {
+  case EggTexture::F_unspecified:
+    return "u";
+
+  case EggTexture::F_rgba:
+    return "a";
+
+  case EggTexture::F_rgba12:
+    return "a12";
+
+  case EggTexture::F_rgba8:
+    return "a8";
+
+  case EggTexture::F_rgba4:
+    return "a4";
+
+  case EggTexture::F_rgba5:
+    return "a5";
+
+  case EggTexture::F_rgb:
+    return "c";
+
+  case EggTexture::F_rgb12:
+    return "c12";
+
+  case EggTexture::F_rgb8:
+    return "c8";
+
+  case EggTexture::F_rgb5:
+    return "c5";
+
+  case EggTexture::F_rgb332:
+    return "c3";
+
+  case EggTexture::F_luminance_alpha:
+    return "t"; // t for two-channel
+
+  case EggTexture::F_red:
+    return "r";
+
+  case EggTexture::F_green:
+    return "g";
+
+  case EggTexture::F_blue:
+    return "b";
+
+  case EggTexture::F_alpha:
+    return "a";
+
+  case EggTexture::F_luminance:
+    return "l";
+  }
+
+  return "x";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::get_filter_string
+//       Access: Private, Static
+//  Description: Returns a short string representing the given
+//               EggTexture filter type.
+////////////////////////////////////////////////////////////////////
+string TextureProperties::
+get_filter_string(EggTexture::FilterType filter_type) {
+  switch (filter_type) {
+  case EggTexture::FT_unspecified:
+    return "u";
+
+  case EggTexture::FT_nearest:
+    return "n";
+
+  case EggTexture::FT_linear:
+    return "l";
+
+  case EggTexture::FT_nearest_mipmap_nearest:
+    return "m1";
+    
+  case EggTexture::FT_linear_mipmap_nearest:
+    return "m2";
+   
+  case EggTexture::FT_nearest_mipmap_linear:
+    return "m3";
+    
+  case EggTexture::FT_linear_mipmap_linear:
+    return "m";
+  }
+  
+  return "x";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::get_type_string
+//       Access: Private, Static
+//  Description: Returns a short string representing whether the color
+//               and/or alpha type has been specified or not.
+////////////////////////////////////////////////////////////////////
+string TextureProperties::
+get_type_string(PNMFileType *color_type, PNMFileType *alpha_type) {
+  if (color_type == (PNMFileType *)NULL) {
+    return "";
+  }
+  if (alpha_type == (PNMFileType *)NULL) {
+    return "c";
+  }
+  return "a";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::union_format
+//       Access: Private, Static
+//  Description: Returns the EggTexture format which is the more
+//               specific of the two.
+////////////////////////////////////////////////////////////////////
+EggTexture::Format TextureProperties::
+union_format(EggTexture::Format a, EggTexture::Format b) {
+  switch (a) {
+  case EggTexture::F_unspecified:
+    return b;
+
+  case EggTexture::F_rgba:
+    switch (b) {
+    case EggTexture::F_rgba12:
+    case EggTexture::F_rgba8:
+    case EggTexture::F_rgba4:
+    case EggTexture::F_rgba5:
+      return b;
+
+    default:
+      return a;
+    };
+
+  case EggTexture::F_rgb:
+    if (b != EggTexture::F_unspecified) {
+      return b;
+    }
+    return a;
+
+  default:
+    return a;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::union_filter
+//       Access: Private, Static
+//  Description: Returns the EggTexture filter type which is the more
+//               specific of the two.
+////////////////////////////////////////////////////////////////////
+EggTexture::FilterType TextureProperties::
+union_filter(EggTexture::FilterType a, EggTexture::FilterType b) {
+  if ((int)a < (int)b) {
+    return b;
+  } else {
+    return a;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void TextureProperties::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_TextureProperties);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void TextureProperties::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  datagram.add_bool(_got_num_channels);
+  datagram.add_int32(_num_channels);
+  datagram.add_int32((int)_format);
+  datagram.add_int32((int)_minfilter);
+  datagram.add_int32((int)_magfilter);
+  writer->write_pointer(datagram, _color_type);
+  writer->write_pointer(datagram, _alpha_type);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int TextureProperties::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr(plist.size() >= 2, 0);
+  int index = 0;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_color_type, plist[index], index);
+  }
+  index++;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_alpha_type, plist[index], index);
+  }
+  index++;
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::make_TextureProperties
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* TextureProperties::
+make_TextureProperties(const FactoryParams &params) {
+  TextureProperties *me = new TextureProperties;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureProperties::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void TextureProperties::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _got_num_channels = scan.get_bool();
+  _num_channels = scan.get_int32();
+  _format = (EggTexture::Format)scan.get_int32();
+  _minfilter = (EggTexture::FilterType)scan.get_int32();
+  _magfilter = (EggTexture::FilterType)scan.get_int32();
+  manager->read_pointer(scan, this);  // _color_type
+  manager->read_pointer(scan, this);  // _alpha_type
+}

+ 92 - 0
pandatool/src/egg-palettize/textureProperties.h

@@ -0,0 +1,92 @@
+// Filename: textureProperties.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREPROPERTIES_H
+#define TEXTUREPROPERTIES_H
+
+#include <pandatoolbase.h>
+
+#include <eggTexture.h>
+#include <typedWriteable.h>
+
+class PNMFileType;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TextureProperties
+// Description : This is the set of characteristics of a texture that,
+//               if different from another texture, prevent the two
+//               textures from sharing a PaletteImage.  It includes
+//               properties such as mipmapping, number of channels,
+//               etc.
+////////////////////////////////////////////////////////////////////
+class TextureProperties : public TypedWriteable {
+public:
+  TextureProperties();
+  TextureProperties(const TextureProperties &copy);
+  void operator = (const TextureProperties &copy);
+
+  bool has_num_channels() const;
+  int get_num_channels() const;
+  bool uses_alpha() const;
+
+  string get_string() const;
+  void update_properties(const TextureProperties &other);
+  void fully_define();
+
+  void update_egg_tex(EggTexture *egg_tex) const;
+
+  bool operator < (const TextureProperties &other) const;
+  bool operator == (const TextureProperties &other) const;
+  bool operator != (const TextureProperties &other) const;
+
+  bool _got_num_channels;
+  int _num_channels;
+  EggTexture::Format _format;
+  EggTexture::FilterType _minfilter, _magfilter;
+  PNMFileType *_color_type;
+  PNMFileType *_alpha_type;
+
+private:
+  static string get_format_string(EggTexture::Format format);
+  static string get_filter_string(EggTexture::FilterType filter_type);
+  static string get_type_string(PNMFileType *color_type, 
+				PNMFileType *alpha_type);
+
+  static EggTexture::Format union_format(EggTexture::Format a,
+					 EggTexture::Format b);
+  static EggTexture::FilterType union_filter(EggTexture::FilterType a,
+					     EggTexture::FilterType b);
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_TextureProperties(const FactoryParams &params);
+
+public:
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    register_type(_type_handle, "TextureProperties",
+		  TypedWriteable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif
+

+ 687 - 0
pandatool/src/egg-palettize/textureReference.cxx

@@ -0,0 +1,687 @@
+// Filename: textureReference.cxx
+// Created by:  drose (29Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "textureReference.h"
+#include "textureImage.h"
+#include "paletteImage.h"
+#include "sourceTextureImage.h"
+#include "texturePlacement.h"
+#include "palettizer.h"
+
+#include <indent.h>
+#include <eggTexture.h>
+#include <eggData.h>
+#include <eggGroupNode.h>
+#include <eggNurbsSurface.h>
+#include <eggVertexPool.h>
+#include <datagram.h>
+#include <datagramIterator.h>
+#include <bamReader.h>
+#include <bamWriter.h>
+
+#include <math.h>
+
+TypeHandle TextureReference::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextureReference::
+TextureReference() {
+  _egg_tex = (EggTexture *)NULL;
+  _tex_mat = LMatrix3d::ident_mat();
+  _inv_tex_mat = LMatrix3d::ident_mat();
+  _source_texture = (SourceTextureImage *)NULL;
+  _placement = (TexturePlacement *)NULL;
+  _uses_alpha = false;
+  _any_uvs = false;
+  _min_uv.set(0.0, 0.0);
+  _max_uv.set(0.0, 0.0);
+  _wrap_u = EggTexture::WM_unspecified;
+  _wrap_v = EggTexture::WM_unspecified;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextureReference::
+~TextureReference() {
+  clear_placement();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::from_egg
+//       Access: Public
+//  Description: Sets up the TextureReference using information
+//               extracted from an egg file.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+from_egg(EggData *data, EggTexture *egg_tex) {
+  _egg_tex = egg_tex;
+  _egg_data = data;
+
+  if (_egg_tex->has_transform()) {
+    _tex_mat = _egg_tex->get_transform();
+    if (!_inv_tex_mat.invert_from(_tex_mat)) {
+      _inv_tex_mat = LMatrix3d::ident_mat();
+    }
+  } else {
+    _tex_mat = LMatrix3d::ident_mat();
+    _inv_tex_mat = LMatrix3d::ident_mat();
+  }
+
+  Filename filename = _egg_tex->get_filename();
+  Filename alpha_filename;
+  if (_egg_tex->has_alpha_file()) {
+    alpha_filename = _egg_tex->get_alpha_file();
+  }
+
+  _properties._format = _egg_tex->get_format();
+  _properties._minfilter = _egg_tex->get_minfilter();
+  _properties._magfilter = _egg_tex->get_magfilter();
+
+  string name = filename.get_basename();
+  TextureImage *texture = pal->get_texture(name);
+  _source_texture = texture->get_source(filename, alpha_filename);
+  _source_texture->update_properties(_properties);
+
+  _uses_alpha = false;
+  EggAlphaMode::AlphaMode alpha_mode = _egg_tex->get_alpha_mode();
+  if (alpha_mode == EggAlphaMode::AM_unspecified) {
+    if (_source_texture->get_size()) {
+      _uses_alpha = 
+	_egg_tex->has_alpha_channel(_source_texture->get_num_channels());
+    }
+
+  } else if (alpha_mode == EggAlphaMode::AM_off) {
+    _uses_alpha = false;
+    
+  } else {
+    _uses_alpha = true;
+  }
+
+  get_uv_range(_egg_data);
+
+  _wrap_u = egg_tex->determine_wrap_u();
+  _wrap_v = egg_tex->determine_wrap_v();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_source
+//       Access: Public
+//  Description: Returns the SourceTextureImage that this object
+//               refers to.
+////////////////////////////////////////////////////////////////////
+SourceTextureImage *TextureReference::
+get_source() const {
+  return _source_texture;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_texture
+//       Access: Public
+//  Description: Returns the TextureImage that this object refers to.
+////////////////////////////////////////////////////////////////////
+TextureImage *TextureReference::
+get_texture() const {
+  return _source_texture->get_texture();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::has_uvs
+//       Access: Public
+//  Description: Returns true if this TextureReference actually uses
+//               the texture on geometry, with UV's and everything, or
+//               false otherwise.  Strictly speaking, this should
+//               always return true.
+////////////////////////////////////////////////////////////////////
+bool TextureReference::
+has_uvs() const {
+  return _any_uvs;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_min_uv
+//       Access: Public
+//  Description: Returns the minimum UV coordinate in use for the
+//               texture by this reference.
+////////////////////////////////////////////////////////////////////
+const TexCoordd &TextureReference::
+get_min_uv() const {
+  nassertr(_any_uvs, _min_uv);
+  return _min_uv;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_max_uv
+//       Access: Public
+//  Description: Returns the maximum UV coordinate in use for the
+//               texture by this reference.
+////////////////////////////////////////////////////////////////////
+const TexCoordd &TextureReference::
+get_max_uv() const {
+  nassertr(_any_uvs, _max_uv);
+  return _max_uv;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_wrap_u
+//       Access: Public
+//  Description: Returns the specification for the wrapping in the U
+//               direction.
+////////////////////////////////////////////////////////////////////
+EggTexture::WrapMode TextureReference::
+get_wrap_u() const {
+  return _wrap_u;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_wrap_v
+//       Access: Public
+//  Description: Returns the specification for the wrapping in the V
+//               direction.
+////////////////////////////////////////////////////////////////////
+EggTexture::WrapMode TextureReference::
+get_wrap_v() const {
+  return _wrap_v;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::set_placement
+//       Access: Public
+//  Description: Sets the particular TexturePlacement that is
+//               appropriate for this egg file.  This is called by
+//               EggFile::choose_placements().
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+set_placement(TexturePlacement *placement) {
+  if (_placement != placement) {
+    if (_placement != (TexturePlacement *)NULL) {
+      // Remove our reference from the old placement object.
+      _placement->remove_egg(this);
+    }
+    _placement = placement;
+    if (_placement != (TexturePlacement *)NULL) {
+      // Add our reference to the new placement object.
+      _placement->add_egg(this);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::clear_placement
+//       Access: Public
+//  Description: Removes any reference to a TexturePlacement.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+clear_placement() {
+  set_placement((TexturePlacement *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_placement
+//       Access: Public
+//  Description: Returns the particular TexturePlacement that is
+//               appropriate for this egg file.  This will not be
+//               filled in until EggFile::choose_placements() has been
+//               called.
+////////////////////////////////////////////////////////////////////
+TexturePlacement *TextureReference::
+get_placement() const {
+  return _placement;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::update_egg
+//       Access: Public
+//  Description: Updates the egg file with all the relevant
+//               information to reference the texture in its new home,
+//               wherever that might be.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+update_egg() {
+  if (_egg_tex == (EggTexture *)NULL) {
+    // Not much we can do if we don't have an actual egg file to
+    // reference.
+    return;
+  }
+
+  nassertv(_placement != (TexturePlacement *)NULL);
+
+  // We check for an OmitReason of OR_none, rather than asking
+  // is_placed(), because in this case we don't want to consider an
+  // OR_solitary texture as having been placed.
+  if (_placement->get_omit_reason() != OR_none) {
+    // The texture does not appear on a palette.  This is the easy
+    // case; we simply have to update the texture reference to the new
+    // texture location.
+    TextureImage *texture = _placement->get_texture();
+    nassertv(texture != (TextureImage *)NULL);
+    texture->update_egg_tex(_egg_tex);
+    return;
+  }
+
+  // The texture *does* appear on a palette.  This means we need to
+  // not only update the texture reference, but also adjust the UV's.
+  // In most cases, we can do this by simply applying a texture matrix
+  // to the reference.
+  PaletteImage *image = _placement->get_image();
+  nassertv(image != (PaletteImage *)NULL);
+
+  image->update_egg_tex(_egg_tex);
+  // Palette images never wrap.
+  _egg_tex->set_wrap_mode(EggTexture::WM_clamp);
+  _egg_tex->set_wrap_u(EggTexture::WM_unspecified);
+  _egg_tex->set_wrap_v(EggTexture::WM_unspecified);
+
+  LMatrix3d new_tex_mat;
+  _placement->compute_tex_matrix(new_tex_mat);
+
+  // Compose the new texture matrix with whatever matrix was already
+  // there, if any.
+  _egg_tex->set_transform(_tex_mat * new_tex_mat);
+
+  // Finally, go back and actually adjust the UV's to match what we
+  // claimed they could be.
+  if (pal->_remap_uv != Palettizer::RU_never) {
+    update_uv_range(_egg_data);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::output
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+output(ostream &out) const {
+  out << *_source_texture << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::write
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level) 
+    << get_texture()->get_name();
+
+  if (_uses_alpha) {
+    out << " alpha";
+  }
+
+  if (_any_uvs) {
+    // Compute the fraction of the image that is covered by the UV's
+    // minmax rectangle.
+    TexCoordd box = _max_uv - _min_uv;
+    double area = box[0] * box[1];
+
+    out << " used " << floor(area * 10000.0 + 0.5) / 100.0 << "%";
+  }
+
+  if (_properties._format != EggTexture::F_unspecified) {
+    out << " " << _properties._format;
+  }
+
+  switch (_properties._minfilter) {
+  case EggTexture::FT_nearest_mipmap_nearest:
+  case EggTexture::FT_linear_mipmap_nearest:
+  case EggTexture::FT_nearest_mipmap_linear:
+  case EggTexture::FT_linear_mipmap_linear:
+    out << " mipmap";
+    break;
+
+  default:
+    break;
+  }
+
+  out << "\n";
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::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.
+//
+//               If pal->_remap_uv is not RU_never, this will also
+//               attempt to remap the UV's found so that the midpoint
+//               lies in the unit square (0,0) - (1,1), in the hopes
+//               of maximizing overlap of UV coordinates between
+//               different polygons.  However, the hypothetical
+//               translations are not actually applied to the egg file
+//               at this point (because we might decide not to place
+//               the texture in a palette); they will actually be
+//               applied when update_uv_range(), below, is called
+//               later.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+get_uv_range(EggGroupNode *group) {
+  EggGroupNode::iterator ci;
+
+  bool group_any_uvs = false;
+  TexCoordd group_min_uv, group_max_uv;
+
+  for (ci = group->begin(); ci != group->end(); ci++) {
+    EggNode *child = (*ci);
+    if (child->is_of_type(EggNurbsSurface::get_class_type())) {
+      EggNurbsSurface *nurbs = DCAST(EggNurbsSurface, child);
+      if (nurbs->has_texture() && nurbs->get_texture() == _egg_tex) {
+	// Here's a NURBS surface that references the texture.  Unlike
+	// other kinds of geometries, NURBS don't store UV's; they're
+	// implicit in the surface.  NURBS UV's will always run in the
+	// range (0,0) - (1,1).  However, we do need to apply the
+	// texture matrix.
+
+	// We also don't count the NURBS surfaces in with the group's
+	// UV's, because we can't adjust the UV's on a NURBS, so
+	// counting them up would be misleading (the reason we count
+	// up the group UV's is so we can consider adjusting them
+	// later).  Instead, we just accumulate the NURBS UV's
+	// directly into our total.
+
+	static const int num_nurbs_uvs = 4;
+	static TexCoordd nurbs_uvs[num_nurbs_uvs] = {
+	  TexCoordd(0.0, 0.0),
+	  TexCoordd(0.0, 1.0),
+	  TexCoordd(1.0, 1.0),
+	  TexCoordd(1.0, 0.0)
+	};
+
+	for (int i = 0; i < num_nurbs_uvs; i++) {
+	  TexCoordd uv = nurbs_uvs[i] * _tex_mat;
+	  collect_uv(_any_uvs, _min_uv, _max_uv, uv, uv);
+	}
+      }
+
+    } else if (child->is_of_type(EggPrimitive::get_class_type())) {
+      EggPrimitive *geom = DCAST(EggPrimitive, child);
+      if (geom->has_texture() && geom->get_texture() == _egg_tex) {
+	// Here's a piece of geometry that references this texture.
+	// Walk through its vertices and get its UV's.
+	TexCoordd geom_min_uv, geom_max_uv;
+
+	if (get_geom_uvs(geom, geom_min_uv, geom_max_uv)) {
+	  if (pal->_remap_uv == Palettizer::RU_poly) {
+	    LVector2d trans = translate_uv(geom_min_uv, geom_max_uv);
+	    geom_min_uv += trans;
+	    geom_max_uv += trans;
+	  }
+	  collect_uv(group_any_uvs, group_min_uv, group_max_uv, 
+		     geom_min_uv, geom_max_uv);
+	}
+      }
+
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      EggGroupNode *cg = DCAST(EggGroupNode, child);
+      get_uv_range(cg);
+    }
+  }
+
+  if (group_any_uvs) {
+    if (pal->_remap_uv == Palettizer::RU_group) {
+      LVector2d trans = translate_uv(group_min_uv, group_max_uv);
+      group_min_uv += trans;
+      group_max_uv += trans;
+    }
+    collect_uv(_any_uvs, _min_uv, _max_uv, group_min_uv, group_max_uv);
+  }
+}	
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::update_uv_range
+//       Access: Private
+//  Description: Actually applies the UV translates that were assumed
+//               in the previous call to get_uv_range().
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+update_uv_range(EggGroupNode *group) {
+  EggGroupNode::iterator ci;
+
+  bool group_any_uvs = false;
+  TexCoordd group_min_uv, group_max_uv;
+
+  for (ci = group->begin(); ci != group->end(); ci++) {
+    EggNode *child = (*ci);
+    if (child->is_of_type(EggNurbsSurface::get_class_type())) {
+      // We do nothing at this point for a Nurbs.  Nothing we can do
+      // about these things.
+
+    } else if (child->is_of_type(EggPrimitive::get_class_type())) {
+      EggPrimitive *geom = DCAST(EggPrimitive, child);
+      if (geom->has_texture() && geom->get_texture() == _egg_tex) {
+	TexCoordd geom_min_uv, geom_max_uv;
+
+	if (get_geom_uvs(geom, geom_min_uv, geom_max_uv)) {
+	  if (pal->_remap_uv == Palettizer::RU_poly) {
+	    LVector2d trans = translate_uv(geom_min_uv, geom_max_uv);
+	    trans = trans * _inv_tex_mat;
+	    if (!trans.almost_equal(LVector2d::zero())) {
+	      translate_geom_uvs(geom, trans);
+	    }
+	  } else {
+	    collect_uv(group_any_uvs, group_min_uv, group_max_uv,
+		       geom_min_uv, geom_max_uv);
+	  }
+	}
+      }
+
+    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
+      EggGroupNode *cg = DCAST(EggGroupNode, child);
+      update_uv_range(cg);
+    }
+  }
+
+  if (group_any_uvs && pal->_remap_uv == Palettizer::RU_group) {
+    LVector2d trans = translate_uv(group_min_uv, group_max_uv);
+    trans = trans * _inv_tex_mat;
+    if (!trans.almost_equal(LVector2d::zero())) {
+      for (ci = group->begin(); ci != group->end(); ci++) {
+	EggNode *child = (*ci);
+	if (child->is_of_type(EggPrimitive::get_class_type())) {
+	  EggPrimitive *geom = DCAST(EggPrimitive, child);
+	  if (geom->has_texture() && geom->get_texture() == _egg_tex) {
+	    translate_geom_uvs(geom, trans);
+	  }
+	}
+      }
+    }
+  }
+}	
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::get_geom_uvs
+//       Access: Private
+//  Description: Determines the minimum and maximum UV range for a
+//               particular primitive.  Returns true if it has any
+//               UV's, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TextureReference::
+get_geom_uvs(EggPrimitive *geom,
+	     TexCoordd &geom_min_uv, TexCoordd &geom_max_uv) {
+  bool geom_any_uvs = false;
+
+  EggPrimitive::iterator pi;
+  for (pi = geom->begin(); pi != geom->end(); ++pi) {
+    EggVertex *vtx = (*pi);
+    if (vtx->has_uv()) {
+      TexCoordd uv = vtx->get_uv() * _tex_mat;
+      collect_uv(geom_any_uvs, geom_min_uv, geom_max_uv, uv, uv);
+    }
+  }
+
+  return geom_any_uvs;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::translate_geom_uvs
+//       Access: Private
+//  Description: Applies the indicated translation to each UV in the
+//               primitive.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+translate_geom_uvs(EggPrimitive *geom, const TexCoordd &trans) const {
+  EggPrimitive::iterator pi;
+  for (pi = geom->begin(); pi != geom->end(); ++pi) {
+    EggVertex *vtx = (*pi);
+    if (vtx->has_uv()) {
+      EggVertex vtx_copy(*vtx);
+      vtx_copy.set_uv(vtx_copy.get_uv() + trans);
+      geom->replace(pi, vtx->get_pool()->create_unique_vertex(vtx_copy));
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::collect_uv
+//       Access: Private, Static
+//  Description: Updates any_uvs, min_uv, and max_uv with the
+//               indicated min and max UV's already determined.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+collect_uv(bool &any_uvs, TexCoordd &min_uv, TexCoordd &max_uv,
+	   const TexCoordd &got_min_uv, const TexCoordd &got_max_uv) {
+  if (any_uvs) {
+    min_uv.set(min(min_uv[0], got_min_uv[0]), 
+	       min(min_uv[1], got_min_uv[1]));
+    max_uv.set(max(max_uv[0], got_max_uv[0]), 
+	       max(max_uv[1], got_max_uv[1]));
+  } else {
+    // The first UV.
+    min_uv = got_min_uv;
+    max_uv = got_max_uv;
+    any_uvs = true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::translate_uv
+//       Access: Private, Static
+//  Description: Returns the needed adjustment to translate the given
+//               bounding box so that its center lies in the unit
+//               square (0,0) - (1,1).
+////////////////////////////////////////////////////////////////////
+LVector2d TextureReference::
+translate_uv(const TexCoordd &min_uv, const TexCoordd &max_uv) {
+  TexCoordd center = (min_uv + max_uv) / 2;
+  return LVector2d(-floor(center[0]), -floor(center[1]));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_TextureReference);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::write_datagram
+//       Access: Public, Virtual
+//  Description: Fills the indicated datagram up with a binary
+//               representation of the current object, in preparation
+//               for writing to a Bam file.
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+write_datagram(BamWriter *writer, Datagram &datagram) {
+  // We don't write _egg_tex, _egg_data, or _tex_mat; that's specific
+  // to the session.
+
+  writer->write_pointer(datagram, _source_texture);
+  writer->write_pointer(datagram, _placement);
+
+  datagram.add_bool(_uses_alpha);
+  datagram.add_bool(_any_uvs);
+  datagram.add_float64(_min_uv[0]);
+  datagram.add_float64(_min_uv[1]);
+  datagram.add_float64(_max_uv[0]);
+  datagram.add_float64(_max_uv[1]);
+  datagram.add_int32((int)_wrap_u);
+  datagram.add_int32((int)_wrap_v);
+  _properties.write_datagram(writer, datagram);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::complete_pointers
+//       Access: Public, Virtual
+//  Description: Called after the object is otherwise completely read
+//               from a Bam file, this function's job is to store the
+//               pointers that were retrieved from the Bam file for
+//               each pointer object written.  The return value is the
+//               number of pointers processed from the list.
+////////////////////////////////////////////////////////////////////
+int TextureReference::
+complete_pointers(vector_typedWriteable &plist, BamReader *manager) {
+  nassertr((int)plist.size() >= 2, 0);
+  int index = 0;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_source_texture, plist[index], index);
+  }
+  index++;
+
+  if (plist[index] != (TypedWriteable *)NULL) {
+    DCAST_INTO_R(_placement, plist[index], index);
+  }
+  index++;
+
+  return index;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::make_TextureReference
+//       Access: Protected
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+////////////////////////////////////////////////////////////////////
+TypedWriteable* TextureReference::
+make_TextureReference(const FactoryParams &params) {
+  TextureReference *me = new TextureReference;
+  BamReader *manager;
+  Datagram packet;
+
+  parse_params(params, manager, packet);
+  DatagramIterator scan(packet);
+
+  me->fillin(scan, manager);
+  return me;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureReference::fillin
+//       Access: Protected
+//  Description: Reads the binary data from the given datagram
+//               iterator, which was written by a previous call to
+//               write_datagram().
+////////////////////////////////////////////////////////////////////
+void TextureReference::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  manager->read_pointer(scan, this);  // _source_texture
+  manager->read_pointer(scan, this);  // _placement
+
+  _uses_alpha = scan.get_bool();
+  _any_uvs = scan.get_bool();
+  _min_uv[0] = scan.get_float64();
+  _min_uv[1] = scan.get_float64();
+  _max_uv[0] = scan.get_float64();
+  _max_uv[1] = scan.get_float64();
+  _wrap_u = (EggTexture::WrapMode)scan.get_int32();
+  _wrap_v = (EggTexture::WrapMode)scan.get_int32();
+  _properties.fillin(scan, manager);
+}

+ 121 - 0
pandatool/src/egg-palettize/textureReference.h

@@ -0,0 +1,121 @@
+// Filename: textureReference.h
+// Created by:  drose (28Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREREFERENCE_H
+#define TEXTUREREFERENCE_H
+
+#include <pandatoolbase.h>
+
+#include "textureProperties.h"
+
+#include <luse.h>
+#include <typedWriteable.h>
+
+class TextureImage;
+class SourceTextureImage;
+class Filename;
+class EggData;
+class EggTexture;
+class EggGroupNode;
+class EggPrimitive;
+class TexturePlacement;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TextureReference
+// Description : This is the particular reference of a texture
+//               filename by an egg file.  It also includes
+//               information about the way in which the egg file uses
+//               the texture; e.g. does it repeat.
+////////////////////////////////////////////////////////////////////
+class TextureReference : public TypedWriteable {
+public:
+  TextureReference();
+  ~TextureReference();
+
+  void from_egg(EggData *data, EggTexture *egg_tex);
+
+  SourceTextureImage *get_source() const;
+  TextureImage *get_texture() const;
+
+  bool has_uvs() const;
+  const TexCoordd &get_min_uv() const;
+  const TexCoordd &get_max_uv() const;
+
+  EggTexture::WrapMode get_wrap_u() const;
+  EggTexture::WrapMode get_wrap_v() const;
+
+  void set_placement(TexturePlacement *placement);
+  void clear_placement();
+  TexturePlacement *get_placement() const;
+
+  void update_egg();
+
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level = 0) const;
+
+private:
+  void get_uv_range(EggGroupNode *group);
+  void update_uv_range(EggGroupNode *group);
+  
+  bool get_geom_uvs(EggPrimitive *geom,
+		    TexCoordd &geom_min_uv, TexCoordd &geom_max_uv);
+  void translate_geom_uvs(EggPrimitive *geom, const TexCoordd &trans) const;
+  static void collect_uv(bool &any_uvs, TexCoordd &min_uv, TexCoordd &max_uv,
+			 const TexCoordd &got_min_uv,
+			 const TexCoordd &got_max_uv);
+  static LVector2d translate_uv(const TexCoordd &min_uv,
+				const TexCoordd &max_uv);
+
+  EggTexture *_egg_tex;
+  EggData *_egg_data;
+  LMatrix3d _tex_mat, _inv_tex_mat;
+  SourceTextureImage *_source_texture;
+  TexturePlacement *_placement;
+
+  bool _uses_alpha;
+
+  bool _any_uvs;
+  TexCoordd _min_uv, _max_uv;
+  EggTexture::WrapMode _wrap_u, _wrap_v;
+
+  TextureProperties _properties;
+
+  // The TypedWriteable interface follows.
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *writer, Datagram &datagram); 
+  virtual int complete_pointers(vector_typedWriteable &plist, 
+				BamReader *manager);
+
+protected:
+  static TypedWriteable *make_TextureReference(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWriteable::init_type();
+    register_type(_type_handle, "TextureReference",
+		  TypedWriteable::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &
+operator << (ostream &out, const TextureReference &ref) {
+  ref.output(out);
+  return out;
+}
+
+#endif
+
+

+ 39 - 0
pandatool/src/egg-palettize/textureRequest.cxx

@@ -0,0 +1,39 @@
+// Filename: textureRequest.cxx
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "textureRequest.h"
+#include "palettizer.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureRequest::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TextureRequest::
+TextureRequest() {
+  _got_size = false;
+  _got_num_channels = false;
+  _x_size = 0;
+  _y_size = 0;
+  _num_channels = 0;
+  _format = EggTexture::F_unspecified;
+  _minfilter = EggTexture::FT_unspecified;
+  _magfilter = EggTexture::FT_unspecified;
+  _omit = false;
+  _margin = 0;
+  _repeat_threshold = 0.0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureRequest::pre_txa_file
+//       Access: Public
+//  Description: Sets some state up that must be set prior to reading
+//               the .txa file.
+////////////////////////////////////////////////////////////////////
+void TextureRequest::
+pre_txa_file() {
+  _margin = pal->_margin;
+  _repeat_threshold = pal->_repeat_threshold;
+}

+ 42 - 0
pandatool/src/egg-palettize/textureRequest.h

@@ -0,0 +1,42 @@
+// Filename: textureRequest.h
+// Created by:  drose (29Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TEXTUREREQUEST_H
+#define TEXTUREREQUEST_H
+
+#include <pandatoolbase.h>
+
+#include "textureProperties.h"
+
+#include <eggTexture.h>
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TextureRequest
+// Description : These are the things that a user might explicitly
+//               request to adjust on a texture via a line in the .txa
+//               file.
+////////////////////////////////////////////////////////////////////
+class TextureRequest {
+public:
+  TextureRequest();
+  void pre_txa_file();
+
+  TextureProperties _properties;
+
+  bool _got_size;
+  bool _got_num_channels;
+  int _x_size;
+  int _y_size;
+  int _num_channels;
+  EggTexture::Format _format;
+  EggTexture::FilterType _minfilter;
+  EggTexture::FilterType _magfilter;
+  bool _omit;
+  int _margin;
+  double _repeat_threshold;
+};
+
+#endif
+

+ 202 - 0
pandatool/src/egg-palettize/txaFile.cxx

@@ -0,0 +1,202 @@
+// Filename: txaFile.cxx
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "txaFile.h"
+#include "string_utils.h"
+#include "palettizer.h"
+#include "paletteGroup.h"
+#include "textureImage.h"
+
+#include <notify.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaFile::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TxaFile::
+TxaFile() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaFile::read
+//       Access: Public
+//  Description: Reads the indicated .txa filename, and returns true
+//               if successful, or false if there is an error.
+////////////////////////////////////////////////////////////////////
+bool TxaFile::
+read(Filename filename) {
+  filename.set_text();
+  ifstream in;
+  if (!filename.open_read(in)) {
+    nout << "Unable to open " << filename << "\n";
+    return false;
+  }
+
+  string line;
+  getline(in, line);
+  int line_number = 1;
+
+  while (!in.eof() && !in.fail()) {
+    bool okflag = true;
+
+    // Strip off the comment.
+    size_t hash = line.find('#');
+    if (hash != string::npos) {
+      line = line.substr(0, hash);
+    }
+    line = trim_left(line);
+    if (line.empty()) {
+      // Empty lines are ignored.
+
+    } else if (line[0] == ':') {
+      // This is a keyword line.
+      vector_string words;
+      extract_words(line, words);
+      if (words[0] == ":group") {
+	okflag = parse_group_line(words);
+
+      } else {
+	nout << "Invalid keyword: " << line << "\n";
+	okflag = false;
+      }
+
+    } else {
+      _lines.push_back(TxaLine());
+      TxaLine &txa_line = _lines.back();
+
+      okflag = txa_line.parse(line);
+    }
+
+    if (!okflag) {
+      nout << "Error on line " << line_number << " of " << filename << "\n";
+      return false;
+    }
+
+    getline(in, line);
+    line_number++;
+  }
+
+  if (!in.eof()) {
+    nout << "I/O error reading " << filename << "\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaFile::match_egg
+//       Access: Public
+//  Description: Searches for a matching line in the .txa file for the
+//               given egg file and applies its specifications.  If a
+//               match is found, returns true; otherwise, returns
+//               false.  Also returns false if all the matching lines
+//               for the egg file include the keyword "cont".
+////////////////////////////////////////////////////////////////////
+bool TxaFile::
+match_egg(EggFile *egg_file) const {
+  Lines::const_iterator li;
+  for (li = _lines.begin(); li != _lines.end(); ++li) {
+    if ((*li).match_egg(egg_file)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaFile::match_texture
+//       Access: Public
+//  Description: Searches for a matching line in the .txa file for the
+//               given texture and applies its specifications.  If a
+//               match is found, returns true; otherwise, returns
+//               false.  Also returns false if all the matching lines
+//               for the texture include the keyword "cont".
+////////////////////////////////////////////////////////////////////
+bool TxaFile::
+match_texture(TextureImage *texture) const {
+  Lines::const_iterator li;
+  for (li = _lines.begin(); li != _lines.end(); ++li) {
+    if ((*li).match_texture(texture)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaFile::write
+//       Access: Public
+//  Description: Outputs a representation of the lines that were read
+//               in to the indicated output stream.  This is primarily
+//               useful for debugging.
+////////////////////////////////////////////////////////////////////
+void TxaFile::
+write(ostream &out) const {
+  Lines::const_iterator li;
+  for (li = _lines.begin(); li != _lines.end(); ++li) {
+    out << (*li) << "\n";
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaFile::parse_group_line
+//       Access: Private
+//  Description: Handles the line in a .txa file that begins with the
+//               keyword ":group" and indicates the relationships
+//               between one or more groups.
+////////////////////////////////////////////////////////////////////
+bool TxaFile::
+parse_group_line(const vector_string &words) {
+  vector_string::const_iterator wi;
+  wi = words.begin();
+  assert (wi != words.end());
+  ++wi;
+
+  const string &group_name = (*wi);
+  PaletteGroup *group = pal->get_palette_group(group_name);
+  ++wi;
+
+  enum State {
+    S_none,
+    S_with,
+    S_dir,
+  };
+  State state = S_none;
+
+  while (wi != words.end()) {
+    const string &word = (*wi);
+    if (word == "with") {
+      state = S_with;
+
+    } else if (word == "dir") {
+      state = S_dir;
+
+    } else {
+      switch (state) {
+      case S_none:
+	nout << "Invalid keyword: " << word << "\n";
+	return false;
+
+      case S_with:
+	group->group_with(pal->get_palette_group(word));
+	break;
+
+      case S_dir:
+	group->set_dirname(word);
+	state = S_none;
+	break;
+      }
+    }
+
+    ++wi;
+  }
+
+  return true;
+}

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

@@ -0,0 +1,43 @@
+// Filename: txaFile.h
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TXAFILE_H
+#define TXAFILE_H
+
+#include <pandatoolbase.h>
+
+#include "txaLine.h"
+
+#include <filename.h>
+#include <vector_string.h>
+
+#include <vector>
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TxaFile
+// Description : This represents the .txa file (usually textures.txa)
+//               that contains the user instructions for resizing,
+//               grouping, etc. the various textures.
+////////////////////////////////////////////////////////////////////
+class TxaFile {
+public:
+  TxaFile();
+
+  bool read(Filename filename);
+
+  bool match_egg(EggFile *egg_file) const;
+  bool match_texture(TextureImage *texture) const;
+
+  void write(ostream &out) const;
+
+private:
+  bool parse_group_line(const vector_string &words);
+
+  typedef vector<TxaLine> Lines;
+  Lines _lines;
+};
+
+#endif
+

+ 467 - 0
pandatool/src/egg-palettize/txaLine.cxx

@@ -0,0 +1,467 @@
+// Filename: txaLine.cxx
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "txaLine.h"
+#include "string_utils.h"
+#include "eggFile.h"
+#include "palettizer.h"
+#include "textureImage.h"
+#include "sourceTextureImage.h"
+#include "paletteGroup.h"
+
+#include <notify.h>
+#include <pnmFileType.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaLine::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+TxaLine::
+TxaLine() {
+  _size_type = ST_none;
+  _scale = 0.0;
+  _x_size = 0;
+  _y_size = 0;
+  _num_channels = 0;
+  _format = EggTexture::F_unspecified;
+  _got_margin = false;
+  _margin = 0;
+  _got_repeat_threshold = false;
+  _repeat_threshold = 0.0;
+  _color_type = (PNMFileType *)NULL;
+  _alpha_type = (PNMFileType *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaLine::parse
+//       Access: Public
+//  Description: Accepts a string that defines a line of the .txa file
+//               and parses it into its constinuent parts.  Returns
+//               true if successful, false on error.
+////////////////////////////////////////////////////////////////////
+bool TxaLine::
+parse(const string &line) {
+  size_t colon = line.find(':');
+  if (colon == string::npos) {
+    nout << "Colon required.\n";
+  }
+
+  // Chop up the first part of the string (preceding the colon) into
+  // its individual words.  These are patterns to match.
+  vector_string words;
+  extract_words(line.substr(0, colon), words);
+
+  vector_string::iterator wi;
+  for (wi = words.begin(); wi != words.end(); ++wi) {
+    _patterns.push_back(GlobPattern(*wi));
+  }
+
+  if (_patterns.empty()) {
+    nout << "No texture or egg filenames given.\n";
+    return false;
+  }
+
+  // Now chop up the rest of the string (following the colon) into its
+  // individual words.  These are keywords and size indications.
+  words.clear();
+  extract_words(line.substr(colon + 1), words);
+
+  wi = words.begin();
+  while (wi != words.end()) {
+    const string &word = *wi;
+    nassertr(!word.empty(), false);
+
+    if (isdigit(word[0])) {
+      // This is either a new size or a scale percentage.
+      if (_size_type != ST_none) {
+	nout << "Invalid repeated size request: " << word << "\n";
+	return false;
+      }
+      if (word[word.length() - 1] == '%') {
+	// It's a scale percentage!
+	_size_type = ST_scale;
+	const char *nptr = word.c_str();
+	char *endptr;
+	_scale = strtod(nptr, &endptr);
+	if (*endptr != '%') {
+	  nout << "Invalid scale factor: " << word << "\n";
+	  return false;
+	}
+	++wi;
+
+      } else {
+	// Collect a number of consecutive numeric fields.
+	vector<int> numbers;
+	while (wi != words.end() && isdigit((*wi)[0])) {
+	  const string &word = *wi;
+	  const char *nptr = word.c_str();
+	  char *endptr;
+	  numbers.push_back(strtol(nptr, &endptr, 0));
+	  if (*endptr != '\0') {
+	    nout << "Invalid size: " << word << "\n";
+	  }
+	  ++wi;
+	}
+	if (numbers.size() < 2) {
+	  nout << "At least two size numbers must be given, or a percent sign used to indicate scaling.\n";
+	  return false;
+
+	} else if (numbers.size() == 2) {
+	  _size_type = ST_explicit_2;
+	  _x_size = numbers[0];
+	  _y_size = numbers[1];
+
+	} else if (numbers.size() == 3) {
+	  _size_type = ST_explicit_3;
+	  _x_size = numbers[0];
+	  _y_size = numbers[1];
+	  _num_channels = numbers[2];
+
+	} else {
+	  nout << "Too many size numbers given.\n";
+	  return false;
+	}
+      }
+
+    } else {
+      // The word does not begin with a digit; therefore it's either a
+      // keyword or an image file type request.
+      if (word == "omit") {
+	_keywords.push_back(KW_omit);
+
+      } else if (word == "nearest") {
+	_keywords.push_back(KW_nearest);
+
+      } else if (word == "linear") {
+	_keywords.push_back(KW_linear);
+
+      } else if (word == "mipmap") {
+	_keywords.push_back(KW_mipmap);
+
+      } else if (word == "cont") {
+	_keywords.push_back(KW_cont);
+
+      } else if (word == "margin") {
+	++wi;
+	if (wi == words.end()) {
+	  nout << "Argument required for 'margin'.\n";
+	  return false;
+	} 
+	  
+	const string &arg = (*wi);
+	const char *nptr = arg.c_str();
+	char *endptr;
+	_margin = strtol(nptr, &endptr, 10);
+	if (*endptr != '\0') {
+	  nout << "Not an integer: " << arg << "\n";
+	  return false;
+	}
+	if (_margin < 0) {
+	  nout << "Invalid margin: " << _margin << "\n";
+	  return false;
+	}
+	_got_margin = true;
+
+      } else if (word == "repeat") {
+	++wi;
+	if (wi == words.end()) {
+	  nout << "Argument required for 'repeat'.\n";
+	  return false;
+	}
+	 
+	const string &arg = (*wi);
+	const char *nptr = arg.c_str();
+	char *endptr;
+	_repeat_threshold = strtod(nptr, &endptr);
+	if (*endptr != '\0' && *endptr != '%') {
+	  nout << "Not a number: " << arg << "\n";
+	  return false;
+	}
+	if (_repeat_threshold < 0.0) {
+	  nout << "Invalid repeat threshold: " << _repeat_threshold << "\n";
+	  return false;
+	}
+	_got_repeat_threshold = true;
+
+      } else {
+	// Maybe it's a format name.
+	EggTexture::Format format = EggTexture::string_format(word);
+	if (format != EggTexture::F_unspecified) {
+	  _format = format;
+	} else {
+	  // Maybe it's a group name.
+	  PaletteGroup *group = pal->test_palette_group(word);
+	  if (group != (PaletteGroup *)NULL) {
+	    _palette_groups.insert(group);
+	    
+	  } else {
+	    // Maybe it's an image file request.
+	    if (!parse_image_type_request(word, _color_type, _alpha_type)) {
+	      return false;
+	    }
+	  }
+	}
+      }
+      ++wi;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaLine::match_egg
+//       Access: Public
+//  Description: Compares the patterns on the line to the indicated
+//               EggFile.  If they match, updates the egg with the
+//               appropriate information.  Returns true if a match is
+//               detected and the search for another line should stop,
+//               or false if a match is not detected (or if the
+//               keyword "cont" is present, which means the search
+//               should continue regardless).
+////////////////////////////////////////////////////////////////////
+bool TxaLine::
+match_egg(EggFile *egg_file) const {
+  string name = egg_file->get_name();
+
+  bool matched_any = false;
+  Patterns::const_iterator pi;
+  for (pi = _patterns.begin(); 
+       pi != _patterns.end() && !matched_any;
+       ++pi) {
+    matched_any = (*pi).matches(name);
+  }
+
+  if (!matched_any) {
+    // No match this line; continue.
+    return false;
+  }
+
+  bool got_cont = false;
+  Keywords::const_iterator ki;
+  for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
+    switch (*ki) {
+    case KW_omit:
+      break;
+
+    case KW_nearest:
+    case KW_linear:
+    case KW_mipmap:
+      // These mean nothing to an egg file.
+      break;
+
+    case KW_cont:
+      got_cont = true;
+      break;
+    }
+  }
+
+  egg_file->_assigned_groups.make_union
+    (egg_file->_assigned_groups, _palette_groups);
+
+  if (got_cont) {
+    // If we have the "cont" keyword, we should keep scanning for
+    // another line, even though we matched this one.
+    return false;
+  }
+
+  // Otherwise, in the normal case, a match ends the search for
+  // matches.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaLine::match_texture
+//       Access: Public
+//  Description: Compares the patterns on the line to the indicated
+//               TextureImage.  If they match, updates the texture
+//               with the appropriate information.  Returns true if a
+//               match is detected and the search for another line
+//               should stop, or false if a match is not detected (or
+//               if the keyword "cont" is present, which means the
+//               search should continue regardless).
+////////////////////////////////////////////////////////////////////
+bool TxaLine::
+match_texture(TextureImage *texture) const {
+  string name = texture->get_name();
+
+  bool matched_any = false;
+  Patterns::const_iterator pi;
+  for (pi = _patterns.begin(); 
+       pi != _patterns.end() && !matched_any;
+       ++pi) {
+    matched_any = (*pi).matches(name);
+  }
+
+  if (!matched_any) {
+    // No match this line; continue.
+    return false;
+  }
+
+  SourceTextureImage *source = texture->get_preferred_source();
+  TextureRequest &request = texture->_request;
+
+  if (!request._got_size) {
+    switch (_size_type) {
+    case ST_none:
+      break;
+
+    case ST_scale:
+      if (source->get_size()) {
+	request._got_size = true;
+	request._x_size = (int)(source->get_x_size() * _scale / 100.0);
+	request._y_size = (int)(source->get_y_size() * _scale / 100.0);
+      }
+      break;
+
+    case ST_explicit_3:
+      request._got_num_channels = true;
+      request._num_channels = _num_channels;
+      // fall through
+
+    case ST_explicit_2:
+      request._got_size = true;
+      request._x_size = _x_size;
+      request._y_size = _y_size;
+      break;
+    }
+  }
+
+  if (_got_margin) {
+    request._margin = _margin;
+  }
+
+  if (_got_repeat_threshold) {
+    request._repeat_threshold = _repeat_threshold;
+  }
+  
+  if (_color_type != (PNMFileType *)NULL) {
+    request._properties._color_type = _color_type;
+    request._properties._alpha_type = _alpha_type;
+  }
+
+  if (_format != EggTexture::F_unspecified) {
+    request._format = _format;
+  }
+  
+  bool got_cont = false;
+  Keywords::const_iterator ki;
+  for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
+    switch (*ki) {
+    case KW_omit:
+      request._omit = true;
+      break;
+
+    case KW_nearest:
+      request._minfilter = EggTexture::FT_nearest;
+      request._magfilter = EggTexture::FT_nearest;
+      break;
+
+    case KW_linear:
+      request._minfilter = EggTexture::FT_linear;
+      request._magfilter = EggTexture::FT_linear;
+      break;
+
+    case KW_mipmap:
+      request._minfilter = EggTexture::FT_linear_mipmap_linear;
+      request._magfilter = EggTexture::FT_linear_mipmap_linear;
+      break;
+
+    case KW_cont:
+      got_cont = true;
+      break;
+    }
+  }
+
+  texture->_explicitly_assigned_groups.make_union
+    (texture->_explicitly_assigned_groups, _palette_groups);
+
+  if (got_cont) {
+    // If we have the "cont" keyword, we should keep scanning for
+    // another line, even though we matched this one.
+    return false;
+  }
+
+  // Otherwise, in the normal case, a match ends the search for
+  // matches.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaLine::output
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TxaLine::
+output(ostream &out) const {
+  Patterns::const_iterator pi;
+  for (pi = _patterns.begin(); pi != _patterns.end(); ++pi) {
+    out << (*pi) << " ";
+  }
+  out << ":";
+
+  switch (_size_type) {
+  case ST_none:
+    break;
+
+  case ST_scale:
+    out << " " << _scale << "%";
+    break;
+
+  case ST_explicit_2:
+    out << " " << _x_size << " " << _y_size;
+    break;
+
+  case ST_explicit_3:
+    out << " " << _x_size << " " << _y_size << " " << _num_channels;
+    break;
+  }
+
+  if (_got_margin) {
+    out << " margin " << _margin;
+  }
+
+  if (_got_repeat_threshold) {
+    out << " repeat " << _repeat_threshold;
+  }
+
+  Keywords::const_iterator ki;
+  for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
+    switch (*ki) {
+    case KW_omit:
+      out << " omit";
+      break;
+
+    case KW_nearest:
+      out << " nearest";
+      break;
+
+    case KW_linear:
+      out << " linear";
+      break;
+
+    case KW_mipmap:
+      out << " mipmap";
+      break;
+
+    case KW_cont:
+      out << " cont";
+      break;
+    }
+  }
+
+  PaletteGroups::const_iterator gi;
+  for (gi = _palette_groups.begin(); gi != _palette_groups.end(); ++gi) {
+    out << " " << (*gi)->get_name();
+  }
+
+  if (_color_type != (PNMFileType *)NULL) {
+    out << " " << _color_type->get_suggested_extension();
+    if (_alpha_type != (PNMFileType *)NULL) {
+      out << "," << _alpha_type->get_suggested_extension();
+    }
+  }
+}

+ 86 - 0
pandatool/src/egg-palettize/txaLine.h

@@ -0,0 +1,86 @@
+// Filename: txaLine.h
+// Created by:  drose (30Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TXALINE_H
+#define TXALINE_H
+
+#include <pandatoolbase.h>
+
+#include "paletteGroups.h"
+
+#include <globPattern.h>
+#include <eggTexture.h>
+
+#include <vector>
+
+class PNMFileType;
+class EggFile;
+class TextureImage;
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : TxaLine
+// Description : This is a single matching line in the .txa file.  It
+//               consists of a list of names (texture names or egg
+//               file names), followed by a colon and an optional size
+//               and a set of keywords.
+////////////////////////////////////////////////////////////////////
+class TxaLine {
+public:
+  TxaLine();
+
+  bool parse(const string &line);
+
+  bool match_egg(EggFile *egg_file) const;
+  bool match_texture(TextureImage *texture) const;
+
+  void output(ostream &out) const;
+
+private:
+  typedef vector<GlobPattern> Patterns;
+  Patterns _patterns;
+
+  enum SizeType {
+    ST_none,
+    ST_scale,
+    ST_explicit_2,
+    ST_explicit_3
+  };
+
+  SizeType _size_type;
+  float _scale;
+  int _x_size;
+  int _y_size;
+  int _num_channels;
+  EggTexture::Format _format;
+
+  bool _got_margin;
+  int _margin;
+  bool _got_repeat_threshold;
+  double _repeat_threshold;
+
+  enum Keyword {
+    KW_omit,
+    KW_nearest,
+    KW_linear,
+    KW_mipmap,
+    KW_cont
+  };
+
+  typedef vector<Keyword> Keywords;
+  Keywords _keywords;
+
+  PaletteGroups _palette_groups;
+
+  PNMFileType *_color_type;
+  PNMFileType *_alpha_type;
+};
+
+INLINE ostream &operator << (ostream &out, const TxaLine &line) {
+  line.output(out);
+  return out;
+}
+
+#endif
+

+ 25 - 13
pandatool/src/eggbase/eggMultiFilter.cxx

@@ -126,6 +126,30 @@ post_command_line() {
   return EggMultiBase::post_command_line();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggMultiFilter::get_output_filename
+//       Access: Protected
+//  Description: Returns the output filename of the egg file with the
+//               given input filename.  This is based on the user's
+//               choice of -inplace, -o, or -d.
+////////////////////////////////////////////////////////////////////
+Filename EggMultiFilter::
+get_output_filename(const Filename &source_filename) const {
+  if (_got_output_filename) {
+    nassertr(!_inplace && !_got_output_dirname && _eggs.size() == 1, Filename());
+    return _output_filename;
+    
+  } else if (_got_output_dirname) {
+    nassertr(!_inplace, Filename());
+    Filename result = source_filename;
+    result.set_dirname(_output_dirname);
+    return result;
+  }
+
+  nassertr(_inplace, Filename());
+  return source_filename;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggMultiFilter::write_eggs
 //       Access: Protected
@@ -138,19 +162,7 @@ write_eggs() {
   Eggs::iterator ei;
   for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) {
     EggData *data = (*ei);
-    Filename filename = data->get_egg_filename();
-
-    if (_got_output_filename) {
-      nassertv(!_inplace && !_got_output_dirname && _eggs.size() == 1);
-      filename = _output_filename;
-
-    } else if (_got_output_dirname) {
-      nassertv(!_inplace);
-      filename.set_dirname(_output_dirname);
-
-    } else {
-      nassertv(_inplace);
-    }
+    Filename filename = get_output_filename(data->get_egg_filename());
 
     nout << "Writing " << filename << "\n";
     filename.make_dir();

+ 1 - 0
pandatool/src/eggbase/eggMultiFilter.h

@@ -24,6 +24,7 @@ protected:
   virtual bool handle_args(Args &args);
   virtual bool post_command_line();
 
+  Filename get_output_filename(const Filename &source_filename) const;
   void write_eggs();
 
 protected: