Browse Source

Added :textureswap feature to create extra palettes with swapped textures

Gyedo Jeon 16 years ago
parent
commit
81bdd7146e

+ 44 - 0
pandatool/src/palettizer/paletteGroup.cxx

@@ -424,6 +424,36 @@ prepare(TextureImage *texture) {
   TexturePlacement *placement = new TexturePlacement(texture, this);
   TexturePlacement *placement = new TexturePlacement(texture, this);
   _placements.insert(placement);
   _placements.insert(placement);
 
 
+  // [gjeon] update swapTexture information
+  TextureSwapInfo::iterator tsi = _textureSwapInfo.find(texture->get_name());
+  if (tsi != _textureSwapInfo.end()) {
+    vector_string swapTextures = (*tsi).second;
+
+    vector_string::const_iterator wi;
+    wi = swapTextures.begin();
+    ++wi;
+    ++wi;
+
+    // [gjeon] since swapped texture usually didn't mapped to any egg file
+    // we need to create soucreTextureImage by using original texture file's info
+    const string originalTextureName = (*wi);
+    TextureImage *originalTexture = pal->get_texture(originalTextureName);
+    SourceTextureImage *source = originalTexture->get_preferred_source();
+    const Filename originalTextureFilename = source->get_filename();
+    const Filename originalTextureAlphaFilename = source->get_alpha_filename();
+    int originalTextureAlphaFileChannel = source->get_alpha_file_channel();
+
+    ++wi;
+    while (wi != swapTextures.end()) {
+      const string &swapTextureName = (*wi);
+      TextureImage *swapTextureImage = pal->get_texture(swapTextureName);
+      Filename swapTextureFilename = Filename(originalTextureFilename.get_dirname(), swapTextureName + "." + originalTextureFilename.get_extension());
+      swapTextureImage->get_source(swapTextureFilename, originalTextureAlphaFilename, originalTextureAlphaFileChannel);
+      placement->_textureSwaps.push_back(swapTextureImage);
+      ++wi;
+    }
+  }  
+
   return placement;
   return placement;
 }
 }
 
 
@@ -779,3 +809,17 @@ fillin(DatagramIterator &scan, BamReader *manager) {
     _margin_override = scan.get_int16();
     _margin_override = scan.get_int16();
   } 
   } 
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteGroup::add_texture_swap_info
+//       Access: Public
+//  Description: Store textureswap information from textures.txa
+////////////////////////////////////////////////////////////////////
+void PaletteGroup::
+add_texture_swap_info(const string sourceTextureName, const vector_string &swapTextures) {
+  TextureSwapInfo::iterator tsi = _textureSwapInfo.find(sourceTextureName);
+  if (tsi != _textureSwapInfo.end()) {
+    _textureSwapInfo.erase(tsi);
+  }
+  _textureSwapInfo.insert(TextureSwapInfo::value_type(sourceTextureName, swapTextures));
+}

+ 6 - 0
pandatool/src/palettizer/paletteGroup.h

@@ -25,6 +25,7 @@
 
 
 #include "pset.h"
 #include "pset.h"
 #include "pvector.h"
 #include "pvector.h"
+#include "vector_string.h"
 
 
 class EggFile;
 class EggFile;
 class TexturePlacement;
 class TexturePlacement;
@@ -89,6 +90,8 @@ public:
   void setup_shadow_images();
   void setup_shadow_images();
   void update_images(bool redo_all);
   void update_images(bool redo_all);
 
 
+  void add_texture_swap_info(const string sourceTextureName, const vector_string &swapTextures);
+
 private:
 private:
   string _dirname;
   string _dirname;
   int _egg_count;
   int _egg_count;
@@ -103,6 +106,9 @@ private:
   typedef pmap<TextureProperties, PalettePage *> Pages;
   typedef pmap<TextureProperties, PalettePage *> Pages;
   Pages _pages;
   Pages _pages;
 
 
+  typedef pmap<string, vector_string> TextureSwapInfo;
+  TextureSwapInfo _textureSwapInfo;
+
   // The TypedWritable interface follows.
   // The TypedWritable interface follows.
 public:
 public:
   static void register_with_read_factory();
   static void register_with_read_factory();

+ 187 - 4
pandatool/src/palettizer/paletteImage.cxx

@@ -155,6 +155,8 @@ PaletteImage() {
   _index = 0;
   _index = 0;
   _new_image = false;
   _new_image = false;
   _got_image = false;
   _got_image = false;
+
+  _swapped_image = 0;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -173,10 +175,33 @@ PaletteImage(PalettePage *page, int index) :
   _y_size = pal->_pal_y_size;
   _y_size = pal->_pal_y_size;
   _new_image = true;
   _new_image = true;
   _got_image = false;
   _got_image = false;
+  _swapped_image = 0;
+
+  setup_filename();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+PaletteImage::
+PaletteImage(PalettePage *page, int index, unsigned swapIndex) :
+  _page(page),
+  _index(index),
+  _swapped_image(swapIndex)
+{
+  _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;
 
 
   setup_filename();
   setup_filename();
 }
 }
 
 
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PaletteImage::get_page
 //     Function: PaletteImage::get_page
 //       Access: Public
 //       Access: Public
@@ -293,6 +318,17 @@ place(TexturePlacement *placement) {
   if (find_hole(x, y, placement->get_x_size(), placement->get_y_size())) {
   if (find_hole(x, y, placement->get_x_size(), placement->get_y_size())) {
     placement->place_at(this, x, y);
     placement->place_at(this, x, y);
     _placements.push_back(placement);
     _placements.push_back(placement);
+
+    // [gjeon] create swappedImages
+    TexturePlacement::TextureSwaps::iterator tsi;
+    for (tsi = placement->_textureSwaps.begin(); tsi != placement->_textureSwaps.end(); ++tsi) {
+      if ((tsi - placement->_textureSwaps.begin()) >= _swappedImages.size()) {
+        PaletteImage *swappedImage = new PaletteImage(_page, _swappedImages.size(), tsi - placement->_textureSwaps.begin() + 1);
+        swappedImage->_masterPlacements = &_placements;
+        _swappedImages.push_back(swappedImage);
+      }
+    }
+
     return true;
     return true;
   }
   }
 
 
@@ -314,7 +350,6 @@ unplace(TexturePlacement *placement) {
     _placements.erase(pi);
     _placements.erase(pi);
     pi = find(_placements.begin(), _placements.end(), placement);
     pi = find(_placements.begin(), _placements.end(), placement);
   }
   }
-
   _cleared_regions.push_back(ClearedRegion(placement));
   _cleared_regions.push_back(ClearedRegion(placement));
 }
 }
 
 
@@ -373,7 +408,7 @@ check_solitary() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void PaletteImage::
 void PaletteImage::
 optimal_resize() {
 optimal_resize() {
-  if (is_empty()) {
+  if (is_empty()) { // && (_swapped_image == 0)) {
     return;
     return;
   }
   }
 
 
@@ -392,12 +427,20 @@ optimal_resize() {
       success = true;
       success = true;
       resized_any = true;
       resized_any = true;
     }
     }
+
   } while (success);
   } while (success);
 
 
   if (resized_any) {
   if (resized_any) {
     nout << "Resizing "
     nout << "Resizing "
          << FilenameUnifier::make_user_filename(get_filename()) << " to "
          << FilenameUnifier::make_user_filename(get_filename()) << " to "
          << _x_size << " " << _y_size << "\n";
          << _x_size << " " << _y_size << "\n";
+
+    // [gjeon] resize swapped images, also
+    SwappedImages::iterator si;
+    for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
+      PaletteImage *swappedImage = (*si);
+      swappedImage->resize_swapped_image(_x_size, _y_size);
+    }  
   }
   }
 }
 }
 
 
@@ -471,6 +514,22 @@ resize_image(int x_size, int y_size) {
   return packed;
   return packed;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::resize_swapped_image
+//       Access: Public
+//  Description: Attempts to resize the palette image, and repack all
+//               of the textures within the new size.  Returns true if
+//               successful, false otherwise.  If this fails, it will
+//               still result in repacking all the textures in the
+//               original size.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+resize_swapped_image(int x_size, int y_size) {
+  // Finally, apply the new size and try to fit all the textures.
+  _x_size = x_size;
+  _y_size = y_size;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PaletteImage::write_placements
 //     Function: PaletteImage::write_placements
 //       Access: Public
 //       Access: Public
@@ -518,6 +577,13 @@ reset_image() {
 void PaletteImage::
 void PaletteImage::
 setup_shadow_image() {
 setup_shadow_image() {
   _shadow_image.make_shadow_image(_basename);
   _shadow_image.make_shadow_image(_basename);
+
+  // [gjeon] setup shadoe_image of swappedImages
+  SwappedImages::iterator si;
+  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
+    PaletteImage *swappedImage = (*si);
+    swappedImage->setup_shadow_image();
+  }  
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -576,6 +642,25 @@ update_image(bool redo_all) {
           needs_update = true;
           needs_update = true;
         }
         }
       }
       }
+
+      // [gjeon] to find out all of the swappable textures is up to date
+      TexturePlacement::TextureSwaps::iterator tsi;
+      for (tsi = placement->_textureSwaps.begin(); tsi != placement->_textureSwaps.end(); ++tsi) {
+        TextureImage *swapTexture = (*tsi);
+
+        if (swapTexture->is_texture_named()) {
+          SourceTextureImage *sourceSwapTexture = swapTexture->get_preferred_source();
+
+          if (sourceSwapTexture != (SourceTextureImage *)NULL &&
+              sourceSwapTexture->get_filename().compare_timestamps(get_filename()) > 0) {
+            // The source image is newer than the palette image; we need to
+            // regenerate.
+            placement->mark_unfilled();
+            needs_update = true;
+          }
+        }
+      }
+
     }
     }
   }
   }
 
 
@@ -585,12 +670,21 @@ update_image(bool redo_all) {
   }
   }
 
 
   get_image();
   get_image();
+  // [gjeon] get swapped images, too
+  get_swapped_images();
 
 
   // Set to black any parts of the image that we recently unplaced.
   // Set to black any parts of the image that we recently unplaced.
   ClearedRegions::iterator ci;
   ClearedRegions::iterator ci;
   for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) {
   for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) {
     ClearedRegion &region = (*ci);
     ClearedRegion &region = (*ci);
     region.clear(_image);
     region.clear(_image);
+
+    // [gjeon] clear swapped images also
+    SwappedImages::iterator si;
+    for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
+      PaletteImage *swappedImage = (*si);
+      region.clear(swappedImage->_image);
+    }
   }
   }
   _cleared_regions.clear();
   _cleared_regions.clear();
 
 
@@ -599,6 +693,14 @@ update_image(bool redo_all) {
     TexturePlacement *placement = (*pi);
     TexturePlacement *placement = (*pi);
     if (!placement->is_filled()) {
     if (!placement->is_filled()) {
       placement->fill_image(_image);
       placement->fill_image(_image);
+
+      // [gjeon] fill swapped images
+      SwappedImages::iterator si;
+      for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
+        PaletteImage *swappedImage = (*si);
+        swappedImage->update_filename();
+        placement->fill_swapped_image(swappedImage->_image, si - _swappedImages.begin());
+      }
     }
     }
   }
   }
 
 
@@ -609,6 +711,17 @@ update_image(bool redo_all) {
   }
   }
 
 
   release_image();
   release_image();
+
+  // [gjeon] write and release swapped images
+  SwappedImages::iterator si;
+  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
+    PaletteImage *swappedImage = (*si);
+    swappedImage->write(swappedImage->_image);
+    if (pal->_shadow_color_type != (PNMFileType *)NULL) {
+      swappedImage->_shadow_image.write(swappedImage->_image);
+    }
+    swappedImage->release_image();
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -662,7 +775,6 @@ update_filename() {
   return false;
   return false;
 }
 }
 
 
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PaletteImage::setup_filename
 //     Function: PaletteImage::setup_filename
 //       Access: Private
 //       Access: Private
@@ -713,6 +825,11 @@ setup_filename() {
       ++si;
       ++si;
     }
     }
   }
   }
+
+  if (_swapped_image > 0) {
+    _basename += "_swp_";
+    _basename += format_string(_swapped_image);
+  }
     
     
   // We must end the basename with a dot, so that it does not appear
   // We must end the basename with a dot, so that it does not appear
   // to have a filename extension.  Otherwise, an embedded dot in the
   // to have a filename extension.  Otherwise, an embedded dot in the
@@ -731,7 +848,7 @@ setup_filename() {
   if (_shadow_image.make_shadow_image(_basename)) {
   if (_shadow_image.make_shadow_image(_basename)) {
     any_changed = true;
     any_changed = true;
   }
   }
-
+  
   return any_changed;
   return any_changed;
 }
 }
 
 
@@ -849,6 +966,72 @@ get_image() {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::get_swapped_image
+//       Access: Public
+//  Description: Reads or generates the PNMImage for swapped textures
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+get_swapped_image(int index) {
+  if (_got_image) {
+    return;
+  }
+
+  if (!_new_image) {
+    if (pal->_shadow_color_type != (PNMFileType *)NULL) {
+      if (_shadow_image.get_filename().exists() && _shadow_image.read(_image)) {
+        _got_image = true;
+        return;
+      }
+    } else {
+      if (get_filename().exists() && read(_image)) {
+        _got_image = true;
+        return;
+      }
+    }
+  }
+
+  nout << "Generating new "
+       << FilenameUnifier::make_user_filename(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());
+  _image.fill(pal->_background[0], pal->_background[1], pal->_background[2]);
+  if (_image.has_alpha()) {
+    _image.alpha_fill(pal->_background[3]);
+  }
+
+  _new_image = false;
+  _got_image = true;
+
+  // Now fill up the image.
+  Placements::iterator pi;
+  for (pi = _masterPlacements->begin(); pi != _masterPlacements->end(); ++pi) {
+    TexturePlacement *placement = (*pi);
+    if (placement->_textureSwaps.size() > index)
+      placement->fill_swapped_image(_image, index);
+    else
+      placement->fill_image(_image);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PaletteImage::get_swapped_images
+//       Access: Public
+//  Description: Reads or generates the PNMImage that corresponds to
+//               the palette as it is known so far.
+////////////////////////////////////////////////////////////////////
+void PaletteImage::
+get_swapped_images() {
+  SwappedImages::iterator si;
+  for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) {
+    PaletteImage *swappedImage = (*si);
+    swappedImage->get_swapped_image(si - _swappedImages.begin());
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PaletteImage::release_image
 //     Function: PaletteImage::release_image
 //       Access: Public
 //       Access: Public

+ 11 - 0
pandatool/src/palettizer/paletteImage.h

@@ -39,6 +39,7 @@ private:
 
 
 public:
 public:
   PaletteImage(PalettePage *page, int index);
   PaletteImage(PalettePage *page, int index);
+  PaletteImage(PalettePage *page, int index, unsigned swapIndex);
 
 
   PalettePage *get_page() const;
   PalettePage *get_page() const;
 
 
@@ -52,6 +53,7 @@ public:
 
 
   void optimal_resize();
   void optimal_resize();
   bool resize_image(int x_size, int y_size);
   bool resize_image(int x_size, int y_size);
+  void resize_swapped_image(int x_size, int y_size);
 
 
   void write_placements(ostream &out, int indent_level = 0) const;
   void write_placements(ostream &out, int indent_level = 0) const;
   void reset_image();
   void reset_image();
@@ -67,6 +69,8 @@ private:
   void get_image();
   void get_image();
   void release_image();
   void release_image();
   void remove_image();
   void remove_image();
+  void get_swapped_image(int index);
+  void get_swapped_images();
 
 
   // The ClearedRegion object keeps track of TexturePlacements that
   // The ClearedRegion object keeps track of TexturePlacements that
   // were recently removed and thus need to be set to black.
   // were recently removed and thus need to be set to black.
@@ -92,6 +96,8 @@ private:
   typedef pvector<TexturePlacement *> Placements;
   typedef pvector<TexturePlacement *> Placements;
   Placements _placements;
   Placements _placements;
 
 
+  Placements *_masterPlacements;
+
   PalettePage *_page;
   PalettePage *_page;
   int _index;
   int _index;
   string _basename;
   string _basename;
@@ -99,9 +105,14 @@ private:
   bool _new_image;
   bool _new_image;
   bool _got_image;
   bool _got_image;
   PNMImage _image;
   PNMImage _image;
+  
+  unsigned _swapped_image; // 0 for non swapped image
 
 
   ImageFile _shadow_image;
   ImageFile _shadow_image;
 
 
+  typedef pvector<PaletteImage *> SwappedImages;
+  SwappedImages _swappedImages;
+
   // The TypedWritable interface follows.
   // The TypedWritable interface follows.
 public:
 public:
   static void register_with_read_factory();
   static void register_with_read_factory();

+ 2 - 4
pandatool/src/palettizer/palettizer.cxx

@@ -37,7 +37,7 @@ Palettizer *pal = (Palettizer *)NULL;
 // allows us to easily update egg-palettize to write out additional
 // allows us to easily update egg-palettize to write out additional
 // information to its pi file, without having it increment the bam
 // information to its pi file, without having it increment the bam
 // version number for all bam and boo files anywhere in the world.
 // version number for all bam and boo files anywhere in the world.
-int Palettizer::_pi_version = 19;
+int Palettizer::_pi_version = 20;
 // Updated to version 8 on 3/20/03 to remove extensions from texture key names.
 // Updated to version 8 on 3/20/03 to remove extensions from texture key names.
 // Updated to version 9 on 4/13/03 to add a few properties in various places.
 // Updated to version 9 on 4/13/03 to add a few properties in various places.
 // Updated to version 10 on 4/15/03 to add _alpha_file_channel.
 // Updated to version 10 on 4/15/03 to add _alpha_file_channel.
@@ -50,7 +50,7 @@ int Palettizer::_pi_version = 19;
 // Updated to version 17 on 3/02/07 to add TextureImage::_txa_wrap_u etc.
 // Updated to version 17 on 3/02/07 to add TextureImage::_txa_wrap_u etc.
 // Updated to version 18 on 5/13/08 to add TextureProperties::_quality_level.
 // Updated to version 18 on 5/13/08 to add TextureProperties::_quality_level.
 // Updated to version 19 on 7/16/09 to add PaletteGroup::_override_margin
 // Updated to version 19 on 7/16/09 to add PaletteGroup::_override_margin
-
+// Updated to version 20 on 7/27/09 to add TexturePlacement::_swapTextures
 
 
 int Palettizer::_min_pi_version = 8;
 int Palettizer::_min_pi_version = 8;
 // Dropped support for versions 7 and below on 7/14/03.
 // Dropped support for versions 7 and below on 7/14/03.
@@ -562,7 +562,6 @@ process_all(bool force_texture_read, const Filename &state_filename) {
   EggFiles::const_iterator efi;
   EggFiles::const_iterator efi;
   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
   for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) {
     EggFile *egg_file = (*efi).second;
     EggFile *egg_file = (*efi).second;
-
     egg_file->pre_txa_file();
     egg_file->pre_txa_file();
     _txa_file.match_egg(egg_file);
     _txa_file.match_egg(egg_file);
     egg_file->post_txa_file();
     egg_file->post_txa_file();
@@ -583,7 +582,6 @@ process_all(bool force_texture_read, const Filename &state_filename) {
   // .txa file.
   // .txa file.
   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
     TextureImage *texture = (*ti).second;
     TextureImage *texture = (*ti).second;
-
     if (force_texture_read || texture->is_newer_than(state_filename)) {
     if (force_texture_read || texture->is_newer_than(state_filename)) {
       texture->read_source_image();
       texture->read_source_image();
     }
     }

+ 123 - 0
pandatool/src/palettizer/texturePlacement.cxx

@@ -885,6 +885,109 @@ fill_image(PNMImage &image) {
   _texture->release_source_image();
   _texture->release_source_image();
 }
 }
 
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: TexturePlacement::fill_swapped_image
+//       Access: Public
+//  Description: Fills in the rectangle of the swapped palette image
+//               represented by the texture placement with the image
+//               pixels.
+////////////////////////////////////////////////////////////////////
+void TexturePlacement::
+fill_swapped_image(PNMImage &image, int index) {
+  nassertv(is_placed());
+
+  _is_filled = true;
+
+  // We determine the pixels to place the source image at by
+  // transforming the unit texture box: the upper-left and lower-right
+  // corners.  These corners, in the final texture coordinate space,
+  // represent where on the palette image the original texture should
+  // be located.
+
+  LMatrix3d transform;
+  compute_tex_matrix(transform);
+  TexCoordd ul = TexCoordd(0.0, 1.0) * transform;
+  TexCoordd lr = TexCoordd(1.0, 0.0) * transform;
+
+  // Now we convert those texture coordinates back to pixel units.
+  int pal_x_size = _image->get_x_size();
+  int pal_y_size = _image->get_y_size();
+
+  int top = (int)floor((1.0 - ul[1]) * pal_y_size + 0.5);
+  int left = (int)floor(ul[0] * pal_x_size + 0.5);
+  int bottom = (int)floor((1.0 - lr[1]) * pal_y_size + 0.5);
+  int right = (int)floor(lr[0] * pal_x_size + 0.5);
+
+  // And now we can determine the size to scale the image to based on
+  // that.  This may not be the same as texture->size() because of
+  // margins.
+  int x_size = right - left;
+  int y_size = bottom - top;
+  nassertv(x_size >= 0 && y_size >= 0);
+
+  // Now we get a PNMImage that represents the swapped texture at that
+  // size.
+  TextureSwaps::iterator tsi;
+  tsi = _textureSwaps.begin() + index;
+  TextureImage *swapTexture = (*tsi);
+  const PNMImage &source_full = swapTexture->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.  We do this by walking through the
+  // rectangular region on the palette image that we have reserved for
+  // this texture; for each pixel in this region, we determine its
+  // appropriate color based on its relation to the actual texture
+  // image location (determined above), and on whether the texture
+  // wraps or clamps.
+  for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) {
+    int sy = y - top;
+
+    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 = _placed._x; x < _placed._x + _placed._x_size; x++) {
+      int sx = x - left;
+
+      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, y, source.get_xel(sx, sy));
+      if (alpha) {
+        if (source_alpha) {
+          image.set_alpha(x, y, source.get_alpha(sx, sy));
+        } else {
+          image.set_alpha(x, y, 1.0);
+        }
+      }
+    }
+  }
+
+  swapTexture->release_source_image();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TexturePlacement::flag_error_image
 //     Function: TexturePlacement::flag_error_image
 //       Access: Public
 //       Access: Public
@@ -1003,6 +1106,12 @@ write_datagram(BamWriter *writer, Datagram &datagram) {
     writer->write_pointer(datagram, (*ri));
     writer->write_pointer(datagram, (*ri));
   }
   }
 
 
+  datagram.add_int32(_textureSwaps.size());
+  TextureSwaps::const_iterator tsi;
+  for (tsi = _textureSwaps.begin(); tsi != _textureSwaps.end(); ++tsi) {
+    writer->write_pointer(datagram, (*tsi));
+  }
+
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1046,6 +1155,13 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
     index++;
     index++;
   }
   }
 
 
+  for (i = 0; i < _num_textureSwaps; i++) {
+    TextureImage *swapTexture;
+    DCAST_INTO_R(swapTexture, p_list[index], index);
+    _textureSwaps.push_back(swapTexture);
+    index++;
+  }
+
   return index;
   return index;
 }
 }
 
 
@@ -1094,6 +1210,13 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
 
   _num_references = scan.get_int32();
   _num_references = scan.get_int32();
   manager->read_pointers(scan, _num_references);
   manager->read_pointers(scan, _num_references);
+
+  if (Palettizer::_read_pi_version >= 20) {
+    _num_textureSwaps = scan.get_int32();
+  } else {
+    _num_textureSwaps = 0;
+  }
+  manager->read_pointers(scan, _num_textureSwaps);
 }
 }
 
 
 
 

+ 5 - 1
pandatool/src/palettizer/texturePlacement.h

@@ -91,8 +91,12 @@ public:
   bool is_filled() const;
   bool is_filled() const;
   void mark_unfilled();
   void mark_unfilled();
   void fill_image(PNMImage &image);
   void fill_image(PNMImage &image);
+  void fill_swapped_image(PNMImage &image, int index);
   void flag_error_image(PNMImage &image);
   void flag_error_image(PNMImage &image);
 
 
+  typedef pvector<TextureImage *> TextureSwaps;
+  TextureSwaps _textureSwaps;
+
 private:
 private:
   void compute_size_from_uvs(const TexCoordd &min_uv, const TexCoordd &max_uv);
   void compute_size_from_uvs(const TexCoordd &min_uv, const TexCoordd &max_uv);
 
 
@@ -127,8 +131,8 @@ private:
   // This value is only filled in while reading from the bam file;
   // This value is only filled in while reading from the bam file;
   // don't use it otherwise.
   // don't use it otherwise.
   int _num_references;
   int _num_references;
-
   int _margin_override;
   int _margin_override;
+  int _num_textureSwaps;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 37 - 0
pandatool/src/palettizer/txaFile.cxx

@@ -91,6 +91,9 @@ read(istream &in, const string &filename) {
       } else if (words[0] == ":cutout") {
       } else if (words[0] == ":cutout") {
         okflag = parse_cutout_line(words);
         okflag = parse_cutout_line(words);
 
 
+      } else if (words[0] == ":textureswap") {
+        okflag = parse_textureswap_line(words);
+
       } else {
       } else {
         nout << "Invalid keyword " << words[0] << "\n";
         nout << "Invalid keyword " << words[0] << "\n";
         okflag = false;
         okflag = false;
@@ -577,6 +580,7 @@ parse_remap_line(const vector_string &words) {
   return true;
   return true;
 }
 }
 
 
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TxaFile::parse_cutout_line
 //     Function: TxaFile::parse_cutout_line
 //       Access: Private
 //       Access: Private
@@ -608,3 +612,36 @@ parse_cutout_line(const vector_string &words) {
 
 
   return true;
   return true;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: TxaFile::parse_textureswap_line
+//       Access: Private
+//  Description: Handles the line in a .txa file that begins with the
+//               keyword ":textureswap" and indicates the relationships
+//               between textures to be swapped.
+////////////////////////////////////////////////////////////////////
+bool TxaFile::
+parse_textureswap_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;
+
+  string sourceTextureName = (*wi);
+  ++wi;
+
+  //vector_string swapTextures;
+  //copy(words.begin(), words.end(), swapTextures);
+  //group->add_texture_swap_info(sourceTextureName, swapTextures);
+  size_t dot = sourceTextureName.rfind('.');
+  if (dot != string::npos) {
+    sourceTextureName = sourceTextureName.substr(0, dot);
+  }
+  group->add_texture_swap_info(sourceTextureName, words);
+  
+  return true;
+}

+ 1 - 0
pandatool/src/palettizer/txaFile.h

@@ -55,6 +55,7 @@ private:
   bool parse_round_line(const vector_string &words);
   bool parse_round_line(const vector_string &words);
   bool parse_remap_line(const vector_string &words);
   bool parse_remap_line(const vector_string &words);
   bool parse_cutout_line(const vector_string &words);
   bool parse_cutout_line(const vector_string &words);
+  bool parse_textureswap_line(const vector_string &words);
 
 
   typedef pvector<TxaLine> Lines;
   typedef pvector<TxaLine> Lines;
   Lines _lines;
   Lines _lines;